云计算百科
云计算领域专业知识百科平台

双缓冲flag=2/3乒乓机制:代码级实现与变体设计

_adcBuf.data[index++] = SWAP16(data)填A缓冲,满了标记flag=2切B;B满标flag=3切回A;采集线程看到flag≠0就打包帧头+CRC发USB。这套两行注释的乒乓机制是500K数据流的无间隙保障。本文从代码讲到变体。


一、机制全貌(代码解析原文)

1.1 写侧(DMA完成/数据处理中)

// 双缓冲机制(源码注释原文):
// A缓冲满 → 标记flag=2,切换到B缓冲
// B缓冲满 → 标记flag=3,切换回A缓冲
_adcBuf.data[_uadc.adc_collectIndex++] = SWAP16(ADC数据);

if(_uadc.adc_collectIndex >= BUF_SIZE) {
if(_adcFunctionParam.当前缓冲 == A) {
adc_collectFullFlag = 2; // A就绪
_adcFunctionParam.当前缓冲 = B;
} else {
adc_collectFullFlag = 3; // B就绪
_adcFunctionParam.当前缓冲 = A;
}
_uadc.adc_collectIndex = 0; // 新缓冲从头填
}

1.2 读侧(采集线程)

if(adc_collectFullFlag == 2) {
usb_sendMsg(&_adcBuf, frameLen); // 发A
adc_collectFullFlag = 0;
}
else if(adc_collectFullFlag == 3) {
usb_sendMsg(&_adcBufB, frameLen); // 发B
adc_collectFullFlag = 0;
}


二、时序图解

时间 →

填充侧: [AAAAAA][BBBBBB][AAAAAA][BBB…
↓flag=2 ↓flag=3
发送侧: [发A][ ][发B][ ][发A]

关键性质:
① 填充与发送永远不碰同一块内存 ✓
→ 无锁! (单生产者+单消费者+乒乓)
② USB发送耗时波动(20ms~50ms)
只要在"另一块填充完"之前完成 → 不丢数
③ flag只有0/2/3三值:
写侧: 0→2或3(填满时)
读侧: 2或3→0(取走后)


三、为什么不用环形缓冲?

环形缓冲方案:
填充指针+读取指针+满判定
→ 需要原子操作/关中断保护(指针竞态)
→ USB需要的数据块跨环形接缝
(帧重组逻辑复杂)

乒乓方案:
边界永远在缓冲边缘 → 帧天然完整 ✓
无指针竞态(一个flag单向流转)
代价: 单块必须整帧大小(内存稍大)

→ 采集卡"帧式上传"场景,乒乓完胜
(环形更适合字节流场景)


四、并发安全性分析

共享变量: adc_collectFullFlag (u8)

写侧上下文: 数据处理(优先级0线程内)
读侧上下文: 同一线程!
→ 其实写侧是DMA中断/其他上下文时才需谨慎

flag流转: 0→2→0→3→0…
单字节写: ARM上天然原子
→ 无需互斥量 ✓

潜在时序(若写侧在中断):
flag=2瞬间,读侧可能正在检查
→ 最多晚一拍处理,不会错乱
→ "最终一致"足够(数据不丢,延迟一拍)


五、变体设计

5.1 三缓冲(应对USB大抖动)

flag: 4=A满 5=B满 6=C满

USB卡顿50ms@500K:
两缓冲: 需在半缓冲周期内发完,险
三缓冲: 多一块垫背 ✓
代价: RAM×1.5
→ H743的1MB RAM可以豪一把

5.2 带序号的双缓冲(检测丢帧)

frame: head + seqNo + data + crc
seqNo每帧+1
上位机: 发现seqNo跳变 → 知道丢帧
→ 弱USB环境下量化丢包率(在线记录仪丢包
可能性的自检手段,博文126)

5.3 大小帧自适应

低速(10K以下): 小缓冲快发(低延迟观测)
高速(500K): 大缓冲(帧效率优先)
→ BUF_SIZE随采样率切换


六、内存布局估算

500KSPS×8CH×2B = 8MB/s
USB帧周期设50ms:
每帧数据 = 8MB/s × 50ms = 400KB?!

实际: D223帧更短(10~20ms):
每帧 80~160KB → A+B两块约320KB
H743 RAM 1MB ✓ 可容纳
(F407的192KB就装不下 → 又一个必须H7的理由)


七、常见问题

问题解答
波形周期性丢一段? flag处理延迟;查线程阻塞源
上位机帧错位? 帧头+CRC定位重同步
内存不够? 缩短帧周期/减通道
想更低延迟? 小缓冲+更高发送频率

八、总结

双缓冲flag=2/3实现了:

要素设计
结构 A/B两块+单flag
写侧 满则标记2或3并切换
读侧 见2/3即发,清0
无锁 单向流转天然安全
优势 帧完整+免环形接缝
变体 三缓冲/序号/自适应

一个字节的flag,指挥两块内存的乒乓接力——双缓冲是嵌入式数据流最经典也最被低估的设计。它没有环形缓冲的数学优雅,却有"帧天然完整、并发天然安全"的工程实在。500KSPS的洪流里,每一帧都完整抵达,靠的就是这份朴素。


下篇预告:《RT-Thread双线程调度:优先级0的采集与优先级1的命令》

赞(0)
未经允许不得转载:网硕互联帮助中心 » 双缓冲flag=2/3乒乓机制:代码级实现与变体设计
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!