Basic Applications of UART Serial Communication

Three Basic Types of Communication

The commonly used communication can be divided into three categories based on transmission direction: simplex communication, half-duplex communication, and full-duplex communication. Simplex communication allows information to be transmitted only in one direction, while the other party cannot send information back. For example, TV remote controls and radio broadcasts are both examples of simplex communication technology. Half-duplex communication allows data to be transmitted back and forth between both parties, but only one party can send information at a time. A typical example is our walkie-talkies. Full-duplex communication allows data to be sent and received simultaneously, just like our phone conversations, where we can hear the other person’s voice while speaking.

Introduction to UART Module

IO ports simulate serial communication, allowing everyone to understand the essence of serial communication. However, our microcontroller programs need to constantly check and scan the IO ports for received data, which takes up a lot of the microcontroller’s running time. At this point, clever people might think that we are not very concerned about the communication process itself; we only need the result of the communication, which is the received data. Thus, we can create a hardware module within the microcontroller to automatically receive data and notify us once it is done. Our 51 microcontroller has such a UART module. To use it correctly, we also need to configure the corresponding special function registers. The structure of the UART serial port in the 51 microcontroller consists of the Serial Control Register SCON, and the sending and receiving circuits. Let’s first understand the Serial Control Register SCON, as shown in Table 11-1 and Table 11-2.

Table 11-1 SCON – Bit Allocation of Serial Control Register (Address 0x98, Bit Addressable)
Bit 7 6 5 4 3 2 1 0
Symbol SM0 SM1 SM2 REN TB8 RB8 TI RI
Reset Value 0 0 0 0 0 0 0 0

Table 11-2 SCON – Description of Serial Control Register Bits

Bit Symbol Description
7 SM0 These two bits together determine the mode of serial communication, which can be Mode 0 to Mode 3, a total of 4 modes. The most commonly used is Mode 1, where SM0=0 and SM1=1. Below, we will focus on Mode 1, while the other modes will be briefly mentioned.
6 SM1
5 SM2 Multi-machine communication control bit (rarely used), directly cleared in Mode 1.
4 REN Enables serial reception. Set by software to enable reception, cleared by software to disable reception.
3 TB8 Data for the 9th bit to be sent in Modes 2 and 3 (rarely used).
2 RB8 Data for the 9th bit received in Modes 2 and 3 (rarely used), used to receive the stop bit in Mode 1.
1 TI Transmit interrupt flag. When the sending circuit reaches the middle position of the stop bit, TI is set to 1 by hardware and must be cleared by software.
0 RI Receive interrupt flag. When the receiving circuit reaches the middle position of the stop bit, RI is set to 1 by hardware and must be cleared by software.

Having learned so much about register configuration, I believe that for most students, the SCON register should no longer be a challenge, and they should be able to understand and configure it themselves. Among the four modes of the serial port, Mode 1 is the most commonly used, which we previously mentioned as having 1 start bit, 8 data bits, and 1 stop bit. Next, we will detail the working details and usage of Mode 1. As for the other three modes, they are quite similar, and when you encounter a need to use them, you can refer to the relevant materials. When we use IO ports to simulate serial communication, the baud rate of the serial port is reflected through the interrupt of Timer T0. In the hardware serial port module, there is a dedicated baud rate generator to control the speed of sending and receiving data. For the STC89C52 microcontroller, this baud rate generator can only be generated by Timer T1 or Timer T2, and cannot be generated by Timer T0, which is a completely different concept from our simulated communication. If using Timer 2, additional registers need to be configured, and by default, Timer 1 is used. In this chapter, we will mainly explain using Timer T1 as the baud rate generator. The baud rate generator in Mode 1 must use Timer T1 in Mode 2, which is the auto-reload mode. The calculation formula for the timer’s reload value is: TH1 = TL1 = 256 – Crystal Frequency / 12 / 2 / 16 / Baud Rate. There is also a power management register PCON related to baud rate, whose highest bit can double the baud rate. If you write PCON |= 0x80, the calculation formula becomes: TH1 = TL1 = 256 – Crystal Frequency / 12 / 16 / Baud Rate. Let me explain the meaning of the numbers in the baud rate formula. 256 is the overflow value of the 8-bit timer, which is also the overflow value of TL1. The crystal frequency on our development board is 11059200. The 12 indicates that one machine cycle equals 12 clock cycles. It is worth noting the 16; let me elaborate on this. When receiving data in IO port simulated serial communication, the data is sampled at the middle position of the bit. In reality, the serial port module is more complex and precise than our simulation. It samples a single signal 16 times, and if the 7th, 8th, and 9th samples are high, it determines that the bit data is 1; if two of the three samples are low, it determines that the bit is 0. This way, even if there is unexpected interference causing one incorrect data read, the final data correctness can still be ensured. Understanding the serial port sampling mode, I would like to leave you with a thought question: “What if the calculation of ‘Crystal Frequency / 12 / 2 / 16 / Baud Rate’ results in a non-integer or a decimal? What is the allowable deviation?” Once you understand this part, you will also understand why our crystal frequency is 11.0592M. The sending and receiving circuits of serial communication have two physically named SBUF registers, both with the address 0x99, but one is used for sending buffer and the other for receiving buffer. This means there are two rooms with the same room number; one only allows people to exit, while the other only allows people to enter. This way, we can achieve full-duplex communication with UART without interference. However, logically, we only operate on SBUF each time, and the microcontroller automatically selects whether to read from or write to the receiving or sending SBUF based on the operation performed on it. Later in the program, we will thoroughly understand this issue.

UART Serial Program

Generally, the basic steps for writing a serial communication program are as follows:

  1. Configure the serial port to Mode 1.

  2. Configure Timer T1 to Mode 2, which is auto-reload mode.

  3. Calculate the initial values of TH1 and TL1 based on the baud rate; if needed, use PCON to double the baud rate.

  4. Open the timer control register TR1 to start the timer.

It is particularly important to note that when using T1 as the baud rate generator, do not enable T1 interrupts. Let’s first look at the program code when directly changing from IO port simulated serial communication to using the hardware UART module. You will see that the program has become much simpler because most of the work is done by the hardware module for us. The program functionality is completely the same as that of IO port simulation.

#include <reg52.h>
void ConfigUART(unsigned int baud);
void main(){
ConfigUART(9600); // Configure baud rate to 9600
while (1){
while (!RI); // Wait for reception to complete
RI = 0; // Clear receive interrupt flag
SBUF = SBUF + 1; // Send back received data + 1
while (!TI); // Wait for transmission to complete
TI = 0; // Clear transmit interrupt flag
}
}
/* Serial port configuration function, baud - communication baud rate */
void ConfigUART(unsigned int baud){
SCON = 0x50; // Configure serial port to Mode 1
TMOD &= 0x0F; // Clear T1 control bits
TMOD |= 0x20; // Configure T1 to Mode 2
TH1 = 256 - (11059200/12/32)/baud; // Calculate T1 reload value
TL1 = TH1; // Initial value equals reload value
ET1 = 0; // Disable T1 interrupt
TR1 = 1; // Start T1
}

Of course, this program still uses the method of waiting for the receive and transmit interrupt flags in the main loop. In actual engineering development, this is not practical. We only used this for intuitive comparison to show students that hardware modules can greatly simplify program code. In actual use of the serial port, we will utilize serial port interrupts. Now let’s look at the program implemented using interrupts. Please note that since the same serial port interrupt triggers both receiving and sending, we must first determine which type of interrupt occurred in the serial port interrupt function and then handle it accordingly.

#include <reg52.h>
void ConfigUART(unsigned int baud);
void main(){
EA = 1; // Enable global interrupt
ConfigUART(9600); // Configure baud rate to 9600
while (1);
}
/* Serial port configuration function, baud - communication baud rate */
void ConfigUART(unsigned int baud){
SCON = 0x50; // Configure serial port to Mode 1
TMOD &= 0x0F; // Clear T1 control bits
TMOD |= 0x20; // Configure T1 to Mode 2
TH1 = 256 - (11059200/12/32)/baud; // Calculate T1 reload value
TL1 = TH1; // Initial value equals reload value
ET1 = 0; // Disable T1 interrupt
ES = 1; // Enable serial port interrupt
TR1 = 1; // Start T1
}
/* UART Interrupt Service Function */
void InterruptUART() interrupt 4{
if (RI){ // Byte received
RI = 0; // Manually clear receive interrupt flag
SBUF = SBUF + 1; // Send back received data + 1, left is sending SBUF, right is receiving SBUF
}
if (TI){ // Byte transmission completed
TI = 0; // Manually clear transmit interrupt flag
}
}

You can experiment to see if the effect is consistent with the previous implementation using IO port simulation, and the main loop is completely free, allowing us to add other functional codes at will.

Leave a Comment