环形缓冲区

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了环形缓冲区相关的知识,希望对你有一定的参考价值。

参考技术A 本质上是一个数组,但是它又跟普通的数组不太一样,普通的数组只有一个指针,移动下标是index+1的方式;
而环形缓冲区有两个指针:
head指针:表示读取数据的指针;
tail指针:表示写数据的指针。
它是如何实现环形读写的呢?
通过对读写指针的移动进行取模(求余)运算实现的。
对于读指针,如果当前位置有数据可读,那么读取后指针移动,移动方式为:(head+1)%size。
对于写指针,如果当前位置可写入数据,那么写入数据后指针移动,移动方式为:(tail+1)%size。
通过求余的计算方式,每当指针的值达到size-1时,总能通过求余的方式回到0的位置,而后再进行相应的读写操作,指针再继续按照上述方式进行移动。

环形缓冲区主要用于数据读写,相对应的有两个重要的判断条件:
1、缓冲区为空不能读;
2、缓冲区已满不能写。

微控制器的环形缓冲区

【中文标题】微控制器的环形缓冲区【英文标题】:Ringbuffer for a microcontroller 【发布时间】:2021-11-26 13:03:58 【问题描述】:

这是我现在面临的一个场景,我有一个中断(线程)UART,它正在从我从串行端口获得的值中读取环形缓冲区,并将值从串行端口写入环形缓冲区。 我有一个主循环,可以访问该环形缓冲区以从中读取值,同时编写 AT 命令,并将这些 AT 命令写入环形缓冲区。 我需要环形缓冲区是无锁的还是用信号量或互斥体包围共享数据?我没有让互斥锁或信号量工作的操作系统。 我已经阅读了很多关于该主题的内容,看来我需要一个无锁环形缓冲区。在 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);

【问题讨论】:

我觉得需要一些代码来说明一下对缓冲区的操作,同时还要更清楚地列出生产者和消费者。听起来 UART ISR 可以生产东西,而且主线程确实生产/消费,这似乎不平衡且令人困惑。或许您需要两个环形缓冲区,每个缓冲区在中断和主代码之间单向运行? @unwind 我有一些代码。 我昨天给了你一个帖子的链接,该帖子解释了为什么需要信号量以及如何在裸机 MCU 上以最简单的方式实现它。发生了什么?这是链接,electronics.stackexchange.com/a/409570/6102,它解释了 volatile 的使用以及如何在同一答案中使用信号量。这是两个不相关的问题。 @Lundin 该帖子已被版主删除。 AhmedSaleh,提示:考虑使用包含 1 个 struct 且有 5 个成员的数组,而不是 5 个数组对象。 【参考方案1】:

您需要在中断处理程序和“主线程”之间进行同步,所以是的,需要一种互斥锁。没有操作系统的常用方法是在“进入临界区”之前禁用中断,然后重新启用。这确保了关键部分是“原子的”(即同时没有中断触发)。

在 ARM 上,我会使用比较和交换指令

你用的是什么编译器?

GCC 和 Clang 具有原子比较和交换的内在函数。

参见例如

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

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

C11 通过&lt;stdatomic.h&gt; 支持它我相信:https://en.cppreference.com/w/c/atomic/atomic_compare_exchange

【讨论】:

其实我知道禁用/启用中断。但问题是我同时在等待来自其他设备的一些中断。所以禁用/启用中断对我来说没有好处。我正在使用 Keil uVision。 如果Keil没有好的内在函数,你可以使用en.wikipedia.org/wiki/Peterson%27s_algorithm作为穷人的互斥(我没用过) Morten,UV 表示“需要同步”。鉴于共享 int turn; 不是 volatile 并且 P1 可以在 turn = 1;while (flag[1] == true &amp;&amp; turn == 1) 之间运行,彼得森算法被破坏。 莫顿,而不是“之前禁用中断......之后重新启用。”,我发现“记录中断状态,禁用......之后重新存储。”更有用。 @chux-ReinstateMonica 我同意保存状态并在之后重新启用更有用,但是我们很快就会进入实现细节,所以我是在笼统地说。我同意你的看法。 Peterson's:实现同步原语时必须小心。我不是说 wiki-article 提供实现,只是解释概念:)【参考方案2】:

从概念上讲,我建议将环形缓冲区分为读取缓冲区和写入缓冲区(可以是环形缓冲区)。

当您使用中断时,您就有并发性,这意味着您需要保护共享数据(即您的缓冲区)。这通常由互斥锁或信号量完成。但是,它们仅适用于 OS 或 RTOS。

所以最常见的做法是暂时序列化你的并发元素。这可以通过禁用 UART 中断来完成。更准确地说,您需要停用所有中断,这会触发使用共享资源的代码。

【讨论】:

其实我知道禁用/启用中断。但问题是我同时在等待来自其他设备的一些中断。所以禁用/启用中断对我来说没有好处。 你可以只禁用UART中断。禁用/启用中断时,您这样做只是为了写入共享变量,因此只需要几条指令。所以,除非你处于硬实时环境中,否则快速停用它并再次激活它通常不是问题

以上是关于环形缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

环形缓冲区大小和反写阈值

微控制器的环形缓冲区

环形缓冲区

关于环形缓冲区

数据结构之环形缓冲器

优秀的内存规划方法——环形缓冲区(ring buffer)