🚀 我的STM32学习专栏:[点击直达 | 持续更新中]

适用平台:STM32F103C8T6(STM32F10x系列标准库)
阅读建议:配合《STM32F10xxx参考手册》第10章 DMA一起食用,效果更佳。

前置知识:需掌握GPIO、RCC时钟、ADC基础(本文ADC部分)


一、前言

在STM32里,CPU就像一个"大管家",要处理运算、要响应中断、还要来回搬运数据。如果每次ADC采完样、串口收到数据,都让CPU亲自去"搬货",那它就没时间干正事了。DMA(Direct Memory Access,直接存储器存取) 就是专门请来的"搬运工"——它能在不占用CPU资源的情况下,自动把数据从一个地方搬到另一个地方。这篇文章我就结合江科大的课程笔记,把DMA的核心知识点梳理清楚。


二、知识点讲解

2.1 DMA到底能搬什么?

DMA本质上就是一个数据搬运工,它主要在以下三者之间搬数据:

  • 外设寄存器(如 ADC_DRUSART_DR

  • 运行内存 SRAM(存变量、数组的地方)

  • 程序存储器 Flash(存代码的地方,但DMA只能读不能写)

  • DMADirect Memory Access)直接存储器存取

  • DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源

  • 12个独立可配置的通道: DMA17个通道), DMA25个通道)

  • 每个通道都支持软件触发和特定的硬件触发

  • STM32F103C8T6 DMA资源:DMA17个通道)

我的理解:你可以把外设寄存器想象成"前台收件箱",SRAM是"仓库",DMA就是"快递员"。ADC每转换完一次,数据先放到 ADC_DR 这个"收件箱"里,DMA就负责把它搬到SRAM的"仓库"数组里存起来,全程不需要CPU动手,就相当于一个数据转运小助手。


2.2 存储器映像——先搞清楚"地图"

在讲DMA之前,必须先知道STM32里各个存储器的"住址",不然连数据从哪搬到哪都不知道。

存储器映像图

我的理解:ROM是"只读"的,掉电不丢;RAM是"易失"的,掉电就没了。DMA搬运最常涉及的就是 0x4000 0000 区域的外设寄存器0x2000 0000 区域的SRAM


2.3 DMA框图——看看"快递员"住哪

DMA框图(图21)

从框图里能看到几个关键信息:

  • Cortex-M3 内核在左上角,包含CPU和内核外设。

  • DMA1 有7个通道,DMA2 有5个通道(但F103C8T6只有DMA1)。

  • DMA通过总线矩阵访问存储器,它和CPU是"平级"的主动单元,都能读写存储器。

  • 当DMA和CPU同时想访问同一个目标时,总线仲裁器会协调,保证CPU至少有一半的总线带宽。

我的理解:总线矩阵就像"高速公路",CPU和DMA都是"车主"。仲裁器就是"交警",防止撞车。DMA1和DMA2各自有一条专用DMA总线,所以它们搬数据时不会堵死CPU的路。


2.4 DMA基本结构——"搬运工"的工作流程

DMA基本结构图

这张图是理解DMA的核心。一次完整的DMA转运,需要配置以下几个参数:

① 起始地址(从哪搬?搬到哪?)
  • 外设地址:数据源或目的地的地址

  • 存储器地址:SRAM中数组的地址

② 数据宽度(一次搬多少?)
  • Byte(字节,8位)uint8_t

  • HalfWord(半字,16位)uint16_t

  • Word(字,32位)uint32_t

③ 地址是否自增(搬完一个,指针要不要++?)
  • 比如ADC扫描模式,外设地址固定是 ADC_DR(不自增),存储器地址要自增(依次存到数组里)。

④ 传输计数器(总共搬几次?)
  • 这是一个自减计数器,比如写7,就搬7次,减到0就停止。

  • 注意:写传输计数器时,必须先关闭DMA(DMA_Cmd(DISABLE)),写完再开启。

⑤ 自动重装器(搬完要不要再来一轮?)
  • 不重装(单次模式):计数器到0就结束,适合转运一次数组。

  • 重装(循环模式):计数器到0自动恢复初始值,配合ADC连续扫描用。

⑥ 触发方式(什么时候搬?)
  • 硬件触发:由外设事件触发,如ADC转换完成、串口收到数据、定时器更新等。

  • 软件触发(M2M模式):以最快速度连续搬运,适合存储器→存储器的转运。

我的理解:触发方式由 M2M位 决定。M2M=1是软件触发(Memory to Memory),M2M=0是硬件触发。软件触发和循环模式不能同时用,因为软件触发会以最快速度把计数器清零,而循环模式又会自动重装,结果就是DMA停不下来,一直狂转。


2.5 DMA请求映像——通道怎么选?

DMA1请求映像(图22)

DMA1有7个通道,每个通道对应不同的硬件触发源:

通道 硬件触发源
通道1 ADC1、TIM2_CH3、TIM4_CH1
通道2 USART3_TX、TIM1_CH1、TIM2_UP、TIM3_CH3、SPI1_RX
通道3 USART3_RX、TIM1_CH2、TIM2_CH3、TIM3_CH4、TIM3_UP、SPI1_TX
通道4 USART1_TX、TIM1_CH4、TIM1_TRIG、TIM1_COM、TIM4_CH2、SPI2S2_RX、I2C2_TX
通道5 USART1_RX、TIM1_UP、SPI2S2_TX、TIM2_CH1、TIM4_CH3、I2C2_RX
通道6 USART2_RX、TIM1_CH3、TIM3_CH1、TIM3_TRIG、TIM4_CH1、I2C1_TX
通道7 USART2_TX、TIM2_CH2、TIM2_CH4、TIM4_UP、I2C1_RX

我的理解硬件触发源是"绑定"在固定通道上的。比如你想用ADC1触发DMA,就必须选通道1;想用TIM2更新事件触发,就必须选通道2。不能乱选!但软件触发(M2M)不挑通道,随便哪个通道都能用。


2.6 数据宽度与对齐——大小不一样怎么办?

数据宽度与对齐(表57)

当源端宽度和目标宽度不一致时,DMA会自动处理:

  • 小数据 → 大数据:高位补0。比如8位 0xB0 转16位,变成 0x00B0

  • 大数据 → 小数据:高位舍弃。比如16位 0xB1B0 转8位,只保留低8位 0xB0

我的理解:这个规则要记住,不然ADC采出来是16位的,如果你DMA配成8位传输,那高8位就被丢掉了,数据直接错一半。


2.7 数据转运示例——从数组A到数组B

数据转运+DMA示意图

这是一个典型的 存储器→存储器 转运:

  • 外设地址 = DataA 数组首地址

  • 存储器地址 = DataB 数组首地址

  • 数据宽度 = 8位(uint8_t

  • 两边地址都自增

  • 传输计数器 = 7

  • 触发方式 = 软件触发(M2M=1)

  • 不需要自动重装(单次模式)

转运完成后,DataB 的内容和 DataA 完全一样。注意:这是"复制",不是"剪切",源数据不会消失。


2.8 ADC扫描模式 + DMA——最经典的搭档

ADC扫描模式+DMA

这是DMA最常见的应用场景:

  • ADC配置为扫描模式,一次触发依次转换7个通道。

  • 每个通道转换完成后,结果都放到同一个 ADC_DR 寄存器里。

  • 如果不及时搬走,下一个通道的结果就会覆盖上一个。

  • DMA配置为:外设地址=ADC_DR不自增),存储器地址=ADValue数组(自增),数据宽度=16位(半字),传输计数器=7。

  • 触发方式选ADC硬件触发(通道1)。

  • 如果ADC是连续扫描,DMA就配循环模式,实现"ADC不停地采,DMA不停地搬"的自动化流程。

我的理解:没有DMA的ADC扫描模式就是"耍流氓"——CPU得在中断里疯狂读 ADC_DR,稍微慢一点数据就丢了。有了DMA,CPU可以完全不管,等7个通道全部采完再看数组就行。


三、个人理解 / 心得

DMA这个外设,学的时候感觉参数很多很杂,但理清楚之后就一个核心逻辑:从哪搬?搬到哪?搬多少?什么时候搬?搬完怎么办? 这五个问题对应的就是起始地址、数据宽度、传输计数器、触发方式、自动重装器。只要把这五个参数想明白了,配置DMA就是填表的事情。

另外,通道和硬件触发源的绑定关系一定要记牢,我在最开始学的时候总以为通道可以随便选,结果ADC的DMA死活不触发,后来才发现ADC1只能绑在通道1上。这种"坑"踩一次就记住了。


四、踩坑记录

踩坑点 现象 原因 解决办法
通道选错 DMA不触发,数据不搬运 硬件触发源和通道是绑定的,如ADC1必须用通道1 查请求映像表,选对通道
传输计数器写不进去 程序卡死或计数器不变 DMA开启时不能写计数器 DMA_Cmd(DISABLE),写完计数器再 ENABLE
软件触发+循环模式 DMA停不下来,一直转运 软件触发以最快速度清零计数器,循环模式又自动重装,形成死循环 两者不能同时用
数据宽度配错 ADC数据只有低8位有效 DMA配成了8位传输,16位ADC数据高8位被舍弃 根据实际数据类型选HalfWord或Word
地址不自增 数组里所有元素都一样 存储器地址没开自增,每次都写到同一个位置 存储器端地址配置为自增模式

五、总结表格

知识点 核心内容
DMA本质 协助CPU完成数据搬运,无需CPU干预
搬运方向 外设↔存储器、存储器↔存储器
F103C8T6资源 只有DMA1,共7个通道
触发方式 硬件触发(M2M=0)/ 软件触发(M2M=1)
传输计数器 自减计数,到0停止;写之前必须先关DMA
自动重装 单次模式(不重装)/ 循环模式(重装)
数据宽度 Byte(8bit)、HalfWord(16bit)、Word(32bit);小转大补0,大转小舍弃
地址自增 决定每次转运后指针是否++
通道绑定 硬件触发源固定绑定特定通道,不可随意更改
经典应用 ADC扫描模式+DMA,解决数据覆盖问题

六、结语

写这篇文章的时候,我把江科大的视频又回顾了一遍,DMA这个外设虽然参数多,但逻辑非常清晰。建议大家在看完理论之后,先用软件触发做存储器到存储器的转运练手,熟悉基本结构之后,再去做ADC+DMA的硬件触发,循序渐进就不容易乱。

📌 开发环境:Keil5 + STM32F103C8T6最小系统板 + ST-Link
📌 参考资料:江科大STM32入门教程、《STM32F10xxx参考手册》
📌 前置文章江科大STM32入门:ADC模数转换器详解与代码实战

如果本文对你有帮助,欢迎点赞 👍 收藏 ⭐ !有任何问题欢迎在评论区交流,一起学习进步!

Logo

一站式 AI 云服务平台

更多推荐