Follow+Star Public Account Number, don’t miss out on exciting contentSource | NetworkUsing FIFO in MCU communication can prevent packet loss due to large data volumes. Today, I will discuss a method for implementing FIFO through a custom communication protocol format.
1. Overview
First, let’s list the shortcomings of traditional serial port data transmission and reception:
- Each byte of received data generates a receive interrupt. This does not effectively utilize the serial port hardware FIFO, leading to an increased number of interrupts.
- Response data is sent using a wait-and-send method. Since the time for serial data transmission is far slower than the CPU processing time, waiting for the serial port to send the current byte before sending the next byte wastes CPU resources and negatively impacts overall system responsiveness (at 1200bps, sending one byte takes about 10ms; if sending dozens of bytes, the CPU remains in a waiting state for a long time).
- Response data is sent using interrupt-driven methods. This adds an additional interrupt source, increasing the overall interrupt count, which can affect system stability (from a reliability perspective, the fewer interrupt events, the better).
- To address the above shortcomings, we will combine a commonly used custom communication protocol to provide a complete solution.
2. Serial Port FIFO
The serial port FIFO can be understood as a dedicated buffer for the serial port, which operates on a first-in, first-out basis. The data reception FIFO and data transmission FIFO are usually two independent hardware components.Data received by the serial port is first placed into the reception FIFO. When the data in the FIFO reaches a trigger value (usually 1, 2, 4, 8, or 14 bytes) or if the data in the FIFO has not reached the set value but no new data has been received for a certain period (usually the time to transmit 3.5 characters), the CPU is notified to generate a receive interrupt; data to be sent must first be written into the transmission FIFO, and as long as the transmission FIFO is not empty, the hardware will automatically send the data in the FIFO.The number of bytes written to the transmission FIFO is limited by the maximum depth of the FIFO, which typically allows a maximum of 16 bytes to be written at once.The data mentioned above is related to specific hardware; different CPU types have different characteristics, so refer to the corresponding data sheets before use.
3. Data Reception and Packaging
FIFO can cache data received from the serial port, allowing us to use FIFO to reduce the number of interrupts. Taking the NXP LPC1778 chip as an example, the trigger level for the reception FIFO can be set to 1, 2, 4, 8, or 14 bytes, with 8 bytes or 14 bytes recommended, which is also the default value for PC serial port reception FIFO.Thus, when a large amount of data is received, an interrupt will only be generated every 8 or 14 bytes (except for the last reception), significantly reducing the number of interrupts compared to generating one interrupt for each byte received.Setting the reception FIFO to 8 or 14 bytes is also quite simple; still using the LPC1778 as an example, it only requires setting the UART FIFO control register UnFCR.The received data must comply with the communication protocol specifications, as data and protocol are inseparable. Typically, we need to package the received data into a frame according to the protocol and then hand it over to the upper layer for processing. Below is an introduction to a custom protocol frame format and a general method for packaging it into a frame.The custom protocol format is shown in Figure 3-1.
- Frame Header: Usually consists of 3 to 5 bytes of 0xFF or 0xEE
- Address Number: The address number of the device to communicate with, 1 byte
- Command Number: Corresponds to different functions, 1 byte
- Length: The number of bytes in the data area, 1 byte
- Data: Related to the specific command number, the length of the data area can be 0, and the total length of the frame should not exceed 256 bytes
- Checksum: XOR checksum (1 byte) or CRC16 checksum (2 bytes), this example uses CRC16 checksum
Next, we will introduce how to package the received data into a frame according to the format shown in Figure 3-1.3.1 Define Data Structuretypedef struct { uint8_t * dst_buf; // Pointer to the reception buffer uint8_t sfd; // Frame header flag, either 0xFF or 0xEE uint8_t sfd_flag; // Found frame header, usually 3 to 5 bytes of FF or EE uint8_t sfd_count; // Number of frame headers, usually 3 to 5 uint8_t received_len; // Number of bytes received uint8_t find_fram_flag; // Set to 1 after finding a complete frame uint8_t frame_len; // Total length of this frame data, this area is optional } find_frame_struct;3.2 Initialize Data Structure, Generally Placed in Serial Port Initialization/** * @brief Initialize the data structure for finding frames * @param p_fine_frame: Pointer to the packaged frame data structure variable * @param dst_buf: Pointer to the frame buffer * @param sfd: Frame header flag, usually 0xFF or 0xEE */ void init_find_frame_struct(find_frame_struct * p_find_frame,uint8_t *dst_buf,uint8_t sfd) { p_find_frame->dst_buf=dst_buf; p_find_frame->sfd=sfd; p_find_frame->find_fram_flag=0; p_find_frame->frame_len=10; p_find_frame->received_len=0; p_find_frame->sfd_count=0; p_find_frame->sfd_flag=0; } 3.3 Data Packaging Program/** * @brief Find a frame of data, return the number of processed data items * @param p_find_frame: Pointer to the packaged frame data structure variable * @param src_buf: Pointer to the raw data received from the serial port * @param data_len: Number of raw data items received this time from src_buf * @param sum_len: Maximum length of the frame buffer * @return Number of data items processed this time */ uint32_t find_one_frame(find_frame_struct * p_find_frame,const uint8_t * src_buf,uint32_t data_len,uint32_t sum_len) { uint32_t src_len=0; while(data_len–) { if(p_find_frame ->sfd_flag==0) { // No start frame header found if(src_buf[src_len++]==p_find_frame ->sfd) { p_find_frame ->dst_buf[p_find_frame ->received_len++]=p_find_frame ->sfd; if(++p_find_frame ->sfd_count==5) { p_find_frame ->sfd_flag=1; p_find_frame ->sfd_count=0; p_find_frame ->frame_len=10; } } else { p_find_frame ->sfd_count=0; p_find_frame ->received_len=0; } } else { // Is it the “length” byte? Y-> Get the length of this frame data if(7==p_find_frame ->received_len) { p_find_frame ->frame_len=src_buf[src_len]+5+1+1+1+2; // Frame header + address number + command number + data length + checksum if(p_find_frame->frame_len>=sum_len) { // The handling method here may vary depending on the specific application MY_DEBUGF(SLAVE_DEBUG,(“Data length exceeds buffer!\n”)); p_find_frame->frame_len= sum_len; } } p_find_frame ->dst_buf[p_find_frame->received_len++]=src_buf[src_len++]; if(p_find_frame ->received_len==p_find_frame ->frame_len) { p_find_frame ->received_len=0; // A frame is complete p_find_frame ->sfd_flag=0; p_find_frame ->find_fram_flag=1; return src_len; } } } p_find_frame ->find_fram_flag=0; return src_len; } Example of usage:Define data structure variable:find_frame_struct slave_find_frame_srt;Define reception data buffer:#define SLAVE_REC_DATA_LEN 128uint8_t slave_rec_buf[SLAVE_REC_DATA_LEN];Call the structure variable initialization function in the serial port initialization:init_find_frame_struct(&slave_find_frame_srt,slave_rec_buf,0xEE);Call the data packaging function in the serial port receive interrupt:find_one_frame(&slave_find_frame_srt,tmp_rec_buf,data_len,SLAVE_REC_DATA_LEN);Where rec_buf is the temporary buffer for serial port reception, data_len is the length of the data received this time.
4. Data Transmission
As mentioned earlier, the traditional wait-and-send method wastes CPU resources, while the interrupt-driven method, although it does not waste CPU resources, adds an additional interrupt source. In our usage, we found that timer interrupts are used in almost every application. We can utilize timer interrupts and hardware FIFO for data transmission. With proper design, this transmission method will not waste CPU resources and will not add extra interrupt sources and events.It should be noted that this method is not suitable for all applications. For those applications that do not enable timer interrupts, this method is certainly unsupported. Additionally, if the timer interrupt interval is too long while the communication baud rate is particularly high, this method may also be unsuitable.The communication baud rates currently used by the company are generally low (1200bps, 2400bps). At these baud rates, a timer interval of less than or equal to 10ms is sufficient. If the timer interval is less than or equal to 1ms, it can support 115200bps.The main idea of this method is: after the timer interrupt is triggered, check if there is data to be sent. If there is data to be sent and it meets the sending conditions, place the data into the sending FIFO. For the LPC1778, a maximum of 16 bytes of data can be placed at once. The hardware will then automatically start sending without CPU involvement.Next, we will introduce how to use the timer to send data, with the hardware platform being RS485. Since sending requires operating the serial port registers and RS485 direction control pins, it is closely related to the hardware. The following code uses the LPC1778 hardware, but the concept is universal.4.1 Define Data Structure/* Serial port frame sending structure */ typedef struct { uint16_t send_sum_len; // Length of the frame data to be sent uint8_t send_cur_len; // Length of data already sent uint8_t send_flag; // Send flag uint8_t * send_data; // Pointer to the data buffer to be sent } uart_send_struct; 4.2 Timer Processing Function/** * @brief Timer sending function, called in the timer interrupt, reduces sending wait without using send interrupts * @param UARTx: Pointer to the base address of the hardware serial port register * @param p: Pointer to the serial port frame sending structure variable */ #define FARME_SEND_FALG 0x5A #define SEND_DATA_NUM 12 static void uart_send_com(LPC_UART_TypeDef *UARTx,uart_send_struct *p) { uint32_t i; uint32_t tmp32; if(UARTx->LSR &(0x01<<6)) // Send is empty { if(p->send_flag==FARME_SEND_FALG) { RS485ClrDE; // Set RS485 to send state tmp32=p->send_sum_len-p->send_cur_len; if(tmp32>SEND_DATA_NUM) // Fill the sending FIFO with byte data { for(i=0;i<send_data_num;i++) { UARTx->THR=p->send_data[p->send_cur_len++]; } } else { for(i=0;i<tmp32;i++) { UARTx->THR=p->send_data[p->send_cur_len++]; } p->send_flag=0; } } else { RS485SetDE; } } } </tmp32;i++) </send_data_num;i++) Where RS485ClrDE is a macro definition that sets RS485 to send mode; RS485SetDE is also a macro definition that sets RS485 to receive mode.Example of usage:Define data structure variable:uart_send_struct uart0_send_str;Define sending buffer:uint8_t uart0_send_buf[UART0_SEND_LEN];Wrap the timer processing function for the specific hardware serial port:void uart0_send_data(void){ uart_send_com(LPC_UART0,&uart0_send_str);}Place the wrapped function uart0_send_data(); in the timer interrupt handler;When data needs to be sent, set the serial port frame sending structure variable:uart0_send_str.send_sum_len=data_len; // data_len is the length of the data to be sentuart0_send_str.send_cur_len=0; // Fixed to 0uart0_send_str.send_data=uart0_send_buf; // Bind the sending bufferuart0_send_str.send_flag=FARME_SEND_FALG; // Set the send flagDisclaimer:This article’s material is sourced from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact me for removal.———— END ————