STM32 进阶封神之路(二十一):DMA 深度解析 —— 从直接内存访问到无 CPU 干预数据传输(底层原理 + 寄存器配置)
上一篇我们掌握了 ADC 模拟信号采集的全场景应用,这一篇聚焦 STM32 的 “数据搬运神器”——DMA 直接内存访问。DMA(Direct Memory Access)是 STM32 内置的高速数据传输控制器,能够在不占用 CPU 资源的情况下,实现外设与内存、内存与内存之间的高速数据搬运,广泛应用于 ADC 采集、串口通信、SPI 闪存读写、DAC 输出等高频数据传输场景。
本文基于实战资料,从 DMA 核心价值、硬件架构、传输原理,到通道配置、寄存器解析、初始化流程,手把手带你吃透 DMA 的底层逻辑,为下一篇实战(ADC+DMA 采集、串口 DMA 收发)打下坚实基础,同时覆盖高频面试考点!
一、DMA 核心认知:为什么它是 “CPU 减负神器”?
1. DMA 的核心作用与价值
在 DMA 出现之前,外设与内存的数据传输必须依赖 CPU 干预:CPU 需先从外设读取数据到寄存器,再写入内存(或反之),这个过程中 CPU 无法执行其他任务,导致资源浪费。DMA 的核心价值就是 “解放 CPU”:
(1)核心作用
- 无 CPU 干预数据传输:DMA 独立于 CPU 工作,自动完成外设与内存、内存与内存之间的数据搬运;
- 高速传输:传输速度仅受总线带宽限制(STM32F103 DMA 最大传输速率可达 72MB/s);
- 多通道支持:支持多个外设同时传输,通过通道优先级仲裁实现有序传输;
- 降低功耗:CPU 可在 DMA 传输期间进入睡眠模式,减少系统功耗。
(2)核心应用场景
- ADC 高速采集:ADC 连续采集数据,DMA 自动将采样值写入内存,CPU 无需等待;
- 串口大数据收发:串口接收 / 发送大量数据(如文件传输),DMA 替代 CPU 中断处理,避免频繁中断占用资源;
- SPI 闪存读写:W25Q64 等 SPI 闪存的批量数据读写,DMA 提升传输效率;
- DAC 波形生成:DMA 循环传输波形数据到 DAC,实现高频信号输出;
- 内存数据搬运:SRAM 内部、Flash 与 SRAM 之间的大批量数据复制。
2. DMA 与 CPU 中断传输的核心区别(面试高频)
很多新手混淆 DMA 与中断传输,两者虽都能 “并行处理任务”,但本质逻辑完全不同:
表格
| 对比维度 | DMA 传输 | CPU 中断传输 |
|---|---|---|
| 核心逻辑 | 硬件直接搬运数据,无 CPU 参与 | CPU 响应中断后,手动读写数据 |
| CPU 占用 | 几乎为 0(仅需初始化配置) | 中断响应和数据处理占用 CPU 资源 |
| 传输效率 | 高(硬件级传输,无软件开销) | 中(受中断响应时间和软件处理速度限制) |
| 适用场景 | 大批量、高频数据传输(如 ADC 连续采集、文件传输) | 小批量、低频率数据传输(如按键中断、串口单字节接收) |
| 配置复杂度 | 较高(需配置通道、传输模式、数据长度等) | 较低(仅需配置中断和数据处理函数) |
| 功耗 | 低(CPU 可休眠) | 较高(CPU 需频繁响应中断) |
示例对比:ADC 采集 1000 个数据
- 中断传输:ADC 每采集 1 个数据触发 1 次中断,CPU 需响应 1000 次中断,每次中断读取数据并写入内存;
- DMA 传输:ADC 连续采集 1000 个数据,DMA 自动将所有数据写入内存,CPU 仅需在传输完成后处理一次数据。
3. STM32 DMA 硬件架构深度解析
STM32F103 系列内置 2 个 DMA 控制器(DMA1、DMA2),其中 DMA1 支持 7 个通道,DMA2 支持 5 个通道,每个通道对应特定的外设请求,硬件架构决定了其传输能力:
(1)核心架构框图
plaintext
外设(ADC1/USART1/SPI1等)→ DMA请求 → 通道选择 → 优先级仲裁器 → 数据搬运控制器 → 内存(SRAM/Flash)
↓
控制寄存器(配置传输模式、数据长度等)
(2)关键模块说明
- DMA 控制器:STM32F103 有 2 个控制器(DMA1、DMA2),DMA2 仅在大容量和中容量芯片中存在(如 STM32F103ZET6),小容量芯片(如 STM32F103C8T6)仅支持 DMA1;
- 通道(Channel) :每个 DMA 控制器包含多个通道,每个通道对应特定的外设请求(如 DMA1_Channel1 对应 ADC1),通道是 DMA 传输的 “专属通道”;
- 优先级仲裁器:当多个通道同时请求传输时,按优先级高低排序(高优先级通道优先传输),支持 4 级优先级(最高、高、中、低);
- 数据搬运控制器:核心执行单元,负责按配置的传输模式、数据长度、地址增量方式搬运数据;
- 请求映射:外设需通过特定的 DMA 通道发起传输请求,不可随意选择(如 ADC1 只能通过 DMA1_Channel1 请求)。
4. DMA 核心传输术语(必掌握)
-
传输方向:
- 外设→内存(Peripheral to Memory):如 ADC 采集数据写入 SRAM;
- 内存→外设(Memory to Peripheral):如 SRAM 数据通过串口发送;
- 内存→内存(Memory to Memory):如 SRAM 内部数据复制;
-
传输模式:
- 正常模式(Normal mode):传输完成后停止 DMA,需手动重启;
- 循环模式(Circular mode):传输完成后自动重启,持续循环传输(如 ADC 连续采集);
-
地址增量模式:
- 外设地址增量(Peripheral increment mode):传输时外设地址是否递增(通常关闭,外设寄存器地址固定);
- 内存地址增量(Memory increment mode):传输时内存地址是否递增(通常开启,连续写入内存);
-
数据宽度:
- 外设数据宽度(Peripheral data size):8 位、16 位、32 位(需与外设数据格式匹配);
- 内存数据宽度(Memory data size):8 位、16 位、32 位(需与内存数据类型匹配);
-
传输计数器:记录待传输的数据个数,传输完成后计数器清零(正常模式)或自动重载(循环模式)。
二、STM32 DMA 通道与外设映射(实战选型关键)
DMA 通道与外设的映射关系是配置的核心,必须严格遵循,否则无法触发 DMA 传输。以下是 STM32F103 DMA1 的常用通道映射表(小容量芯片适用):
表格
| DMA 控制器 | 通道号 | 对应外设请求 | 典型应用 |
|---|---|---|---|
| DMA1 | Channel1 | ADC1、TIM2_CH3 | ADC1 数据采集 |
| DMA1 | Channel2 | USART2_TX、TIM1_CH4 | USART2 发送数据 |
| DMA1 | Channel3 | USART2_RX、TIM3_CH1 | USART2 接收数据 |
| DMA1 | Channel4 | USART1_TX、TIM1_CH1 | USART1 发送数据 |
| DMA1 | Channel5 | USART1_RX、TIM1_CH2 | USART1 接收数据 |
| DMA1 | Channel6 | SPI1_TX、TIM3_CH1 | SPI1 发送数据 |
| DMA1 | Channel7 | SPI1_RX、TIM3_CH2 | SPI1 接收数据 |
关键注意:
- 每个通道可对应多个外设请求,但同一时间仅能激活一个请求;
- 实战选型时,需根据外设选择对应的 DMA 通道(如 ADC1→DMA1_Channel1,USART1_RX→DMA1_Channel5)。
三、DMA 核心寄存器解析(底层配置关键)
DMA 的配置本质是操作控制寄存器,以下是初始化和传输过程中涉及的核心寄存器,结合配置流程解析:
1. DMA 通道配置寄存器(DMA_CCRx)
-
地址:DMA1_Channel1 的 CCR 寄存器地址为 0x40020000,通道 2~7 依次递增 0x14;
-
核心作用:配置 DMA 通道的传输模式、地址增量、数据宽度、优先级等;
-
关键位说明:
- EN(bit0):通道使能位(1 = 使能,0 = 禁用);
- CIRC(bit5):循环模式使能位(1 = 循环,0 = 正常);
- PINC(bit6):外设地址增量使能(1 = 使能,0 = 禁用,通常为 0);
- MINC(bit7):内存地址增量使能(1 = 使能,0 = 禁用,通常为 1);
- PSIZE(bit8~9):外设数据宽度(00=8 位,01=16 位,10=32 位);
- MSIZE(bit10~11):内存数据宽度(00=8 位,01=16 位,10=32 位);
- PL(bit12~13):通道优先级(00 = 低,01 = 中,10 = 高,11 = 最高);
- DIR(bit14):传输方向(0 = 外设→内存,1 = 内存→外设);
- MEM2MEM(bit14):内存到内存模式(仅当 DIR=1 时,该位 = 1 表示内存→内存)。
2. DMA 通道传输计数器寄存器(DMA_CNDTRx)
-
地址:DMA1_Channel1 的 CNDTR 寄存器地址为 0x40020004;
-
核心作用:存储待传输的数据个数(0~65535);
-
关键说明:
- 传输过程中,计数器自动递减,每次传输 1 个数据,计数器减 1;
- 传输完成后,计数器清零(正常模式)或自动重载初始值(循环模式);
- 可通过读取该寄存器获取剩余传输数据个数。
3. DMA 通道外设地址寄存器(DMA_CPARx)
- 地址:DMA1_Channel1 的 CPAR 寄存器地址为 0x40020008;
- 核心作用:存储外设数据寄存器的地址(如 ADC1_DR 的地址 = 0x40012440);
- 关键说明:外设地址通常固定,传输过程中不会变化(PINC=0)。
4. DMA 通道内存地址寄存器(DMA_CMARx)
- 地址:DMA1_Channel1 的 CMAR 寄存器地址为 0x4002000C;
- 核心作用:存储内存缓冲区的起始地址(如 SRAM 中数组的首地址);
- 关键说明:若开启内存地址增量(MINC=1),传输过程中内存地址自动递增(递增步长 = 数据宽度,如 16 位数据递增 2 字节)。
5. DMA 中断状态寄存器(DMA_ISR)
-
地址:0x40020000 + 0x00000078(DMA1_ISR);
-
核心作用:存储所有通道的中断状态标志;
-
关键位说明:
- TCIFx(bit1、bit5、bit9 等):通道 x 传输完成中断标志(1 = 传输完成);
- HTIFx(bit2、bit6、bit10 等):通道 x 半传输中断标志(1 = 传输完成一半);
- TEIFx(bit3、bit7、bit11 等):通道 x 传输错误中断标志(1 = 传输错误)。
四、DMA 初始化核心流程(必掌握!)
DMA 的初始化流程严格遵循 “使能时钟→配置通道参数→设置传输计数器→使能通道” 的顺序,以下以 “ADC1→DMA1_Channel1→内存” 传输为例,解析完整流程:
1. 步骤 1:使能 DMA 控制器时钟
DMA 控制器属于 AHB 总线外设,需通过 RCC 寄存器使能时钟:
c
运行
// 使能DMA1时钟(DMA1挂载AHB总线)
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
底层寄存器操作:RCC->AHBENR |= (1<<0)(DMA1 时钟使能位为 bit0)。
2. 步骤 2:配置 DMA 通道参数(DMA_CCRx)
通过DMA_InitTypeDef结构体配置通道核心参数,对应寄存器位:
c
运行
DMA_InitTypeDef DMA_InitStruct;
// 1. 选择DMA通道(ADC1→DMA1_Channel1)
DMA_InitStruct.DMA_Channel = DMA_Channel_1;
// 2. 配置传输方向(外设→内存)
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; // SRC=源,外设为数据来源
// 3. 配置内存地址(SRAM中存储ADC数据的数组首地址)
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)adc_data_buf; // adc_data_buf为uint16_t数组
// 4. 配置外设地址(ADC1数据寄存器地址)
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
// 5. 内存地址增量使能(开启,连续写入内存)
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 6. 外设地址增量使能(关闭,ADC_DR地址固定)
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 7. 内存数据宽度(16位,与ADC采样值宽度匹配)
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
// 8. 外设数据宽度(16位,ADC_DR为16位寄存器)
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
// 9. 传输模式(循环模式,持续采集)
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
// 10. 通道优先级(中优先级)
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;
// 11. 内存到内存模式(关闭,仅外设→内存)
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
// 12. 写入配置参数到DMA通道
DMA_Init(DMA1_Channel1, &DMA_InitStruct);
3. 步骤 3:设置传输计数器(DMA_CNDTRx)
配置待传输的数据个数,若为循环模式,计数器会自动重载:
c
运行
// 配置传输数据个数(1000个数据,adc_data_buf数组长度≥1000)
DMA_SetCurrDataCounter(DMA1_Channel1, 1000);
底层寄存器操作:DMA1_Channel1->CNDTR = 1000。
4. 步骤 4:使能 DMA 通道
c
运行
// 使能DMA1_Channel1
DMA_Cmd(DMA1_Channel1, ENABLE);
底层寄存器操作:DMA1_Channel1->CCR |= (1<<0)(EN 位 = 1)。
5. 步骤 5:使能外设 DMA 请求(如 ADC1 DMA 请求)
DMA 传输需由外设发起请求(如 ADC 转换完成后请求 DMA 搬运数据),需在了你那个外设中使能 DMA 请求:
c
运行
// 使能ADC1的DMA请求(ADC_CR2的DMA位=1)
ADC_DMACmd(ADC1, ENABLE);
底层寄存器操作:ADC1->CR2 |= (1<<8)(ADC_CR2 的 DMA 位为 bit8)。
6. 步骤 6:(可选)配置 DMA 中断
若需在传输完成、半传输或传输错误时触发中断,需配置 NVIC 和 DMA 中断使能:
c
运行
// 使能DMA1_Channel1传输完成中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
// 配置NVIC中断优先级
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
五、DMA 相关面试高频题(附标准答案)
1. 问题 1:STM32 DMA 的核心作用是什么?与 CPU 中断传输相比有哪些优势?
标准答案:
-
核心作用:在不占用 CPU 资源的情况下,实现外设与内存、内存与内存之间的高速数据传输;
-
优势:
- 解放 CPU:传输过程无需 CPU 干预,CPU 可执行其他任务或进入睡眠模式;
- 传输效率高:硬件级传输,无软件开销,速度仅受总线带宽限制;
- 降低功耗:CPU 无需频繁响应中断,减少功耗;
- 适配大批量数据:适合 ADC 连续采集、串口文件传输等高频、大批量数据场景。
2. 问题 2:STM32 DMA 支持哪些传输方向?各自的典型应用是什么?
标准答案:
-
支持三种传输方向:
- 外设→内存(Peripheral to Memory):典型应用是 ADC 采集数据写入 SRAM、串口接收大数据存储到内存;
- 内存→外设(Memory to Peripheral):典型应用是 SRAM 数据通过串口发送、DMA 驱动 DAC 输出波形;
- 内存→内存(Memory to Memory):典型应用是 SRAM 内部大批量数据复制、Flash 数据搬运到 SRAM。
3. 问题 3:STM32 DMA 的通道与外设的映射关系是什么?如何选择 DMA 通道?
标准答案:
-
映射关系:每个 DMA 通道对应特定的外设请求(如 DMA1_Channel1 对应 ADC1,DMA1_Channel5 对应 USART1_RX),由 STM32 硬件设计固定,不可随意更改;
-
选择方法:
- 确定待传输的外设(如 ADC1、USART1);
- 查阅 STM32 参考手册的 “DMA 通道与外设请求映射表”;
- 选择该外设对应的 DMA 通道(如 ADC1→DMA1_Channel1);
- 确保同一时间仅一个外设使用该通道。
4. 问题 4:DMA 的循环模式和正常模式有什么区别?各自适用场景是什么?
标准答案:
- 循环模式(Circular mode):传输完成后,DMA 传输计数器自动重载初始值,继续循环传输,无需手动重启;适用于连续、周期性数据传输(如 ADC 连续采集、DAC 循环输出波形);
- 正常模式(Normal mode):传输完成后,DMA 通道自动关闭,传输计数器清零,需手动调用
DMA_SetCurrDataCounter和DMA_Cmd重启传输;适用于一次性数据传输(如单次文件发送、内存数据复制)。
六、总结:DMA 底层原理核心要点与实战铺垫
1. 核心要点回顾
- DMA 本质:无 CPU 干预的硬件数据搬运控制器,核心价值是 “解放 CPU + 高速传输”;
- 核心参数:传输方向、通道选择、地址增量、数据宽度、传输模式、优先级,决定传输逻辑;
- 初始化流程:使能 DMA 时钟→配置通道参数→设置传输计数器→使能通道→使能外设 DMA 请求;
- 关键映射:外设与 DMA 通道的映射关系固定,需按手册选型;
- 应用场景:大批量、高频数据传输,如 ADC 采集、串口大数据收发、SPI 闪存读写。
2. 下一篇实战铺垫
掌握底层原理后,下一篇我们将聚焦 DMA 实战开发,覆盖:
- ADC+DMA 采集:ADC1 连续采集光敏电阻数据,DMA 自动写入内存,实现无 CPU 干预采集;
- 串口 DMA 收发:USART1 通过 DMA 接收大数据(如字符串),DMA 自动存储到缓冲区;
- 内存到内存传输:实现 SRAM 数组数据复制,验证 DMA 传输效率;
- 中断处理:配置 DMA 传输完成中断,处理采集或传输后的数据。