一探究竟:DMA传输的完整流程
做嵌入式这么多年,我觉得理解DMA就像学骑自行车——刚开始觉得复杂,一旦掌握了节奏,就会觉得它简直是硬件世界的自动挡。今天咱们就来掰扯清楚DMA传输的完整流程:请求、响应、数据传输、中断处理。这四个动作串起来,就是DMA的整个生命周期。
一句话概括:DMA传输就是外设或软件发起请求,DMA控制器总线仲裁后拿到总线权,然后搬数据,最后通知CPU“活干完了”。
4.1 第一步:DMA请求
一切故事从“谁需要搬数据”开始。DMA请求有两种来源:
- 外设请求:比如UART收到一个字节、ADC转换完成、SD卡准备好数据。外设拉高DMA请求信号(DREQ),告诉DMA控制器“我有数据要搬”。
- 软件请求:CPU写寄存器手动启动DMA。比如memcpy加速、内存清零、缓冲区搬运。
我个人习惯把外设请求当作边沿触发,软件请求当成电平触发来理解。为什么呢?因为外设请求通常只发一次,数据就绪后撤掉;软件请求则需要一直保持高电平直到DMA开始响应。
避坑指南:我在调一个SPI DMA时,外设DREQ信号设计成了电平触发,而DMA控制器只认边沿触发,结果数据只传了一次就卡死了。后来我量了信号才发现——嗯,硬件握手协议的匹配非常关键。
4.2 第二步:DMA响应
DMA控制器收到请求后,不会立刻就干。它要先向CPU要总线控制权。这个过程叫总线仲裁。
你想想看,CPU正在执行指令,DMA说“让一下,我要用总线”。这时候DMA控制器拉高总线请求信号(HREQ或BUSREQ),CPU会在当前总线周期结束后,拉高总线授权信号(HACK或BUSACK),把总线让出来。
有些系统支持突发传输,DMA一下占住总线传好几个数据;有些用周期窃取,DMA只在CPU不访存的时钟周期里偷着传输。这两种方式各有特点:
| 模式 | 总线占用 | 对CPU影响 | 适合场景 |
|---|---|---|---|
| 周期窃取 | 每个数据只占几个周期 | CPU几乎无感知 | 低速外设(UART、I2C) |
| 突发传输 | 连续占总线直到传完 | CPU被冻住 | 高速外设(SDIO、高速ADC) |
在我做过的一个视频采集项目里,用的是突发传输。一帧图像800x600,DMA一次抢到总线后哗啦啦全传完,CPU趁着垂直消隐期才动。效果不错,但记得要计算好总线阻塞时间,别让实时任务饿死。
4.3 第三步:数据传输
拿到总线后,DMA控制器开始干活了。它按照之前配置好的参数,从源地址读数据,写到目的地址。这个过程中:
- 源地址:可以是外设数据寄存器、内存缓冲区、还是另一个外设。
- 目的地址:可以是内存、外设输出寄存器。
- 传输宽度:8位、16位、32位,必须和源/目的对齐。
- 传输计数:每传一个数据,计数器减1。减到0就触发完成信号。
DMA控制器内部有专门的地址发生器,可以自动递增或递减地址。比如外设地址固定(每次从同一个寄存器读),内存地址递增(写到连续缓冲区)。这种硬件自动调整地址的方式,极大减轻了CPU的负担。
核心指标:数据传输阶段最关注的参数是吞吐率和延迟。吞吐率受总线时钟、数据宽度、突发长度影响;延迟受总线仲裁和DMA控制器状态机响应速度影响。
4.4 第四步:中断处理
数据搬运完成后,DMA控制器做两件事:
- 拉高中断请求信号(DMAC_IRQ),通知CPU。
- 如果有自动重载配置,重新加载计数值,准备下一次传输。
CPU收到中断后,在中断服务程序(ISR)里:
- 清除中断标志位
- 检查传输完成状态
- 处理数据(比如启动下一个DMA、唤醒等待队列)
注意:千万不要在ISR里做耗时操作!我见过有人直接在DMA中断里做memcpy、打印日志,结果中断嵌套把自己搞崩溃了。建议ISR里只设标志位或唤醒任务,实际处理放到主循环或任务里。
4.5 流程图:DMA传输全生命周期的可视呈现
下面我用一张SVG流程图,把整个DMA传输过程串起来,方便你理解其中的时序和状态变化。
4.6 全流程时序分析
从图上你可以看到,DMA传输的四个主要步骤依次执行。但实际硬件中可能有流水线重叠,比如在搬运上一个数据的同时,开始仲裁下一个请求。不过在理解层面,我建议你先把它们看成串行过程,等调通后再考虑优化。
我曾经在一个FPGA项目中,把DMA的中断服务设成了最高优先级,结果ISR里又启动了新一轮传输,导致上一个传输的完成标志还没清除就被覆盖——嗯,这叫竞争条件。后来我在ISR里加了互斥访问保护,才解决问题。
常见陷阱:
- 传输计数器和地址指针在溢出处没处理好,导致DMA疯狂跑飞。
- 中断标志位未及时清除,DMA不再响应新请求。
- 源地址和目的地址对齐规则不一致,比如源是4字节对齐,目的是2字节对齐,DMA可能会自动截断或报错。
4.7 小结与实践建议
DMA传输的基本流程就像一个自动化的数据搬运工:请求、响应、搬运、通知。你只要把配置设对,剩下的交给硬件。但硬件不会替你思考——地址计算、总线竞争、中断延迟这些坑,都得靠经验填平。
我的建议:
- 第一次调DMA,先用手动单次传输模式,别急着开循环或链表。
- 用逻辑分析仪抓一下DREQ/HACK/数据总线,确认握手时序对不对。
- 中断服务程序里只做最轻量级的处理,复杂逻辑放主循环。
- 如果碰到DMA传输一半卡死,优先检查传输计数器是否提前归零。