Bird
Raised Fist0
Embedded Cprogramming~10 mins

Ring buffer for UART receive in Embedded C - Step-by-Step Execution

Choose your learning style10 modes available
Concept Flow - Ring buffer for UART receive
UART receives byte
↓
next = (write_index + 1) % 4
↓
if (next != read_index)
↓
Discard byte
↓
write_index = next
↓
Read & process when ready
↓
Repeat
This flow shows how bytes received from UART are stored in a ring buffer only if space available (next != read_index), handling wrap-around and overflow by discarding.
Execution Sample
Embedded C
volatile uint8_t buffer[4];
volatile uint8_t write_index = 0;
volatile uint8_t read_index = 0;

void uart_receive_byte(uint8_t byte) {
  uint8_t next = (write_index + 1) % 4;
  if (next != read_index) {
    buffer[write_index] = byte;
    write_index = next;
  } // else buffer full, byte lost
}
This code stores incoming UART bytes into a 4-byte ring buffer (effective capacity 3), advancing write_index and avoiding overwrite if next == read_index (full).
Execution Table
StepActionwrite_indexread_indexnextBuffer StateOverflow?
1Start: write_index=0, read_index=000-[_, _, _, _]No
2Receive byte 'A' (65)001[65, _, _, _]No
3Update write_index to 110-[65, _, _, _]No
4Receive byte 'B' (66)102[65, 66, _, _]No
5Update write_index to 220-[65, 66, _, _]No
6Receive byte 'C' (67)203[65, 66, 67, _]No
7Update write_index to 330-[65, 66, 67, _]No
8Receive byte 'D' (68)300[65, 66, 67, _]Yes (buffer full, byte lost)
9Read byte at read_index=0 ('A')30-[65, 66, 67, _]No
10Increment read_index to 131-[65, 66, 67, _]No
11Receive byte 'D' (68) retry310[65, 66, 67, 68]No
12Update write_index to 0 (wrap)01-[65, 66, 67, 68]No
13End
💡 Execution demonstrates filling to capacity (3/4 slots), overflow discard, read to free space, then store with wrap-around.
Variable Tracker
VariableStartAfter Step 3After Step 5After Step 7After Step 8After Step 10After Step 11After Step 12Final
write_index012333300
read_index000001111
buffer[_, _, _, _][65, _, _, _][65, 66, _, _][65, 66, 67, _][65, 66, 67, _][65, 66, 67, _][65, 66, 67, 68][65, 66, 67, 68][65, 66, 67, 68]
Key Moments - 3 Insights
Why does the buffer reject the byte 'D' at step 8 even though there is space in the array?
At step 8, next = (3 + 1) % 4 = 0 equals read_index = 0, indicating the buffer is logically full (3 bytes stored). The check prevents overwrite of unread data by discarding when next == read_index.
How does the buffer handle wrapping around when write_index reaches the end?
At step 12, after storing 'D' at index 3, write_index = (3 + 1) % 4 = 0, wrapping around to reuse the buffer circularly.
What happens when read_index is incremented at step 10?
Incrementing read_index from 0 to 1 frees logical space. Now next = 0 != read_index = 1, allowing storage of 'D' at step 11 without overflow.
Visual Quiz - 3 Questions
Test your understanding
Look at the execution table at step 8. Why is the byte 'D' not stored in the buffer?
ABecause the byte 'D' is invalid
BBecause next equals read_index, indicating buffer full
CBecause the buffer array is physically full of zeros
DBecause read_index is ahead of write_index
💡 Hint
Check the 'Overflow?' column at step 8 and the condition comparing 'next' and 'read_index'.
At which step does write_index wrap back to 0?
AStep 12
BStep 8
CStep 10
DStep 11
💡 Hint
Look at the 'write_index' column and see when it changes from 3 to 0.
If read_index was not incremented at step 10, what would happen when byte 'D' arrives again?
AThe byte 'D' would be stored successfully
BThe buffer would overflow and overwrite unread data
CThe byte 'D' would be rejected again due to full buffer
DThe write_index would move forward anyway
💡 Hint
Refer to the buffer full check condition and the variable_tracker values for read_index remaining 0.
Concept Snapshot
Ring buffer stores UART bytes in a fixed-size array.
Use write_index and read_index to track positions.
Increment indices with modulo to wrap around.
Check if next = (write_index + 1) % SIZE == read_index to detect full.
If full, discard new bytes until read advances.
Read bytes by advancing read_index similarly (separate logic).
Full Transcript
This example shows a ring buffer for UART receive in embedded C. Incoming bytes compute next = (write_index + 1) % 4. If next != read_index, store at write_index and advance. This leaves one slot empty to distinguish empty (write==read) from full (next==read). After 3 bytes, full detected on 4th (next wraps to read pos), discarded. Reading advances read_index, freeing space for more writes with wrap-around. Efficient for streams without data copies.

Practice

(1/5)
1. What is the main purpose of using a ring buffer for UART receive data?
easy
A. To encrypt UART data before processing
B. To increase the UART baud rate automatically
C. To convert UART data into parallel data
D. To store incoming data in a circular way to prevent data loss

Solution

  1. Step 1: Understand UART data flow

    UART data arrives asynchronously and can be lost if not stored properly.
  2. Step 2: Role of ring buffer

    A ring buffer stores data in a circular way so new data overwrites old only when full, preventing data loss.
  3. Final Answer:

    To store incoming data in a circular way to prevent data loss -> Option D
  4. Quick Check:

    Ring buffer = circular storage for UART data [OK]
Hint: Ring buffer means circular storage to avoid losing data [OK]
Common Mistakes:
  • Confusing ring buffer with baud rate control
  • Thinking it encrypts data
  • Assuming it converts data format
2. Which of the following is the correct way to declare a ring buffer array of size 64 bytes in embedded C?
easy
A. char ringBuffer[64];
B. int ringBuffer = 64;
C. char ringBuffer();
D. char ringBuffer[;]

Solution

  1. Step 1: Understand array declaration syntax

    In C, arrays are declared with type, name, and size in square brackets.
  2. Step 2: Check each option

    char ringBuffer[64]; correctly declares a char array of size 64. Others are invalid syntax or wrong types.
  3. Final Answer:

    char ringBuffer[64]; -> Option A
  4. Quick Check:

    Array declaration = type name[size] [OK]
Hint: Arrays use type name[size] syntax in C [OK]
Common Mistakes:
  • Using parentheses instead of brackets
  • Assigning size to variable instead of array
  • Leaving size empty or incorrect
3. Given this code snippet for a ring buffer with size 8:
uint8_t buffer[8];
uint8_t head = 0;
uint8_t tail = 0;

// Add data
buffer[head] = 0x55;
head = (head + 1) % 8;

// Add data
buffer[head] = 0xAA;
head = (head + 1) % 8;

// Read data
uint8_t data = buffer[tail];
tail = (tail + 1) % 8;
What is the value of data after this code runs?
medium
A. 0xAA
B. 0x55
C. 0x00
D. Undefined

Solution

  1. Step 1: Track buffer writes

    First, 0x55 is stored at buffer[0], head moves to 1. Then 0xAA stored at buffer[1], head moves to 2.
  2. Step 2: Read from tail

    Tail is 0, so reading buffer[0] returns 0x55, then tail moves to 1.
  3. Final Answer:

    0x55 -> Option B
  4. Quick Check:

    Read at tail=0 returns first stored value 0x55 [OK]
Hint: Read from tail pointer, which points to oldest data [OK]
Common Mistakes:
  • Reading from head instead of tail
  • Forgetting modulo wrap-around
  • Assuming data is 0xAA instead of 0x55
4. Identify the bug in this ring buffer code snippet for UART receive:
if ((head + 1) % BUFFER_SIZE == tail) {
    // Buffer full
    return;
}
buffer[head] = data;
head = (head + 1) % BUFFER_SIZE;
medium
A. Buffer full condition is correct, no bug
B. Buffer full check should use '==' instead of '!='
C. Buffer full check misses parentheses around addition
D. Buffer full condition should check if head == tail

Solution

  1. Step 1: Understand buffer full condition

    Buffer is full when next head position equals tail, meaning no space to write.
  2. Step 2: Analyze given condition

    The condition uses (head + 1) % BUFFER_SIZE == tail, which correctly detects full buffer.
  3. Final Answer:

    Buffer full condition is correct, no bug -> Option A
  4. Quick Check:

    Full when next head equals tail [OK]
Hint: Full buffer when next head equals tail [OK]
Common Mistakes:
  • Using wrong comparison operator
  • Forgetting modulo operation
  • Checking head == tail for full (actually empty)
5. You want to implement a function uart_receive_byte() that reads one byte from the ring buffer if available, or returns -1 if empty. Which code snippet correctly implements this?
hard
A. int uart_receive_byte() { if (tail == head) return 0; uint8_t data = buffer[tail]; tail = (tail + 1) % BUFFER_SIZE; return data; }
B. int uart_receive_byte() { if (head != tail) return -1; uint8_t data = buffer[head]; head = (head + 1) % BUFFER_SIZE; return data; }
C. int uart_receive_byte() { if (head == tail) return -1; uint8_t data = buffer[tail]; tail = (tail + 1) % BUFFER_SIZE; return data; }
D. int uart_receive_byte() { if (tail == head) return -1; uint8_t data = buffer[head]; head = (head + 1) % BUFFER_SIZE; return data; }

Solution

  1. Step 1: Check empty condition

    Buffer is empty when head equals tail, so return -1 in that case.
  2. Step 2: Read data and update tail

    Read from buffer[tail], then move tail forward with modulo wrap.
  3. Final Answer:

    Function returns -1 if empty, else data from tail and updates tail -> Option C
  4. Quick Check:

    Empty if head==tail; read from tail pointer [OK]
Hint: Return -1 if head==tail; read from tail and advance tail [OK]
Common Mistakes:
  • Checking wrong empty condition
  • Reading from head instead of tail
  • Returning 0 instead of -1 when empty
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard