STM32 DMA控制器配置:手把手教程(从零实现)

STM32 DMA控制器配置实战:从零实现高效串口通信

在嵌入式开发中,你是否遇到过这样的场景?系统跑着跑着突然卡顿,调试发现CPU被UART中断“淹没”——每来一个字节就进一次中断,波特率115200意味着每秒上万次中断。这不仅拖慢主逻辑,还容易丢数据。

有没有办法让CPU“解放双手”,把搬数据这种重复劳动交给别人干?

有,这就是DMA(Direct Memory Access)——STM32里最值得掌握的外设加速技术之一。它就像一条专用的数据高速公路,让外设和内存之间可以直接对话,完全绕开CPU这个“交通指挥员”。

今天我们就以STM32F4系列为例,手把手带你用DMA实现UART串口的循环接收与发送,彻底告别高频中断烦恼。


为什么你需要DMA?

我们先来看一组真实对比:

方式每秒中断次数(115200bps)CPU占用估算实时性
中断方式接收~11,500次高(>30%)差(易丢帧)
DMA方式接收可降至每256字节1次 → ~45次极低(<5%)

看出差距了吗?减少99%以上的中断频率,这是什么概念?相当于原来你在接电话时每秒钟被打断十几次,现在变成几分钟才响一次铃声。

更别说在ADC采样、音频播放、图像传输等大数据量场景下,DMA几乎是刚需。


STM32的DMA架构长什么样?

STM32F4系列有两个DMA控制器:DMA1 和 DMA2,每个都有8个数据流(Stream 0~7),每个数据流又可以绑定不同的通道(Channel)连接不同外设。

比如你要用UART1_RX走DMA,就得查手册找到它对应的是哪个DMA、哪个Stream、哪个Channel。

📌关键点来了
- UART1_RX → DMA2_Stream2 → Channel 4
- UART1_TX → DMA2_Stream7 → Channel 4

这些映射关系不是随便定的,必须对照《STM32F4xx参考手册》第10章确认。一旦配错,DMA就不会响应。


核心参数怎么设?别再死记硬背了!

很多人学DMA卡在一堆寄存器配置上,其实只要理解它的“工作模式”,一切就顺了。

想象一下你要安排一个人帮你搬运箱子:
- 从哪搬?→ 源地址
- 搬到哪去?→ 目标地址
- 搬几个?→ 数据长度
- 搬完要不要继续?→ 是否循环
- 地址要不要自动加?→ 地址增量
- 谁说了算?→ 优先级

把这些类比套到DMA里,是不是清晰多了?

下面是实际开发中最常设置的几个参数及其含义:

参数解释典型值
Direction数据流向外设→内存 / 内存→外设 / 内存→内存
PeriphInc/MemInc外设/内存地址是否自增接收时外设地址固定,内存递增
DataAlignment数据宽度字节(Byte)、半字(HalfWord)、全字(Word)
Mode传输模式单次(Normal)、循环(Circular)
Priority优先级高/中/低/非常低
FIFOMode是否启用缓冲提高突发效率,建议开启

记住一句话:外设地址通常不自增,内存地址要自增;接收用循环模式,发送看需求。


手把手教你配置DMA接收UART数据

我们现在要做一件事:让STM32通过UART持续接收主机发来的命令,并存入缓冲区,全程不打扰CPU。

第一步:初始化UART并关联DMA句柄

#include "stm32f4xx_hal.h" UART_HandleTypeDef huart1; DMA_HandleTypeDef hdma_usart1_rx; uint8_t rx_buffer[256]; // 接收缓冲区 void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_RX; // 只启用接收 huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; HAL_UART_Init(&huart1); // 关键!将DMA接收句柄绑定到UART结构体 __HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx); }

注意这行宏:

__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);

它的作用是告诉HAL库:“以后调用HAL_UART_Receive_DMA()的时候,就用我定义的这个DMA句柄。”

没有这一步,后续启动DMA会失败。


第二步:配置DMA控制器

void MX_DMA_Init(void) { __HAL_RCC_DMA2_CLK_ENABLE(); // 必须先使能DMA2时钟! hdma_usart1_rx.Instance = DMA2_Stream2; // 使用DMA2 Stream2 hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; // UART1_RX属于Channel 4 hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设→内存 hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不变(始终读DR寄存器) hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增 hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; // 循环模式!重点! hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH; hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // 简单应用可关闭 if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK) { Error_Handler(); } // 配置中断优先级并使能 HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); }

📌 特别说明几个易错点:
-DMA_PERIPH_TO_MEMORY:表示数据从外设流向内存,即接收。
-PeriphInc = DISABLE:因为所有数据都来自USART1->DR,地址不变。
-Mode = DMA_CIRCULAR:缓冲区满后自动回卷,适合长期监听。
- 中断必须打开,否则无法感知“一半已满”或“全部填满”的时机。


第三步:启动DMA接收

只需要一行代码:

void Start_UART_DMA_Reception(void) { HAL_UART_Receive_DMA(&huart1, rx_buffer, 256); }

执行之后,DMA就开始工作了。每当UART收到一个字节,硬件就会自动把它搬到rx_buffer里,直到填满256个字节。

期间CPU完全可以去做别的事,比如处理传感器数据、跑控制算法,甚至进入低功耗睡眠。


第四步:中断服务函数 & 回调处理

当DMA完成一半或全部传输时,会产生中断。我们在ISR中调用标准处理函数即可:

void DMA2_Stream2_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_usart1_rx); }

然后利用HAL提供的回调函数插入业务逻辑:

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 前128字节已接收完毕,可以开始解析前半段命令 ParseCommand(rx_buffer, 128); } } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { // 后128字节接收完成,处理后半部分 ParseCommand(rx_buffer + 128, 128); } }

这两个回调分别在半传输完成全传输完成时触发,非常适合做双缓冲数据处理。

例如你正在接收一个JSON指令包,可以在HalfCplt时预处理前半部分,在RxCplt时拼接完整并执行命令。


发送也能用DMA吗?当然!

发送同样可以用DMA,尤其适合批量发送大量数据,比如日志输出、波形上传、固件更新。

只需稍微改一下方向和实例:

DMA_HandleTypeDef hdma_usart1_tx; void MX_DMA_Tx_Init(void) { hdma_usart1_tx.Instance = DMA2_Stream7; hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4; hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart1_tx.Init.Mode = DMA_NORMAL; // 发送一般不用循环 hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM; HAL_DMA_Init(&hdma_usart1_tx); __HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_tx); // 绑定发送句柄 }

发送调用也很简单:

uint8_t tx_data[] = "Hello over DMA!\r\n"; HAL_UART_Transmit_DMA(&huart1, tx_data, sizeof(tx_data));

发送完成后也会触发HAL_UART_TxCpltCallback(),可用于通知“发送结束”。


实战经验分享:那些年踩过的坑

❌ 坑一:忘记开DMA时钟

__HAL_RCC_DMA2_CLK_ENABLE(); // 必须加!否则DMA不工作

❌ 坑二:缓冲区未对齐导致HardFault

特别是使用FIFO模式时,要求内存地址按4字节对齐。声明缓冲区时建议加上对齐属性:

__ALIGN_BEGIN uint8_t rx_buffer[256] __ALIGN_END;

或者用静态分配确保对齐。

❌ 坑三:没关优化导致变量被编译器优化掉

如果你在中断中修改了某个标志位,记得用volatile修饰:

volatile uint8_t dma_transfer_complete = 0;

否则编译器可能认为这个变量没被使用而直接删掉。

✅ 秘籍:如何判断DMA还在运行?

查看状态寄存器:

if (__HAL_DMA_GET_FLAG(&hdma_usart1_rx, DMA_FLAG_TCIF2)) { // 传输已完成 }

也可以用API查询:

if (HAL_DMA_GetState(&hdma_usart1_rx) == HAL_DMA_STATE_READY) { // DMA空闲 }

更进一步:双缓冲模式真的无缝吗?

STM32的DMA支持双缓冲模式(Double Buffer Mode),只需设置Mode = DMA_DOUBLE_BUFFER_Memory并提供两个缓冲区指针。

启用后,DMA会在两个缓冲区间自动切换,CPU处理当前块的同时,DMA往另一个块写入新数据,真正做到“零等待”。

不过要注意:双缓冲只能用于循环模式,且初始化时就要指定两个缓冲区地址。

示例:

hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; // 不是单独的DoubleBuffer宏 // 双缓冲需在启动函数中指定两块内存 HAL_UARTEx_ReceiveToIdle_DMA(&huart1, buffer1, buffer2, 128);

具体接口因HAL版本略有差异,请查阅最新文档。


总结一下:DMA到底带来了什么?

  • CPU解脱了:不再为每个字节奔波,专注核心任务;
  • 系统更稳了:避免中断风暴,降低丢数据风险;
  • 功耗更低了:CPU能更快进入Sleep模式;
  • 实时性更强了:数据传输由硬件精确控制;
  • 扩展性更好了:轻松应对ADC、I2S、SDMMC等高带宽需求。

掌握DMA,是你从“会写代码”迈向“懂系统设计”的重要一步。


下一步你可以尝试……

  • 结合RTOS,在DMA回调中发送消息队列唤醒任务;
  • 用DMA+ADC实现无损音频采集;
  • 配合LTDC+DMA2D做图形界面刷新;
  • 在STM32H7上体验MDMA带来的AXI总线级性能飞跃。

如果你也在用DMA解决实际问题,欢迎留言交流你的应用场景和调试心得!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1156997.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费突破付费墙:零成本获取专业内容的终极解决方案

免费突破付费墙&#xff1a;零成本获取专业内容的终极解决方案 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 还在为高额订阅费用而烦恼&#xff1f;面对优质内容却被付费墙阻挡在外…

开发者必看:IndexTTS2项目结构与核心模块解析(小白版)

开发者必看&#xff1a;IndexTTS2项目结构与核心模块解析&#xff08;小白版&#xff09; 1. 引言&#xff1a;为什么需要深入理解IndexTTS2的架构&#xff1f; 在当前AIGC快速发展的背景下&#xff0c;文本转语音&#xff08;TTS&#xff09;技术正从“能说”向“说得像人”…

Holistic Tracking电商直播应用:手势交互系统部署实战

Holistic Tracking电商直播应用&#xff1a;手势交互系统部署实战 1. 引言 1.1 业务场景描述 随着电商直播行业的迅猛发展&#xff0c;用户对互动体验的要求日益提升。传统直播中主播与观众的交互方式主要依赖语音和文字&#xff0c;缺乏沉浸感和即时反馈。为了增强直播间的…

FFXIV插件开发终极指南:5分钟快速上手游戏自定义功能

FFXIV插件开发终极指南&#xff1a;5分钟快速上手游戏自定义功能 【免费下载链接】Dalamud FFXIV plugin framework and API 项目地址: https://gitcode.com/GitHub_Trending/da/Dalamud Dalamud框架是FFXIV&#xff08;最终幻想14&#xff09;游戏中最强大的插件开发平…

GetQzonehistory:QQ空间历史说说一键备份终极指南

GetQzonehistory&#xff1a;QQ空间历史说说一键备份终极指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 还在为那些承载青春记忆的QQ空间说说无法批量保存而烦恼吗&#xff1f;Get…

Bypass Paywalls Clean技术解析:突破付费墙的信息获取方案

Bypass Paywalls Clean技术解析&#xff1a;突破付费墙的信息获取方案 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 在数字化内容日益丰富的今天&#xff0c;优质信息往往被付费墙所…

亲测IndexTTS2 V23,微PE启动超快,语音情感真实自然

亲测IndexTTS2 V23&#xff0c;微PE启动超快&#xff0c;语音情感真实自然 在AI语音合成技术不断演进的当下&#xff0c;部署效率与用户体验的一致性正成为决定项目能否落地的关键。传统方式中&#xff0c;环境依赖、驱动缺失、权限限制等问题常常让一个功能完备的TTS系统止步…

5种简单有效的内容解锁方案:普通用户如何轻松绕过付费墙限制

5种简单有效的内容解锁方案&#xff1a;普通用户如何轻松绕过付费墙限制 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 你是否曾经遇到过这样的情况&#xff1a;在网上找到一篇很有价…

GetQzonehistory:QQ空间历史说说一键备份完全指南

GetQzonehistory&#xff1a;QQ空间历史说说一键备份完全指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 还在为那些承载着青春记忆的QQ空间说说无法批量保存而烦恼吗&#xff1f;G…

超详细版串口初始化流程:新手避坑指南

串口初始化从踩坑到精通&#xff1a;一位工程师的实战手记刚入行做嵌入式开发那会儿&#xff0c;我花了整整两天才让STM32的串口“吐”出第一个Hello World。不是代码写错了&#xff0c;也不是硬件坏了——而是我在初始化流程里漏了一步看似不起眼的操作&#xff1a;忘了把GPIO…

3步搞定付费墙:Chrome浏览器免费阅读付费内容的终极指南

3步搞定付费墙&#xff1a;Chrome浏览器免费阅读付费内容的终极指南 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 在信息爆炸的时代&#xff0c;我们常常遇到这样的情况&#xff1a…

Holistic Tracking误检率高?图像预处理优化实战解决方案

Holistic Tracking误检率高&#xff1f;图像预处理优化实战解决方案 1. 引言&#xff1a;AI 全身全息感知中的挑战与机遇 随着虚拟主播、元宇宙交互和智能健身等应用的兴起&#xff0c;对全维度人体感知的需求日益增长。MediaPipe Holistic 模型作为当前最成熟的端到端多模态…

MediaPipe Holistic实战:智能医疗康复评估系统

MediaPipe Holistic实战&#xff1a;智能医疗康复评估系统 1. 引言&#xff1a;AI驱动的康复评估新范式 随着人工智能在计算机视觉领域的持续突破&#xff0c;基于深度学习的人体动作分析技术正逐步渗透到医疗健康领域。传统的康复评估依赖于专业医师的主观判断和昂贵的动作捕…

手把手教你用AI智能证件照制作工坊制作完美证件照

手把手教你用AI智能证件照制作工坊制作完美证件照 1. 引言&#xff1a;为什么你需要一个本地化、全自动的证件照解决方案&#xff1f; 在日常生活中&#xff0c;无论是办理身份证、护照、签证&#xff0c;还是投递简历、报名考试&#xff0c;我们几乎都离不开标准尺寸的证件照…

GetQzonehistory:轻松备份QQ空间所有历史说说的完整教程

GetQzonehistory&#xff1a;轻松备份QQ空间所有历史说说的完整教程 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 还在担心QQ空间里那些承载着青春回忆的说说会随着时间流逝而消失吗&…

GetQzonehistory终极指南:一键完整备份你的QQ空间青春回忆

GetQzonehistory终极指南&#xff1a;一键完整备份你的QQ空间青春回忆 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 还记得那些年在QQ空间留下的青涩足迹吗&#xff1f;那些深夜写下的…

如何3分钟免费解锁150+付费网站:智能内容访问终极指南

如何3分钟免费解锁150付费网站&#xff1a;智能内容访问终极指南 【免费下载链接】bypass-paywalls-chrome-clean 项目地址: https://gitcode.com/GitHub_Trending/by/bypass-paywalls-chrome-clean 在信息时代&#xff0c;优质内容往往被付费墙所限制。智能内容解锁工…

MediaPipe Holistic部署案例:工厂安全监控系统

MediaPipe Holistic部署案例&#xff1a;工厂安全监控系统 1. 引言 1.1 业务场景描述 在现代工业生产中&#xff0c;工人的操作行为直接关系到生产安全与效率。传统视频监控系统多依赖人工回看录像&#xff0c;难以实现实时预警和主动干预。尤其在高危作业场景&#xff08;如…

Holistic Tracking手势识别延迟?管道优化部署案例解析

Holistic Tracking手势识别延迟&#xff1f;管道优化部署案例解析 1. 技术背景与问题提出 在虚拟主播、元宇宙交互和智能监控等前沿应用场景中&#xff0c;全身体感交互已成为提升用户体验的核心能力。传统的单模态感知&#xff08;如仅姿态或仅手势&#xff09;已无法满足复…

3步快速备份QQ空间:终极数据导出完整指南

3步快速备份QQ空间&#xff1a;终极数据导出完整指南 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 你是否曾经想要永久保存那些记录青春岁月的QQ空间说说&#xff1f;那些承载着成长记…