STM32 DMA+PWM 多路脉冲输出:破解半传输中断与更新事件竞争导致的波形毛刺
👁 1 阅读 · 2026-08-27 · 嵌入式
在STM32中,使用DMA驱动PWM实现多路高精度脉冲输出时,DMA半传输中断与PWM更新事件之间的竞争是波形毛刺的常见根源。本文深入剖析竞争机制,提出基于双缓冲、中断优先级配置和事件同步的三种规避策略,并给出完整代码示例与调试要点,帮助开发者彻底消除毛刺,提升输出质量。
# 引言
在工业控制、电机驱动和精密测量中,STM32的定时器PWM结合DMA是生成多路高精度脉冲序列的常用方案。然而,当DMA半传输中断(HT)与PWM更新事件(UEV)在时间上重叠时,可能导致DMA传输时序错乱,进而产生波形毛刺。本文将分析竞争根源,并给出实用的规避方案。
## 一、竞争机制剖析
### 1.1 DMA+PWM工作原理
- **PWM生成**:定时器(如TIM1)的CCR寄存器决定占空比,更新事件(UEV)在每个周期结束时触发,将影子寄存器值加载到实际寄存器。
- **DMA传输**:DMA在更新事件触发下,从内存搬运新的占空比数据到CCR寄存器,实现动态调制。
- **半传输中断**:当DMA传输完成一半时触发HT中断,常用于双缓冲切换,以提前准备下一批数据。
### 1.2 竞争场景
- 当DMA传输数据量较大时,HT中断可能在某个PWM周期中间触发。
- 若HT中断服务函数(ISR)执行时间过长,或与UEV中断(若开启)同时到达,可能导致DMA传输暂停或数据错位。
- 更关键的是:HT中断中若修改DMA配置(如切换内存地址),可能发生在UEV触发DMA传输的瞬间,造成传输冲突,产生毛刺。
## 二、规避策略
### 策略1:双缓冲 + 延迟切换
- **原理**:使用两个内存缓冲区,DMA交替读取。HT中断仅设置标志位,不在中断中直接切换DMA地址,而是通过主循环或定时器轮询在安全时刻切换。
- **实现**:
- 配置DMA循环模式,缓冲区大小设为2*N(N为单次脉冲数)。
- HT中断置位`flag_half`,TC中断置位`flag_complete`。
- 在主循环中检查标志,调用`HAL_DMA_Start_IT`重新配置源地址(需先停止DMA)。
### 策略2:中断优先级与临界区保护
- **原理**:将DMA中断优先级设为最高,PWM更新中断(若使用)设为次高,确保DMA传输不被抢占。同时,在HT ISR中关闭PWM更新中断(TIMx->DIER &= ~TIM_DIER_UIE),操作完成后再恢复。
- **注意**:关闭UEV中断会导致PWM周期内无中断,但DMA仍由硬件触发,不影响传输。
### 策略3:使用DMA的FIFO和突发传输
- **原理**:配置DMA的FIFO模式,并启用突发传输(Burst),一次传输多个数据,减少DMA占用总线的时间,降低与UEV冲突概率。
- **实现**:设置`DMA_InitTypeDef`中的`DMA_FIFOMode`和`DMA_BurstSize`。
## 三、完整代码示例(以STM32F407为例)
以下代码演示策略2(中断优先级+临界区)的实现。
### 3.1 定时器与DMA配置
```c
// 定时器PWM配置(TIM1,通道1-4)
void PWM_Init(void) {
TIM_HandleTypeDef htim1;
htim1.Instance = TIM1;
htim1.Init.Prescaler = 84-1; // 84MHz/84=1MHz
htim1.Init.Period = 1000-1; // 1kHz PWM
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim1);
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
for (int i=1; i<=4; i++) {
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, i*2-1); // 通道1,2,3,4
}
// 使能更新中断(用于同步)
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
}
// DMA配置(以TIM1_CH1为例,其他通道类似)
void DMA_Init(void) {
DMA_HandleTypeDef hdma_tim1_ch1;
hdma_tim1_ch1.Instance = DMA1_Stream2; // 根据实际映射
hdma_tim1_ch1.Init.Channel = DMA_CHANNEL_6; // TIM1_CH1映射
hdma_tim1_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim1_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim1_ch1.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim1_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_tim1_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_tim1_ch1.Init.Mode = DMA_CIRCULAR; // 循环模式
hdma_tim1_ch1.Init.Priority = DMA_PRIORITY_HIGH; // 高优先级
hdma_tim1_ch1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_tim1_ch1);
// 连接DMA到定时器
__HAL_LINKDMA(&htim1, hdma[TIM_DMA_ID_CC1], hdma_tim1_ch1);
}
```
### 3.2 中断处理与临界区
```c
volatile uint8_t pwm_update_enabled = 1;
// DMA半传输中断回调
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim) {
// 进入临界区:关闭PWM更新中断
__HAL_TIM_DISABLE_IT(&htim1, TIM_IT_UPDATE);
pwm_update_enabled = 0;
// 处理半缓冲数据(例如预加载下一批)
// 注意:此处不切换DMA地址,仅做数据准备
// 退出临界区:恢复更新中断
__HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE);
pwm_update_enabled = 1;
}
// PWM更新中断回调(用于同步)
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
// 可在此处进行精确同步操作,但需保持简短
}
// 主循环中安全切换DMA缓冲区(策略1的补充)
void ProcessDMAFlags(void) {
static uint8_t use_half = 0;
if (flag_half) {
flag_half = 0;
// 停止DMA,切换源地址,重新启动
HAL_DMA_Abort(&hdma_tim1_ch1);
HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)buffer_half, (uint32_t)&htim1.Instance->CCR1, N);
use_half = 1;
}
if (flag_complete) {
flag_complete = 0;
HAL_DMA_Abort(&hdma_tim1_ch1);
HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)buffer_full, (uint32_t)&htim1.Instance->CCR1, N);
use_half = 0;
}
}
```
### 3.3 启动输出
```c
int main(void) {
HAL_Init();
PWM_Init();
DMA_Init();
// 启动DMA传输(循环模式)
HAL_DMA_Start_IT(&hdma_tim1_ch1, (uint32_t)buffer_full, (uint32_t)&htim1.Instance->CCR1, N);
// 使能定时器DMA请求
__HAL_TIM_ENABLE_DMA(&htim1, TIM_DMA_CC1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
while(1) {
ProcessDMAFlags();
}
}
```
## 四、注意事项
- **中断优先级**:务必设置DMA中断优先级高于任何可能抢占它的中断(如外部中断),但低于系统节拍(若使用RTOS)。
- **临界区保护**:在HT ISR中关闭UEV中断时,需确保操作时间极短(<1us),否则会影响PWM精度。
- **缓冲区对齐**:DMA缓冲区建议按32字节对齐,避免缓存一致性问题(若使用D-Cache)。
- **调试技巧**:使用逻辑分析仪观察PWM波形,同时监控DMA的NDTR寄存器,确认传输无错位。
- **多通道扩展**:多路PWM时,每个通道独立DMA流,但共享定时器,需注意DMA带宽,必要时降低PWM频率或使用FIFO。
## 五、总结
通过合理配置中断优先级、使用临界区保护以及双缓冲延迟切换,可以有效规避DMA半传输中断与PWM更新事件的竞争,消除波形毛刺。实际项目中,建议结合具体硬件和实时性要求,组合使用上述策略,并充分测试极端负载情况。