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库)

// 假设使用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更新事件解耦,彻底避免中断竞争,实现多路高精度方波输出。此方法适用于对波形质量要求高的场景,如多轴电机控制。