UART Serial Communication Template (Feel free to take it)

UART Serial Communication Template (Feel free to take it) UART is frequently used in our embedded development process. It is quite simple, but to avoid reinventing the wheel, I have specifically summarized the serial communication software into a template for everyone to reference~Generally, our serial protocol format is as follows:

[Frame Header] [Length] [Data Field] [Checksum] [Frame Tail]
  • Frame Header: A fixed byte sequence (0xAA, 0x55), used to synchronize the start of the frame (Key role: solves the “sticky packet” problem in byte streams).
  • Length: The number of bytes in the data field (used to dynamically determine the frame length).
  • Data Field: The actual valid data (can include commands, parameters, etc.).
  • Checksum: Such as checksum (Checksum), CRC16/32, used to verify data integrity.
  • Frame Tail: Fixed byte (such as<span><span>0xCC</span></span>), assists in confirming the end of the frame (optional, can be omitted if the length field is reliable enough).

First up is the initialization of UART. Since this varies for each chip, please adjust according to your actual situation.

void uart_init(void){    printf("uart0_init\n");}

Next is the UART send function, which is also straightforward. Each chip has its corresponding transmission function interface, essentially writing the data to be sent into the register.

void uart_send(u8 *data, uint len){    for(uint i = 0; i < len; i++) {       uart_putc(data[i]);    }}

Finally, we have the UART interrupt receive function, which is the key point~As the receiver, we must consider the data processing capability to avoid missing any data sent by the other party. Therefore, we can create a circular buffer array rx_buffer in the receive interrupt to store the data from the other party.

#define BUFFER_SIZE  128u8 rx_buffer[BUFFER_SIZE];  // Buffer arrayvolatile u8 rx_head = 0;    // Write pointer (modified by ISR)volatile u8 rx_tail = 0;    // Read pointer (modified by main loop)volatile u8 buffer_full = 0;// Buffer overflow flagvoid uart_isr_func(void){    if ( UARTCON & BIT(9)) {  //RX Pending        UARTCPND = BIT(9);        u8 data = UARTDATA;   // Received one byte of data        // Write to circular buffer (need to check for overflow)        uint8_t next_head = (rx_head + 1) % BUFFER_SIZE;        if (next_head != rx_tail) {  // Buffer not full            rx_buffer[rx_head] = data;            rx_head = next_head;        } else {            buffer_full = 1;  // Mark overflow (can be handled as needed: overwrite/discard)        }    }}

After storing the data, since we cannot perform complex parsing in the interrupt, we need to call the parsing function parse_protocol in the loop to parse the received rx_buffer. The parsing function includes two aspects: one is to determine the validity of the data sent by the other party, and the other is to process the response to valid data.

// Parsing state definitiontypedef enum {    STATE_WAIT_HEADER1,  // Waiting for the first byte of the frame header (e.g., 0xA5)    STATE_WAIT_HEADER2,  // Waiting for the second byte of the frame header (e.g., 0x11)    STATE_WAIT_LEN,      // Waiting for the length byte (e.g., 0x01)    STATE_FUNC,          // Waiting for the scope (e.g., 0x03)    STATE_WAIT_DATA,     // Receiving data field (e.g., 0x00)    STATE_WAIT_CHECKSUM, // Waiting for checksum (e.g., 0xA1)    STATE_WAIT_TAIL      // Waiting for frame tail (e.g., 0xCC)} ParseState;// Frame parsing context (records temporary data of the current frame)typedef struct {    ParseState state;       // Current parsing state    uint8_t data_len;       // Length of the data field (parsed from the frame)    uint8_t data_received;  // Number of received data bytes    uint8_t frame_data[64]; // Stores the data field of the current frame    uint8_t checksum;       // Checksum (temporarily calculated)} FrameParser;// Initialize parserFrameParser parser = {    .state = STATE_WAIT_HEADER1,    .data_len = 0,    .data_received = 0,    .checksum = 0}; // Read a byte from the buffer (called by the main loop, need to check if the buffer is not empty)uint8_t read_buffer(uint8_t *data) {    if (rx_head == rx_tail) return 0;  // Buffer empty    *data = rx_buffer[rx_tail];    rx_tail = (rx_tail + 1) % BUFFER_SIZE;    return 1;}// Business logic: process complete framevoid process_frame(uint8_t *data, uint8_t len) {    u8 uart_ack_buf[5] = {0xA8,0x11,0x03,0x1,0xCC};    // Execute command based on data field content    if(data[0] == 0x00) {    } else if(data[0] == 0x01) {    } else if(data[0] == 0x02) {    }    uart0_send(uart_ack_buf,5); // Acknowledgment after receiving completion}void parse_protocol(void){    uint8_t data;    while (read_buffer(&data)) {  // Loop to read all bytes from the buffer        switch (parser.state) {            case STATE_WAIT_HEADER1:                if (data == 0xAA) {  // Match the first byte of the frame header                    parser.state = STATE_WAIT_HEADER2;                }                break;            case STATE_WAIT_HEADER2:                if (data == 0x55) {  // Match the second byte of the frame header                    parser.state = STATE_WAIT_LEN;                    parser.checksum = 0;  // Reset checksum                } else {                    // Incomplete frame header, revert to waiting for the first byte (fault tolerance)                    parser.state = STATE_WAIT_HEADER1;                }                break;             case STATE_WAIT_LEN:                parser.data_len = data;        // Record data field length                parser.data_received = 0;      // Reset reception count                parser.checksum += data;       // Accumulate checksum with length byte                if (parser.data_len == 0) {    // If data field length is 0, jump directly to checksum                    parser.state = STATE_WAIT_CHECKSUM;                } else {                    parser.state = STATE_WAIT_DATA;                }                break;             case STATE_WAIT_DATA:                // Store data and accumulate checksum                parser.frame_data[parser.data_received] = data;                parser.checksum += data;                parser.data_received++;                // Data field reception complete, jump to checksum                if (parser.data_received >= parser.data_len) {                    parser.state = STATE_WAIT_CHECKSUM;                }                break;             case STATE_WAIT_CHECKSUM:                if (data == parser.checksum) {  // Checksum passed                    parser.state = STATE_WAIT_TAIL;                } else {                    // Checksum failed, discard current frame, reset state                    parser.state = STATE_WAIT_HEADER1;                }                break;                             case STATE_WAIT_TAIL:                if (data == 0xCC) {  // Match frame tail, frame parsing complete                    // Process complete frame (call business logic here)                    process_frame(parser.frame_data, parser.data_len);                }                // Regardless of whether the frame tail matches, reset state to wait for the next frame                parser.state = STATE_WAIT_HEADER1;                break;        }    }}

<span>That's it for the serial protocol template. You can modify it slightly based on your protocol documentation.</span><span>I will continue to provide practical content in the future~</span>

Leave a Comment