DMA 基础概念:控制器工作原理、传输类型与关键寄存器
大家好,我是你们这堂课的主讲。今天咱们聊聊 DMA——直接存储器访问。这玩意儿,说白了就是给 CPU 配了个专职的“数据搬运工”。
我早年做一款工业相机的时候,CPU 要一边处理图像算法,一边还得把传感器数据往内存里搬。结果呢?帧率上不去,CPU 占用率却飙到 90%。后来上了 DMA,CPU 瞬间就“解放”了,帧率翻了一倍。从那时起我就明白:搞嵌入式,不懂 DMA 就是白干。
DMA 控制器的工作原理
DMA 控制器,你可以把它想象成一个独立的小处理器。它不跑你的业务代码,只干一件事:搬数据。
它的工作流程其实很简单,就三步:
- CPU 下达指令:你告诉 DMA:“从地址 A 搬 N 个字节到地址 B,搬完了告诉我一声。”
- DMA 接管总线:DMA 向 CPU 请求总线控制权。CPU 让出总线,DMA 开始干活。
- DMA 完成传输:数据搬完,DMA 释放总线,然后发个中断通知 CPU:“活干完了,你来处理吧。”
这里有个关键点:总线仲裁。CPU 和 DMA 不能同时用总线。谁用?得有个裁判。常见的仲裁方式有“固定优先级”和“轮询”两种。我习惯在低延迟场景用固定优先级,让 DMA 优先,保证数据不丢。
核心要点:DMA 的本质是“卸载 CPU 的 I/O 负担”。它让 CPU 从繁琐的数据搬运中解脱出来,专心做计算和控制。
下面这张图,画的就是 DMA 传输的完整流程。你一看就明白。
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 字节对齐) |
| 传输计数寄存器 | 指定传输的数据单元数量 | 单位是“数据宽度”,不是字节 |
| 控制寄存器 | 配置传输模式、方向、宽度等 | 循环模式慎用,防止数据覆盖 |
| 状态寄存器 | 查询传输状态和错误标志 | 传输完成后记得清除标志位 |