news 2026/4/6 14:53:31

一文说清DMA存储器到外设传输工作原理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文说清DMA存储器到外设传输工作原理

一文讲透DMA存储器到外设传输:从原理到实战

你有没有遇到过这样的场景?

在做一个音频播放项目时,为了让DAC输出连续的波形,你用定时器每几十微秒触发一次中断,CPU从中断里把下一个采样点写进DAC寄存器。结果系统一跑起来,CPU占用率直接飙到80%以上,UI卡顿、通信延迟,连个简单的按键响应都变得迟钝。

问题出在哪?不是你的代码写得不好,而是你在“用人扛沙袋”——明明可以靠传送带自动送料,却非得让工人一趟趟搬。

这个“传送带”,就是DMA(Direct Memory Access)

今天我们就来彻底搞清楚:当数据要从内存送到外设时,DMA到底是怎么工作的?它是如何解放CPU、实现高效传输的?


为什么需要DMA?

先回到那个音频例子。

假设你要播放一个44.1kHz采样率的音频,意味着每秒要向DAC写入44,100次数据。如果每次都要CPU亲自出手:

  • 每次中断至少消耗几十个时钟周期;
  • 频繁上下文切换带来额外开销;
  • CPU根本没时间干别的事。

这就像让你一边炒菜,一边每隔3毫秒去开门拿快递——饭还能做好吗?

而DMA的作用,就是把这个“拿快递”的任务交给门卫。你只负责告诉他:“这里有256个包裹,地址是DAC门口,按顺序送,送完叫我。” 然后就可以专心炒菜了。

关键价值一句话总结:

让CPU专注思考,让DMA负责跑腿。


DMA控制器是怎么干活的?

我们以STM32这类常见MCU为例,拆解一下DMA在“内存→外设”模式下的工作流程。

它不是魔法,而是一套精密的自动化流水线

想象一下工厂里的装配线:

  • 原材料放在某个货架上(内存缓冲区);
  • 成品接收口固定在一个工位(外设寄存器);
  • 传送带(DMA控制器)知道从哪取料、送到哪、送多少、怎么送。

这套系统要正常运转,必须提前设定好以下参数:

参数说明
源地址内存中数据起始位置,比如&buffer[0]
目标地址外设的数据寄存器地址,如&DAC->DHR12R1
数据宽度每次传8位、16位还是32位?需和外设匹配
地址增量源地址是否自动+1(是),目标地址是否+1(否)
传输数量总共传多少个数据单元
触发源谁说了算才能开始传?通常是外设发出请求

这些信息统称为DMA通道配置,由CPU在启动前设置好。

工作流程四步走

  1. 准备阶段
    CPU配置DMA通道:告诉它起点、终点、搬多少、怎么搬。就像给门卫发任务清单。

  2. 等待信号
    DMA进入待机状态,静静监听目标外设是否“准备好收货”。比如DAC完成上次转换后,会主动发出一个硬件信号:“我可以接下一个数据了!”

  3. 启动搬运
    一旦收到请求,DMA立刻接管总线控制权(通过总线仲裁),从内存读出一个数据,写入外设寄存器。整个过程不经过CPU。

  4. 循环执行直到结束
    每传一次,DMA自动递增源地址指针(比如指向buffer[1]),目标地址保持不变(始终是DAC的那个寄存器)。重复上述过程,直到所有数据传完。

最后,DMA可以发一个中断通知CPU:“活干完了。”


外设是如何“喊”DMA来帮忙的?

很多人误以为DMA是自己主动跑的,其实不然——它更像是一个听命行事的快递员。

真正发起动作的是外设本身

仍以DAC为例:

  1. DAC内部有一个数据保持寄存器(DHR),用于存放待转换的数字值;
  2. 当前数据完成D/A转换后,硬件逻辑检测到DHR可被重写;
  3. 此时,DAC自动拉高其DMA Request信号线;
  4. 这个信号连接到DMA控制器的请求输入端;
  5. DMA感知到请求,立即执行一次传输,将新数据写入DHR;
  6. 写完后DAC自动开始下一次转换,同时释放请求信号;
  7. 等转换再次完成,流程重复……

这就形成了一个闭环流水线:

[内存] → (DMA) → [DAC DHR] → [模拟输出] ↑_________| 完成反馈

整个过程完全由硬件驱动,无需软件干预,节奏精准、延迟极低。

✅ 小贴士:这种机制叫做硬件握手(Hardware Handshake),比软件轮询或中断更高效、更可靠。


支持哪些外设有资格“叫”DMA?

并不是所有外设都能发起DMA请求。只有那些高频交互、对实时性要求高的设备才配备这项能力。

常见的支持DMA的外设有:

外设应用场景
DAC音频输出、波形生成
ADC高速采样、传感器采集
SPI / I2C / USART大量数据收发
TIMPWM输出、编码器接口
SAI多通道音频传输

它们都有一个共同特点:需要持续不断地与内存交换数据

如果你查看芯片手册中的外设框图,会发现这些模块通常多了一条名为DMA_REQTXE/TXE_DMAEN的控制路径,专门用来对接DMA控制器。


实战代码:用DMA驱动DAC播放正弦波

下面我们看一段真实的STM32 HAL库代码,演示如何使用DMA将内存中的波形数据发送给DAC。

#include "stm32f4xx_hal.h" DAC_HandleTypeDef hdac; uint16_t sine_wave[256]; // 存储一个周期的正弦波样本 // DAC初始化 void MX_DAC_Init(void) { __HAL_RCC_DAC_CLK_ENABLE(); hdac.Instance = DAC; HAL_DAC_Init(&hdac); DAC_ChannelConfTypeDef sConfig = {0}; sConfig.DAC_Trigger = DAC_TRIGGER_NONE; // 不使用外部触发 sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1); } // 启动DMA传输 void Start_Audio_Playback(void) { // 填充正弦波数据(略) Generate_Sine_Wave(sine_wave, 256); // 启动DMA传输:内存 → DAC HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave, 256, DAC_ALIGN_12B_R); // 12位右对齐 }

这段代码的关键在于这一行:

HAL_DAC_Start_DMA(...)

它背后做了什么?

  1. 自动配置DMA通道;
  2. 设置源地址为sine_wave数组首地址;
  3. 设置目标地址为 DAC 的数据寄存器;
  4. 设置传输方向为内存→外设;
  5. 开启DMA请求使能;
  6. 启动第一个传输。

从此以后,只要DAC完成一次转换,就会自动请求下一个数据,DMA立即响应并送上新样本。整个过程CPU全程“躺平”。


高级技巧:双缓冲实现无缝播放

上面的例子只能播256个点,播完就停了。实际应用中我们希望连续播放,怎么办?

答案是:双缓冲(Double Buffer)或Ping-Pong缓冲

原理很简单:

  • 准备两块内存区域:Buffer A 和 Buffer B;
  • 初始DMA从A读数据;
  • 当A快传完时,DMA触发“半传输中断”;
  • CPU趁机填充B的数据;
  • 传完A后,DMA自动切换到B继续传;
  • 同时CPU填充A,准备下一轮;
  • 如此往复,形成无限循环。

这样就能做到边传边填,避免断音,特别适合音频流、视频帧等连续数据场景。

STM32的DMA控制器原生支持该功能,只需启用Circular ModeDouble Buffer Mode即可。


工程实践中必须注意的坑

再强大的技术,用不好也会翻车。以下是几个常见陷阱及应对策略:

1. 内存未对齐导致传输失败

某些DMA控制器要求源地址按数据宽度对齐。例如:

  • 32位传输 → 起始地址必须是4的倍数;
  • 否则可能触发总线错误或静默失败。

✅ 解法:使用编译器指令强制对齐

__attribute__((aligned(4))) uint16_t sine_wave[256];

2. Cache导致数据不一致(Cortex-M7/M4F等带缓存的芯片)

如果你在高速RAM中生成了数据,但Cache没刷新,DMA可能读到的是旧数据!

✅ 解法:手动清理Cache

SCB_CleanDCache_by_Addr((uint32_t*)&sine_wave, sizeof(sine_wave));

否则你会发现:明明写了新数据,DMA送出去的却是上周的……

3. 低功耗模式下DMA失效

进入Stop模式后,主时钟关闭,DMA和外设也都歇菜了。

✅ 解法:选择合适的唤醒源,或使用低功耗定时器+DMA组合。

4. 忘记开启DMA时钟

很基础,但也最容易犯错。

✅ 解法:检查RCC配置,确保DMA时钟已使能

__HAL_RCC_DMA1_CLK_ENABLE(); // 根据所用通道选择

5. 外设未开启DMA请求

即使DMA配好了,如果外设没打开DMA输出使能,照样没人“叫门”。

✅ 解法:确认外设侧也开启了DMA请求

DAC->CR |= DAC_CR_DMAEN1; // 手动置位DMA使能位

它不只是“搬运工”,更是系统性能的放大器

DMA的价值远不止省点CPU那么简单。它带来的是一种系统级的能力跃迁

对比项中断方式DMA方式
CPU占用高(频繁中断)极低(仅初始化/结束)
数据抖动明显(中断延迟)极小(硬件同步)
最大吞吐受限于中断响应速度接近总线极限
实时性
功耗表现差(频繁唤醒)优(长时间休眠)

特别是在以下场景中,DMA几乎是唯一可行方案:

  • 🔊 音频回放/录音(>16kHz采样率)
  • 📹 图像传感器数据采集
  • 📡 高速串口通信(如UART 1Mbps以上)
  • 🎛️ 精确PWM波形生成(如电机控制)

没有DMA,这些应用要么无法实现,要么成本极高。


结语:掌握DMA,才算真正入门嵌入式系统设计

当你第一次成功用DMA驱动DAC输出平稳的正弦波,而CPU占用率几乎为零时,你会有一种“打通任督二脉”的感觉。

这不是简单的功能实现,而是一种思维方式的转变:

不再把CPU当作万能调度中心,而是把它视为系统的决策大脑,把重复性劳动交给专用硬件去完成。

DMA正是这种思想的最佳体现之一。

未来随着边缘计算、实时AI推理、多传感器融合的发展,设备内部的数据流动只会越来越复杂。届时,不仅要用好DMA,还要学会协调多个DMA通道、优化传输优先级、甚至使用链表式DMA(LLI)构建动态数据流。

但一切的基础,都始于理解清楚:数据是如何从内存安静地流向外设的。

如果你正在学习嵌入式开发,不妨现在就动手试一试:写一个数组,用DMA把它送到DAC或SPI,看看示波器上的波形是否稳定流畅。

那一刻,你会真正体会到——什么叫“让硬件为自己工作”。

欢迎在评论区分享你的DMA实战经验,或者提问踩过的坑,我们一起交流进步。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/31 10:15:00

Proteus 8 Professional电子电路设计超详细版教程

从零开始掌握Proteus 8:电子电路设计与仿真的全能实战指南 你有没有过这样的经历? 花了一周时间画好原理图、打样PCB、焊完板子,结果上电一测——芯片发热、信号异常、单片机不启动。更糟的是,问题出在哪?是电源没接稳…

作者头像 李华
网站建设 2026/3/29 4:02:15

Altium Designer中原理图模板设置手把手教程

Altium Designer中原理图模板设置实战全解:从零搭建高效设计环境你有没有遇到过这样的场景?新项目启动,打开Altium Designer,第一件事不是画电路,而是花半小时手动设置图纸大小、调整栅格、复制粘贴标题栏、填写公司信…

作者头像 李华
网站建设 2026/4/6 14:10:16

Dify平台能否用于舆情监控?新闻聚合与情感分析实践

Dify平台能否用于舆情监控?新闻聚合与情感分析实践 在信息爆炸的今天,一条负面新闻可能在几小时内发酵成全网热议事件。某知名品牌曾因产品缺陷被曝光后24小时内股价下跌8%,而危机爆发前竟无任何系统预警——这正是传统舆情监控系统的典型短板…

作者头像 李华
网站建设 2026/3/30 23:42:43

为工业4.0赋能:Vivado注册2035系统级设计全面讲解

为工业4.0构建可持续FPGA开发体系:从“Vivado注册2035”谈起你有没有遇到过这样的场景?一个运行了八年的产线控制系统,突然因为开发工具许可证到期而无法重新编译固件;或者团队接手老项目时发现,连原始设计用的是哪个版…

作者头像 李华
网站建设 2026/4/2 22:50:31

Dify RAG模块深度优化策略:提高检索准确率的实用技巧

Dify RAG模块深度优化策略:提高检索准确率的实用技巧 在企业级AI应用日益普及的今天,一个常见的挑战浮出水面:如何让大语言模型(LLM)的回答既专业又可信?很多公司尝试用GPT类模型搭建智能客服或知识助手&am…

作者头像 李华
网站建设 2026/3/27 18:54:47

Dify平台冷启动问题解决方案:首次加载优化建议

Dify平台冷启动问题解决方案:首次加载优化建议 在企业级AI应用快速落地的今天,一个常见的尴尬场景是:刚刚部署完Dify平台,信心满满地打开浏览器准备构建智能客服流程,却发现页面卡在加载界面长达十几秒——后台日志显示…

作者头像 李华