For my purposes I just needed a simple FIFO buffer that would work well on my MSP430G2553, which lacks DMA and only has 512 bytes of RAM. Circular or ring buffers are a class of FIFO (first in/first out) buffers that were a good approach for my needs. Ring buffer implementations I researched either wasted a byte of buffer space, required extra fields or were not very performant. revring, the solution I developed, has none of these drawbacks, using just the typical head and tail field.
Update 2015-06-18 17:50 EST: As sharp minds on reddit and HN have pointed out, there are concurrency issues in this approach. In my application, revring_add_byte() runs in interrupt context while revring_remove_byte() runs in my main loop. I have patched the concurrency issue for my application only by disabling/enabling interrupts in revring_remove_byte().
typedef struct revring_s{ /* Pointer to buffer data */ uint8_t* buffer; /* size of buffer */ unsigned size; /* Indication of where to place incoming data. */ unsigned head; /* Indication of where to read incoming data. */ unsigned tail; } revring; /* Initialize buffer. */ void revring_init(revring* rr, uint8_t* buffer, unsigned size){ rr->buffer = buffer; rr->size = size; rr->head = size; rr->tail = size; } /* Test if buffer is empty. */ unsigned revring_empty(const revring* rr){ return rr->head == rr->tail; } /* Test if buffer is full. */ unsigned revring_full(const revring* rr){ return rr->head == 0; } void revring_add_byte(revring* rr, uint8_t byte){ assert(!revring_full(rr)); register unsigned head_tmp = rr->head; /* Place byte at position preceding head. */ --head_tmp; rr->buffer[head_tmp] = byte; /* If byte just went to position 0, wrap next head index around. */ if(0 == head_tmp) head_tmp = rr->size; /* If next head index caught up to tail, then buffer is full. */ if(rr->tail == head_tmp) head_tmp = 0; /* Update head index. */ rr->head = head_tmp; } uint8_t revring_remove_byte(revring* rr){ assert(!revring_empty(rr)); register unsigned tail_tmp = rr->tail; /* Read byte preceding tail. */ --tail_tmp; const uint8_t byte = rr->buffer[tail_tmp]; /* Wrap next tail index around if byte was at 0. */ if(0 == tail_tmp) tail_tmp = rr->size; /* If buffer was full before...it is no longer. Point head to where tail is currently. Disable/enable interrupts for safety. */ __dint(); if(revring_full(rr)) rr->head = rr->tail; /* Update tail index. */ rr->tail = tail_tmp; __eint(); return byte; }Some superficial differences from typical ring buffer implementations are:
The mistake other ring buffers make is that when they are full, they redundantly store the same index twice in head and tail. Another consequence of a full buffer is if we know the value of tail, then we know the value of head of must match. So when adding a byte to a revring buffer makes it full, revring just sets its head to reserved value 0. When a byte is removed from a full buffer, the head is recovered from the value of the tail.
The purpose of going backwards is opportunistic: using 0 as the sentinel value means buffer full and end of buffer checks are very cheap on the MSP430. In particular, using the Constant Generator register value 0 is much cheaper than looking up the size value in RAM. Since characters are read in serially, they are generally processed 1 by 1 in the application code. So it does not matter if the buffer indices increment or decrement as this detail hides behind abstraction.
This buffer will be a component in a UART MSP430 driver I am writing. The driver will not only handle typical I/O, but will also handle RX errors, serial breaks and other border conditions not found handled in other frameworks/source codes.