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.
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
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.
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
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
#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
- Write an echo program that sends back received characters (uart_rx → uart_tx)
- Compute TH1 for 4800bps·19200bps and verify in pc_test
- 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].
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
1) turn off all digits → 2) write the new segment pattern → 3) turn on only that digitChanging the pattern while a digit is on briefly shows a wrong pattern there (ghosting). Always follow off → write → on.
Core example
#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
- Invert SEG_CODE (~) to drive a common-anode display
- Use a timer (lesson 9) to make a counter that increments 0000→0001… every second
- Extend the lookup table to show hex digits (A–F)
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