When you first start learning about microcontrollers, serial communication is often used, but in actual products, serial communication requires a communication protocol. Many people don't understand why communication protocols are used, how to define communication protocols, and how to write programs with communication protocols. Today, let's talk about how serial communication protocols are defined.
Let’s first look at the simplest serial port program.
void Uart1_Init( unsigned int baudrate )
{
unsigned int baud;
baud = 16000000 / baudrate;
Uart1_IO_Init(); //IO port initialization
UART1_CR1 = 0;
UART1_CR2 = 0;
UART1_CR3 = 0;
UART1_BRR2 = ( unsigned char )( ( baud & 0xf000 ) >> 8 ) | ( ( unsigned char )( baud & 0x000f ) );
UART1_BRR1 = ( ( unsigned char )( ( baud & 0x0ff0 ) >> 4 ) );
UART1_CR2_bit.REN = 1; //Receive enable
UART1_CR2_bit.TEN = 1; //Send enable
UART1_CR2_bit.RIEN = 1; //Receive interrupt enable
}
//Blocking send function
void SendChar( unsigned char dat )
{
while( ( UART1_SR & 0x80 ) == 0x00 ); //Send data register empty
UART1_DR = dat;
}
//Receive interrupt function
#pragma vector = 20
__interrupt void UART1_Handle( void )
{
unsigned char res;
res = UART1_DR;
return;
}
There are three main functions: an initialization function, a sending function, and a receiving interrupt. First, define a simple protocol, for example: when a 1 is received, the LED light turns on, and when a 0 is received, the LED light turns off. Then the receiving interrupt function can be modified to:
#pragma vector = 20
__interrupt void UART1_Handle( void )
{
unsigned char res;
res = UART1_DR;
if( res == 0x01 )
LED = 1;
else if( res == 0 )
LED = 0;
return;
}
Directly judge the received data value and control the state of the LED light according to the data value.
What if you need to control two LED lights? You need to send two data to control the status of the LED lights.
Next, we will make the protocol more complicated. The first bit of data controls LED1, and the second bit of data controls LED2. Similarly, 1 turns on the LED, and 0 turns off the LED. If this is the case, when receiving data, we cannot control the LED status after receiving one bit of data, because there are two bits of data to be sent this time, and we must distinguish the first and second bits of data. So we can consider using an array to receive data. Modify the program as follows:
#pragma vector = 20
unsigned char res[2];
unsigned char cnt=0;
__interrupt void UART1_Handle( void )
{
res[ cnt++ ] = UART1_DR;
if( res[ 0 ] == 0x01 )
LED1 = 1;
else if( res[ 0 ] == 0 )
LED1 = 0;
if( res[ 1 ] == 0x01 )
LED2 = 1;
else if( res[ 1 ] == 0 )
LED2 = 0;
return;
}
In this way, the received data is stored in an array, and then the subscript is used to determine the data to control the state of the LED light.
If you need to control three LEDs, the data to be sent must be increased by one bit. Adding a third LED, the data can be received in the same way. Modify the program as follows:
#pragma vector = 20
unsigned char res[3];
unsigned char cnt=0;
__interrupt void UART1_Handle( void )
{
res[ cnt++ ] = UART1_DR;
if( res[ 0 ] == 0x01 )
LED1 = 1;
else if( res[ 0 ] == 0 )
LED1 = 0;
if( res[ 1 ] == 0x01 )
LED2 = 1;
else if( res[ 1 ] == 0 )
LED2 = 0;
if( res[ 2 ] == 0x01 )
LED3 = 1;
else if( res[ 2 ] == 0 )
LED3 = 0;
return;
}
In this way, even if more LED controls are added, the communication is still applicable. It seems that such a protocol is sufficient for use. But think about it carefully, this protocol seems to have no problem. The only disadvantage is that it will treat each received data as a valid command. If the host computer does not send a command to light up the LED, but there is interference on the serial port line, if the interference signal happens to be 0 or 1, then the program may malfunction. It is necessary to use a flag to determine whether the currently sent data is a real command or interference when receiving data. Use a special value as the beginning of the receiving instruction. Here, 0XA5 is selected as the beginning of the data. Why 0xA5 is selected? Because the binary digits of 0xA5 1010 0101 are just one 1 and one 0, separated. Using this number as the beginning can largely avoid interference signals, because interference signals are generally not such high-low, high-low and very regular signals. So the protocol is changed to 0xA5 as the first data, as a flag for the host computer to send a command, and then 0 and 1 are used to represent the state of the LED. 0 means the LED is off, and 1 means the LED is on. If we need to light up 3 LEDs, the command sent by the host computer is 0xA5 0x01 0x01 0x01, a start mark, followed by three control signals. The program is modified as follows:
#pragma vector = 20
unsigned char res[4];
unsigned char cnt=0;
__interrupt void UART1_Handle( void )
{
res[ cnt++ ] = UART1_DR;
if( res[ 0 ] == 0xA5 )
{
if( res[ 1 ] == 0x01 )
LED1 = 1;
else if( res[ 1 ] == 0 )
LED1 = 0;
if( res[ 2 ] == 0x01 )
LED2 = 1;
else if( res[ 2 ] == 0 )
LED2 = 0;
if( res[ 3 ] == 0x01 )
LED3 = 1;
else if( res[ 3 ] == 0 )
LED3 = 0;
}
return;
}
In this way, when the first data is received, it is first determined whether it is 0xA5. If it is 0xA5, it means that it is the instruction sent, and the subsequent commands are executed. If the first data is not 0xA5, it means that it is an interference signal, and the command is not executed. In this way, interference signals can be avoided, which may cause the program to malfunction. Is this okay? Careful analysis, if the interference signal does not occur at the beginning of the data, but occurs at the end of the data, for example, I only need to control two LED lights now, the instruction is 0xA5 0x01 0x01, but after receiving the first few data, interference occurs, and the data has an extra 0x01, then the data received by the microcontroller becomes 0xA5 0x01 0x01 0x01, causing the third light to be turned on by mistake. In order to avoid this interference, an end mark can be added to represent the end of sending data, and 0x5A is used as the end, which is exactly the opposite of the start mark. Then if you want to control two lights at this time, the data sent becomes 0xA5 0x01 0x01 0x5A. The code is modified to:
#pragma vector = 20
unsigned char res[4];
unsigned char cnt=0;
__interrupt void UART1_Handle( void )
{
res[ cnt++ ] = UART1_DR;
if( ( res[ 0 ] == 0xA5 )&&( res[ 3 ] == 0x5A ) )
{
if( res[ 1 ] == 0x01 )
LED1 = 1;
else if( res[ 1 ] == 0 )
LED1 = 0;
if( res[ 2 ] == 0x01 )
LED2 = 1;
else if( res[ 2 ] == 0 )
LED2 = 0;
}
return;
}
In this way, by simultaneously judging the start and end flags of the sent data, it is ensured that the received data is a real command and data interference is avoided. However, after careful observation, a new problem was discovered. The data position subscript of the end flag is fixed, which means that only 4 bytes can be sent each time data is sent, which means that only two LED lights can be controlled each time. If you want to increase the number of controlled LED lights, you have to modify the program. This is very inconvenient in actual operation. Can you dynamically identify how many data have been sent? So I thought that when sending instructions, I tell the microcontroller that I want to control the number of LEDs. The microcontroller automatically determines how many LED lights need to be lit according to the number value. Therefore, the protocol is modified to add another bit after the start flag, representing the number of LED lights to be controlled, followed by the light or extinguish command, and finally the end flag. If you want to light up 2 LED lights now, the data sent is: 0xA5 0x02 0x01 0x01 0x5A, and the 0x02 after the start flag means that two LED lights are to be controlled. If you want to light up 3 lights, the data sent is 0xA5 0x03 0x01 0x01 0x01 0x5A. So how do you determine the position of the end mark? By observing the above two sets of data, we can find that if we control 2 LED lights, the end mark is at the 4th position, and if we control 3 LED lights, the end mark is at the 5th position. The position of the end mark is just 2 more than the number of controlled LED lights, so the program is modified as follows:
#pragma vector = 20
unsigned char res[5];
unsigned char cnt=0;
unsigned char num=0;
__interrupt void UART1_Handle( void )
{
res[ cnt++ ] = UART1_DR;
if( res[ 0 ] == 0xA5 )
{
num = res[1];
if( res [ num + 2] == 0x5A )
{
if( num == 3 )
{
if( res[ 2 ] == 0x01 )
LED1 = 1;
else if( res[ 2 ] == 0 )
LED1 = 0;
if( res[ 3 ] == 0x01 )
LED2 = 1;
else if( res[ 3 ] == 0 )
LED2 = 0;
if( res[ 4 ] == 0x01 )
LED2 = 1;
else if( res[ 4 ] == 0 )
LED2 = 0;
}
if( num == 2 )
{
if( res[ 2 ] == 0x01 )
LED1 = 1;
else if( res[ 2 ] == 0 )
LED1 = 0;
if( res[ 3 ] == 0x01 )
LED2 = 1;
else if( res[ 3 ] == 0 )
LED2 = 0;
}
}
}
return;
}
By adding a quantity judgment to the protocol, the program can dynamically set the state of the LED light. However, I feel that there are too many codes in the serial port interrupt. Data reception and data processing are placed in one function, which makes the program difficult to read. Can the data reception and data processing be separated? Then you can only receive data in the serial port interrupt function. Set the flag bit after the data is received, and then process the received data in the main function. So modify the program to:
#pragma vector = 20
unsigned char res[5];
unsigned char cnt = 0;
unsigned char num = 0;
unsigned char receive_ok = 0;
__interrupt void UART1_Handle( void )
Previous article:STM8 learning notes---How to determine the register name in the program
Next article:STM8L RTC Summary (2) Initialization and Configuration
Recommended ReadingLatest update time:2024-11-16 11:45
- Popular Resources
- Popular amplifiers
- STM8 C language programming (1) - basic program and startup code analysis
- Description of the BLDC back-EMF sampling method based on the STM8 official library
- STM32 MCU project example: Smart watch design based on TouchGFX (8) Associating the underlying driver with the UI
- uCOS-III Kernel Implementation and Application Development Practical Guide - Based on STM32 (Wildfire)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications