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
相关产品推荐
相关产品推荐

