DMA 基础概念:控制器工作原理、传输类型与关键寄存器

大家好,我是你们这堂课的主讲。今天咱们聊聊 DMA——直接存储器访问。这玩意儿,说白了就是给 CPU 配了个专职的“数据搬运工”。

我早年做一款工业相机的时候,CPU 要一边处理图像算法,一边还得把传感器数据往内存里搬。结果呢?帧率上不去,CPU 占用率却飙到 90%。后来上了 DMA,CPU 瞬间就“解放”了,帧率翻了一倍。从那时起我就明白:搞嵌入式,不懂 DMA 就是白干。

DMA 控制器的工作原理

DMA 控制器,你可以把它想象成一个独立的小处理器。它不跑你的业务代码,只干一件事:搬数据。

它的工作流程其实很简单,就三步:

  1. CPU 下达指令:你告诉 DMA:“从地址 A 搬 N 个字节到地址 B,搬完了告诉我一声。”
  2. DMA 接管总线:DMA 向 CPU 请求总线控制权。CPU 让出总线,DMA 开始干活。
  3. DMA 完成传输:数据搬完,DMA 释放总线,然后发个中断通知 CPU:“活干完了,你来处理吧。”

这里有个关键点:总线仲裁。CPU 和 DMA 不能同时用总线。谁用?得有个裁判。常见的仲裁方式有“固定优先级”和“轮询”两种。我习惯在低延迟场景用固定优先级,让 DMA 优先,保证数据不丢。

核心要点:DMA 的本质是“卸载 CPU 的 I/O 负担”。它让 CPU 从繁琐的数据搬运中解脱出来,专心做计算和控制。

下面这张图,画的就是 DMA 传输的完整流程。你一看就明白。

DMA 传输工作流程 CPU DMA 控制器 外设 内存 ① 配置 DMA ② 请求数据 ③ 数据 ④ 写入内存 ⑤ 传输完成中断 注意 DMA 传输期间 CPU 可以执行其他任务

DMA 的两种核心传输类型

DMA 能搬的数据,无非就两个来源:内存和外设。所以传输类型也分两种。

1. 内存到内存(Memory-to-Memory)

这种模式,说白了就是把数据从内存的一块区域搬到另一块区域。比如你把一个数组复制到另一个数组,或者做内存拷贝。

我当年做音频处理时,经常需要把录音缓冲区里的数据搬到处理缓冲区。用 CPU 的 memcpy?太慢了。用 DMA 的 Mem-to-Mem 模式,一次传输几 KB,CPU 完全不用管,效率高得多。

小技巧:Mem-to-Mem 传输通常需要软件触发启动。你设置好源地址、目的地址和长度,然后写一个启动位,DMA 就开始干活了。

2. 外设到内存(Peripheral-to-Memory)

这是最常用的模式。外设(比如 ADC、UART、SPI)产生数据,DMA 自动把数据搬到内存里。

举个例子:你用一个 SPI 接口的传感器,每 1ms 产生 100 个字节。如果没有 DMA,CPU 每 1ms 就要被中断一次,去读 SPI 数据寄存器。有了 DMA,你只需要配置好 SPI 的 DMA 请求,数据就会自动流到内存缓冲区。CPU 呢?该干嘛干嘛。

反过来,内存到外设(Memory-to-Peripheral)也是一样的道理。比如你要通过 DAC 播放一段音频,DMA 可以把内存里的音频数据自动喂给 DAC,CPU 只需要告诉 DMA:“开始搬吧。”

传输类型 数据流向 典型场景 触发方式
内存到内存 内存 → 内存 数据拷贝、缓冲区搬移 软件触发
外设到内存 外设 → 内存 ADC 采样、UART 接收 硬件请求
内存到外设 内存 → 外设 DAC 输出、SPI 发送 硬件请求

关键寄存器解析

配置 DMA,说白了就是填几个寄存器。不同芯片的寄存器名字可能不一样,但核心逻辑是相通的。我以最常见的 ARM Cortex-M 系列为例,给你拆解一下。

1. 源地址寄存器(DMA_SAR / SRC_ADDR)

这个寄存器存的是数据来源的地址。如果是外设到内存,源地址就是外设的数据寄存器地址。如果是内存到内存,源地址就是源缓冲区的地址。

2. 目的地址寄存器(DMA_DAR / DST_ADDR)

数据要搬到哪去?就写在这里。目的地址可以是内存地址,也可以是外设的数据寄存器地址。

3. 传输计数寄存器(DMA_NDTR / CNT)

这个寄存器告诉 DMA:“你要搬多少个数据单元?” 注意,是“数据单元”,不是字节。单元大小由后面的配置决定。

我曾经踩过的坑:有一次我配置 DMA 传输,计数寄存器写的是字节数,但数据宽度设的是 32 位。结果 DMA 只搬了四分之一的数。后来我才发现,计数寄存器的值是以“数据宽度”为单位的。比如你设数据宽度为 32 位,计数为 100,那实际传输的是 100 个 32 位数据,也就是 400 个字节。

4. 控制寄存器(DMA_CR / CTRL)

这个寄存器是 DMA 的“大脑”。里面包含:

  • 数据宽度:8 位、16 位还是 32 位?
  • 地址增量模式:源地址和目的地址是否自动递增?
  • 传输方向:内存到内存、外设到内存还是内存到外设?
  • 循环模式:传输完成后是否自动从头开始?
  • 中断使能:传输完成、传输错误等中断是否开启?

5. 状态寄存器(DMA_SR / STATUS)

这个寄存器告诉你 DMA 当前的状态:是否正在传输?有没有发生错误?传输完成标志位有没有置位?

下面这个表格,把关键寄存器的作用和配置要点总结了一下。

寄存器 作用 配置要点
源地址寄存器 指定数据来源地址 外设模式时填外设地址;内存模式时填内存地址
目的地址寄存器 指定数据目标地址 注意地址对齐(32 位数据需 4 字节对齐)
传输计数寄存器 指定传输的数据单元数量 单位是“数据宽度”,不是字节
控制寄存器 配置传输模式、方向、宽度等 循环模式慎用,防止数据覆盖
状态寄存器 查询传输状态和错误标志 传输完成后记得清除标志位