我正在微控制器中构建一个应用程序。问题是,我在串口接收数据。它是使用中断编写的,我猜它与线程相同。那么,如果我不能使用锁,我怎么能在缓冲区中获取这些数据并保证一些完整性呢?
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;
}
当中断被禁用时微控制器接收到一个字节。重新启用中断后,将立即调用中断处理程序。
您可以通过禁用消费者代码中的指令周围的中断来完成此操作,这些指令访问缓冲区并更新它的头/尾或任何指针。
几乎每个有用的串行外设都可以在接收下一个字的同时缓冲接收到的字,因此您可以在一小部分字时间内禁用中断。
如果使用 C 等语言编写,则需要在共享变量上使用 volatile 关键字,以防止编译器以可能破坏正常上下文和中断上下文之间共享的方式优化实际访问。
如果您有三个变量控制您的队列:put_index
、get_index
和count
,put_index
仅由生产者线程、get_index
消费者线程和count
两者使用。
对于您的特定平台,更新特定数据类型将是原子的;如果您将这种数据类型用于count
,那么如果put()
操作检查count
并且如果它未满,则将数据添加到put_index
, 更新然后作为最后一个操作put_index
递增。该操作检查,如果它不为零,则从 中获取数据,更新并减少它作为最后一个操作。count
get()
count
get_index
get_index
通过确保它count
是volatile
原子的,并确保它仅在写入的数据和索引有效后递增,仅在读取数据后递减,则不需要锁定。
关键是确保仅依赖单个原子共享变量,而不是通过单独的 put 和 get 索引确定缓冲区状态。
如果您可以将缓冲区大小设为 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 字节,那么如果不允许将超过 255 或 65,535 字节放入缓冲区,则可以使用 8 位或 16 位“索引”值。另请注意,如果不允许完全填充缓冲区,则 fetch_byte 例程可以return buff[(fetch_inx++) & 1023];
使用在它实际被读取之前)。
假设 stuff_byte[stuff_inx]
直到数据进入缓冲区后才fetch_byte
写入,并且直到数据被读取后才写入[fetch_inx]
,这两个例程应该能够在不同的中断或主线上下文中独立执行而不受干扰。