4. DMA控制器硬件架构:通道、请求、优先级、传输宽度与突发传输
DMA控制器,说白了就是CPU的「数据搬运工」。CPU告诉它「把A地址的数据搬到B地址,搬完通知我」,然后就可以去干别的事了。但要让这个搬运工干活利索,你得先搞懂它的内部结构。
我刚开始接触DMA时,总觉得这东西就是个黑盒子。后来在STM32F4上调试一个高速ADC采集项目,DMA死活不工作,我才被迫把寄存器手册翻了个遍。嗯,从那以后,我养成了一个习惯:写驱动前,先把DMA控制器的硬件架构理清楚。
4.1 通道(Channel)—— 独立的搬运工
DMA控制器内部有多个通道,每个通道就是一条独立的数据搬运流水线。你可以把它们想象成工厂里的多个传送带,每个传送带负责一条数据流。
| 特性 | 说明 |
|---|---|
| 独立性 | 每个通道有独立的源地址、目的地址、传输计数、控制配置 |
| 并行性 | 多个通道可以同时工作(但受总线带宽限制) |
| 中断独立 | 每个通道可以单独配置传输完成/错误中断 |
| 资源独占 | 一个外设的DMA请求通常只能映射到固定通道 |
我记得有一次,一个同事把所有外设的DMA请求都映射到了通道0,结果系统跑起来后,ADC、SPI、UART的数据全混在一起。排查了半天才发现是通道冲突。所以我的建议是:每个外设尽量分配独立的通道,除非你明确知道它们在时间上不会同时工作。
通道数量因芯片而异:低端MCU通常有2-4个通道,高端芯片(如STM32H7)可达16个以上。选型时一定要确认通道数是否满足你的外设数量需求。
4.2 请求(Request)—— 搬运工的启动信号
DMA通道不会自己开始搬运数据,它需要一个触发信号。这个信号就是DMA请求。
请求分为两类:
- 硬件请求:由外设(ADC、UART、SPI、定时器等)自动产生。比如ADC转换完成时,它会向DMA控制器发送一个请求信号,告诉DMA「数据准备好了,快来搬」。
- 软件请求:由CPU写寄存器手动触发。常用于内存到内存的拷贝,或者调试时模拟外设行为。
你想想看,如果没有请求机制,DMA就得不停地轮询外设有没有数据——那还不如CPU自己搬呢。请求机制让DMA做到了事件驱动,只有外设需要时才工作,省电又高效。
个人经验:在调试DMA驱动时,我经常先用软件请求验证通道配置是否正确。如果软件请求能正常传输,但硬件请求不行,那问题大概率出在外设的DMA请求使能或映射配置上。
4.3 优先级(Priority)—— 谁先搬?
当多个DMA通道同时请求传输时,谁先获得总线使用权?这就靠优先级来裁决了。
优先级通常有两种配置方式:
- 固定优先级:通道编号越小,优先级越高(如通道0 > 通道1 > 通道2)。简单粗暴,但不够灵活。
- 可编程优先级:每个通道可以独立设置优先级等级(如高、中、低)。我更喜欢这种方式,因为它能根据实际需求动态调整。
我曾经在一个多通道数据采集系统中踩过坑:ADC和定时器都用了DMA,但ADC的优先级设低了。结果在高负载时,ADC数据经常丢失,因为定时器的DMA请求总是抢在ADC前面。解决方案很简单——把ADC的优先级提到最高。
注意:优先级设置不是越高越好。如果一个高优先级通道持续占用总线,低优先级通道可能会被「饿死」,永远得不到服务。合理做法是:对实时性要求高的外设(如音频、视频)设高优先级,对批量传输(如日志记录)设低优先级。
4.4 传输宽度(Transfer Width)—— 一次搬多少?
传输宽度指的是DMA每次搬运的数据位宽。常见的有:
| 宽度 | 适用场景 |
|---|---|
| 8位(字节) | UART、I2C、单字节传感器数据 |
| 16位(半字) | 音频数据(16位PCM)、部分ADC |
| 32位(字) | 内存拷贝、32位ADC、LCD帧缓冲 |
| 64位(双字) | 高端芯片的快速内存传输 |
这里有个关键点:源宽度和目的宽度可以不同。比如,一个8位的UART外设可以向32位的内存缓冲区传输数据。DMA控制器会自动处理对齐和填充。
但要注意——不是所有组合都支持。我记得在某个项目中,我想把16位ADC数据直接写到32位SRAM,结果DMA配置死活不通过。查手册才发现,那个DMA控制器要求源宽度必须小于等于目的宽度,而且两者必须是整数倍关系。
最佳实践:尽量保持源和目的宽度一致。如果必须不同,务必查阅芯片参考手册的「DMA传输宽度兼容性表」。
4.5 突发传输(Burst Transfer)—— 一次搬一串
突发传输是DMA性能的关键。普通传输模式下,DMA每次搬运一个数据单元就要释放总线,重新仲裁。而突发传输允许DMA一次连续搬运多个数据单元,中间不释放总线。
突发长度通常可配置为:
- 单次(Single):每次传输1个数据单元,相当于非突发模式
- 4拍(INCR4):连续传输4个数据单元
- 8拍(INCR8):连续传输8个数据单元
- 16拍(INCR16):连续传输16个数据单元
突发传输的好处很明显:总线利用率高。因为每次总线仲裁都有开销,一次仲裁搬多个数据显然更划算。但坏处也很明显:总线被长时间占用,其他主设备(包括CPU)可能被阻塞。
我的建议:对于实时性要求高的系统,突发长度不要设太大。我曾经在一个电机控制项目里把DMA突发长度设为16,结果CPU的中断响应延迟从2μs飙升到15μs,电机控制环路直接崩溃。后来改成INCR4,问题解决。
4.6 整体架构图
下面这张图展示了DMA控制器的核心架构和各个模块之间的关系:
4.7 配置实战要点
了解了硬件架构后,配置DMA时你需要关注以下几个关键参数:
- 通道选择:确认外设的DMA请求映射到哪个通道
- 优先级设置:根据实时性需求分配优先级
- 传输宽度:源和目的宽度匹配,或确认兼容性
- 突发长度:平衡传输效率和总线占用
- 地址模式:固定地址还是递增地址
- 传输模式:单次、循环还是内存到内存
常见陷阱:很多芯片的DMA通道和外设请求之间不是一一对应的,而是通过一个请求映射矩阵来配置。比如STM32的DMA1,通道0可以映射到ADC1、TIM2_CH3、SPI1_RX等多个外设,但同一时间只能选一个。配置时一定要仔细核对映射表。