STM32开发关于DMA 核心概念与工程选型(附完整代码配置)
DMA 核心概念与工程选型从“CPU逐字节搬运”到“硬件自动传输”——STM32 DMA 完整学习笔记一、DMA 核心概念DMADirect Memory Access直接内存访问是 STM32 内置的硬件搬运外设。核心作用不占用 CPU由硬件自动搬运数据大幅解放内核提升系统实时性。CPU 只需要初始化配置 → 启动 DMA → 坐等完成中断搬运全程无需干预。DMA 三大优势无需 CPU 逐字节读取零占用内核硬件自动触发、自动搬运、速度极快适合高速、连续、大批量数据传输二、DMA 三大工作模式1. 普通模式Normal工作机制设定传输长度 NDMA 搬运 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适用场景一次性整块数据发送、刷屏、烧录、上报。真实项目应用工业 HMILCD/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位DAC0~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无外设、无硬件触发、纯内存搬运。只能普通模式禁止循环模式。真实项目应用液晶屏 UIFlash 字库/图片搬运到 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 支持三种中断事件通过“线或”方式共享一个中断向量中断类型标志位触发条件典型用途传输完成TCTCIF全部数据搬运完毕通知CPU数据已就绪半传输完成HTHTIF搬运到缓冲区一半实现“软双缓冲”防止覆盖传输错误TETEIF总线错误、地址非法异常处理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_VeryHighHighMediumLow硬件优先级软件优先级相同时通道号越小优先级越高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)内存→内存

相关新闻