Panel For Example Panel For Example Panel For Example

Using Hardware FIFO to Reduce Receive Interrupts

Author : Adrian September 22, 2025

Overview

Although there are many communication methods available, the serial port remains one of the most widely used interfaces in embedded systems.

This article explains how to use UARTs with hardware FIFO to reduce receive interrupt frequency. It presents a custom communication frame format and a framing method. It also describes a timer-driven transmit approach that avoids using UART transmit interrupts while keeping system responsiveness.

1. Introduction

Serial ports are widely used because they are simple and low cost. Paired with RS485 chips, they enable long-distance, noise-robust local networks. As product functionality increases, system tasks become more complex and require timely response. Most modern microcontrollers (ARM7, Cortex-M3) include UART hardware FIFOs. This article shows how to use hardware FIFO to reduce receive interrupts and improve transmit efficiency. First, common limitations of traditional serial I/O are listed:

  • Each received byte triggers a receive interrupt. This does not leverage the UART hardware FIFO and results in excessive interrupts.
  • Reply data is sent in a blocking wait manner. Serial transmission time is much slower than CPU processing time, so waiting for each byte to be transmitted wastes CPU resources and reduces overall system responsiveness (at 1200 bps, sending one byte takes about 10 ms; sending dozens of bytes can keep the CPU waiting a long time).
  • Reply data is sent using transmit interrupts. This adds another interrupt source and increases total interrupt events, which can affect system reliability. From a reliability standpoint, fewer interrupts are generally preferable.

To address these issues, a common custom protocol is combined with FIFO usage to provide a complete solution.

2. UART FIFO

UART FIFO is a dedicated buffer that operates in FIFO order. Receive and transmit FIFOs are usually independent. Received data is placed into the receive FIFO; when the FIFO reaches a trigger level (commonly 1, 2, 4, 8, 14 bytes) or when no new data arrives for a timeout period (commonly 3.5 character times), the UART notifies the CPU to generate a receive interrupt. Transmit data is written into the transmit FIFO; as long as the FIFO is not empty, the hardware automatically shifts out data. The number of bytes you can write at once depends on FIFO depth, typically up to 16 bytes. These details depend on the specific hardware and should be checked in the device datasheet.

3. Receiving and Framing

Because FIFO buffers received bytes, it can be used to reduce interrupt frequency. For example, on NXP's lpc1778 the receive FIFO trigger level can be set to 1, 2, 4, 8, or 14 bytes; using 8 or 14 bytes is common and matches typical PC UART FIFO defaults. When large amounts of data arrive, an interrupt is generated only every 8 or 14 bytes (except for the final partial chunk), greatly reducing receive interrupt count compared with an interrupt per byte.

Setting the FIFO trigger to 8 or 14 bytes is straightforward; for example, set the UART FIFO control register. Received bytes must conform to the protocol. Typically, incoming bytes are packaged into frames and handed to higher layers. Below is a custom frame format and a general framing method.

Custom protocol frame

 

  • Frame start: typically 3–5 bytes of 0xFF or 0xEE
  • Address: 1 byte device address
  • Command: 1 byte command code
  • Length: 1 byte indicating the number of data bytes
  • Data: command-dependent, length can be 0; total frame length should not exceed 256 bytes
  • Checksum: XOR checksum (1 byte) or CRC16 (2 bytes); this example uses CRC16

3.1 Define data structure

typedef struct { 
    uint8_t *dst_buf; // pointer to receive buffer 
    uint8_t sfd; // start-of-frame marker, 0xFF or 0xEE 
    uint8_t sfd_flag; // start-of-frame found, typically 3-5 FF or EE 
    uint8_t sfd_count; // count of start-of-frame bytes, typically 3-5 
    uint8_t received_len; // number of bytes already received 
    uint8_t find_fram_flag; // set to 1 after a complete frame is found 
    uint8_t frame_len; // total length of this frame, optional 
} find_frame_struct;

3.2 Initialize the structure (typically in UART init)

/** 
 * @brief    Initialize the frame-finding data structure 
 * @param    p_find_frame: pointer to the frame-finding structure 
 * @param    dst_buf: pointer to the frame buffer 
 * @param    sfd: start-of-frame marker 
 */ 
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 Framing routine

/** 
 * @brief    Find one frame of data. Returns the number of bytes processed. 
 * @param    p_find_frame: pointer to the frame-finding structure 
 * @param    src_buf: pointer to the raw serial receive data 
 * @param    data_len: number of new bytes in src_buf for this call 
 * @param    sum_len: maximum frame buffer length 
 * @return   number of bytes processed 
 */ 
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) { // start-of-frame not yet 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 this the "length" byte? If yes, get this frame's data length 
            if(7==p_find_frame->received_len) { 
                p_find_frame->frame_len=src_buf[src_len]+5+1+1+1+2; // start-of-frame + address + command + data length + checksum 
                if(p_find_frame->frame_len>=sum_len) { // this handling may vary by application 
                    MY_DEBUGF(SLAVE_DEBUG,("数据长度超出缓存! ")); 
                    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; // one frame 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 usage: define the frame structure variable:

find_frame_struct slave_find_frame_srt;

Define the receive buffer:

#define SLAVE_REC_DATA_LEN 128 
uint8_t slave_rec_buf[SLAVE_REC_DATA_LEN];

Initialize in UART init:

init_find_frame_struct(&slave_find_frame_srt,slave_rec_buf,0xEE);

Call the framing routine in the UART receive handler:

find_one_frame(&slave_find_frame_srt,tmp_rec_buf,data_len,SLAVE_REC_DATA_LEN);

Here, tmp_rec_buf is a temporary buffer holding newly received UART bytes and data_len is the number of new bytes received this time.

4. Transmitting Data

The blocking send method wastes CPU resources, while using a transmit interrupt increases interrupt count. In many applications a timer interrupt is already present. By using the timer interrupt plus the UART hardware FIFO, data can be loaded into the transmit FIFO periodically. Properly designed, this approach avoids CPU blocking and does not add extra interrupt sources.

Note this method does not suit all applications. Systems without a periodic timer interrupt cannot use it. Also, if the timer interval is long and the baud rate is very high, the method may not be suitable. The company currently uses relatively low baud rates (1200 bps, 2400 bps). At these rates, a timer interval of 10 ms or less is sufficient. If the timer interval is 1 ms or less, 115200 bps can be supported.

The core idea: on each timer tick, check whether data needs to be sent. If so and if transmit conditions are met, write data into the transmit FIFO. On lpc1778, up to 16 bytes can be written at once. The hardware then handles transmission automatically with no CPU involvement.

4.1 Define transmit data structure

/* UART frame send structure */ 
typedef struct { 
    uint16_t send_sum_len; // length of frame data to send 
    uint8_t send_cur_len; // number of bytes already sent 
    uint8_t send_flag; // send flag 
    uint8_t *send_data; // pointer to data buffer to send 
} uart_send_struct;

4.2 Timer-driven handler

/** 
 * @brief    Periodic send function, called in timer interrupt to reduce send wait without using send interrupts 
 * @param    UARTx: pointer to hardware UART register base 
 * @param    p: pointer to UART frame send structure 
 */ 
#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)) // transmit empty 
    { 
        if(p->send_flag==FARME_SEND_FALG) { 
            RS485ClrDE; // set RS485 to transmit state 
            tmp32=p->send_sum_len-p->send_cur_len; 
            if(tmp32>SEND_DATA_NUM) // fill bytes into transmit FIFO 
            { 
                for(i=0;iTHR=p->send_data[p->send_cur_len++]; 
            } 
        } else { 
            for(i=0;iTHR=p->send_data[p->send_cur_len++]; 
            p->send_flag=0; 
        } 
    } else { 
        RS485SetDE; 
    } 
}

Example usage: define the send structure variable:

uart_send_struct uart0_send_str;

Define the send buffer:

uint8_t uart0_send_buf[UART0_SEND_LEN];

Wrap the handler for the specific UART:

void uart0_send_data(void) { 
    uart_send_com(LPC_UART0,&uart0_send_str); 
}

Call uart0_send_data() from the timer interrupt. When data needs to be sent, set the send structure:

uart0_send_str.send_sum_len=data_len; // data_len is the length to send 
uart0_send_str.send_cur_len=0; // set to 0 
uart0_send_str.send_data=uart0_send_buf; // bind the send buffer 
uart0_send_str.send_flag=FARME_SEND_FALG; // set send flag

5. Conclusion

This article describes an efficient serial data send/receive method and provides concrete code examples. When processor tasks increase, this approach offers a low-overhead option that can improve overall system performance.