1. DMA 基础概念:数据搬运的“隐形管家”
大家好,欢迎来到这门实战课的第一章。今天咱们聊聊 DMA——这个在嵌入式系统里默默干活、却经常被新手忽略的家伙。
DMA 的全称是 Direct Memory Access,直接存储器访问。说白了,它就是一个专门负责数据搬运的硬件模块。你想想看,CPU 本来要处理各种计算、控制逻辑,结果还得花大量时间把数据从外设搬到内存,或者从内存搬到外设——这多浪费啊。DMA 就是来解放 CPU 的。
1.1 什么是 DMA?
DMA 是一个独立于 CPU 的硬件引擎。它可以在不占用 CPU 核心的情况下,在内存和外设之间、或者内存的不同区域之间传输数据。
我刚开始做嵌入式开发时,对 DMA 的理解就是“一个能自己搬数据的东西”。后来在项目里踩过坑才明白,它的价值远不止于此。
核心要点:DMA 传输的发起者可以是软件(CPU 配置后启动),也可以是硬件(外设触发)。传输过程中,CPU 可以去做其他事情。
1.2 DMA 与 CPU 的数据搬运对比
咱们直接上表格,一目了然:
| 对比维度 | CPU 直接搬运 | DMA 搬运 |
|---|---|---|
| 占用核心 | 全程占用 CPU | 仅配置和完成时占用 |
| 传输速度 | 受限于 CPU 指令执行速度 | 硬件级,通常更快 |
| 并发能力 | 无法同时处理其他任务 | CPU 可并行处理其他任务 |
| 适用场景 | 小数据量、简单操作 | 大数据量、周期性传输 |
| 功耗 | CPU 高负载运行,功耗高 | 专用硬件,功耗更低 |
| 编程复杂度 | 简单,直接读写寄存器 | 需要配置 DMA 控制器 |
举个例子。假设你要从一个 ADC 外设读取 1024 个采样数据。用 CPU 轮询的话,每个数据都要 CPU 去读寄存器、存内存——这期间 CPU 啥也干不了。用 DMA 的话,你配好源地址、目的地址、数据长度,然后 DMA 自己就把活干完了,CPU 可以去做 FFT 计算或者处理其他外设。
我的经验:在 STM32 上做音频采集时,我试过用 CPU 轮询 I2S 接口。结果 CPU 占用率飙到 80% 以上,系统几乎没法响应其他中断。换成 DMA 后,CPU 占用率直接降到 5% 以下。差距就是这么明显。
1.3 DMA 在嵌入式系统中的角色
DMA 在嵌入式系统里扮演什么角色?我总结了三句话:
- 性能加速器:让 CPU 从繁重的数据搬运中解脱出来
- 实时性保障:配合外设触发,实现低延迟的数据传输
- 功耗优化器:CPU 可以更早进入休眠模式
你想想看,现在的嵌入式系统越来越复杂。传感器数据采集、显示刷新、网络通信、音频处理……哪个不需要搬数据?如果没有 DMA,CPU 就得疲于奔命。
我记得有个项目是做多通道数据采集系统,8 个 ADC 同时采样,每个通道 10kHz 的采样率。如果用 CPU 中断方式,每秒要处理 80,000 次中断——这还不算数据处理的时间。后来用 DMA 的循环模式和双缓冲技术,CPU 只需要每隔一段时间来处理一次完整的数据块。嗯,这才是正确的做法。
1.4 DMA 传输的基本流程
咱们用一张 SVG 图来展示 DMA 传输的典型流程:
这个流程看起来简单,但实际项目中每个环节都有坑。比如配置源地址时,外设地址寄存器是否支持自增?目的地址是内存缓冲区,要不要考虑对齐?传输长度是字节还是字?
避坑提醒:我曾经在一个项目中,DMA 配置了循环模式,但忘记设置缓冲区大小与传输长度的匹配关系。结果数据覆盖了后面的内存区域,导致系统随机崩溃。排查了两天才找到问题。所以,DMA 配置一定要仔细核对地址和长度。
1.5 DMA 的传输模式
DMA 控制器通常支持以下几种传输模式:
- 单次传输:传输一次后停止,需要重新配置才能再次启动
- 循环传输:传输完成后自动重新开始,适合连续数据流
- 双缓冲传输:两个缓冲区交替使用,一个用于 DMA 写入,一个供 CPU 处理
- 链表传输:通过描述符链表实现多个不连续内存块的传输
我个人最常用的是循环模式和双缓冲模式。比如做音频播放,用循环模式让 DMA 持续往 DAC 送数据;做数据采集,用双缓冲模式让 CPU 处理一个缓冲区的同时,DMA 往另一个缓冲区写数据——这样就不会丢数据。
1.6 为什么在 Rust 中学习 DMA?
你可能会问:用 C 也能做 DMA 驱动,为什么非要用 Rust?
原因很简单:安全。DMA 操作涉及内存的直接访问,在 C 里很容易出现缓冲区溢出、野指针、数据竞争等问题。Rust 的所有权系统和借用检查器,能在编译时就帮你发现这些问题。
举个例子。在 C 里,你可能会不小心把 DMA 缓冲区的地址传给了另一个线程,然后两个地方同时读写——这就是数据竞争。在 Rust 里,编译器会强制你使用合适的同步原语,或者通过所有权转移来避免这种情况。
而且,Rust 的嵌入式生态越来越成熟。像 embedded-dma 这样的 crate,提供了类型安全的 DMA 抽象。咱们后面的章节会深入讲解。
本章小结:DMA 是嵌入式系统中不可或缺的组件。它通过硬件方式实现数据搬运,解放 CPU,提升系统性能和实时性。理解 DMA 的基本概念、传输流程和模式,是后续编写 Rust DMA 驱动的基础。