news 2026/5/6 7:06:04

一文说清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/5/3 6:15:49

1、Joomla! 1.5 SEO:提升网站搜索引擎友好度的全面指南

Joomla! 1.5 SEO:提升网站搜索引擎友好度的全面指南1. 引言Joomla! 作为一款易于使用和理解的免费内容管理系统,受到了众多网站建设者的青睐。然而,在网站建成后,许多人会发现访客数量较少,且在搜索引擎中的排名不如预…

作者头像 李华
网站建设 2026/5/3 0:33:43

Dify与Zapier类工具集成前景:无代码自动化再升级

Dify与Zapier类工具集成前景:无代码自动化再升级 在企业数字化转型的浪潮中,一个越来越明显的矛盾浮出水面:业务部门迫切需要智能化能力来提升效率,但AI开发却依然高度依赖算法、工程和运维团队的深度参与。结果往往是——创意卡在…

作者头像 李华
网站建设 2026/4/23 1:40:13

Dify开发者文档质量评测:新手上手是否足够友好?

Dify开发者文档质量评测:新手上手是否足够友好? 在大语言模型(LLM)技术席卷各行各业的今天,越来越多企业与开发者希望将AI能力快速落地到实际业务中。然而,现实往往并不轻松——提示工程调不准、RAG系统搭建…

作者头像 李华
网站建设 2026/5/3 23:38:12

Dify与LangChain对比:谁更适合企业级AI应用开发?

Dify与LangChain对比:谁更适合企业级AI应用开发? 在AI技术加速渗透各行各业的今天,越来越多企业试图将大语言模型(LLM)融入核心业务流程——从智能客服到自动报告生成,从知识管理到个性化推荐。然而&#x…

作者头像 李华
网站建设 2026/5/2 11:25:44

通过OpenMV实现农作物计数:快速理解方案

用OpenMV做农作物计数:从零开始的田间智能实践你有没有试过在烈日下弯着腰,一株一株地数玉米苗?我做过。那是一次农业调研任务——评估某地块的出苗率。两亩地,三个人,花了整整半天,结果还因为视觉疲劳出现…

作者头像 李华
网站建设 2026/4/25 9:11:56

Dify如何支持离线环境部署?内网隔离场景下的应用

Dify如何支持离线环境部署?内网隔离场景下的应用 在金融、政务和军工等对数据安全有着严苛要求的行业中,系统的运行往往被严格限制在完全隔离的内网环境中——没有外网访问权限,所有服务必须本地化部署。这种“空气隔绝”式的网络策略虽然保障…

作者头像 李华