一文读懂 DMA:工作流程、传输模式与典型应用场景汇总
1.DMA 直接内存访问简介
在传统的数据传输方式中,单片机的 CPU 负责从一个外设(如 I2C、串口、ADC 等)读取数据并将其存储到内存中,或者从内存中读取数据并写入到外设中。这种方法需要 CPU 不断地处理读写操作,容易造成 CPU 负载过高,影响系统的整体性能。
DMA 的引入改变了这一点,DMA(Direct Memory Access,直接内存访问)是一种允许硬件子系统在不经过 CPU(中央处理单元)干预的情况下直接与内存交换数据的技术。DMA 大大提高了系统性能,特别是在需要大量数据传输的应用中,如音频/视频处理、网络通信和数据采集。

使用 DMA 直接内存读取,可以在不需要 CPU 参与的情况下,实现外设和内存直接的数据传输
使用 DMA 进行数据传输,可以在后台自动完成数据传输,CPU 无需参与具体的数据搬运工作,这样可以腾出更多的 CPU 资源用于执行其他任务;同时由于 DMA 控制器直接与内存和设备进行数据交换,可以以比 CPU 控制更快的速度传输数据。
DMA 传输相比于外设中断后 CPU 在中断回调函数中读取数据的方法而言,可以显著减少 CPU 处理数据传输相关中断的次数,从而降低系统的中断开销。
2.DMA 的工作原理
DMA 控制器它可以直接与系统总线进行通信,并负责管理设备和内存之间的数据传输。DMA 控制器可以通过以下几个步骤工作:

DMA 数据传输流程简易示意图
- DMA 传输请求:在使用 DMA 之前,我们需要首先配置 DMA 数据传输的源地址、目的地址和传输数据大小等信息,当一个外设(如串口或 ADC)需要进行数据传输时,它向 DMA 控制器发送一个传输请求,此时,CPU 可以配置并启动 DMA 传输,但传输的实际数据操作由 DMA 控制器接管,DMA 控制器暂时接管系统总线的控制权;
- DMA 传输:DMA 控制器通过系统总线直接访问内存,将数据从源地址(如设备)移动到目标地址(如内存),或从内存移动到设备。这一过程不需要 CPU 的干预,DMA 控制器可以在后台完成传输操作,数据传输通过 DMA 通道进行;需要注意的是,负责数据传输的 DMA 通道是依赖于系统总线的;
- 中断通知:当 DMA 传输完成后,DMA 控制器会向 CPU 发送一个中断信号,通知传输已结束,同时将系统总线控制器交给 CPU 此时,CPU 可以继续处理与数据相关的任务,而不必花费时间和资源在传输数据上。
可以看到,通过 DMA 控制器和 DMA 通道能够使设备能够传输数据,而不会导致 CPU 工作过载;如果没有 DMA 通道,CPU 将使用系统总线将指令和数据发送到外设总线,然后通过外设总线到达目标外设设备,然后在此目标设备中用于存储数据的数组中复制每条数据,使用外设总线在读/写过程中会占用 CPU,并且在操作完成之前不允许执行其他工作。
这里,CPU、DMA 和其他外设都共享同一条系统总线。如果 CPU 正在使用总线执行内存访问操作,而此时 DMA 也需要通过总线将数据传输到内存或从内存读取数据,二者就会出现争抢现象:此时,系统中会有一个仲裁机制来决定总线的使用权,由谁来控制总线;仲裁器会根据任务的优先级、紧急性和系统状态等因素来决定是 CPU 还是 DMA 优先使用总线。
然而,使用 DMA 进行数据传输也会遇到一些问题,例如缓存一致性问题:在 MCU 具有缓存机制(如 STM32 单片机的 F7 系列、U5 系列和 H7 系列等)的情况下,CPU 在读取或写入数据时,为了提高数据访问的速度通常会先访问缓存 cache,而不是直接访问 RAM 存储器;然而,DMA 控制器是直接与 RAM 通信的,它绕过了 CPU 缓存。

DMA 缓存一致性问题示意图
这会导致以下问题:
- 数据过时:如果 CPU 从缓存中读取了某一段数据,并且在缓存中进行了修改,但未及时将这些修改同步回 RAM 中,那么 DMA 控制器读取的 RAM 数据将是过时的:例如当 CPU 访问内存中的位置 X 时,当前值将被存储在高速缓存中,X 上的后续操作将更新 X 的缓存副本,但不会更新 X 的内存 RAM 版本;如果在设备下次尝试访问 X 之前缓存未刷新到内存,则设备将收到陈旧的 X 值;
- 数据不一致:反过来,如果 DMA 控制器向 RAM 写入数据,而 CPU 缓存未及时无效化或更新,则 CPU 从缓存中读取的数据可能是不正确的,导致数据不一致性。
为了避免或解决 MCU 中的缓存一致性问题,可以使用以下方法:
- 禁用缓存:在一些情况下,可以选择在使用 DMA 之前禁用缓存,以确保 DMA 操作不会受到缓存不一致的影响。然而,这可能会导致性能下降,因为禁用缓存后,所有的内存访问都需要直接访问 RAM;
- 缓存清除(Cache Clean)和无效化(Invalidate)操作:
- 清除缓存(Cache Clean):在 DMA 操作之前,可以强制 CPU 将缓存中的数据写回 RAM 中,确保 RAM 中的数据与缓存一致;
- 无效化缓存(Cache Invalidate):在 DMA 操作之后,可以强制 CPU 无效化缓存,使其在下一次访问时重新从 RAM 中获取数据。
- 使用不缓存的内存区域:一些 MCU 支持标记某些内存区域为“不可缓存”(Non-Cacheable)。可以将 DMA 使用的内存区域标记为不可缓存,以避免缓存一致性问题;
- 使用专用 DMA 区域:有些 MCU 可能会有专门用于 DMA 操作的内存区域,避免了与 CPU 缓存的冲突,在进行 DMA 操作时,只在这些专用区域进行数据传输。
3.DMA 的类型
根据数据传输的方向和方式,DMA 可以分为以下几种类型:

- 内存到内存(Memory-to-Memory)传输:这种传输模式下,DMA 控制器将数据从一个内存区域复制到另一个内存区域,常用于快速复制大块内存数据,例如图像/视频帧缓存的快速交换或网络数据包在内存间的快速转发。
- 外设到内存(Peripheral-to-Memory)传输:DMA 控制器将外设的数据直接写入内存,而无需 CPU 介入,例如从存储设备(SD 卡、NAND Flash 等)读取数据到内存、从 ADC 外设直接采集传感器数据到内存或从存储设备(SD 卡、NAND Flash 等)读取数据到内存。
- 内存到外设(Memory-to-Peripheral)传输:DMA 控制器将内存中的数据(如一段音频数据)直接传输到外设(如音频输出设备),例如从内存输出音频/视频数据到 DAC、显示屏等外设或将处理好的数据从内存输出到通信接口(UART、SPI 等)进行发送。
- 外设到外设(Peripheral-to-Peripheral)传输:常用于存储设备间的数据拷贝、外设之间的数据转换与同步或加速加密/解密处理,例如从 SD 卡向 NAND Flash 拷贝数据、从 ADC 采集数据后通过 DMA 传输到 DAC 进行模拟量输出、两个外设输出双通道音频数据等。
4.DMA 的传输模式
DMA 控制器通常支持多种传输模式,以下是常见的几种:
- 单次传输模式(Single Transfer Mode):每次传输一个数据单位(如一个字节或一个字),这种模式下,DMA 控制器在每个数据传输之后释放总线控制权,使得 CPU 或其他设备可以访问总线;适用于对总线有严格共享需求的场合(比如同时有 CPU 处理任务和外设传输小批量数据);
- 突发传输模式(Burst Transfer Mode):DMA 控制器在获得总线控制权后,连续传输一批数据(如一块内存数据),在此期间,DMA 控制器独占总线,不释放控制权,直到传输完成;适用于需要高速数据传输的场合(比如从硬盘拷贝大文件或是摄像头高速采集图像,追求最快传输速度,暂时占用总线不影响核心任务);
- 循环缓冲传输模式(Circular Buffer Mode):DMA 控制器在一个预定的缓冲区内循环传输数据,适用于连续数据流的应用,比如数据采集,持续把传感器数据存进缓冲区;
- 自动请求模式(Auto-Request Mode):当需要重复进行同样的数据传输时(比如每隔一段时间就把传感器数据传到内存),DMA 控制器会自动发送请求并执行传输,适用于周期性数据传输(比如每隔 100ms 采集一次温度数据、每隔 50ms 发送一次串口数据)。
5.DMA 的结构
对于 MCU 的 DMA 单元来说,往往可以分成以下几个部分:

MCU 中 DMA 单元基本结构
- DMA 通道(DMA Channels):MCU 中的 DMA 控制器通常支持多个 DMA 通道,每个通道可以独立配置,用于不同的外设或内存之间的数据传输,同时每个通道都可以同时处理不同的数据流,并且可以设置不同的优先级;当多个通道同时请求总线访问时,优先级较高的通道会先获得访问权;
- 地址寄存器(Address Registers)
- 源地址寄存器(Source Address Register, SAR):保存要读取数据的源地址,这个地址可以是外设的寄存器地址,也可以是内存地址;
- 目标地址寄存器(Destination Address Register, DAR):保存数据要写入的目标地址,目标地址同样可以是外设寄存器地址或内存地址;
- 地址自增/自减功能:在大多数 MCU 中,DMA 控制器可以配置为在每次传输后自动增加或减少地址指针,便于处理连续的数据块:
- 比如从内存地址 100 搬完,自动跳到 101,依次往后;或从地址 200 往前跳到 199;
- 便于处理连续的数据块(比如搬运一整段文件、一帧图像),不用反复配置地址,提升传输效率。
- 数据寄存器(Data Registers)
- FIFO 缓存:用于在 DMA 传输中临时存储数据,确保数据能够连续传输,同时可以减少数据请求与实际传输之间的延迟;
- 数据计数寄存器(Data Counter, DCR):这个寄存器保存当前通道传输的数据量(通常以字节或字为单位),当数据计数减到 0 时,就意味着本次传输完成,DMA 会停止该任务或触发后续通知;
- 传输尺寸控制(Transfer Size Control):可以配置传输的数据宽度,如 8 位、16 位或 32 位,用于适配不同外设和内存的数据格式,确保传输的数据“规格匹配”,避免出现数据错位或错误(比如外设只能接收 8 位数据,就不能按 16 位传输)。
- 控制寄存器(Control Registers)
- 通道控制寄存器(Channel Control Register, CCR):用于配置每个 DMA 通道的工作模式,包括启动或停止 DMA 传输、设置优先级、选择触发事件等;
- 中断控制(Interrupt Control):DMA 控制器可以配置在传输完成、传输错误或特定的传输进度时生成中断,CPU 收到中断信号后,会执行对应的中断处理程序(比如传输完成后,通知软件读取数据;传输错误后,触发重试或报警逻辑)。
- 触发器(Triggers)
- 硬件触发:由外设的特定事件自动触发。比如 ADC(模数转换器)完成一次电压采集、UART(串口)接收完一个数据、摄像头采集完一帧图像等,这些外设会主动向 DMA 发送“触发信号”,DMA 收到后就自动启动传输;
- 软件触发:通过写入控制寄存器,软件也可以手动触发 DMA 传输。
- DMA 仲裁器(Arbiter)
- 总线仲裁:DMA 控制器需要与 CPU 和其他主设备(如另一个 DMA 控制器或总线接口)共享 MCU 的系统总线,仲裁器负责管理 DMA 通道之间的总线访问权,确保系统的稳定和高效运行;
- 循环和固定优先级:仲裁器可以基于通道的优先级来决定总线访问顺序,常见的仲裁策略包括固定优先级和循环优先级(即轮询)。

在传统的数据传输方式中,单片机的 CPU 负责从一个外设(如 I2C、串口、ADC 等)读取数据并将其存储到内存中,或者从内存中读取数据并写入到外设中。这种方法需要 CPU 不断地处理读写操作,容易造成 CPU 负载过高,影响系统的整体性能。
浙公网安备 33010602011771号