Common Methods for Receiving and Parsing UART Data

Follow+Star PublicAccount, don’t miss out on exciting content

Common Methods for Receiving and Parsing UART Data

Author | strongerHuang

WeChat Public Account | strongerHuang

The UART serial port is a common communication method in embedded development, but many people still do not know how to use the serial port.

Today, I will share a few points about serial ports:

  • Serial Port Receiving Methods

  • Processing Received Data

  • Communication Protocol Parsing

Serial Port Receiving Methods

Common methods for receiving data from the serial port (the other end of communication) include:

  • Polling (query) the receive register

  • Interrupt to receive data

Polling, means querying the receive register for data at regular intervals (usually milliseconds, or even microseconds). If there is data, process the received data.

Interrupt, when there is no data being received, the CPU performs its own tasks. When data is received, the UART controller will respond to the interrupt and notify the CPU that there is work to be done.

Have you considered the drawbacks of polling?

Low efficiency: The CPU spends most of its time querying;

Non-real-time response: If multiple data packets are received in a short time, and the CPU is busy processing a relatively time-consuming task (e.g., sending a data packet), it may not query the received data in time, leading to potential data loss. (Especially in earlier years when serial ports did not have FIFO functionality)

Therefore, whether it is a UART serial port, or I2C, SPI, CAN, and other serial communications, the most commonly used method is interrupt reception, and polling is rarely used.

I previously maintained some old code (a pit), where the CLI serial port used polling, leading to data loss, overflow errors, and many other issues, which made me work overtime several times…

Processing Received Data

When an interrupt indicates that data has arrived, how do you process the received data?

I have seen some small projects that directly perform some application logic inside the interrupt function. For example: the serial port interrupt receives data sent from a sensor, displays the data and performs some responsive actions.

Keep the interrupt function code as minimal as possible, and minimize time-consuming logic and applications.

When data arrives via interrupt, it is generally processed using FIFO.

1. Simple array reception, application parsing, and processing

For example:

static uint8_t gRxCnt = 0;static uint8_t gRxBuf[10];
void USART1_IRQHandler(void){  //...  gDgus_RxBuf[gRxCnt] = (uint8_t)USART_ReceiveData(USART1);  gRxCnt++;  //...  }
void App(void){  //...  if(0 < gRxCnt)  {    // Copy received data    gRxCnt = 0;    // Parse received data and process  }}

2. Receive a complete frame of data in the interrupt function before processing

For example:

void USART1_IRQHandler(void){  static uint8_t RxCnt = 0;                      // Count value  static uint8_t RxNum = 0;                      // Number
  if((USART1->SR & USART_FLAG_RXNE) == USART_FLAG_RXNE)  {    gDgus_RxBuf[RxCnt] = (uint8_t)USART_ReceiveData(USART1);    RxCnt++;
    /* Check frame header */    if(gDgus_RxBuf[0] != DGUS_FRAME_HEAD1)       // Received frame header 1    {      RxCnt = 0;      return;    }    if((2 == RxCnt) && (gDgus_RxBuf[1] != DGUS_FRAME_HEAD2))    {      RxCnt = 0;      return;    }
    /* Determine the length of a frame of data */    if(RxCnt == 3)    {      RxNum = gDgus_RxBuf[2] + 3;    }
    /* Received a complete frame of data */    if((6 <= RxCnt) && (RxNum <= RxCnt))    {      RxCnt = 0;      OSMboxPost(EventMBox_Touch, gDgus_RxBuf);  // Send message to mailbox (execute touch operation)    }  }}

After parsing a complete frame of data in the interrupt function, you can notify the application through flags (in bare-metal systems), or send it to the application via message queues or mailboxes (in RTOS systems).

3. RTOS queue and mailbox reception

For example:

void DEBUG_COM_IRQHandler(void){  static uint8_t Data;
  if(USART_GetITStatus(DEBUG_COM, USART_IT_RXNE) != RESET)  {    Data = USART_ReceiveData(DEBUG_COM);    CLI_RcvDateFromISR(Data); // Below this function has been separated out  }}
void CLI_RcvDateFromISR(uint8_t RcvData){  static portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
  if(xCLIRcvQueue != NULL)  {    xQueueSendFromISR(xCLIRcvQueue, &RcvData, &xHigherPriorityTaskWoken);  }}

When an interrupt indicates that a byte of data has arrived, it sends that byte of data through the message queue. If the data is not processed in time, it is still stored in the queue.

Communication Protocol Parsing

In the second method above, for simple communication protocols, it is acceptable to handle processing directly within the interrupt function for relatively smaller projects.

However, if the project is larger and more complex, or if the protocol is more complicated, then the second method as described is not advisable.

1. Bare-metal environment

In a bare-metal environment, it is recommended to use the first method: interrupt array to buffer data (FIFO), and let the application parse the communication protocol.

2. RTOS environment

In an RTOS environment, it is recommended to use the third method: receive data through message queues, mailboxes, etc., and then send (notify) the application to parse the protocol.

Of course, the above are just common methods; specific implementations need to consider your project’s actual situation.

Similarly, other communications like I2C, CAN, etc., if there are protocol parsing needs, follow a similar approach.

For example, in a previous sharing about MavLink, I implemented it using CAN:

void CAN_RX_IRQHandler(void){  static CanRxMsg RxMessage;  static MAVRCV_QUEUE_TypeDef MAVRcvQueue_Union;
  CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);                                                 // Copy length, data  MAVRcvQueue_Union.MAVRcvStruct.MAVLink_Len = RxMessage.DLC;  memcpy(&MAVRcvQueue_Union.MAVRcvStruct.MAVLink_Buf[0], &RxMessage.Data[0], RxMessage.DLC);
  MAVLink_RcvDateFromISR(&MAVRcvQueue_Union.MAVLinkRcv_Queue[0]);}

Finally, the above content is just to provide ideas; the code may not necessarily fit your project.

———— END ————

Common Methods for Receiving and Parsing UART Data

● Column “Embedded Tools”

● Column “Embedded Development”

● Column “Keil Tutorial”

● Selected Tutorials from the Embedded Column

Follow the public account and reply “Join Group” to join the technical exchange group according to the rules, reply “1024” to see more content.

Common Methods for Receiving and Parsing UART Data

Common Methods for Receiving and Parsing UART Data

Click “Read Original” to see more shares.

Leave a Comment