2. 嵌入式存储层次基础
DMA控制器,说白了就是个硬件搬运工。它不需要CPU插手,自己就能把数据从A点搬到B点。但很多工程师一上来就写代码,结果发现数据不对、系统卡死,回头一看——连DMA的硬件结构都没搞明白。
我个人习惯,调试任何DMA问题之前,先把芯片手册里的DMA控制器框图翻出来,对着硬件信号捋一遍。今天我们就从最底层的硬件架构说起。
DMA通道:独立的搬运工
每个DMA控制器内部都有多个通道。你可以把每个通道想象成一个独立的搬运工,各自负责一条数据传输任务。
| 通道特性 | 说明 |
|---|---|
| 独立配置 | 每个通道有自己的源地址、目的地址、传输宽度、优先级 |
| 独立触发源 | 每个通道可以绑定不同的外设请求信号 |
| 独立中断 | 传输完成、传输错误等事件可以单独上报 |
| 优先级仲裁 | 多个通道同时请求时,由硬件仲裁谁先执行 |
我在项目中遇到过一个问题:两个外设同时使用DMA,结果一个音频数据断断续续,另一个ADC采样丢点。查了半天,发现两个通道的优先级没配好,低优先级的通道一直被抢占。嗯,这里要注意——多通道场景下,优先级不是越高越好,关键看实时性要求。
请求与应答信号:握手协议
DMA不是自己闷头干活的。它需要和外设、总线仲裁器、内存控制器之间通过请求/应答信号来协调。
核心信号对:
- DMA请求(DREQ):外设告诉DMA控制器“我有数据要传”
- DMA应答(DACK):DMA控制器告诉外设“收到,我开始传了”
- 总线请求(BREQ):DMA控制器向总线仲裁器申请总线控制权
- 总线应答(BACK):总线仲裁器把总线交给DMA
我曾经调试过一个UART DMA接收的问题,数据总是少几个字节。用逻辑分析仪抓了DREQ和DACK信号,发现DMA应答信号拉高后,UART的FIFO还没准备好数据——时序没对齐。说白了,握手信号的时序关系,比寄存器配置更容易踩坑。
源地址与目的地址寄存器
这两个寄存器决定了数据从哪来、到哪去。看似简单,但地址的递增模式经常让人翻车。
| 地址模式 | 典型场景 | 注意事项 |
|---|---|---|
| 固定地址 | 外设数据寄存器(如UART DR、ADC DR) | 外设地址不会变,每次读写同一个寄存器 |
| 递增地址 | 内存缓冲区、帧缓冲区 | 地址按传输宽度自动增加 |
| 递减地址 | 某些FIFO或堆栈操作 | 用得少,但有些芯片支持 |
你想想看,如果源地址配成了递增,但外设寄存器是固定的——第一次读对了,第二次就读到了隔壁寄存器,数据全乱套。我建议,外设端一律配成固定地址,内存端根据需求选递增或固定。
传输宽度与突发传输
传输宽度就是每次搬运的数据量。常见的有8位、16位、32位,有些高端控制器还支持64位甚至128位。
传输宽度匹配原则:
- 源端宽度 ≠ 目的端宽度时,DMA控制器会自动做数据打包/解包
- 但性能会下降,因为需要额外的对齐操作
- 最佳实践:源和目的宽度保持一致
突发传输是DMA性能的关键。突发传输允许DMA一次请求就连续传输多个数据单元,而不是传一个数据就释放一次总线。
| 传输模式 | 总线占用 | 传输效率 | 适用场景 |
|---|---|---|---|
| 单次传输 | 每次传输后释放总线 | 低 | 低带宽外设、实时性要求高的场景 |
| 突发传输 | 连续传输多个数据后才释放 | 高 | 内存拷贝、显示刷新、大数据块搬运 |
突发长度(Burst Length)通常可以配置为4、8、16等。但要注意——突发长度越大,总线被占用的时间越长,其他总线主设备(比如CPU)就会被饿死。我曾经在一个多核系统上,把DMA的突发长度设成了64,结果CPU的cache refill被严重延迟,系统响应变得一卡一卡的。
避坑指南:
- 突发传输不要超过系统总线的最大支持长度
- 实时性要求高的外设(如音频I2S),建议用单次传输或小突发
- 大数据块搬运(如LCD刷新),用大突发提升吞吐量
DMA传输的完整流程
把上面这些硬件要素串起来,一次典型的DMA传输流程是这样的:
- CPU配置DMA通道:源地址、目的地址、传输宽度、突发长度、传输大小
- 外设产生DMA请求(DREQ)
- DMA控制器向总线仲裁器申请总线控制权(BREQ)
- 总线仲裁器授权(BACK),DMA获得总线
- DMA向外设发送应答信号(DACK),开始传输
- 按配置的突发长度连续传输数据
- 传输完成后,DMA释放总线,触发中断
这个流程看起来简单,但每个环节都可能出问题。我调试过最头疼的一个案例:DMA配置看起来全对,但就是不触发传输。最后发现是外设的DMA请求使能位没开——硬件上DREQ信号根本没拉起来。
这张图把DMA传输的核心信号流都画出来了。你调试的时候,拿着逻辑分析仪去抓这些信号,基本能定位90%的问题。
调试要点总结:
- 先确认DMA通道是否使能,外设的DMA请求是否打开
- 用逻辑分析仪抓DREQ/DACK,看握手时序是否正常
- 检查源地址和目的地址的递增模式是否匹配
- 突发长度不要设太大,避免总线被长时间占用
- 传输宽度尽量保持一致,避免性能损失