What is the ring buffer pattern for UART, and why is it essential?
A ring buffer (circular buffer) is a fixed-size array with two indices — a head (write pointer) and a tail (read pointer) — that wrap around when they reach the end. The ISR writes incoming bytes at the head, and the main loop (or a processing task) reads bytes from the tail. As long as the consumer keeps up with the producer, the buffer absorbs bursts without data loss.
#define BUF_SIZE 128 // Must be power of 2 for fast modulostatic volatile uint8_t buf[BUF_SIZE];static volatile uint16_t head = 0; // Written by ISRstatic volatile uint16_t tail = 0; // Read by main loopvoid UART_IRQHandler(void) {buf[head] = UART->DR;head = (head + 1) & (BUF_SIZE - 1); // Fast wrap}int uart_read(uint8_t *byte) {if (tail == head) return -1; // Empty*byte = buf[tail];tail = (tail + 1) & (BUF_SIZE - 1);return 0;}
The ring buffer is essential because it decouples the interrupt timing from the application processing timing. Without it, the ISR must either process each byte immediately (making it long and blocking) or use a single-byte holding register (which overruns if the main loop is delayed by even one byte period). The buffer size should be chosen based on the maximum burst length and the worst-case processing latency. Making the size a power of two allows the modulo operation to be replaced by a bitmask AND, which is a single-cycle operation on ARM.
A common trap: forgetting to declare head and tail as volatile. Without volatile, the compiler may cache the value of head in a register inside the main loop and never re-read it from memory, so the main loop never sees new data arriving from the ISR.
Source: UART Q&A
