如何在嵌入式应用中使用环形缓冲区并保证完整性

时间:2013-06-05 17:05:32

标签: c embedded circular-buffer

我正在微控制器中构建应用程序。问题是,我在串口中接收数据。它是使用中断编写的,我猜这与线程相同。那么,如果我不能使用锁,我怎样才能在缓冲区中获取这些数据并保证一定的完整性?

4 个答案:

答案 0 :(得分:4)

当您访问中断外的受保护变量(如环形缓冲区,读写位置)时,只需禁用接收中断,因此在您需要输入缓冲区中的字节数或需要弹出一个字节的情况下:

int GetBytesAvailable()
{
    int result;        
    DisableReceiveInterrupt();
    result = writePos - readPos;
    EnableReceiveInterrupt();
    if (result < 0)
        result += RINGBUFFER_SIZE;
    return result;
}

int GetNextByte()
{
    int result = -1;
    DisableReceiveInterrupt();
    if (readPos != writePos)
    {
        result = RingBuffer[readPos++];
        readPos %= RINGBUFFER_SIZE;
    }
    EnableReceiveInterrupt();
    return result;
}

当微控制器在中断禁用时接收到一个字节时。一旦重新启用中断,就会调用中断处理程序。

答案 1 :(得分:1)

您可以通过禁用访问缓冲区的消费者代码中的指令中断并更新它的头/尾或任何指针来实现此目的。

几乎每个有用的串行外设都可以在接收下一个字时缓冲接收字,因此您可以在一小部分字时间内禁用中断。

如果使用C语言编写,则需要在共享变量上使用volatile关键字,以防止编译器以可能破坏正常和中断上下文之间共享的方式优化实际访问。

答案 2 :(得分:1)

如果您有三个控制队列的变量:put_indexget_indexcountput_index仅由制作者线程使用,使用者主题为get_index,两者均为count

对于您的特定平台,更新特定数据类型将是原子的;如果您为count使用这样的数据类型,那么如果put()操作检查count并且它是否已满,则会在put_index添加数据,更新{{1然后将put_index作为 last 操作递增。 count操作检查get(),如果它不为零,则从count获取数据,更新get_index并将其减少为 last 操作。

确保get_indexcount和原子,并确保在之后仅增加所写的数据和索引有效,并且仅在之后减少 读取数据然后不需要锁定。

关键是要确保仅依赖单个原子共享变量,而不是通过单独的put和get索引来确定缓冲区状态。

答案 3 :(得分:1)

如果你可以使缓冲区大小为2的幂,最简单的方法是简单地使用两个适当大小的无符号值,它们能够处理缓冲区大小的数字以及缓冲区。一个值表示已将多少字节放入缓冲区;另一个说明已删除了多少字节。不要将这些值与缓冲区的大小挂钩;只是让它们增加,让它们围绕有问题的整数大小。

unsigned short fetch_inx, stuff_inx;
unsigned char buff[1024];

void stuff_byte(uint8_t dat)
{
  if ((unsigned short)(stuff_inx - fetch_inx) >= 1024)
    // buffer is full
  else
  {
    buff[stuff_inx & 1023] = dat;
    stuff_inx++;
  }
}
int fetch_byte(void)
{
  uint8_t result;
  if (fetch_inx == stuff_inx)
    return -1;
  result = buff[fetch_inx & 1023];
  fetch_inx++; // Must be done after previous statement--see text
  return result;
}

如果缓冲区大小正好是256或65,536字节,则可以使用8位或16位“索引”值,如果不允许将超过255或65,535字节放入缓冲区。另请注意,如果一个不允许缓冲区完全填充,fetch_byte例程可能会使用return buff[(fetch_inx++) & 1023];,但如果缓冲区可以完全填充(因为即将读取的缓冲区槽),这将是不安全的在实际阅读之前,它将有资格进行回收。)

如果stuff_byte在数据进入缓冲区之后才写入[stuff_inx],并且fetch_byte在读取数据之前不写[fetch_inx],则两个例程都应该能够在不受干扰的情况下,在不同的中断或主线上下文中独立执行。