STM32开发关于DMA 核心概念与工程选型(附完整代码配置)
DMA 核心概念与工程选型
从“CPU逐字节搬运”到“硬件自动传输”——STM32 DMA 完整学习笔记
一、DMA 核心概念
DMA(Direct Memory Access,直接内存访问) 是 STM32 内置的硬件搬运外设。
核心作用:不占用 CPU,由硬件自动搬运数据,大幅解放内核,提升系统实时性。
CPU 只需要:初始化配置 → 启动 DMA → 坐等完成中断,搬运全程无需干预。
DMA 三大优势
-
无需 CPU 逐字节读取,零占用内核
-
硬件自动触发、自动搬运、速度极快
-
适合高速、连续、大批量数据传输
二、DMA 三大工作模式
1. 普通模式(Normal)
工作机制:设定传输长度 N,DMA 搬运 N 个数据后自动停止、通道关闭。如需再次传输,必须软件重新使能、重载计数。
特点:一次性任务、自动停机、不会覆盖数据。
缺点:无法持续接收数据流,停机后新数据直接丢包。
2. 循环模式(Circular)
工作机制:数据搬运到末尾后,硬件自动重载计数器,DMA 永久运行、永不停止。数据写满缓冲区后自动从头覆盖,形成环形缓冲区。
特点:7×24 不间断传输、无停机、适合流式数据。
风险:CPU 处理不及时会覆盖旧数据,必须搭配中断分包。
3. 内存到内存模式(Mem2Mem)
唯一纯内存搬运模式,无任何外设参与。只能普通模式,不支持循环模式。由软件启动 DMA,不需要硬件触发信号。
用途:大块内存快速拷贝,替代 memcpy。
三、DMA 四种传输方向(工程最核心)
DMA 所有项目应用,全部来自下面 4 种组合。
方向一:外设 → 内存(接收方向)
本质:外部设备数据自动流入 MCU 内存。
固定配置:外设地址不递增(DMA_PeripheralInc_Disable)、内存地址递增(DMA_MemoryInc_Enable)。
① 外设→内存 + 普通模式(Normal)
适用场景:一次性、有限长度、触发式采集。
真实项目应用:
-
仪器仪表:按键触发单次 ADC 采样、均值滤波采集
-
传感器设备:I2C/SPI 单次读取温湿度、压力数据
-
物联网设备:上电一次性读取 Flash 配置参数
-
工业检测:单次采集一组电压电流数据
核心逻辑:采完一组就停机,不需要一直运行。
c
/**
* @brief 外设→内存 + 普通模式:单次ADC采样
* @note ADC1_Channel0 → 内存数组,采样完成后DMA自动停止
*/
void ADC_DMA_Normal_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)g_adc_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设→内存
DMA_InitStructure.DMA_BufferSize = 10; // 采集10个点后停止
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
// 使能DMA传输完成中断(可选)
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
② 外设→内存 + 循环模式(Circular)
适用场景:不间断流式数据、永久实时采集。
真实项目应用(工业最常用):
-
AGV 小车、机器人:RS485/USART 持续接收运动控制指令
-
充电桩、光伏逆变器:不间断 ADC 采集电压电流
-
音频设备:麦克风 I2S 持续录音
-
运动控制:SPI 编码器实时位置持续采集
-
智能家居:串口持续接收遥控、蓝牙数据流
工业标准方案:循环 DMA + 串口 IDLE 空闲中断(实现不定长帧接收)
c
/**
* @brief 外设→内存 + 循环模式:串口DMA持续接收
* @note DMA自动将USART1收到的数据循环存入缓冲区
* 配合IDLE空闲中断实现不定长帧接收
*/
#define USART_DMA_BUF_SIZE 256
uint8_t g_usart_rx_buffer[USART_DMA_BUF_SIZE];
void USART1_DMA_RX_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel5); // USART1_RX 对应 DMA1_Channel5
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)g_usart_rx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设→内存
DMA_InitStructure.DMA_BufferSize = USART_DMA_BUF_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式[reference:8]
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
// 使能DMA半满中断 + 完成中断(防覆盖)[reference:9]
DMA_ITConfig(DMA1_Channel5, DMA_IT_HT | DMA_IT_TC, ENABLE);
DMA_Cmd(DMA1_Channel5, ENABLE);
// 开启串口空闲中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
}
方向二:内存 → 外设(发送方向)
本质:MCU 内存数据自动推送到外设,向外输出信号。
固定配置:外设地址固定(DMA_PeripheralInc_Disable)、内存自增(DMA_MemoryInc_Enable)。
① 内存→外设 + 普通模式(Normal)
适用场景:一次性整块数据发送、刷屏、烧录、上报。
真实项目应用:
-
工业 HMI:LCD/OLED/SPI 屏幕一次性刷屏
-
存储设备:SPI Flash 一页数据烧录
-
物联网设备:定时上报日志、上报传感器数据
-
串口设备:一次性发送数据包、指令帧
c
/**
* @brief 内存→外设 + 普通模式:串口DMA发送
* @note 将内存中的字符串通过DMA发送到USART1
*/
void USART1_DMA_TX_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel4); // USART1_TX 对应 DMA1_Channel4
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)g_tx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存→外设
DMA_InitStructure.DMA_BufferSize = strlen((char*)g_tx_buffer);
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 普通模式[reference:12]
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
// 使能传输完成中断
DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
DMA_Cmd(DMA1_Channel4, ENABLE);
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
}
// 启动一次DMA发送
void USART1_DMA_Send(uint8_t *data, uint16_t len)
{
DMA_Cmd(DMA1_Channel4, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel4, len);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)data;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel4, ENABLE);
}
② 内存→外设 + 循环模式(Circular)
适用场景:持续周期性输出固定波形/音频。
真实项目应用:
-
信号发生器:DAC 循环输出正弦波、三角波、方波
-
变频器、电机驱动:DAC 模拟波形持续输出
-
音频设备:循环播放提示音、报警音
-
灯光控制器:SPI 持续输出时序信号
重点坑点:运行中修改缓存必须用双缓冲,否则波形撕裂、数据错乱。
c
/**
* @brief 内存→外设 + 循环模式:DAC输出正弦波
* @note DMA循环从正弦波表中搬运数据到DAC寄存器
* 配合定时器触发,实现连续波形输出[reference:16]
*/
#define SINE_WAVE_POINTS 32
uint16_t g_sine_wave[SINE_WAVE_POINTS]; // 预计算的正弦波表
void DAC_DMA_Circular_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 预计算正弦波表(12位DAC,0~4095)
for (int i = 0; i < SINE_WAVE_POINTS; i++) {
g_sine_wave[i] = (uint16_t)(2048 + 2047 * sin(2 * PI * i / SINE_WAVE_POINTS));
}
DMA_DeInit(DMA1_Channel3); // DAC1 对应 DMA1_Channel3
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&DAC->DHR12R1;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)g_sine_wave;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存→外设
DMA_InitStructure.DMA_BufferSize = SINE_WAVE_POINTS;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式[reference:17]
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel3, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel3, ENABLE);
// 使能DAC的DMA请求
DAC_DMACmd(DAC_Channel_1, ENABLE);
DAC_Cmd(DAC_Channel_1, ENABLE);
}
方向三:内存 → 内存(Mem2Mem)
无外设、无硬件触发、纯内存搬运。只能普通模式,禁止循环模式。
真实项目应用:
-
液晶屏 UI:Flash 字库/图片搬运到 SRAM 显存
-
图像设备:摄像头大数据块内存平移、拷贝
-
系统初始化:批量拷贝配置参数
-
替代库函数
memcpy,大幅节省 CPU 资源
绝对不能用:串口、ADC、DAC、SPI 等所有外设传输。
c
/**
* @brief 内存→内存模式:大块数据快速拷贝
* @note 纯内存搬运,无外设参与,只能普通模式
*/
#define MEM2MEM_BUF_SIZE 1024
uint32_t g_src_buffer[MEM2MEM_BUF_SIZE];
uint32_t g_dst_buffer[MEM2MEM_BUF_SIZE];
void DMA_Mem2Mem_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 注意:Mem2Mem只能使用DMA2(大容量芯片)或特定通道[reference:23]
// STM32F103ZET6 使用 DMA1_Channel1 ~ 7 均可支持 Mem2Mem
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)g_src_buffer; // 源地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)g_dst_buffer; // 目标地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 从源读(此处把外设当内存用)
DMA_InitStructure.DMA_BufferSize = MEM2MEM_BUF_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 源地址递增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 目标地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 只能普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 使能内存到内存模式[reference:25]
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
// 使能传输完成中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);
}
// 启动一次内存拷贝(软件触发,无需外设信号)
void DMA_Mem2Mem_Start(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MEM2MEM_BUF_SIZE);
DMA_Cmd(DMA1_Channel1, ENABLE); // 软件启动,立即开始搬运
}
四、DMA 中断类型详解
DMA 支持三种中断事件,通过“线或”方式共享一个中断向量:
| 中断类型 | 标志位 | 触发条件 | 典型用途 |
|---|---|---|---|
| 传输完成(TC) | TCIF | 全部数据搬运完毕 | 通知CPU数据已就绪 |
| 半传输完成(HT) | HTIF | 搬运到缓冲区一半 | 实现“软双缓冲”,防止覆盖 |
| 传输错误(TE) | TEIF | 总线错误、地址非法 | 异常处理 |
c
/**
* @brief DMA中断服务函数(以DMA1_Channel5为例)
* @note 需在中断中判断具体中断源[reference:31]
*/
void DMA1_Channel5_IRQHandler(void)
{
// 半传输完成中断(HT)—— 前半段数据已就绪[reference:32]
if (DMA_GetITStatus(DMA1_IT_HT5) != RESET) {
DMA_ClearITPendingBit(DMA1_IT_HT5);
// 处理缓冲区前半部分数据[reference:33]
Process_Half_Buffer(0, USART_DMA_BUF_SIZE / 2);
}
// 传输完成中断(TC)—— 全部数据已就绪[reference:34]
if (DMA_GetITStatus(DMA1_IT_TC5) != RESET) {
DMA_ClearITPendingBit(DMA1_IT_TC5);
// 处理缓冲区后半部分数据[reference:35]
Process_Half_Buffer(USART_DMA_BUF_SIZE / 2, USART_DMA_BUF_SIZE);
}
// 传输错误中断(TE)
if (DMA_GetITStatus(DMA1_IT_TE5) != RESET) {
DMA_ClearITPendingBit(DMA1_IT_TE5);
// 错误处理
Error_Handler();
}
}
半满中断 + 完成中断 = 软双缓冲:将单个大缓冲区逻辑上分成两半,DMA 写入前半段时触发 HT 中断,CPU 处理前半段数据;DMA 继续写入后半段,两者互不干扰。这是 STM32F1 系列没有硬件双缓冲时的替代方案。
五、DMA 优先级仲裁
当多个 DMA 通道同时请求传输时,需通过优先级仲裁决定响应顺序:
-
软件优先级(4级):
DMA_Priority_VeryHigh>High>Medium>Low -
硬件优先级:软件优先级相同时,通道号越小优先级越高
c
// 高优先级通道配置示例
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
六、面试高频标准答案
1. 为什么串口接收要用循环 DMA,不用普通 DMA?
普通 DMA 收满长度会自动停机,串口数据流是不定长、随时到来,停机后会丢包。循环 DMA 永久运行,配合 IDLE 空闲中断可实现工业级不定长帧接收。
2. DAC 输出波形为什么必须循环模式?
普通模式发送完一次数据就停止,电平固定不变,无法形成连续波形。循环模式可以重复循环波形表,输出连续正弦/三角波。
3. Mem2Mem 为什么不能循环?
硬件设计禁止,循环内存拷贝会长期占用总线带宽,抢占外设实时数据流,造成通讯、采样异常。
4. 循环 DMA 最大风险是什么?
缓冲区覆盖。CPU 处理速度跟不上 DMA 写入速度,旧数据被新数据直接覆盖。解决方案:HT/TC 中断 + 双缓冲(软双缓冲)。
5. DMA 的优先级是如何分配的?
DMA 优先级分为软件优先级(4级:VeryHigh/High/Medium/Low)和硬件优先级。当软件优先级相同时,通道号越小的优先级越高。多个 DMA 请求同时发生时,优先级高的先响应。
6. DMA 的“半满中断”有什么用?
在循环 DMA 模式下,半满中断(HT)在 DMA 写到缓冲区一半时触发,配合完成中断(TC)可以将缓冲区拆分为两半,实现“软双缓冲”。当 CPU 在处理前半段数据时,DMA 继续向后半段写入,防止数据覆盖。这是 STM32F1 系列没有硬件双缓冲时的标准替代方案。
七、全文总结(一句话记住所有 DMA)
单次任务用普通,流式任务用循环,外设收发分方向,内存拷贝用 MEM2MEM。
| 场景 | 模式 | 方向 |
|---|---|---|
| 单次ADC采样 | Normal | 外设→内存 |
| 串口持续接收 | Circular | 外设→内存 |
| 一次性发送数据包 | Normal | 内存→外设 |
| DAC连续输出波形 | Circular | 内存→外设 |
| 大块内存拷贝 | Normal (Mem2Mem) | 内存→内存 |
更多推荐


所有评论(0)