STM32 DMA+PWM多路方波输出:破解DMA中断与更新事件竞争导致的波形抖动
👁 2 阅读 · 2026-08-27 · 嵌入式
在STM32中,利用DMA+PWM输出多路高精度方波是电机控制、信号发生等场景的常用方案,但DMA传输完成中断与PWM更新事件(UEV)的竞争常导致波形抖动。本文深入剖析竞争根源,对比三种解决策略,并给出基于定时器级联与DMA突发传输的完整代码示例,助你实现无抖动输出。
# STM32 DMA+PWM多路方波输出:破解DMA中断与更新事件竞争导致的波形抖动
## 问题背景与竞争根源
在STM32中,DMA+PWM输出方波的典型流程:定时器产生更新事件(UEV)触发DMA搬运占空比数据到CCR寄存器,同时DMA传输完成触发中断,用于更新下一组数据。当多路输出时,若DMA中断与UEV时序重叠,可能出现:
- **中断延迟**:DMA中断服务函数执行时间过长,导致下一次UEV到来时CCR数据未更新,输出错误占空比。
- **数据撕裂**:DMA在UEV发生瞬间写入CCR,而定时器正在读取,造成半个周期异常。
根因在于:DMA中断和UEV共享同一优先级,且中断处理非原子性,导致时序竞争。
## 解决策略对比
| 策略 | 原理 | 优点 | 缺点 |
|------|------|------|------|
| 1. 禁用DMA中断,轮询标志 | 主循环查询DMA传输完成标志 | 简单,无中断开销 | 占用CPU,实时性差 |
| 2. 提高中断优先级 | 将DMA中断设为最高优先级 | 减少延迟 | 可能影响其他实时任务 |
| 3. 定时器级联+DMA突发传输 | 主定时器触发从定时器,DMA一次突发更新多路CCR | 无中断竞争,波形稳定 | 配置复杂,占用资源 |
推荐策略3,尤其适合多路输出场景。
## 硬件与软件准备
- **硬件**:STM32F407(或其他带DMA和高级定时器的型号),逻辑分析仪验证波形。
- **软件**:STM32CubeMX + HAL库,或标准外设库。
## 原理详解:定时器级联与DMA突发
**核心思想**:使用两个定时器,主定时器(如TIM1)产生PWM,从定时器(如TIM8)产生触发信号。主定时器的UEV不直接触发DMA,而是通过级联触发从定时器,从定时器再触发DMA突发传输,一次性更新多路CCR值。这样,DMA传输在UEV之后完成,且中断不再参与关键路径。
**关键配置**:
- 主定时器:PWM模式,输出比较通道多路。
- 从定时器:配置为主定时器的从模式,触发方式为“更新事件触发”。
- DMA:配置为循环模式,突发传输,数据宽度32位(对应CCR寄存器)。
## 完整代码示例(基于HAL库)
```c
// 假设使用TIM1(主)和TIM8(从),DMA1_Stream2(TIM1_UP)
// 初始化DMA句柄
typedef struct {
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim8;
DMA_HandleTypeDef hdma_tim1_up;
} PWM_Handle;
PWM_Handle hpwm;
// 占空比数据数组,例如4路,每路存储2个值(用于交替)
uint32_t dutyData[8] = {500, 500, 500, 500, 500, 500, 500, 500};
void PWM_Init(void) {
// 1. 使能时钟
__HAL_RCC_TIM1_CLK_ENABLE();
__HAL_RCC_TIM8_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
// 2. 主定时器TIM1配置
hpwm.htim1.Instance = TIM1;
hpwm.htim1.Init.Prescaler = 84-1; // 84MHz/84=1MHz
hpwm.htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
hpwm.htim1.Init.Period = 1000-1; // 1kHz方波
hpwm.htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
hpwm.htim1.Init.RepetitionCounter = 0;
HAL_TIM_PWM_Init(&hpwm.htim1);
// 3. 从定时器TIM8配置(触发模式)
hpwm.htim8.Instance = TIM8;
hpwm.htim8.Init.Prescaler = 0;
hpwm.htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
hpwm.htim8.Init.Period = 1; // 每次更新触发一次
hpwm.htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
hpwm.htim8.Init.RepetitionCounter = 0;
HAL_TIM_Base_Init(&hpwm.htim8);
// 4. 配置从定时器为外部触发模式(由TIM1更新触发)
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_ITR0; // 根据连接选择ITR0或ITR1等
sSlaveConfig.TriggerPolarity = TIM_TRIGGERPOLARITY_RISING;
sSlaveConfig.TriggerFilter = 0;
HAL_TIM_SlaveConfigSynchro(&hpwm.htim8, &sSlaveConfig);
// 5. 配置PWM通道(例如通道1-4)
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&hpwm.htim1, &sConfigOC, TIM_CHANNEL_1);
// 重复配置通道2-4...
// 6. 配置DMA(突发传输,循环模式)
hpwm.hdma_tim1_up.Instance = DMA1_Stream2;
hpwm.hdma_tim1_up.Init.Channel = DMA_CHANNEL_6; // 根据映射
hpwm.hdma_tim1_up.Init.Direction = DMA_MEMORY_TO_PERIPH;
hpwm.hdma_tim1_up.Init.PeriphInc = DMA_PINC_DISABLE;
hpwm.hdma_tim1_up.Init.MemInc = DMA_MINC_ENABLE;
hpwm.hdma_tim1_up.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hpwm.hdma_tim1_up.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hpwm.hdma_tim1_up.Init.Mode = DMA_CIRCULAR;
hpwm.hdma_tim1_up.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hpwm.hdma_tim1_up);
__HAL_LINKDMA(&hpwm.htim1, hdma[TIM_DMA_ID_UPDATE], hpwm.hdma_tim1_up);
// 7. 启动DMA传输(从dutyData到TIM1->CCR1,突发4次)
HAL_DMA_Start(&hpwm.hdma_tim1_up, (uint32_t)dutyData, (uint32_t)&TIM1->CCR1, 4);
// 8. 启动PWM和从定时器
HAL_TIM_PWM_Start(&hpwm.htim1, TIM_CHANNEL_1);
// 启动其他通道...
HAL_TIM_Base_Start(&hpwm.htim8);
}
// 主循环中无需处理中断,只需更新dutyData数组
void UpdateDuty(uint32_t* newData, uint8_t len) {
memcpy(dutyData, newData, len * sizeof(uint32_t));
}
```
## 注意事项
- **DMA突发长度**:必须与CCR寄存器数量匹配,且数据宽度为32位(因为CCR是32位寄存器)。
- **内存对齐**:dutyData数组建议4字节对齐,避免DMA访问错误。
- **从定时器触发源**:需根据STM32型号的ITR映射表选择正确的ITR编号(如TIM8的ITR0对应TIM1)。
- **禁用DMA中断**:本方案无需DMA中断,确保不产生竞争。
- **调试技巧**:使用逻辑分析仪观察波形,若仍有抖动,检查DMA优先级和从定时器配置。
## 总结
通过定时器级联和DMA突发传输,将DMA传输与PWM更新事件解耦,彻底避免中断竞争,实现多路高精度方波输出。此方法适用于对波形质量要求高的场景,如多轴电机控制。