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传输的完整生命周期。一共四个步骤,很多新手只关心第三步(传输中),忽略了第一和第四步,结果延迟优化全白做。

  1. 初始化配置:CPU设置DMA控制寄存器,包括源地址、目的地址、传输宽度(字节/半字/字)、传输长度、是否地址增量、中断使能等。这是CPU最忙的阶段。
  2. 启动传输:CPU写一个使能位或者触发一次软件请求。DMA控制器开始等待传输请求(硬件触发或软件触发)。
  3. 数据搬运:DMA控制器占用总线,每拍搬运N个数据。每次传输后,源地址和目的地址根据增量配置自动调整。这个阶段CPU可以执行其他代码或进入低功耗模式。
  4. 传输完成:当剩余计数值递减到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系统架构和数据流向。我建议你理解这张图,后面的中断延迟优化都建立在这个模型上。

DMA系统架构与数据流向图 DMA系统架构与数据流向 CPU核心 配置 & 响应中断 DMA控制器 搬运数据 系统存储器 SRAM / SDRAM 外设 UART / SPI / ADC 总线仲裁器 配置/启动 读/写数据 请求/握手 中断请求 说明: CPU配置DMA后,DMA通过总线仲裁器直接访问内存和外设,数据搬运完全由DMA完成。 搬运完成后,DMA通过中断通知CPU,CPU再处理结果数据。

1.5 中断在DMA体系中的角色

DMA传输完成中断是整个课程的核心。你想想看,如果没有中断,CPU怎么知道数据搬完了?轮询标志位又让CPU陷入忙等。所以中断是DMA的“收尾信号”。但这个信号来得快还是慢,直接影响系统实时性。

中断延迟包含几个环节:

  • DMA发出中断信号:从最后一个数据写完毕到中断挂起寄存器置位,通常只有1~2个时钟周期。
  • CPU响应中断:取决于CPU当前执行的指令、优先级屏蔽、中断控制器仲裁等。这部分是延迟大头。
  • 中断向量表跳转:从取向量到执行ISR第一条指令。
  • ISR执行时间:处理数据、清除标志、准备下一轮传输。

我这门课后面会逐层拆解这些延迟,从硬件到软件,从配置到代码,一步步教你压到极致。

先给你一个预热技巧: 在配置DMA时,尽量把中断优先级设高,并且让ISR只做最轻量的工作——把数据指针交给任务,而不是在ISR里做完整处理。我在一个4KHz的音频采集项目中,就是因为ISR里做了太多浮点运算,导致DMA完成中断被其他高优先级中断阻塞了十几微秒,音频出现pop噪声。

好了,这一章先把DMA的底子打好。后面的每章都会基于这个基础知识,深入中断延迟的每个环节。