_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的命令》
网硕互联帮助中心




评论前必须登录!
注册