微控制器的环形缓冲区
Ringbuffer for a microcontroller
这是我现在面临的一个场景,我有一个中断(线程)UART,它正在从我从串行端口获得的值读取到环形缓冲区,并从串行端口写入值到环形缓冲区。
我有一个主循环访问该环形缓冲区以从中读取值,同时编写 AT 命令,并将这些 AT 命令写入环形缓冲区。
我是否需要环形缓冲区无锁或用信号量或互斥锁包围共享数据?我没有 OS 来让互斥量或信号量工作。
我已经阅读了很多关于这个主题的文章,看来我需要一个无锁的环形缓冲区。在 ARM 上,我会使用比较和交换指令。 ringbuffer 是作为数组实现的,所以我不会 运行 进入 ABA 问题
缓冲区声明:
#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();
}
这里我使用这个函数从特定通道和特定 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
}
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);
}
您需要在中断处理程序和“主线程”之间进行同步,所以是的,需要一种互斥体。没有 OS 的常用方法是在“进入临界区”之前禁用中断,然后再重新启用。这确保了关键部分是“原子的”(即同时没有中断触发)。
On ARM I would use a compare and swap instruction
您使用的是什么编译器?
GCC 和 Clang 具有用于原子比较和交换的内在函数。
参见示例
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html
https://llvm.org/docs/Atomics.html
C11 通过 <stdatomic.h>
支持它 我相信:https://en.cppreference.com/w/c/atomic/atomic_compare_exchange
从概念上讲,我建议将环形缓冲区拆分为读取缓冲区和写入缓冲区(可以是环形缓冲区)。
当你使用中断时,你有并发性,这意味着你需要保护共享数据(即你的缓冲区)。这通常由互斥锁或信号量完成。但是,它们仅适用于 OS 或 RTOS。
所以最常见的做法是临时序列化并发元素。这可以通过禁用 UART 中断来完成。更准确地说,您需要停用所有中断,这会触发使用共享资源的代码。
这是我现在面临的一个场景,我有一个中断(线程)UART,它正在从我从串行端口获得的值读取到环形缓冲区,并从串行端口写入值到环形缓冲区。 我有一个主循环访问该环形缓冲区以从中读取值,同时编写 AT 命令,并将这些 AT 命令写入环形缓冲区。 我是否需要环形缓冲区无锁或用信号量或互斥锁包围共享数据?我没有 OS 来让互斥量或信号量工作。 我已经阅读了很多关于这个主题的文章,看来我需要一个无锁的环形缓冲区。在 ARM 上,我会使用比较和交换指令。 ringbuffer 是作为数组实现的,所以我不会 运行 进入 ABA 问题
缓冲区声明:
#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();
}
这里我使用这个函数从特定通道和特定 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
}
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);
}
您需要在中断处理程序和“主线程”之间进行同步,所以是的,需要一种互斥体。没有 OS 的常用方法是在“进入临界区”之前禁用中断,然后再重新启用。这确保了关键部分是“原子的”(即同时没有中断触发)。
On ARM I would use a compare and swap instruction
您使用的是什么编译器?
GCC 和 Clang 具有用于原子比较和交换的内在函数。
参见示例
https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html
https://llvm.org/docs/Atomics.html
C11 通过 <stdatomic.h>
支持它 我相信:https://en.cppreference.com/w/c/atomic/atomic_compare_exchange
从概念上讲,我建议将环形缓冲区拆分为读取缓冲区和写入缓冲区(可以是环形缓冲区)。
当你使用中断时,你有并发性,这意味着你需要保护共享数据(即你的缓冲区)。这通常由互斥锁或信号量完成。但是,它们仅适用于 OS 或 RTOS。
所以最常见的做法是临时序列化并发元素。这可以通过禁用 UART 中断来完成。更准确地说,您需要停用所有中断,这会触发使用共享资源的代码。