MCS-51 UART  


This article is written in support of the UART tool I wrote for the MCS-51 series. Most of the MCS-51 (8051) derivatives have a similar architecture when it comes to UART processing, so this article should be applicable to most of them.

What is UART?

UART (Universal Asynchronous Receiver/Transmitter) is a hardware communication protocol that enables serial communication between devices. It is widely used in embedded systems for data exchange due to its simplicity and efficiency.

The invention of the UART goes back to Digital Equipment Corporation (DEC) in the 1960s, where it was developed as a means to facilitate serial communication between computers and peripheral devices. The UART converts parallel data from the CPU into serial form for transmission and vice versa for reception. In the early 80s IBM adopted the UART for their PCs with the 8250 UART chip, which became a standard for serial communication in personal computers. Later National Semiconductor developed the 16550 UART, which introduced a FIFO buffer to improve data handling and reduce CPU load. Most modern PCs retired the UART hardware in favor of USB, but the protocol is still widely used in embedded systems, and brought out as virtual COM ports over USB adapter chips, which in many cases have an 8051 core inside.

For the MCS-51 series, the predecessor was used as keyboard controller in IBM PCs, and later as serial port controllers. The UART protocol formed the basis of early DIN and PS/2 keyboard communication keyboard, and most implements in the 80s. The MCS-51 and derivatives were preferred choices as controllers for their low cost and ease of programming.

At its core, UART transmission involves a simple shift register mechanism. When data is to be sent, it is loaded into a transmit buffer register. The UART then serially shifts out the bits of the data, starting with a start bit, followed by the data bits, an optional parity bit, and finally one or more stop bits at a predefined baud rate, as shown in the Blog Image above. The image below shows an example fo the waveform of a UART transmission for the character A (ASCII 65, 0x41).

UART Waveform Example

8051 and 8052 UART implementations

As shown above UART is a simple protocol, and the MCS-51 architecture implements it in a straightforward manner. The 8051 and 8052 implementations support full-duplex communication, meaning that data can be sent and received simultaneously on different lines. The UART is controlled through several special function registers (SFRs), primarily the SCON (Serial Control) and SBUF (Serial Buffer) registers. The clock source can be the system clock, Timer 1, or Timer 2 (in the case of the 8052). Now it makes sense, why Timer 1 has a special mode (Mode 2) for baud rate generation. The 8051 does not implement a parity bit. You can use the 9th bit in modes 2 and 3 for parity, but you have to implement the parity calculation yourself in software. Further the 8051 UART does not support hardware flow control (RTS/CTS), so you have to implement this in software if needed too.

The clock modes are as follows:

MCS-51 UART Modes
ModeSM1SM0Description
Mode 0008-bit Shit Register
Mode 1018-bit Variable Baud Rate (by Timer)
Mode 2109-bit Shit Register
Mode 1119-bit Variable Baud Rate (by Timer)

The pins for the peripheral are mapped to P3.0 (RxD) and P3.1 (TxD).

The modes and the operation are controlled by the SCON and subsets of the PCON register.

SCON Register
BitSymbolDescription
7SM0Serial Port Mode 0
6SM1Serial Port Mode 1
5SM2Serial Port Mode 2
4RENReceiver Enable
3TB8Transmit Bit 8, 9th bit to transmit in 9-bit modes
2RB8Receive Bit 8, 9th bit to receive in 9-bit modes
1TITransmit Interrupt Flag
0RIReceive Interrupt Flag

Some offshoots of the MCS-51 series, like the STC89C52, abuse this register in with banking mode to provide additional features, like framing error detection. In this case the FE (Framing Error) flag is mapped to the SM0 bit, when PCON.SMOD0 is set. Note the TI and RI flags are set when transmission or reception is complete, and must be cleared by software (i.e. by your interrupt service routine implementation). As shown below.

Frame Error Detection

The PCON register bits relevant to UART operation are shown below.

PCON Register Relevant Flags
BitSymbolDescription
7SMODDouble Baud Rate in Mode 1 and 3
6SMOD0frame error enable (STC89C52 only)

Mode 0

Mode 0 and 2 are fixed-rate modes. The clock is generated by the system clock, and adjusted for mode 0 as follows:

fbr=fsys12f_{br} = dfrac{f_{sys}}{12}

Note, depending on system cycle implementation (12T, 6T, 1T):

fsys=fosc{12,6,1}f_{sys} = rac{f_{osc}}{ lbrace 12, 6, 1 brace }

Note that in this mode the transmission occurs in a shift register mode. There is no decoupling of transmit and receive. Transmit and receive occur synchronously. For transmission the data is clocked out on the RXD pin, while the TXD pin provides the clock, according to the above formula. On the classic 12T implementation the clock signal goes low on S3P1 and high on S6P2, as shown below.

UART Mode 0 Clocking

This transmission is initiated by any instruction that writes to SBUF. Reception is initiated by setting REN and making sure RI is cleared. By convention REN is set once during initialization, and left set for the duration of operation, and clearing RI starts the first and next reception.

This is an archaic mode that is not seen in modern applications. One possible application from the good old days is to use serial to parallel shift register like the 74HC595 to expand the number of output pins.

Mode 1 Asynchronous Mode with Variable Baud Rate

Compared to mode 0, mode 1 is a much more useful mode. It supports independent transmit and receive operations, and the baud rate is variable, determined by Timer 1 (or Timer 2 in the case of the 8052). The baud rate is calculated as follows:

fbr=2SMOD32×Timer 1 Overflow Ratef_{br} = rac{2^{ ext{SMOD}}}{32} imes ext{Timer 1 Overflow Rate}

or (only on 8052 with Timer 2)

fbr=2SMOD16×Timer 2 Overflow Ratef_{br} = rac{2^{ ext{SMOD}}}{16} imes ext{Timer 2 Overflow Rate}

Where the Timer 1 overflow rate is determined by the mode of Timer 1 and the value in TH1. In the common case of

Transmission or reception occurs as soon as the line drops low (start bit), and ends after 8 bits have been sent or received and the line goes high (stop bit). As shown below

UART Mode 0 Timing

The data is clocked into the SBUF register LSB first. In mode 0 the TB8 and RB8 bits are not used. To receive or transmit more data the SBUF register must be read or written to respectively, which starts the next transmission or reception. The TI and RI flags are set when transmission or reception is complete, and must be cleared by software. If RI is not cleared the next reception will not occur. This ensures that the software has read the received data before the next byte is received.

This is an archaic scheme, most modern Microcontrollers have a FIFO buffer with DMA for UART data, which allows multiple bytes to be received or transmitted without software intervention. But it is simple and effective for basic communication, but requires not to overload the interrupt service routine (ISR) processing to ensure smooth data flow. The best bet is to clear RI within the first few instructions of the ISR.

In terms of framing the receiver counts the 8 bits after the start bit, and expects the line to go high for the stop bit. If the line is still low after the 8 bits have been received, modern derivatives like the STC89C52 set the FE (Framing Error) flag in the SCON register (if enabled via PCON.SMOD0), indicating a framing error. This typically indicates a mismatch in baud rate or noise on the line.

In a practical application, you would implement some checksumming scheme or other error detection mechanism to ensure data integrity for any communication that interacts with the outside world.

Mode 2 and 3

Modes 2 and 3 are similar to modes 0 and 1, but they support 9-bit data transmission. The 9th bit is used for addressing or parity purposes. The baud rate calculations for mode 3 are the same as in and 1.

However, in mode 2 the baud rate is fixed and is calculated as follows:

fbr=fsys64f_{br} = dfrac{f_{sys}}{64}

For legacy multiprocessor communication the 9th bit can be used to indicate whether the byte is an address or data byte. This allows multiple devices to share the same communication line, with each device only responding to messages addressed to it. If SM2 is set, the receiver will only accept data bytes if the 9th bit is set, indicating an address byte.

When the master processor wants to transmit a block of data to one of several slaves, it first sends out an address byte which identifies the target slave.An address byte differs from a data byte in that the 9th bit is 1 in an address byte and 0 in a data byte. With SM2 set, no slave will be interrupted by a data byte. An address byte, however,will interrupt all slaves, so that each slave can examine the received byte and see if it is being addressed. The addressed slave will clear its SM2 bit and prepare to receive the data bytes that will be coming. The slaves that were not being addressed leave their SM2 registers set ignoring the coming data bytes.

The following diagram illustrates this process:

UART Multiprocessor Communication

Baud Rate Generation with Timer 1

The common way to generate variable baud rates for the MCS-51 UART is to use Timer 1 in Mode 2 (8-bit auto-reload). In this mode, Timer 1 counts from the value in TH1 up to 0xFF, overflows, and then reloads TH1 automatically. The overflow rate determines the baud rate for the UART in modes 1 and 3.

For typical baud rates and a 12T MCS-51 implementation with see the Table below. Please use the Baud Rate Tool to generate values for other clock rates and timer modes, or alternatively turn this into an Excel exercise.

Typical Baud Rate Configurations
Baud RateCrystalSMODTH1 ReloadActual RateError
960012.000 MHz10xF989237 %
240012.000 MHz00xF324040.16 %
120012.000 MHz00xE612020.16 %
1920011.059 MHz10xFD192000 %
960011.059 MHz00xFD96000 %
240011.059 MHz00xF424000 %
120011.059 MHz00xF812000 %

Assuming no clock errors, the maximum tolerable baud rate error is around 5% for reliable communication, in practice you want to keep it below 2% to be on the safe side.

Summary

In an era dominated by high-speed interfaces like USB and Ethernet, the humble UART remains remarkably relevant. Its simplicity, low pin count, and ease of implementation make it the go-to choice for debugging, device configuration, and interfacing with a vast array of sensors and modules, from GPS receivers to IoT GSM modems.

Understanding how to configure a timer to generate a precise baud rate, how to poll or use interrupts to manage data flow, and how a deliberate hardware choice like an 11.0592 MHz crystal can simplify the implementation.

Good reference:

That early MCS-51 UART silicon tapeout must have occurred around the time when Donna Summer came out with “On the Radio” in 1979, I wonder if the broadcast mode was inspired by this song? So if you do not interrupt on the right address byte, you might end up listening in broadcast mode… LOL

Youtube video:


Published: 2025-10-29
Updated  : 2025-10-29
Timers on the MCS-51 Series Microcontrollers →