8251 USART and Serial Communication Basics
Talking One Bit at a Time
Open interactive version (quiz + challenge)Real-world analogy
What is it?
The 8251 USART (Universal Synchronous/Asynchronous Receiver/Transmitter) converts parallel data from the CPU into serial bit streams for transmission, and converts incoming serial bit streams back into parallel bytes. It supports both asynchronous mode (with start/stop bits, configurable baud rates) and synchronous mode (continuous clocked data). Programming involves writing a Mode Word (data format) and Command Word (enable Tx/Rx), then polling status bits to send and receive data.
Real-world relevance
Serial communication is everywhere. Your computer's USB ports evolved from the serial port concept. Bluetooth, Wi-Fi, and cellular data are all serial at the physical layer. Arduino boards use UART to communicate with your PC. Modems, GPS modules, and industrial sensors still use classic RS-232 serial (the same standard the 8251 was designed for). Understanding serial framing — start bits, stop bits, parity — is fundamental to debugging any communication protocol.
Key points
- Serial vs Parallel Communication — Parallel sends all 8 bits simultaneously on 8 wires — fast but requires many connections. Serial sends bits one at a time on 1-2 wires — slower per clock but needs far fewer wires, making it practical for long distances and connections between separate devices. At high speeds, serial actually wins because parallel wires suffer from timing skew — which is why USB, Ethernet, and SATA are all serial.
- UART vs USART — UART (Universal Asynchronous Receiver/Transmitter) handles only asynchronous communication — no shared clock between sender and receiver. USART (Universal Synchronous/Asynchronous Receiver/Transmitter) supports BOTH asynchronous and synchronous modes. Async uses start/stop bits to frame each byte. Sync uses a shared clock signal, sending data continuously without start/stop overhead.
- Asynchronous Frame Format — In async mode, each byte is wrapped in a frame: 1 start bit (always LOW), 5-8 data bits (LSB first), optional parity bit (error detection), and 1-2 stop bits (always HIGH). The start bit alerts the receiver that data is coming. The stop bit(s) provide idle time before the next frame. The line is HIGH when idle (no data).
- Baud Rate and Bit Timing — Baud rate is the number of signal changes (bits) per second. At 9600 baud, each bit lasts 1/9600 = 104.17 microseconds. Common baud rates: 300, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200. The 8251 uses an external clock (TxC/RxC) at 1x, 16x, or 64x the baud rate. With 16x oversampling, the receiver samples each bit 16 times and picks the middle samples for reliability.
- 8251 Architecture and Pins — The 8251 connects to the CPU via D0-D7 (data bus), C/D (command/data select, like A0), RD, WR, CS, and CLK. Serial side: TxD (transmit data output), RxD (receive data input), TxC (transmit clock), RxC (receive clock), plus handshaking signals TxRDY, RxRDY, TxE (transmitter empty), DTR, DSR, RTS, CTS for modem control.
- Mode Word — Configuring the 8251 — After reset, the first write to the control register is the Mode Word. It sets: sync/async mode, character length (5-8 bits), parity enable and type (even/odd), number of stop bits (1, 1.5, or 2), and baud rate factor (1x/16x/64x). The mode word is written ONCE after reset — to change it, you must reset the 8251 again.
- Command Word — Controlling Operation — After the mode word, subsequent writes to the control register are Command Words. The command word enables/disables the transmitter and receiver, controls DTR and RTS signals, sends a break character, performs error reset, and can force an internal reset (to re-enter mode word). TxEN must be set to transmit, RxEN must be set to receive.
- Transmitting and Receiving Data — To transmit: check TxRDY status bit (or read port status register bit 0), then write the byte to the data register. The 8251 serializes it and sends on TxD. To receive: check RxRDY status bit (status register bit 1), then read the data register. The 8251 has already deserialized the incoming bits into a byte. Error flags (parity, overrun, framing) are in the status register.
- Start/Stop/Parity Bits Explained — The start bit (always 0/LOW) tells the receiver 'a byte is beginning — start your bit timer!' Data bits follow (LSB first). The optional parity bit provides simple error detection: even parity makes total 1s even, odd parity makes them odd. Stop bits (always 1/HIGH) give the receiver time to process and return to idle. The classic '8N1' format means 8 data bits, No parity, 1 stop bit.
Code example
; =============================================
; 8251 USART — Complete Serial Setup
; =============================================
;
; 8251 at base 80h (data) and 81h (control/status)
; Format: 9600 baud, 8N1, 16x clock
;
; === INITIALIZATION ===
;
; After power-up, send 3 dummy bytes + reset
; (ensures 8251 is in known state)
MOV AL, 00h
OUT 81h, AL ; dummy 1
OUT 81h, AL ; dummy 2
OUT 81h, AL ; dummy 3
MOV AL, 40h ; internal reset command
OUT 81h, AL ; reset 8251
;
; Mode Word: 8 data, no parity, 1 stop, 16x
; 01 00 11 10 = 4Eh
MOV AL, 4Eh
OUT 81h, AL ; write mode word
;
; Command Word: enable Tx, Rx, DTR, RTS
; 0 0 1 1 0 1 1 1 = 37h
MOV AL, 37h
OUT 81h, AL ; write command word
;
; === SEND STRING ===
;
LEA SI, MSG ; point to message
SEND_LOOP:
LODSB ; AL = next char, SI++
CMP AL, 0 ; null terminator?
JE DONE
;
TX_POLL:
IN AL, 81h ; read status
TEST AL, 01h ; TxRDY?
JZ TX_POLL ; wait for ready
LODSB ; reload char (LODSB advanced SI)
DEC SI ; fix SI
MOV AL, [SI-1] ; get the char
OUT 80h, AL ; transmit byte
JMP SEND_LOOP
;
DONE:
; Transmission complete
;
MSG DB 'Hello Serial!', 0Dh, 0Ah, 0
;
; === RECEIVE BYTE ===
RX_POLL:
IN AL, 81h ; read status
TEST AL, 02h ; RxRDY?
JZ RX_POLL
IN AL, 80h ; AL = received byteLine-by-line walkthrough
- 1. Title comment for 8251 USART setup
- 2. Separator line
- 3. Blank line
- 4. 8251 ports: 80h for data read/write, 81h for control/status
- 5. Configuration: 9600 baud, 8 data bits, no parity, 1 stop bit, 16x clock
- 6. Blank line
- 7. === INITIALIZATION SECTION ===
- 8. Blank line
- 9. After power-up, the 8251 might be in an unknown state
- 10. Write three dummy bytes to flush any pending mode/command bytes
- 11. First dummy write to control port
- 12. Second dummy write
- 13. Third dummy write
- 14. Load internal reset command (bit 6 = 1) into AL
- 15. Send reset command — 8251 is now waiting for a mode word
- 16. Blank line
- 17. Mode word for 8 data, no parity, 1 stop, 16x oversampling
- 18. Binary breakdown: 01 (1 stop) 00 (no parity) 11 (8 bits) 10 (16x) = 4Eh
- 19. Write mode word to control register — 8251 format is now set
- 20. Blank line
- 21. Command word: enable transmitter, receiver, DTR, and RTS lines
- 22. Binary: 00110111 = 37h — TxEN, DTR, RxEN, RTS all set to 1
- 23. Write command — 8251 is now active and ready to communicate
- 24. Blank line
- 25. === SEND STRING SECTION ===
- 26. Blank line
- 27. Load address of the null-terminated message string into SI
- 28. SEND_LOOP label — iterate through each character
- 29. LODSB loads byte at [SI] into AL and increments SI
- 30. Check if AL is zero (null terminator marks end of string)
- 31. If null, jump to DONE — message fully transmitted
- 32. Blank line
- 33. TX_POLL label — wait for transmitter to be ready
- 34. Read status register from port 81h
- 35. Test bit 0 (TxRDY) — is the transmitter ready for a new byte?
- 36. If TxRDY is 0, loop back and poll again
- 37. Reload the character (SI was advanced by LODSB)
- 38. Adjust SI back to correct position
- 39. Get the character byte
- 40. Write byte to data register at port 80h — 8251 serializes and transmits it
- 41. Jump back to send the next character
- 42. Blank line
- 43. DONE label — all characters sent
- 44. Blank line
- 45. MSG: the message bytes in memory including carriage return and line feed
- 46. Blank line
- 47. === RECEIVE SECTION ===
- 48. RX_POLL: poll for incoming data
- 49. Read status register
- 50. Test bit 1 (RxRDY) — has a complete byte been received?
- 51. If not, keep polling
- 52. Read received byte from data register — AL contains the deserialized byte
Spot the bug
; Initialize 8251: 8 data, even parity, 1 stop, 16x
;
; Mode word:
; B1B0 = 10 (16x)
; L1L0 = 11 (8 bits)
; PEN = 1 (parity on)
; EP = 1 (even)
; S1S0 = 01 (1 stop)
;
MOV AL, 01 11 1 1 11 10 ; = 7Eh
OUT 81h, AL
;
; Command word: enable Tx and Rx
MOV AL, 05h ; TxEN + RxEN
OUT 80h, AL ; write to data portNeed a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- 8251 USART Explained (Neso Academy)
- 8251 USART Architecture and Programming (GeeksforGeeks)
- Intel 8251A Datasheet (Intel)
- Serial Communication Basics (Ben Eater)