You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32中避免while循环快速更新CCR引发PWM信号不稳定的方案

STM32 PWM占空比同步更新问题解决方案

问题概述

基于168MHz主频的STM32F4,通过定时器Ch1/Ch2生成500Hz PWM控制电机,计划每2ms通过定时器中断更新电压值,但无延迟while循环中高频更新CCR寄存器,导致单PWM周期内CCR被多次修改,引发信号不稳定或异常连续脉冲。

配置细节

  • MCU:STM32F4系列
  • 系统时钟:168MHz
  • PWM频率:500Hz(已配置合适的Prescaler和ARR值)
  • 中断配置:每2ms触发定时器中断更新电机电压
  • 核心问题:while循环高频更新CCR干扰PWM信号生成

疑问解答

1. 高频更新CCR是否会导致信号不稳定或连续脉冲?

会。STM32定时器的CCR寄存器若未启用预加载功能,写入的值会立即生效。当在单PWM周期(2ms,对应500Hz)内多次修改CCR时,当前正在输出的PWM脉冲会被强制改变:比如正处于高电平时突然写入更小的CCR值,引脚会立刻拉低,导致脉冲宽度远小于预期;若写入更大的CCR值,可能会让高电平持续到下一个周期,形成连续脉冲。这种无同步的修改会导致PWM信号出现毛刺、占空比跳变,严重影响电机控制的稳定性。

2. 如何将CCR更新与PWM周期同步?

核心思路是让CCR的修改仅在PWM周期结束时生效,有两种可靠实现方式:

  • 启用CCR预加载(影子寄存器)功能:让用户写入的CCR值暂存在预加载寄存器中,仅当定时器触发更新事件(计数到ARR溢出,即一个PWM周期结束)时,才将预加载值同步到实际生效的影子寄存器,确保每个周期仅更新一次占空比。
  • 把CCR更新逻辑移到定时器更新事件中断中:仅在每个PWM周期结束时计算并更新一次CCR,避免while循环的高频写入。

3. 是否应使用预加载或更新事件中断?

必须启用CCR预加载功能,这是STM32定时器解决此类问题的原生方案,结合更新事件中断效果更佳:

  • 预加载/影子寄存器机制:STM32定时器的每个捕获比较通道都配有预加载寄存器和影子寄存器。开启预加载(通过TIM_CCMR1寄存器的OC1PE/OC2PE位,对应Ch1/Ch2)后,用户写入TIMx->CCR1/TIMx->CCR2的是预加载值,只有当定时器产生更新事件(UEV)时,预加载值才会复制到影子寄存器,真正影响PWM输出。这样即使while循环多次写入CCR,也只会在下一个周期生效,不会干扰当前周期的脉冲。
  • 更新事件中断:如果希望严格控制占空比的更新时机(比如和2ms的状态更新中断同步),可以在定时器更新事件中断中执行CCR的写入操作,确保每2ms(或每个PWM周期)仅更新一次,彻底避免高频写入的问题。

代码修改建议

1. 启用CCR预加载功能

如果使用CubeMX配置,在定时器的Capture Compare Settings中,勾选对应通道的Capture Compare Preload选项;若手动配置代码,需添加如下初始化代码:

// 以TIM2 Ch1和Ch2为例
TIM2->CCMR1 |= TIM_CCMR1_OC1PE | TIM_CCMR1_OC2PE; // 启用Ch1和Ch2的预加载
TIM2->CR1 |= TIM_CR1_ARPE; // 可选:启用ARR预加载,确保周期参数也同步更新

2. 优化PWM更新逻辑

将电压计算和PWM更新与2ms中断同步,避免while循环高频调用:

// 全局变量标记是否需要更新PWM
uint8_t need_update_pwm = 0;
float target_voltage = 0;
MotorControl motor; // 移到全局或确保中断可访问

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        float startTarj = HAL_GetTick();
        if (startTarj >= 5000)
        {
            u = Optimal_Control();
        }
    }

    if (htim->Instance == TIM4)
    {
        // 原有编码器读取和状态计算逻辑
        cartCounter = (int16_t)TIM1->CNT;
        pendCounter = (int16_t)TIM3->CNT;
        currentTime = HAL_GetTick();
        currentState = calculate_system_state(cartCounter, pendCounter, currentTime, pastTime, &pastState);
        pastCartCounter = cartCounter;
        pastPendCounter = pendCounter;
        pastState = currentState;
        pastTime = currentTime;
        position = currentState.cartPosition;
        angle = currentState.pendAngle;
        speed = currentState.cartSpeed;
        omega = currentState.pendSpeed;

        // 在2ms中断中计算目标电压并标记更新
        if (currentState.cartPosition > -0.45 && currentState.cartPosition < 0.45)
        {
            target_voltage = motor.calculateVoltage(u, speed);
            need_update_pwm = 1;
        }
        else
        {
            need_update_pwm = 0;
        }
    }
}

// 修改主循环逻辑
int mymain(void)
{
    MX_USB_HOST_Process();
    HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
    HAL_TIM_Base_Start_IT(&htim4);
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);

    MotorControl motor_local;
    motor = motor_local; // 关联全局变量
    PWMControl pwm(&htim2);
    LQRControl lqr;

    while (1)
    {
        MX_USB_HOST_Process(); // 保留USB处理
        if (need_update_pwm)
        {
            pwm.setPWM(target_voltage);
            need_update_pwm = 0; // 仅更新一次
        }
    }
    return 0;
}

3. 优化PWMControl的setPWM函数

确保写入CCR时利用预加载机制,无需额外同步:

void PWMControl::setPWM(float voltage)
{
    // 假设已实现电压转CCR值的逻辑
    uint32_t ccr_value = voltage_to_ccr(voltage);
    // 写入CCR1和CCR2,预加载会自动在下一个周期生效
    __HAL_TIM_SET_COMPARE(&htim_, TIM_CHANNEL_1, ccr_value);
    __HAL_TIM_SET_COMPARE(&htim_, TIM_CHANNEL_2, ccr_value);
}

内容的提问来源于stack exchange,提问作者Hasan Fashtook

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 02:22:03