当前位置: 首页 > news >正文

DMA —— 让 CPU “偷懒”的数据搬运工

1. 为什么需要 DMA?

想象一下:你有一个串口每 100μs 收到一个字节,需要用 CPU 把这个字节从串口数据寄存器搬到内存的缓冲区里。

  • 没有 DMA 时:每次接收完成,串口中断触发 → CPU 读数据 → 存到数组 → 退出中断。如果数据速率很高(比如 2Mbps),CPU 大部分时间都在“搬砖”,根本没空做其他任务。
  • 有 DMA 时:配置 DMA 通道自动把串口数据搬运到内存,完全不需要 CPU 插手,只在搬完一整个数据块后才产生一次中断通知 CPU。

DMA(Direct Memory Access,直接存储器访问)就是这样一个“硬件搬运工”:它可以在不占用 CPU的情况下,在内存与外设、内存与内存之间快速传输数据。

2. DMA 的核心工作原理

DMA 控制器本身是一个精简的“小 CPU”,它有:

  • 源地址:从哪读数据(可以是外设数据寄存器,也可以是内存数组)。
  • 目标地址:写到哪去。
  • 传输计数器:还要传多少字节。
  • 传输模式:单次、循环、增量/固定地址等。

基本流程

  1. CPU 配置 DMA 通道(源地址、目标地址、数据长度、触发源等)。
  2. 使能 DMA。
  3. 当触发事件发生(比如串口收到一个字节),DMA 自动执行一次搬运:读源地址 → 写目标地址 → 计数器减 1。
  4. 计数器减到 0 时,DMA 产生传输完成中断(可选),通知 CPU 处理。

3. 在 STM32 中的应用场景

场景作用效果
串口(UART)接收DMA 将串口数据自动存入环形缓冲区CPU 只在收到一帧完整数据时才处理
ADC 多通道扫描DMA 将 ADC 结果连续存入数组可实现高频采样(几十 kHz)而 CPU 几乎零负担
I2C/SPI 通信DMA 自动发送/接收数据块配合 RTOS,通信任务可以阻塞直到 DMA 完成
内存拷贝两个内存区域之间高速拷贝memcpy快,且不占用 CPU
定时器 PWM 更新用 DMA 自动修改多个 CCR 值实现复杂的 LED 呼吸灯模式或波形输出

4. 关键概念:循环模式 vs 普通模式

  • 普通模式:传输计数器减到 0 后停止,需要 CPU 重新配置才能再次启动。适合固定长度的数据块(比如发送一个 512 字节的传感器数据包)。
  • 循环模式:计数器减到 0 后自动重装初始值,继续从头搬运。适合环形缓冲区(比如连续 ADC 采样)。STM32 的 DMA 循环模式需要配合FIFO双缓冲区来防止数据覆盖。

5. 双缓冲区(Double Buffer)技巧

某些 STM32 的 DMA 支持双缓冲区(如 DMA2)。两个缓冲区交替使用:

  • 当 DMA 正在填充缓冲区 0 时,CPU 可以处理缓冲区 1。
  • 缓冲区满后,DMA 自动切换目标到另一个缓冲区,并触发中断通知 CPU 处理已满的那个。

这是实现无锁、零拷贝高吞吐数据流的标准方案。

6. 使用 DMA 的注意事项

  1. 数据一致性:如果 DMA 和 CPU 同时访问同一块内存,必须用__DSB()屏障或关中断保护,防止 CPU 读到缓存中的旧数据(尤其在 Cortex-M7 带数据缓存时)。
  2. 对齐要求:某些 DMA 传输要求源/目标地址按字(4 字节)对齐,否则可能出错或降低效率。
  3. 通道冲突:多个外设可能共享同一个 DMA 通道(如 USART1_TX 和 SPI1_RX),需要合理分配优先级和仲裁。
  4. 中断优先级:DMA 传输完成中断通常设较低优先级,避免影响实时任务。
  5. 调试困难:DMA 在后台“偷偷”搬运,一旦配置错(如地址写反),现象很诡异(数据全零或错乱),需要仔细检查。

7. 代码示例(STM32 HAL)

// 配置 ADC1 用 DMA 连续采样 100 个值到数组#defineADC_BUFFER_SIZE100uint32_tadc_buffer[ADC_BUFFER_SIZE];ADC_HandleTypeDef hadc1;DMA_HandleTypeDef hdma_adc1;voidADC_DMA_Init(void){__HAL_RCC_ADC1_CLK_ENABLE();__HAL_RCC_DMA1_CLK_ENABLE();// 配置 DMA 通道hdma_adc1.Instance=DMA1_Channel1;hdma_adc1.Init.Direction=DMA_PERIPH_TO_MEMORY;hdma_adc1.Init.PeriphInc=DMA_PINC_DISABLE;// 外设地址固定(ADC_DR)hdma_adc1.Init.MemInc=DMA_MINC_ENABLE;// 内存地址递增hdma_adc1.Init.PeriphDataAlignment=DMA_PDATAALIGN_WORD;hdma_adc1.Init.MemDataAlignment=DMA_MDATAALIGN_WORD;hdma_adc1.Init.Mode=DMA_CIRCULAR;// 循环模式hdma_adc1.Init.Priority=DMA_PRIORITY_HIGH;HAL_DMA_Init(&hdma_adc1);__HAL_LINKDMA(&hadc1,DMA_Handle,hdma_adc1);// 启动 ADC 并开始 DMA 传输HAL_ADC_Start_DMA(&hadc1,adc_buffer,ADC_BUFFER_SIZE);}

之后,adc_buffer中的值会不断被更新,CPU 可以在主循环中读取最新数据,完全无感知。

总结

DMA 是嵌入式系统实现高吞吐、低 CPU 负载的关键技术。学会 DMA,你的程序就能从“忙得团团转”变成“悠然自得”。

http://www.jsqmd.com/news/683527/

相关文章:

  • 5分钟上手:暗黑破坏神2存档编辑器d2s-editor终极指南
  • 用LM324和OP07给STM32做个电子秤:从传感器接线到ADC采集的保姆级避坑指南
  • 别再乱点Force Checkout了!手把手教你用Git Stash安全保存未提交的修改
  • 【感知数据增强篇】 告别低效炼丹!CV圈的“涨点外挂”Albumentations,一键模拟雨雾天,让你的mAP狂飙!
  • BilibiliCacheVideoMerge:安卓B站缓存视频合并完整教程与弹幕播放指南
  • 深度解析企业级自动驾驶数据集BDD100K:5大技术创新驱动异构多任务学习革命
  • 终极浏览器办公方案:SE Office如何实现免安装文档编辑
  • 抖音直播数据采集:基于Golang的高效实时监控系统完整指南
  • Windows下ONNX环境避坑指南:从CUDA版本匹配到清华源加速,一次搞定onnxruntime-gpu
  • 树莓派5工业级改造:ED-IPC3020硬件解析与应用实践
  • Java最全面试题及答案整理(牛客网最新版)
  • 用STM32F407和蓝牙模块打造手机遥控小车:完整代码解析与OLED屏显驱动
  • 从家电到智能家居:拆解LIN总线如何成为低成本设备联网的“隐形冠军”
  • 如何在Windows和Linux上免费解锁VMware的macOS虚拟机支持
  • Dify客户端AOT架构设计图首度解密(含14处关键注释+12个ILLink配置陷阱+9个P/Invoke安全加固点)
  • 图像增强技术:提升计算机视觉模型性能的关键策略
  • Jetson Orin Nano系统备份翻车实录:用initrd和DD命令完整克隆NVMe硬盘(附详细命令清单)
  • 技术书籍解毒:90分钟高效吸收法
  • 免费开源屏幕标注神器ppInk:3分钟上手Windows最强标注工具
  • Python的__getattr__方法
  • MGit完全指南:如何在Android设备上轻松管理Git仓库
  • [具身智能-412]:10款主流的具身智能仿真工具
  • Bugly跨平台质量监控技术底座与科学评估实践 - 领先技术探路人
  • 从“Hello World”到控制硬件:用汇编语言点亮你的第一个LED灯(基于8086模拟器)
  • 测试数据生成术:合成工具:从数据模拟到智能生成的范式跃迁
  • 终极指南:3分钟搞定OpenMV IDE安装与配置,让视觉开发变得如此简单
  • PPTXjs终极指南:如何在浏览器中直接打开PPT文件
  • 【2026最新版】从零基础入门LangChain:Model与Agent实战指南!
  • Python数据科学工具链:Pandas、NumPy与Scikit-learn高效协作指南
  • Kali Linux 2024.2 安装后必做的第一件事:保姆级换源教程(附清华、阿里云、中科大源地址)