DMA不停接收,先算清读写边界

串口DMA开成循环模式后,中断次数下降了,连续数据也能搬进内存;运行一段时间,协议帧却偶尔错位或被覆盖。DMA只负责把外设寄存器按顺序写进一块循环内存,它不会替CPU记录哪些字节已经处理、哪些字节刚写入、何时发生追尾。稳定接收的关键,是把硬件写位置转换成可靠的软件生产者指针,再管理消费者指针。一旦CPU处理速度短暂落后,若没有明确的溢出策略,循环写入就会把尚未消费的数据静默覆盖。
DMA的剩余计数就是一只绕圈的写指针
假设循环缓冲区长度为N,DMA当前剩余传输数为R,常见写位置可由N−R换算并对N取模。CPU上次处理到old_pos,本次读取到new_pos;若new_pos大于old_pos,新数据是一段连续区间,若new_pos小于old_pos,则说明DMA跨过数组末尾,要先处理尾段再处理头段。读取剩余计数时要遵循具体MCU的寄存器定义,必要时做两次一致性读取,避免恰好在计数更新边沿得到不稳定快照。

图1 MCU内部外设、数据寄存器与DMA请求路径共同决定数据从哪里搬到内存。
半传输和全传输中断只提供检查时机
固定长度高速流可以在半缓冲和全缓冲中断中分别处理前半、后半,处理窗口清楚;不定长串口则常结合空闲线事件,在一段静默后读取当前位置。无论用哪种事件,它都只是提醒CPU“现在可以查看”,并不保证数据边界恰好等于协议帧边界。若中断优先级过低或处理时间超过半个缓冲周期,DMA会再次覆盖尚未消费的数据。事件标志、计数快照和数据区要按正确顺序读取与清除,避免漏掉紧接着到来的新字节。

图2 DMA地址递增、数据宽度与循环模式的配置必须与缓冲区布局一致。
协议解析不要直接趴在DMA写区上
DMA可能在CPU解析时继续写入同一缓存。稳妥做法是先根据位置差把新增字节提交给软件环形队列或分段描述符,再由解析器消费;也可以采用双缓冲,让DMA和CPU分别拥有不同半区。复制会占用内存带宽,却换来清晰的所有权;零拷贝则必须严格保证解析器在DMA覆盖前释放区段。多线程或RTOS环境下,更新生产者、消费者索引时要考虑原子性、缓存一致性和内存屏障,不能只把变量声明成volatile就认为同步完成。

图3 外设数据寄存器的位宽和排列会影响DMA搬运宽度、对齐与内存解释。
溢出必须成为可见故障
生产者追上消费者时,系统要明确选择丢旧数据、丢新数据、暂停接收或触发流控,并增加计数器记录发生次数。静默覆盖最危险,因为上层只看到CRC错或帧头丢失,很难追到真正原因。缓冲深度应由最坏输入速率乘以CPU最长不可服务时间,再加事件与调度裕量;若有突发DMA、Flash擦写或高优先级中断,还要把这些停顿计入。调试时同时记录DMA位置、软件读指针、溢出计数和事件时间戳,边界问题会比打印原始字节更清楚。

图4 环形缓冲区必须同时维护DMA写位置、CPU读位置和追尾后的溢出策略。
一套不靠运气的验证方法
用信号源连续发送带递增序号和校验的字节流,覆盖缓冲区长度的整倍数、整倍数前后一字节和随机突发;同时人为延长某次处理任务,确认溢出计数能出现,而不是悄悄拼错帧。改变波特率、缓存长度和事件策略,检查每次新增区间总长度是否等于发送量。若启用数据缓存,按芯片手册对DMA区域做非缓存映射或正确的失效、清理操作,否则CPU可能读到旧缓存行。复位、停止DMA和重新启动时要把硬件计数、软件索引和解析器状态一起归零,避免新会话从旧半帧继续。若接收数据带有可变长度帧,解析器应能从任意分段位置恢复:帧头可能落在缓冲尾部,长度字段和校验也可能跨越回绕点。不要假设一次空闲中断必然包含完整一帧,可将新增字节连续喂给状态机,由状态机自行寻找帧头、累积长度和验证校验。遇到非法长度或校验失败时,记录丢弃字节数和重新同步位置;只有这些统计可见,现场才能区分线路噪声、协议错误和DMA覆盖。
排查偶发丢帧时,先画出生产者和消费者两条时间线。只要存在一段CPU停顿足以让DMA绕回旧数据,问题就不是协议解析算法,而是缓冲深度、所有权或溢出策略没有闭环。接收路径的日志本身不能阻塞接收。中断里大量格式化打印会延长不可服务时间,反过来制造覆盖。建议只递增计数并保存少量时间戳,在低优先级任务中汇总输出,调试工具才不会改变被测问题。
循环模式只保证DMA继续写,不保证CPU永远来得及读。把写位置、读位置和溢出策略做成显式状态,偶发错帧才会变成可定位事件。你们当前选择丢旧数据还是丢新数据?
声明:
本文由凡亿教育整理,转载请注明来源!
投稿/招聘/广告/课程合作/资源置换 请加微信:13237418207

扫码关注








































