1. PWM与定时器的核心原理
第一次接触PWM时,我盯着示波器上那些方波发呆——为什么简单的电平切换就能控制电机转速?后来才明白,这背后是定时器和PWM通道的精密配合。想象一下,定时器就像个节拍器,而PWM通道则是根据节拍跳舞的舞者。
定时器本质上是个自动计数的模块。以STM32的通用定时器为例,它包含一个16位计数器(CNT),这个计数器会按照设定的频率不断累加。当计数到预设的自动重装载值(ARR)时,就会产生溢出并重新开始计数,形成一个固定周期。这个周期就是PWM波的"心跳"。
PWM通道则是定时器的输出端口。每个通道都有独立的比较寄存器(CCR),当计数器值小于CCR时输出高电平(或低电平,取决于配置),大于CCR时则翻转电平。通过调整CCR的值,就能改变高电平的持续时间,也就是占空比。
实际项目中,我曾用TIM3控制电机,配置参数时发现个有趣现象:当ARR设为999,PSC设为71(系统时钟72MHz),得到的PWM频率正好是1kHz。这验证了公式:
PWM频率 = 定时器时钟频率 / [(PSC + 1) * (ARR + 1)]占空比则是:
占空比 = CCR / (ARR + 1) * 100%2. 硬件配置实战步骤
2.1 时钟树配置要点
在STM32CubeMX中配置时钟时,新手常会忽略时钟分频对定时器的影响。APB1总线上的定时器(如TIM2-TIM4)时钟频率可能和系统时钟不同——如果APB1预分频系数≠1,定时器时钟会倍频。
我曾踩过坑:明明系统时钟72MHz,TIM3挂在APB1上(默认36MHz),但实际测得PWM频率比计算值高一倍。后来发现是CubeMX自动启用了"定时器时钟倍频"功能。建议在RCC配置中确认:
- APB1/APB2预分频系数
- 对应定时器的实际输入时钟
2.2 GPIO复用功能配置
以TIM3_CH1映射到PB4为例,关键配置代码如下:
GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_4; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 必须设为复用推挽输出 GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF2_TIM3; // 查看芯片手册确定复用功能编号 HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);调试时发现,如果忘记设置Alternate字段,PWM输出会完全没反应。不同芯片的复用功能编号可能不同,比如STM32F1系列需要通过重映射寄存器配置,而F4系列直接使用Alternate功能。
2.3 定时器基础参数设置
创建PWM的完整初始化示例:
TIM_HandleTypeDef htim3; htim3.Instance = TIM3; htim3.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; // 1MHz/(999+1)=1kHz htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; HAL_TIM_PWM_Init(&htim3); TIM_OC_InitTypeDef sConfigOC; sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 500; // 初始占空比50% sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);3. 高级应用技巧
3.1 动态调整占空比
在电机调速项目中,需要实时修改PWM占空比。通过以下函数即可动态调整:
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, new_CCR);实测发现,直接操作寄存器速度更快:
TIM3->CCR1 = new_CCR; // 无中间层调用,适合实时性要求高的场景3.2 互补输出与死区时间
驱动H桥电路时,需要配置互补PWM和死区时间。以高级定时器TIM1为例:
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig; sBreakDeadTimeConfig.DeadTime = 45; // 约500ns(根据时钟频率计算) sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);死区时间计算公式:
T_dead = DeadTime * T_dts 其中T_dts = 1/(f_tim / ClockDivision)3.3 中央对齐模式
在变频器应用中,中央对齐模式能减少谐波干扰。配置方法:
htim3.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1;此时计数器先递增到ARR,再递减到0,PWM频率会减半,但波形对称性更好。
4. 常见问题排查
4.1 无PWM输出排查步骤
- 检查时钟使能:确认RCC中开启了定时器和GPIO时钟
- 验证引脚映射:使用CubeMX检查TIMx_CHx是否映射到正确引脚
- 测量引脚状态:用万用表检查引脚是否进入复用模式
- 检查寄存器值:调试模式查看TIMx_CR寄存器的CEN位是否置1
4.2 频率偏差过大
遇到过一个案例:预期10kHz PWM实际只有8kHz。最终发现是:
- 错误地将定时器挂载到了APB1(36MHz)而非APB2(72MHz)
- 预分频值计算时忘记"+1"
建议使用STM32CubeMX的时钟树工具可视化确认时钟路径。
4.3 占空比异常
若占空比与设置值不符,检查:
- ARR值是否过小导致分辨率不足
- 是否误用了PWM模式2(极性相反)
- 是否有其他代码在运行时修改了CCR寄存器
曾经调试四轴飞行器时,电机突然停转,最后发现是看门狗复位导致PWM配置被清除。解决方法是在初始化后立即启动PWM:
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);