You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无OS微控制器UART中断共享环形缓冲区是否需采用无锁设计

裸机UART缓冲区共享保护方案解答

方案选型结论

你当前采用的关中断保护临界区的方案,是单核心裸机场景下最简单可靠的共享资源保护手段,完全不需要额外实现基于CAS的无锁环形缓冲区:

  • 单核心场景下,中断是唯一的异步执行流,关中断即可完全避免共享资源的并发访问冲突
  • 优化后的临界区仅包含指针交换、位置变量修改等几个操作,耗时最多十几个CPU周期,哪怕是Cortex-M0跑10MHz也仅耗时1us多,远低于UART单字节传输时长(1Mbps波特率下单比特传输耗时1us,1字节至少要10us),完全不会导致串口丢包
  • 自行实现无锁队列容易出现内存序、原子性相关的隐性BUG,调试难度远高于关中断方案

当前代码存在的BUG与优化点

你的现有实现存在几个明显的设计缺陷,需要优先修正:

  1. 临界区包含耗时过长的操作
    swapBuffers函数中把512字节缓冲区清零的操作放在了关中断的区间内,清零操作需要消耗数百个CPU周期,高波特率场景下会直接导致串口中断响应不及时丢数据,需要把清零操作移到开中断之后执行。
  2. 用0值判断缓冲区为空的逻辑错误
    串口传输可能收到合法的0x00数据(比如二进制传输、字符串结束符),用c==0判断无数据会直接丢弃合法数据,应该新增独立的变量记录缓冲区有效数据长度。
  3. 未做缓冲区溢出保护
    中断写入逻辑没有判断缓冲区是否已满,缓冲区满后继续写入会直接覆盖未读取的旧数据,需要新增满判断逻辑。
  4. readPos重置逻辑错误
    缓冲区交换后,readPos应该重置为0,从新的读缓冲区起始位置开始读取,现有逻辑会导致读取位置偏移。

修正后的核心代码示例

新增变量定义

// 记录写缓冲区已写入的有效数据长度,中断中写、swap中读
volatile int writeLen[MAX_CHANNEL_COUNT] = {0};
// 记录读缓冲区的总有效数据长度,仅主循环中访问
int readLen[MAX_CHANNEL_COUNT] = {0};

优化后的swapBuffers函数

void swapBuffers(int channel) {
  char *old_write_buf;
  __disable_irq();
  // 临界区仅做指针和状态变量交换,耗时极短
  old_write_buf = writeBuffers[channel];
  writeBuffers[channel] = readBuffers[channel];
  readBuffers[channel] = old_write_buf;
  // 转移有效数据长度,重置写状态
  readLen[channel] = writeLen[channel];
  writeLen[channel] = 0;
  writePos[channel] = 0;
  readPos[channel] = 0;
  __enable_irq();
  // 清零操作移到开中断后执行,不影响中断响应
  memset(writeBuffers[channel], 0, UART_BUFSIZE);
}

优化后的中断写入逻辑

if (IIR & USART_FLAG_RXNE)
{                  
  USARTx->SR &= ~USART_FLAG_RXNE;             
  c = USART_ReceiveData(USARTx);
  // 缓冲区满时直接丢弃新数据,可根据需求修改为覆盖旧数据
  if(writeLen[Channel] < UART_BUFSIZE) {
    writeBuffers[Channel][writePos[Channel]] = c;
    writePos[Channel]++;
    writeLen[Channel]++;
    if(writePos[Channel]>=UART_BUFSIZE) writePos[Channel]=0;
  }
}

优化后的读字符函数

int GetCharUART (char Channel)
{
  // 用有效长度判断是否读完,避免0值误判
  if (readPos[Channel] >= readLen[Channel]) {
    swapBuffers(Channel);
    return EMPTY;
  }
  int c = readBuffers[Channel][readPos[Channel]++];
  return c;
}

可选的无锁方案说明

如果你对中断延迟要求极高,完全不能接受关中断的开销,可以采用单生产者单消费者无锁环形缓冲区,你的场景刚好匹配该模型的适用条件(UART中断是唯一生产者,主循环是唯一消费者):

  • ARM平台下32位整型变量的读写本身就是原子操作,不需要额外CAS指令
  • 仅需要将读写位置声明为volatile,避免编译器优化导致的逻辑错误
  • 不需要关中断,也不需要任何锁操作,开销比关中断方案更低

内容的提问来源于stack exchange,提问作者Andre Ahmed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 07:27:04