DMA控制器架构:通道、信号与寄存器
DMA控制器,说白了就是CPU的「数据搬运工」。CPU告诉它「从哪搬、搬到哪、搬多少」,它就能自己干完,不用CPU一次次打断。我刚开始接触DMA时,总觉得它很神秘,后来拆开一看,核心就那么几个部件。
核心要点:DMA控制器的骨架是「通道 + 请求/应答握手 + 三组核心寄存器」。理解这三样,你就掌握了80%的DMA工作原理。
2.1 DMA通道——独立的数据搬运线
每个DMA通道,就像一条独立的数据流水线。不同外设可以占用不同通道,互不干扰。比如UART用通道0,SPI用通道1,ADC用通道2——它们可以同时工作。
我见过一个项目,工程师把所有外设都挤在同一个通道上,结果数据冲突,调试了三天。嗯,这里要注意:通道是独立资源,不是共享总线。
| 通道特性 | 说明 | 我见过的坑 |
|---|---|---|
| 通道数量 | 通常4~16个,取决于芯片型号 | 别以为通道多就能随便用,优先级要规划好 |
| 通道优先级 | 固定优先级或轮询模式 | 高优先级通道会「饿死」低优先级通道 |
| 通道绑定 | 每个通道可绑定特定外设请求 | 我曾经绑错通道,数据全乱套了 |
2.2 请求与应答信号——DMA的「门铃」
DMA不会自己主动干活。它需要外设「按门铃」——也就是发送请求信号。外设说「我有数据了,快来拿」,DMA收到请求后,回复一个应答信号「收到,我来了」。
这个握手过程,我习惯把它比作两个人打电话:
- DMA请求(DREQ):外设打给DMA,「我有数据要传输」
- DMA应答(DACK):DMA回给外设,「收到,总线我接管了」
- 传输结束(TC):DMA告诉CPU,「活干完了,你继续」
实战经验:我曾经遇到一个ADC采集丢数据的问题。查了半天,发现是DMA请求信号宽度太短,DMA没来得及响应。后来把请求信号拉长了一个时钟周期,问题解决。信号时序,真的不能马虎。
2.3 源地址与目的地址寄存器——DMA的「导航」
DMA要搬数据,总得知道从哪搬、搬到哪。这两个寄存器就是干这个的。
- 源地址寄存器(SAR):存放数据来源的地址。可以是外设数据寄存器地址,也可以是内存地址。
- 目的地址寄存器(DAR):存放数据要去的地址。通常是内存缓冲区地址。
你想想看,如果每次传输后地址都不变,那DMA只能反复读写同一个位置,这有什么用?所以这两个寄存器还有一个重要特性——地址更新模式。
| 地址模式 | 说明 | 典型场景 |
|---|---|---|
| 固定地址 | 传输后地址不变 | 外设数据寄存器(固定地址) |
| 递增地址 | 传输后地址+1/+2/+4 | 内存缓冲区(连续地址) |
| 递减地址 | 传输后地址-1/-2/-4 | 栈操作、FIFO读取 |
避坑指南:我曾经在配置SPI DMA传输时,把源地址设成了递增模式。结果每次传输后,DMA都去读下一个地址,而不是SPI的数据寄存器。数据全读错了。外设地址一定要用固定模式,这个错误我犯过一次就再也没忘。
2.4 传输计数器——DMA的「计步器」
传输计数器(CNDTR),记录还剩多少数据没搬完。每传输一次,计数器减1。减到0时,DMA就知道活干完了,可以触发中断告诉CPU。
我个人习惯在调试时,先检查这个计数器的值。如果它没按预期递减,说明DMA根本没在工作。有一次我排查一个UART DMA接收问题,发现计数器始终是初始值——原来是我忘了使能DMA通道。
关键参数:传输计数器的初始值 = 你要传输的数据单元个数。注意是「单元个数」,不是字节数。如果配置的是16位传输,计数器=100表示传输100个半字(200字节)。
2.5 DMA控制器架构总览
下面这张图,把上面讲的几个核心部件串起来。我建议你多看几遍,理解数据是怎么从外设流到内存的。
2.6 寄存器配置实战要点
配置DMA寄存器时,我建议按这个顺序来,不容易出错:
- 先关通道:修改配置前,确保通道是禁用的。否则配置可能被正在进行的传输干扰。
- 配源地址:外设地址用固定模式,内存地址用递增模式。
- 配目的地址:和源地址模式对应好,别搞反了。
- 设传输计数器:确定要传多少个数据单元。
- 设传输宽度:8位、16位还是32位?必须和外设寄存器宽度一致。
- 使能通道:最后一步,打开通道,等待外设触发。
小技巧:调试时,我习惯在使能通道前,先读一遍所有配置寄存器的值,确认无误再开启。这个习惯帮我省了不少排查时间。
2.7 常见配置错误
| 错误类型 | 现象 | 我曾经的处理方式 |
|---|---|---|
| 地址模式配反 | 数据只传了第一个字节 | 检查SAR/DAR的地址更新位 |
| 传输宽度不匹配 | 数据错位或丢失 | 确保外设和DMA宽度一致 |
| 计数器初始值=0 | DMA不工作 | 计数器=0时,DMA认为传输已完成 |
| 通道优先级冲突 | 低优先级通道卡死 | 合理分配优先级,必要时用轮询模式 |