上一篇文章我们讨论了UART通信三种方式中的查询和中断两种方式,这里讨论第三种:DMA。
DMA 是什么?
Direct Memory Access(直接内存访问)—— 让硬件自动搬运数据,解放 CPU。
传统方式:CPU 搬运数据(循环1000次)
DMA方式: 配置一次 → DMA自动搬运 → 完成后通知CPU
好用的发送
| |
不实用的接收
DMA接收非常不实用,因为需要预先配置好需要接收的字节数:
你配置接收1000字节...
对方只发了100字节就停了...
DMA还在傻等剩下的900字节 → 永远不知道数据已完整
这就导致了,DMA发送非常好用,但是DMA接收单独使用实用性很差,但如果配合IDLE空闲中断,就可以改善这个问题。
DMA + IDLE
IDLE中断是UART硬件自带的检测机制:
RX引脚 -> 接收逻辑 -> IDLE检测 -> 检测到空闲 -> 置位SR[IDLE]标志 -> IDLE=1触发中断
IDLE触发的条件(硬件自动判断):
- 之前有收到至少一个字节
- RX线路持续高电平超过一个字符时间(大约10bit时间)
两个条件同时满足。
CubeMX 配置
需要配置两个 DMA 通道:
USART1_TX:发送用USART1_RX:接收用
还要打开全局中断让IDLE可以触发:
- USART1 → NVIC Settings → USART1 global interrupt → Enable
最后打开DMA中断,不然DMA完成传输的中断不会触发:
- DMA → NVIC 里也要开启对应的 DMA 中断
工作原理
它们是这样配合工作的:
- DMA负责:自动搬运数据到buffer(不消耗CPU)
- IDLE负责:告诉程序"数据传完了,来处理"
接收数据退出的条件有两个:
- 收到了指定的最大数量数据,DMA完成退出
- 没有收到最大数量数据,但是发完了,IDLE中断触发退出
使用方式
| |
函数内部打开了IDLE中断,然后启动DMA接收。因为IDLE+DMA接收太常见了,HAL厂家提前进行了封装,我们只要调用这一个函数即可。
| |
如果process_data处理的速度太慢,可能会导致新数据来了覆盖缓冲区或者没来得及启动接收丢失数据,这时候我们可以考虑使用双缓冲区或者交给后台任务处理。
双缓冲区实现
| |
思考这样一个场景:
缓冲区 100 字节
收到 80 字节 → IDLE 触发 → 回调开始执行
→ 回调执行中...
→ 新数据来了!发来 50 字节
→ DMA 还没重启,数据丢了!
如果配合使用双缓冲区+在回调一开始就重启DMA接收,就能解决。
注:一般来说,DMA和IDLE的中断优先级不会是问题,因为DMA TC中断触发的时候,会关闭DMA接收,禁用IDLE中断,然后调用回调。