Home > Software design >  Ringbuffer for a microcontroller
Ringbuffer for a microcontroller

Time:10-06

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);
}

CodePudding user response:

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

CodePudding user response:

Conceptually, I would recommend splitting the ring buffer into a reading and a writing buffer (can be a ring buffer).

When you use interrupts, you have concurrency, which means that you need to protect shared data (i.e., your buffers). This is often done by mutexes or semaphores. However, they are only available on OSs or RTOSs.

So the most common thing to do is to temporarily serialise your concurrent elements. This can be done by disabling the UART interrupt. More precisely, you need to deactivate all interrupts, which can trigger code that uses the shared resource.

  • Related