1. DMA基础概念:直接存储器访问的定义、工作模式
各位做嵌入式或SoC开发的同行,咱们今天来聊聊DMA。说白了,DMA就是一块硬件搬运工。CPU告诉它“从A搬到B,搬完喊我一声”,然后CPU就可以去干别的事了。你想想看,要是没有DMA,每传一个字节CPU都要亲自跑一趟中断或者轮询,那效率得多低啊。
我入行那会儿,第一次接触DMA是在一个老掉牙的ARM7平台上。当时写UART收发程序,傻乎乎地用CPU轮询每个字节,结果CPU占用率飙到80%多。后来老工程师扔给我一句话:“去把DMA用上。”嗯,从那以后我再也没裸写过串口收发。
1.1 直接存储器访问的定义
直接存储器访问,英文Direct Memory Access,简称DMA。它是一种允许外设或存储器之间直接交换数据的技术,全程不经过CPU核心。CPU只需要把源地址、目的地址、数据长度配置好,然后“点火”启动,DMA控制器就会自动搬数据。搬完了,发个中断通知CPU:“活干完了。”
核心要点: DMA ≠ 零CPU开销。配置DMA寄存器、响应DMA中断、处理链表等等,CPU仍然要参与。但数据搬运阶段CPU完全解放。
为什么CPU要参与配置?因为DMA控制器没有“自主意识”。它不知道从哪里搬、搬多少、搬到哪里去。这些信息必须由CPU在初始化阶段写入控制寄存器。我在项目中见过有人用DMA做音频播放,结果忘记配置源地址增量,DMA每次都从同一个地址读,播出来的声音就变成了“嗡嗡”的噪声。
1.2 DMA的工作模式
DMA工作模式有好几种,不同芯片厂家的称呼可能不太一样,但核心思路就那么几类。我把常用的模式整理成了下表,方便你对比。
| 模式名称 | 工作机制 | 典型场景 | 数据搬完怎么办 |
|---|---|---|---|
| 基本传输模式 | 单次请求→单次传输,传输完停止 | 简单外设(SPI单帧收发) | 自动停止,产生中断 |
| 循环传输模式 | 传输完自动重载起始地址,无限循环 | 音频环形缓冲、连续ADC采样 | 每轮回产生中断(可选) |
| 链表传输模式 | 通过链表结构存储多组配置,逐项执行 | 多块不连续内存的批量搬移 | 链表执行完毕才中断 |
| 轮询传输模式 | CPU不断查询DMA完成标志(不常用) | 测试调试阶段 | 无中断,CPU自旋等待 |
我的习惯: 产品代码里几乎只用循环模式和链表模式。基本模式虽然简单,但每次传输完都要CPU重新配置,中断太频繁。循环模式在音频/视频流里特别好用——数据一直跑,CPU只管在缓冲区满时过来消费就行。
1.3 一次完整的DMA传输过程
咱们拆解一次DMA传输的完整生命周期。一共四个步骤,很多新手只关心第三步(传输中),忽略了第一和第四步,结果延迟优化全白做。
- 初始化配置:CPU设置DMA控制寄存器,包括源地址、目的地址、传输宽度(字节/半字/字)、传输长度、是否地址增量、中断使能等。这是CPU最忙的阶段。
- 启动传输:CPU写一个使能位或者触发一次软件请求。DMA控制器开始等待传输请求(硬件触发或软件触发)。
- 数据搬运:DMA控制器占用总线,每拍搬运N个数据。每次传输后,源地址和目的地址根据增量配置自动调整。这个阶段CPU可以执行其他代码或进入低功耗模式。
- 传输完成:当剩余计数值递减到0时,DMA控制器产生中断请求(如果中断使能),状态寄存器置位。CPU在中断服务程序中读取状态、处理数据、准备下一次传输。
我曾经踩过的坑: 在STM32F4上,我忽略了DMA的FIFO特性。配置了源地址为外设寄存器(每次读取自动清),但DMA的FIFO一次预取了多个数据,导致实际读取次数少于预期。从那以后我每次配DMA都会看一眼FIFO阈值寄存器。
1.4 DMA与CPU的数据交互关系
很多人以为DMA就是“完全独立于CPU”,其实不是。DMA和CPU共享同一个总线矩阵。以ARM Cortex-M系列为例,DMA和CPU通过AHB总线访问存储器。如果两者同时访问同一块内存,总线仲裁器会裁决谁先拿到总线。
我当时在一个图像采集项目里遇到了“总线冲突”问题。DMA正在往SDRAM写摄像头数据,CPU同时要从SDRAM读数据做算法。结果两个Master抢总线,导致DMA带宽下降,图像出现撕裂。解决方法很简单:把CPU读的缓冲区放到另一个内存区域,或者让CPU等DMA传输完再读。
下面这张图展示了典型的DMA系统架构和数据流向。我建议你理解这张图,后面的中断延迟优化都建立在这个模型上。
1.5 中断在DMA体系中的角色
DMA传输完成中断是整个课程的核心。你想想看,如果没有中断,CPU怎么知道数据搬完了?轮询标志位又让CPU陷入忙等。所以中断是DMA的“收尾信号”。但这个信号来得快还是慢,直接影响系统实时性。
中断延迟包含几个环节:
- DMA发出中断信号:从最后一个数据写完毕到中断挂起寄存器置位,通常只有1~2个时钟周期。
- CPU响应中断:取决于CPU当前执行的指令、优先级屏蔽、中断控制器仲裁等。这部分是延迟大头。
- 中断向量表跳转:从取向量到执行ISR第一条指令。
- ISR执行时间:处理数据、清除标志、准备下一轮传输。
我这门课后面会逐层拆解这些延迟,从硬件到软件,从配置到代码,一步步教你压到极致。
先给你一个预热技巧: 在配置DMA时,尽量把中断优先级设高,并且让ISR只做最轻量的工作——把数据指针交给任务,而不是在ISR里做完整处理。我在一个4KHz的音频采集项目中,就是因为ISR里做了太多浮点运算,导致DMA完成中断被其他高优先级中断阻塞了十几微秒,音频出现pop噪声。
好了,这一章先把DMA的底子打好。后面的每章都会基于这个基础知识,深入中断延迟的每个环节。