How to Implement Ring Buffer in Embedded C: Simple Guide
To implement a
ring buffer in embedded C, create a fixed-size array with two indexes: head for writing and tail for reading. Increment these indexes circularly using modulo arithmetic to wrap around the buffer size, allowing continuous data storage and retrieval without shifting elements.Syntax
A ring buffer uses a fixed-size array and two pointers (indexes): head and tail. The head points to where new data is written, and the tail points to where data is read. Both indexes wrap around using modulo with the buffer size to create a circular effect.
Key parts:
buffer[size]: fixed array to hold datahead: index for next writetail: index for next readcount: number of elements currently stored
c
typedef struct {
uint8_t buffer[SIZE];
uint16_t head;
uint16_t tail;
uint16_t count;
} RingBuffer;Example
This example shows a ring buffer storing bytes. It includes functions to add data (ring_buffer_write) and read data (ring_buffer_read). The buffer wraps around when it reaches the end.
c
#include <stdio.h> #include <stdint.h> #include <stdbool.h> #define SIZE 5 typedef struct { uint8_t buffer[SIZE]; uint16_t head; uint16_t tail; uint16_t count; } RingBuffer; void ring_buffer_init(RingBuffer *rb) { rb->head = 0; rb->tail = 0; rb->count = 0; } bool ring_buffer_write(RingBuffer *rb, uint8_t data) { if (rb->count == SIZE) { // Buffer full return false; } rb->buffer[rb->head] = data; rb->head = (rb->head + 1) % SIZE; rb->count++; return true; } bool ring_buffer_read(RingBuffer *rb, uint8_t *data) { if (rb->count == 0) { // Buffer empty return false; } *data = rb->buffer[rb->tail]; rb->tail = (rb->tail + 1) % SIZE; rb->count--; return true; } int main() { RingBuffer rb; ring_buffer_init(&rb); // Write 5 bytes for (uint8_t i = 1; i <= 5; i++) { if (ring_buffer_write(&rb, i)) { printf("Wrote %d\n", i); } else { printf("Buffer full, cannot write %d\n", i); } } // Read 3 bytes uint8_t val; for (int i = 0; i < 3; i++) { if (ring_buffer_read(&rb, &val)) { printf("Read %d\n", val); } } // Write 2 more bytes for (uint8_t i = 6; i <= 7; i++) { if (ring_buffer_write(&rb, i)) { printf("Wrote %d\n", i); } else { printf("Buffer full, cannot write %d\n", i); } } // Read remaining bytes while (ring_buffer_read(&rb, &val)) { printf("Read %d\n", val); } return 0; }
Output
Wrote 1
Wrote 2
Wrote 3
Wrote 4
Wrote 5
Read 1
Read 2
Read 3
Wrote 6
Wrote 7
Read 4
Read 5
Read 6
Read 7
Common Pitfalls
Common mistakes when implementing a ring buffer include:
- Not using modulo to wrap
headandtail, causing index overflow. - Confusing full and empty states; often
head == tailmeans empty, so a separatecountor flag is needed. - Overwriting unread data by writing when buffer is full.
- Not handling buffer full condition properly, leading to data loss or corruption.
c
/* Wrong: No modulo causes overflow */ head = head + 1; // Wrong /* Right: Use modulo to wrap around */ head = (head + 1) % SIZE; // Correct /* Wrong: Using head == tail for both full and empty */ if (head == tail) { // Ambiguous: buffer could be empty or full } /* Right: Use count or separate flag */ if (count == 0) { // Buffer empty } else if (count == SIZE) { // Buffer full }
Quick Reference
- Buffer size: Fixed array size defines max elements.
- Head index: Points to next write position.
- Tail index: Points to next read position.
- Modulo operation: Wrap indexes with
index = (index + 1) % SIZE. - Full vs Empty: Use a count variable or flag to distinguish.
- Write: Add data if buffer not full, then advance head and increment count.
- Read: Retrieve data if buffer not empty, then advance tail and decrement count.
Key Takeaways
Use a fixed-size array with head and tail indexes to implement a ring buffer in embedded C.
Always wrap head and tail indexes using modulo arithmetic to create the circular effect.
Maintain a count or flag to differentiate between full and empty buffer states.
Check buffer full before writing and buffer empty before reading to avoid data loss.
Increment indexes and update count carefully to keep buffer state consistent.