← Back to Embedded C series
🔌
8051
8051 · Prereq: lesson 10 · 8051 · Prereq: lesson 11

11–12. UART Serial Communication · 7-Segment Display

To exchange human-readable text with a PC you need communication; the most basic is UART (serial) — two wires (TXD/RXD) carrying text. UART is asynchronous, so both sides must agree on the baud rate. The 8051 sets serial mode via SCON, baud via Timer1, and moves a byte through SBUF. We send a string at 9600bps·8N1 and learn why 11.0592MHz is used for accuracy. Now we show human-readable digits. A 7-segment uses 7 bar LEDs (a~g) + a dot to draw 0–9. The keys: ① a lookup table mapping a digit to "which bars to light", and ② multiplexing (dynamic drive) that shows multiple digits while saving pins. We light "1234" one digit at a time, very fast, so persistence of vision makes all four appear lit at once.

UARTserialbaudSCONSBUF11.0592MHz7-segmentlookup tablemultiplexingdynamic driveghostingcommon cathode
Duration
⏱ ~1.5–2 hours + ~1.5 hours
Level
📊 Intermediate
Prerequisite
🎯 Lesson 10
OUTCOME
Initialize a 9600bps 8N1 UART with SCON·Timer1·SBUF to send strings to a PC and compute the baud reload (TH1). / Drive a 4-digit 7-segment without ghosting using a 0–9 lookup table and multiplexing (off→write→on order).

What you'll learn

  • 1Explain sync vs async and the UART frame (start·data·stop)
  • 2Set Mode1 (8-bit UART) and receive enable (REN) via SCON
  • 3Generate baud with Timer1 (Mode2 auto-reload) and compute TH1
  • 4Send/receive a byte via SBUF/TI/RI
  • 5Know why 11.0592MHz crystal is used for UART
  • 6Explain segment layout (a~g·dp) and common cathode/anode
  • 7Build a lookup table mapping 0–9 to segment bit patterns
  • 8Drive multiple digits by separating digit-select from segment output
  • 9Show 4 digits at once via multiplexing (time-division + persistence)
  • 10Avoid ghosting with the off→change→on order

11. UART Serial Communication

To exchange human-readable text with a PC you need communication; the most basic is UART (serial) — two wires (TXD/RXD) carrying text. UART is asynchronous, so both sides must agree on the baud rate. The 8051 sets serial mode via SCON, baud via Timer1, and moves a byte through SBUF. We send a string at 9600bps·8N1 and learn why 11.0592MHz is used for accuracy.

Introduction

Serial sends data one bit at a time over one wire. UART works without a clock line (asynchronous), so both sides must use the same baud (e.g. 9600bps). Typical wiring: TXD(P3.1)·RXD(P3.0)·GND.

Key concepts

1) The 8N1 frame

c
idle(1) → [start=0] → [d0..d7] → [stop=1] → idle(1)
                       ↑ data goes LSB (d0) first!
/* data8 · no parity (N) · stop1 = 8N1 */

2) SCON and baud (Timer1)

Standard `SCON = 0x50` → SM1=1 (Mode1, 8-bit UART) + REN=1 (receive enable). The 8051 puts Timer1 in Mode2 (8-bit auto-reload) and makes baud from its overflow rate.

c
baud = Fosc / (384 × (256 - TH1))   (SMOD=0)
/* 11.0592MHz, 9600bps: n = 11059200/(384*9600) = 3 (integer!) → TH1 = 256-3 = 0xFD */
⚠️

11.0592MHz makes n an integer for standard bauds (zero error). 12MHz gives ~8.5% error at 9600, breaking comms — that's why UART boards use 11.0592MHz.

3) SBUF send/receive

c
void uart_tx(unsigned char c){ SBUF=c; while(TI==0); TI=0; }
unsigned char uart_rx(void){ while(RI==0); RI=0; return SBUF; }
/* TI/RI are only set by hardware — software must clear them to 0 each time */

Core example

c
#include <reg52.h>
void uart_init(void){
    SCON = 0x50;            /* Mode1 + REN */
    TMOD &= 0x0F; TMOD |= 0x20;   /* Timer1 Mode2 (baud) */
    TH1 = 0xFD; TL1 = 0xFD;       /* 9600 @ 11.0592MHz */
    TR1 = 1;
}
void uart_tx(unsigned char c){ SBUF=c; while(TI==0); TI=0; }
void uart_puts(const char *s){ while(*s) uart_tx((unsigned char)*s++); }
void main(void){ uart_init(); while(1){ uart_puts("Hello, 8051!\r\n"); } }

Baud reload check (11.0592MHz): 1200→0xE8, 2400→0xF4, 4800→0xFA, 9600→0xFD. Watch output in the simulator's View → Serial Windows → UART #1.

Common mistakes

Q. The first char appears, then it stops.

A. You didn't clear TI. Hardware only sets it, so clear TI=0; after each send (and RI for receive).

Q. Garbled characters.

A. Baud mismatch. Match the terminal baud to 9600 and set Xtal to 11.0592MHz. At 12MHz, TH1=0xFD yields ~10417bps and breaks.

Q. TX works but RX doesn't.

A. REN (receive enable) in SCON may be off (0x50 includes REN=1). Also check you clear RI after reading.

Summary

  • UART is clock-less async serial — both sides agree on baud
  • Frame: start(0) → data 8 (LSB first) → stop(1); no parity = 8N1
  • SCON=0x50 (Mode1·REN), Timer1 Mode2 (TMOD|=0x20) for baud
  • TH1 = 256 - Fosc/(384×baud). 11.0592MHz gives integer values
  • Write SBUF to send·read to receive. Clear TI/RI every time

Exercises

  1. Write an echo program that sends back received characters (uart_rx → uart_tx)
  2. Compute TH1 for 4800bps·19200bps and verify in pc_test
  3. Redefine putchar as UART send to print numbers with printf (and explain why it's heavy on 8051)

12. 7-Segment Display (Multiplexing)

Now we show human-readable digits. A 7-segment uses 7 bar LEDs (a~g) + a dot to draw 0–9. The keys: ① a lookup table mapping a digit to "which bars to light", and ② multiplexing (dynamic drive) that shows multiple digits while saving pins. We light "1234" one digit at a time, very fast, so persistence of vision makes all four appear lit at once.

Introduction

On top of lessons 6–7's port output we add a lookup table and time-division. This lesson assumes common cathode (bit=1 is ON). (μVision's simulator has no 7-segment graphic, so we verify the P0/P2 value cycling in the Port windows/Logic Analyzer, and the digit→code conversion in pc_test.)

Key concepts

1) 0–9 lookup table (common cathode)

Bit layout: a=0 b=1 c=2 d=3 e=4 f=5 g=6 dp=7. Each digit's segments are fixed, so precompute a table and fetch SEG_CODE[n].

c
static const unsigned char SEG_CODE[10] = {
  0x3F,0x06,0x5B,0x4F,0x66, 0x6D,0x7D,0x07,0x7F,0x6F
}; /* 0–9 */

2) Digit select — saving pins

Wiring 4 digits with 7 pins each needs 28 pins. Instead, the 4 digits share the segment pins (a~g) and a digit-select pin picks "which digit". That's 7+4=11 pins. But since segment lines are shared, only one digit can be lit at any instant.

3) Multiplexing and ghost prevention

c
1) turn off all digits  →  2) write the new segment pattern  →  3) turn on only that digit
⚠️

Changing the pattern while a digit is on briefly shows a wrong pattern there (ghosting). Always follow off → write → on.

Core example

c
#include <reg52.h>
#define SEG_PORT P2   /* segments */
#define DIG_PORT P0   /* digit select */
static const unsigned char SEG_CODE[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
static void delay(unsigned int n){ unsigned int i; for(i=0;i<n;i++){} }

void main(void){
    unsigned char digits[4] = {1,2,3,4};
    unsigned char d;
    while(1){
        for(d=0; d<4; d++){
            DIG_PORT = 0x00;                      /* 1) off (ghost prevention) */
            SEG_PORT = SEG_CODE[digits[d]];       /* 2) write pattern */
            DIG_PORT = (unsigned char)(1u << d);  /* 3) turn on only this digit */
            delay(150);
        }
    }
}

PC check: draw the lit segments as letters — 0→ABCDEF-, 1→-BC----, 8→ABCDEFG — to verify the lookup table.

Common mistakes

Q. Digits overlap or show faint ghosting.

A. You changed the pattern before turning the digit off. Always follow off (DIG_PORT=0) → write pattern → turn on digit.

Q. One digit is bright, others are dim.

A. The per-digit hold time is too long, or it stalls on one digit. Cycle the 4 digits fast enough (1–5ms each, ≥50Hz overall) for equal brightness.

Q. Common anode lights all or nothing.

A. This code assumes common cathode (bit=1 ON). For common anode, ON/OFF are reversed — invert (~) the segment code.

Summary

  • 7-segment draws digits with a~g(+dp); common cathode is ON at bit=1
  • Map digit→pattern fast with the lookup table SEG_CODE[n]
  • Sharing segment lines saves pins as you add digits (one digit at a time)
  • Multiplexing: cycle digits fast so persistence shows them all lit
  • Prevent ghosting: off → write pattern → turn on digit

Exercises

  1. Invert SEG_CODE (~) to drive a common-anode display
  2. Use a timer (lesson 9) to make a counter that increments 0000→0001… every second
  3. Extend the lookup table to show hex digits (A–F)
Example code / lecture materials

All lecture materials and example code (with homework and answers) are openly available on GitHub.

View on GitHub ↗

Comments 0

No comments yet. Be the first.

Only members can comment.

Comments with ads, abuse, personal information or illegal content are hidden or removed. Comments are shown with your nickname. Privacy Policy