Search code examples
cstm32firmware

Ringbuffer for a microcontroller


Here is a scenario I'm facing right now, I have an interrupt(thread) UART that is reading to a ringbuffer from the values that I get from the serial port, and also writing the values from the serial port to the ring buffer. I have a main loop that access that ringbuffer for reading the values from it, while writing an AT Command, and also writing to the ring buffer those AT Commands. Do I need the ringbuffer to be lock free or surround the shared data with a semaphore or a mutex ? I don't have an OS for getting a mutex or semaphore working. I have read alot about the subject and it seems I need a lock free ringbuffer. On ARM I would use a compare and swap instruction. The ringbuffer is implemented as an array so I wouldn't run into ABA problem

Declaration of buffers:

#define MAX_CHANNEL_COUNT 5

#define UART_BUFSIZE 512
char buffers[2][MAX_CHANNEL_COUNT][UART_BUFSIZE];

char* writeBuffers[MAX_CHANNEL_COUNT];
char* readBuffers[MAX_CHANNEL_COUNT];
volatile int readPos[MAX_CHANNEL_COUNT] = { 0 };
volatile int writePos[MAX_CHANNEL_COUNT] = { 0 };
here is the interrupt code

void USART_IRQHandler(char Channel, USART_TypeDef *USARTx)
{
    volatile unsigned int IIR;
    int c = 0;
    IIR = USARTx->SR;
    if (IIR & USART_FLAG_RXNE)
    {                  // read interrupt
      USARTx->SR &= ~USART_FLAG_RXNE;             // clear interrupt

        c = USART_ReceiveData(USARTx);
        writeBuffers[Channel][writePos[Channel]] = c;
    writePos[Channel]++;
        if(writePos[Channel]>=UART_BUFSIZE) writePos[Channel]=0;

    }

    if (IIR & USART_FLAG_TXE)
    {
      USARTx->SR &= ~USART_FLAG_TXE;              // clear interrupt
    }
}
code for initializing and swapping the buffers:

void initializeBuffers(void) {
    int i = 0;
    for (i = 0; i < MAX_CHANNEL_COUNT; ++i)
    {
        writeBuffers[i] = buffers[0][i];
        readBuffers[i] = buffers[1][i];
    }
}

void swapBuffers(int channel) {
  int i;
  char * buf = writeBuffers[channel];
    __disable_irq();
  writeBuffers[channel] = readBuffers[channel];
  readBuffers[channel] = buf;
  if ( readPos[channel] == UART_BUFSIZE)
           readPos[channel] = 0;
    
  for (i =0; i < UART_BUFSIZE; i++)
  {
    buf[i] = 0;
  }
    __enable_irq();
}

here I use this function to get a char from a specific channel and from a specific UART

int GetCharUART (char Channel)
{
 int c =  readBuffers[Channel][readPos[Channel]++];

    
  if (c == 0 || readPos[Channel] == UART_BUFSIZE)
  {
    swapBuffers(Channel); // Make this clear your read buffer.
    return EMPTY;
  }
  return c; // Note, your code that calls this should handle the case where c == 0
}

usage of GetCharUart

PutStringUART(UART_GSM, "AT");
PutStringUART(UART_GSM, pCommand);
PutCharUART(UART_GSM, '\r');
count = 0;
timer_100ms = 0;
while (timer_100ms <= timeout)
{
    zeichen = GetCharUART(UART_GSM);
}

Solution

  • You need synchronization between interrupt handlers and the "main-thread", so yes a sort of mutex is needed. The usual method without OS, is to just disable interrupts before "entering the critical section" and re-enable afterwards. That ensures the critical section is "atomic" (i.e. no interrupts fire in the meantime).

    On ARM I would use a compare and swap instruction

    What compiler are you using?

    GCC and Clang has intrinsics for atomic compare-and-swap.

    See e.g.

    https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html

    https://llvm.org/docs/Atomics.html

    C11 supports it via <stdatomic.h> I believe: https://en.cppreference.com/w/c/atomic/atomic_compare_exchange