1. DMA 基础概念:什么是 DMA?为什么需要它?
大家好,我是你们这门课的老朋友。做了十几年嵌入式驱动,DMA 可以说是我打交道最多的外设之一。今天咱们就从最基础的概念聊起,把 DMA 到底是什么、它和 CPU 搬数据有啥区别、以及什么时候该用它,一次性讲透。
1.1 什么是 DMA?
DMA,全称 Direct Memory Access,直接存储器访问。说白了,就是一块硬件,专门负责在内存和外设之间、或者内存和内存之间搬运数据。
你想想看,如果没有 DMA,CPU 得亲自下场搬数据。比如从网卡收一个包,CPU 要先把数据从网卡 FIFO 读到寄存器,再写到内存。一次两次还行,但网络数据是源源不断的,CPU 大部分时间都花在搬砖上了,哪有空干正事?
DMA 就是来解决这个问题的。它是个独立的总线主控,能自己发起读写操作。CPU 只需要告诉它:从哪搬、搬到哪、搬多少,然后就可以去忙别的了。搬完数据,DMA 会发个中断通知 CPU。
核心要点:DMA 是一个硬件数据搬运工,CPU 是它的老板。老板只下指令,不亲自干活。
1.2 DMA 与 CPU 数据搬运的区别
我刚开始做驱动时,总觉得 DMA 可有可无。后来在一个高速数据采集项目里,CPU 被中断和搬数据压得喘不过气,我才真正体会到两者的差距。
咱们直接看对比表:
| 对比维度 | CPU 直接搬运 | DMA 搬运 |
|---|---|---|
| 执行主体 | CPU 核心 | DMA 控制器(独立硬件) |
| 占用 CPU | 100% 占用,无法做其他事 | 仅初始化时占用,搬运过程 CPU 可并行处理 |
| 搬运速度 | 受限于 CPU 主频和指令执行周期 | 接近总线带宽,通常比 CPU 快 2-10 倍 |
| 适合数据量 | 小数据(几十字节以内) | 大数据(几百字节到几兆字节) |
| 中断频率 | 每次传输都可能触发中断 | 一次传输完成才触发中断,频率低 |
| 实现复杂度 | 简单,直接读写寄存器 | 需要配置描述符、处理缓存一致性 |
嗯,这里要注意:不是说 DMA 一定比 CPU 快。对于几个字节的传输,DMA 的初始化开销反而更大。我见过有人对 4 字节的 SPI 数据也用 DMA,结果性能反而下降了。
1.3 DMA 传输的优势
总结下来,DMA 有三大核心优势:
- 解放 CPU:CPU 从繁重的数据搬运中解脱出来,可以专心处理协议栈、算法、用户交互等核心任务。
- 提高吞吐量:DMA 能利用总线突发传输,一次搬一大块数据,效率远高于 CPU 逐字搬运。
- 降低延迟:数据从外设到内存的路径更短,不需要经过 CPU 寄存器中转。
我的经验:在一个 4G 基站的加速器项目中,我们用 DMA 把数据搬运延迟从 12μs 降到了 1.8μs。CPU 利用率从 85% 降到了 15%。这个提升是质变的。
1.4 DMA 的适用场景
不是所有场景都适合用 DMA。我给大家画个图,一看就明白:
具体来说,以下场景强烈建议使用 DMA:
- 高速外设数据流:以太网、USB、SDIO、高速 SPI/I2S
- 多媒体处理:摄像头数据采集、音频播放/录制、显示刷新
- 大块内存拷贝:比如从用户态到内核态的数据传递
- 周期性数据搬移:ADC 采样、定时器触发的数据采集
避坑指南:我曾经在一个低功耗 IoT 项目里,对每 100ms 一次的传感器数据用了 DMA。结果发现 DMA 的初始化功耗和总线仲裁开销,比 CPU 直接读寄存器还大。后来我改成了 PIO 模式,功耗降了 30%。记住:DMA 不是万能的,小数据量别用。
1.5 DMA 传输的基本流程
咱们用代码来感受一下 DMA 的典型工作流程。以 STM32 的 DMA 为例:
// 1. 配置 DMA 通道
DMA_InitTypeDef dma_config;
dma_config.PeriphBaseAddr = (uint32_t)&USART1->DR; // 外设地址
dma_config.MemoryBaseAddr = (uint32_t)rx_buffer; // 内存地址
dma_config.Direction = DMA_PERIPH_TO_MEMORY; // 方向:外设→内存
dma_config.BufferSize = 256; // 传输大小
dma_config.PeriphInc = DMA_PERIPHINC_DISABLE; // 外设地址不递增
dma_config.MemInc = DMA_MEMINC_ENABLE; // 内存地址递增
// 2. 使能 DMA
DMA_Cmd(DMA1_Channel5, ENABLE);
// 3. CPU 去干别的事...
// 4. DMA 传输完成,触发中断
void DMA1_Channel5_IRQHandler(void) {
if(DMA_GetITStatus(DMA1_IT_TC5)) {
// 数据已经安安稳稳在 rx_buffer 里了
process_data(rx_buffer, 256);
DMA_ClearITPendingBit(DMA1_IT_TC5);
}
}
你看,CPU 只需要做两件事:配置和收尾。中间的数据搬运,DMA 全包了。
1.6 小结
DMA 的本质,就是把 CPU 从重复的数据搬运中解放出来。它适合大数据量、周期性、对延迟敏感的场景。但小数据量时,CPU 直接操作反而更高效。
嗯,这一章就到这里。记住一句话:让 CPU 做它擅长的事(运算、控制),让 DMA 做它擅长的事(搬数据)。后面我们会深入 DMA 的硬件架构、描述符链、缓存一致性等实战内容。