
1. PWM基础与呼吸灯实战第一次接触STM32的PWM功能时我被它强大的灵活性惊艳到了。PWM脉冲宽度调制就像个智能开关通过快速通断来控制平均功率输出。想象一下水龙头完全打开时水流最大完全关闭时没有水流而PWM就是让你以极快的速度开关水龙头通过调整打开和关闭的时间比例来控制平均流量。在STM32上配置PWM需要五个关键步骤我把它总结为时钟树-时基-输出-GPIO-启动流程。首先通过RCC开启TIM定时器和GPIO时钟这就像给整个系统通电。然后配置时基单元这里ARR寄存器决定PWM周期PSC预分频器调整计时频率。比如我的呼吸灯项目中使用72MHz主频设置PSC720-1和ARR100-1得到的就是100Hz的PWM频率72MHz/720/100。输出比较单元配置才是PWM的精髓所在。在TIM_OCInitTypeDef结构体中我选择PWM1模式、高电平有效并启用输出。关键点在于CCR寄存器的值决定了占空比 - 当计数器值小于CCR时输出高电平大于时输出低电平。通过动态修改CCR值就能实现呼吸灯效果for(int i0; i100; i){ TIM_SetCompare1(TIM2, i); // 渐亮 Delay_ms(10); } for(int i0; i100; i){ TIM_SetCompare1(TIM2, 100-i); // 渐暗 Delay_ms(10); }实测中发现三个易错点一是忘记GPIO要配置为复用推挽输出模式二是TIM_Cmd忘记启用定时器三是占空比计算要注意ARR值。比如ARR设为100-1时CCR值50对应的就是50%占空比。建议调试时先用示波器观察波形确保基础PWM信号正常再连接LED。2. 深入PWM库函数解析STM32标准库提供了丰富的PWM相关函数我刚开始用时经常搞混。经过几个项目的磨练现在把这些函数分成四大类来记忆第一类是初始化函数TIM_OCxInit系列用于配置各通道参数。这里有个技巧先用TIM_OCStructInit给结构体赋默认值再修改需要的字段能避免遗漏参数。比如PWM模式要设置为TIM_OCMode_PWM1输出状态必须Enable。第二类是动态调节函数最常用的就是TIM_SetComparex系列。在电机控制项目中我通过按键中断实时修改CCR值void EXTI0_IRQHandler(){ if(EXTI_GetITStatus(EXTI_Line0) ! RESET){ static uint16_t duty 0; duty (duty 10) % 100; TIM_SetCompare2(TIM3, duty); EXTI_ClearITPendingBit(EXTI_Line0); } }第三类是高级控制函数包括强制输出(TIM_ForcedOCxConfig)、快速使能(TIM_OCxFastConfig)等。在舵机突然卡顿时强制输出高电平能起到保护作用。第四类是极性配置函数TIM_OCxPolarityConfig可以动态改变输出极性。这个在驱动某些需要反向PWM的电机时特别有用。特别提醒高级定时器TIM1/TIM8还有互补输出和刹车功能适合电机控制。当初我傻傻地用通用定时器驱动大功率电机结果MOS管发热严重后来改用TIM1的互补输出才解决问题。3. 舵机角度精确控制玩过机器人项目的朋友肯定对舵机不陌生。标准舵机需要50Hz的PWM信号周期20ms其中脉冲宽度在0.5ms-2.5ms对应0-180度角度。根据这个特性我重新配置时基单元TIM_TimeBaseInitStruct.TIM_Period 20000-1; // 20ms周期 TIM_TimeBaseInitStruct.TIM_Prescaler 72-1; // 72MHz/721MHz角度转换公式为CCR (Angle/180 * 2000) 500。比如90度对应CCR值15001.5ms脉冲。我封装了角度设置函数void Servo_SetAngle(uint16_t angle){ angle angle 180 ? 180 : angle; uint16_t ccr 500 angle * 2000 / 180; TIM_SetCompare3(TIM4, ccr); }实际调试中发现两个坑一是舵机供电不足会导致抖动建议单独5V电源二是机械限位不要强行超过我的第一个舵机就这样报废了。通过OLED显示当前角度配合按键逐步调试是不错的方法while(1){ KeyNum Key_GetNum(); if(KeyNum 1) angle 10; if(KeyNum 2) angle - 10; Servo_SetAngle(angle); OLED_ShowNum(1,1,angle,3); }4. 直流电机调速系统直流电机控制比前两者复杂需要同时考虑调速和转向。我的方案是用PWM控制速度GPIO控制方向。L298N驱动模块的IN1/IN2接GPIOENA接PWM信号。核心代码如下// 电机初始化 void Motor_Init(){ GPIO_Init(IN1_PIN, GPIO_Mode_Out_PP); GPIO_Init(IN2_PIN, GPIO_Mode_Out_PP); PWM_Init(ENA_PIN); // 初始化PWM通道 } // 设置转速(-100~100) void Motor_SetSpeed(int8_t speed){ if(speed 0){ GPIO_SetBits(IN1_PIN); GPIO_ResetBits(IN2_PIN); }else{ GPIO_ResetBits(IN1_PIN); GPIO_SetBits(IN2_PIN); speed -speed; } TIM_SetCompare1(TIM2, speed); }按键控制逻辑实现了速度阶梯调整和自动反向KeyNum Key_GetNum(); if(KeyNum 1){ speed 20; if(speed 100){ speed -100; // 到达最大值后反向 } } Motor_SetSpeed(speed);在电机项目中PWM频率选择很重要。太低会导致电机啸叫太高可能使驱动芯片过热。经过测试10-20kHz是比较理想的区间。另外一定要加续流二极管否则关断时的反向电动势会损坏电路。有一次我忘记加二极管烧掉了整个驱动板这个教训记忆犹新。5. 高级技巧与故障排查当多个外设需要PWM时定时器通道复用就派上用场了。STM32的定时器通常有4个通道可以独立配置。我的四足机器人项目就用了TIM3的四个通道控制四个舵机。关键是要正确配置GPIO的复用功能并开启AFIO时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);引脚重映射功能在PCB布线受限时特别有用。比如TIM2_CH1默认在PA0可以重映射到PA15。但要注意重映射后的中断向量和GPIO配置也要相应调整。常见问题排查经验无输出先检查时钟使能、GPIO模式(必须AF_PP)、定时器使能频率不对重新计算PSC和ARR值注意寄存器实际值要-1占空比异常确认CCR值范围不超过ARR极性配置正确抖动严重检查电源稳定性适当增加滤波电容对于更复杂的应用可以结合定时器中断实现PWM序列控制。比如我的RGB灯带项目就用TIM_IT_CCx中断实现WS2812协议。记住在中断服务函数中要清除标志位if(TIM_GetITStatus(TIM2, TIM_IT_CC1)){ // 处理逻辑 TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); }