1. 从一个现象说起为什么调亮度不用调电压很多人第一次接触PWM脑子里冒出来的问题都差不多既然LED亮度跟电压电流有关那我直接用一个可调电阻或者DAC把电压调低一点灯不就暗了吗为什么非要搞一个方波出来一会儿开一会儿关这不是多此一举吗我刚开始玩STM32那会儿也是这么想的。手头有块板子PA8接了个LED想着用DAC输出个1.5V结果灯要么不亮要么亮度根本不受控稍微调一点电压就全亮了。后来才明白LED这种二极管的伏安特性是指数型的电压稍微过一点电流就飙上去了你根本没法用电压去精细控制它的亮度。而电机更离谱你给它降一半电压它要么转不动要么扭矩小得可怜带不动负载。PWM解决的就是这个问题。它不去改变电压的幅值而是改变“通电时间”的比例。你可以把它想象成快速开关灯如果一秒钟内灯亮0.5秒、灭0.5秒只要这个开关速度足够快人眼看到的就是一半亮度。电机也一样通电一半时间等效的平均电压就是一半转速自然就下来了。这个“亮的时间占总时间的比例”就是占空比。所以PWM的本质是用时间维度上的比例控制去等效替代幅值上的连续调节。它绕开了LED和电机非线性特性的坑用数字信号实现了模拟控制的效果。这也是为什么从LED调光到电机调速从舵机控制到开关电源PWM几乎无处不在。这篇文章我会从LED亮度的变化讲起一路拆到直流电机为什么能转起来中间穿插STM32上PA8输出PWM的实际配置、GPIO模式的选型逻辑、以及我在调试过程中踩过的那些坑。不管你是刚点亮第一颗LED的新手还是已经用过定时器但没深究原理的老手应该都能从中找到一些有用的东西。2. PWM的核心原理拆解占空比、频率与等效电压2.1 占空比到底控制了什么占空比Duty Cycle的定义很直白在一个PWM周期内高电平时间与整个周期时间的比值。用公式表示就是占空比 D T_on / (T_on T_off) × 100%假设PWM频率是1kHz周期就是1ms。如果高电平持续0.3ms低电平持续0.7ms占空比就是30%。对于LED来说30%的占空比意味着灯只有30%的时间在通电人眼因为视觉暂留效应感受到的亮度大约就是满亮度的30%。但这里有个关键点占空比控制的是平均功率不是瞬时功率。LED在导通的那段时间里电流还是满额电流只是它导通的时间短了。所以你不能用“平均电流”去算LED的峰值电流否则会低估它的实际工作应力。对于直流电机情况类似但更复杂一些。电机的等效平均电压等于电源电压乘以占空比V_avg V_supply × D比如12V电源50%占空比电机两端的平均电压就是6V。但电机是感性负载电流不会突变PWM的高频开关会让电流在电感的作用下变得平滑最终表现为一个连续的转矩输出。这也是为什么电机PWM频率不能太低否则电流纹波太大电机会抖动、发热甚至发出啸叫。2.2 频率选多少才合适PWM频率的选择本质上是在“开关损耗”和“控制平滑度”之间找平衡。频率太低LED会闪烁电机会抖动频率太高MOSFET或驱动芯片的开关损耗会增加效率下降EMI也会变差。我整理了一个常见应用的PWM频率参考表这些都是实际项目中验证过的范围应用场景推荐频率范围原因说明LED调光1kHz ~ 5kHz高于200Hz人眼就不闪了但太低会有频闪效应5kHz以内开关损耗可忽略直流有刷电机10kHz ~ 20kHz高于20kHz超出人耳听觉范围避免啸叫低于10kHz电流纹波大转矩脉动明显舵机控制50Hz标准舵机要求20ms周期脉宽0.5ms~2.5ms对应角度开关电源100kHz ~ 500kHz频率越高电感电容可以越小但开关损耗急剧上升背光驱动20kHz ~ 100kHz避免音频噪声同时满足调光精度要求注意如果你用PWM驱动LED频率选在200Hz到5kHz之间是比较稳妥的。太低会有可见闪烁太高则可能和驱动芯片的响应速度不匹配导致调光线性度变差。2.3 分辨率与占空比精度占空比能调多细取决于定时器的位数。STM32的通用定时器通常是16位的ARR寄存器最大65535意味着占空比可以分成65536个等级。如果你用8位定时器就只有256个等级调光时可能会看到明显的台阶感。举个例子假设PWM频率是1kHz定时器时钟是72MHz那么ARR 72MHz / 1kHz - 1 71999这个值在16位范围内最大65535所以1kHz下你没法用72MHz直接分频得到需要先做预分频。比如预分频设为1定时器时钟变成36MHzARR 36000 - 1 35999这样分辨率就是36000级足够细腻了。实际调光时人眼对亮度的感知是非线性的低占空比区域的亮度变化比高占空比区域更敏感。所以如果你要做平滑的调光效果最好用伽马校正或者查表法而不是线性地改CCR值。这个坑我在做呼吸灯的时候踩过线性调的时候总觉得低亮度段变化太快高亮度段又几乎没变化。3. STM32上PA8输出PWM的完整实操3.1 为什么选PA8定时器通道的映射关系STM32的PWM输出不是随便哪个GPIO都能做的它必须连接到定时器的输出比较通道。以STM32F103为例PA8对应的是TIM1的通道1TIM1_CH1。TIM1是一个高级定时器支持互补输出、死区插入和刹车功能用来做PWM输出绰绰有余。选择PA8有几个实际考虑首先它位置方便很多最小系统板都把LED或者排针引到PA8其次TIM1是高级定时器时钟源独立配置灵活再者PA8的复用功能明确不容易和其他外设冲突。但这里有个容易忽略的点PA8默认是普通GPIO要输出PWM必须配置为复用推挽输出模式。如果你把它配成普通推挽输出然后试图用软件去翻转电平那出来的不是PWM只是普通的GPIO翻转频率和精度都差得远。3.2 GPIO模式选择复用推挽还是普通推挽STM32的GPIO有8种工作模式很多人刚接触时会被这8种模式搞晕。我简单梳理一下和PWM输出相关的几种模式适用场景是否适合PWM输出普通推挽输出软件控制电平翻转不适合无法连接定时器复用推挽输出外设功能输出PWM、UART TX等适合PWM标准配置复用开漏输出需要线与或电平转换一般不用于PWM除非有特殊电平需求模拟输入ADC采样不适合所以PA8输出PWM必须配置为复用推挽输出GPIO_Mode_AF_PP。这个配置告诉芯片这个引脚的控制权交给定时器外设而不是GPIO数据寄存器。实操心得如果你配置完PWM发现引脚没输出第一件事就是检查GPIO模式是不是设成了复用推挽。我见过太多人在这里卡住代码逻辑全对就是模式选错了。3.3 定时器配置的完整流程下面以STM32F103、系统时钟72MHz、PA8输出1kHz、50%占空比为例走一遍完整配置。我用的是标准外设库HAL库的逻辑类似只是函数名不同。第一步开启时钟。TIM1挂在APB2总线上PA8也挂在APB2RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 | RCC_APB2Periph_GPIOA, ENABLE);第二步配置GPIO为复用推挽输出GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure);第三步配置定时器时基。这里需要算一下预分频和ARRTIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Prescaler 72 - 1; // 72MHz / 72 1MHz TIM_TimeBaseStructure.TIM_Period 1000 - 1; // 1MHz / 1000 1kHz TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure);预分频设为71定时器时钟变成72MHz / 72 1MHz。ARR设为999计数到1000溢出所以PWM频率是1MHz / 1000 1kHz。这个计算过程很关键很多人直接抄代码但不知道数值怎么来的换个频率就不会算了。第四步配置PWM输出通道TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 500; // 50%占空比 TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);TIM_Pulse设为500ARR是999所以占空比是500/1000 50%。TIM_OCMode_PWM1的意思是计数器小于CCR时输出高电平大于等于CCR时输出低电平。模式2则相反。第五步开启定时器TIM_Cmd(TIM1, ENABLE);对于高级定时器TIM1还需要额外开启PWM输出使能TIM_CtrlPWMOutputs(TIM1, ENABLE);这个函数很多人会漏掉结果就是定时器在跑但引脚死活没波形。高级定时器有刹车和死区控制默认输出是关闭的必须显式使能。3.4 PWM模式1和模式2的区别TIM_OCMode_PWM1和TIM_OCMode_PWM2的区别简单说就是输出极性相反PWM模式1计数器 CCR时输出有效电平高电平计数器 CCR时输出无效电平低电平PWM模式2计数器 CCR时输出无效电平低电平计数器 CCR时输出有效电平高电平如果你把CCR设为500ARR设为999模式1下前500个计数输出高后500个输出低占空比50%。模式2下前500个输出低后500个输出高占空比也是50%但相位差了180度。实际用的时候模式1和模式2的选择取决于你的驱动电路。比如你用低电平驱动的LED可能就需要模式2或者设置输出极性为低。这个细节在调电机驱动的时候特别重要因为有些驱动芯片是高电平使能有些是低电平使能搞反了电机就不转。4. 从LED到电机PWM调速的底层逻辑4.1 LED亮度变化的本质LED的亮度取决于流过它的电流。在PWM驱动下电流是脉冲式的但人眼有视觉暂留效应感受到的是平均亮度。假设LED满电流是20mAPWM占空比50%那么平均电流就是10mA亮度大约是一半。但这里有个非线性问题LED的亮度与电流并不是严格线性的低电流时效率会下降。而且人眼对亮度的感知近似对数关系所以50%的占空比看起来可能不止50%的亮度。这就是为什么做呼吸灯时线性改变CCR值会感觉亮度变化不均匀。我实测过一个简单的伽马校正表用查表法把线性占空比映射到感知亮度效果比直接线性调好很多。具体做法是预先算好一个256项的数组每一项是校正后的CCR值调光时直接查表。4.2 直流电机为什么能转起来直流电机的转动原理是电磁力通电导体在磁场中受到安培力产生转矩。转矩大小与电流成正比转速与反电动势有关。当你用PWM驱动电机时电机两端的电压是脉冲式的但电机绕组是感性负载电流不会突变。在PWM高电平期间电源通过驱动管给电机绕组充电电流上升低电平期间绕组通过续流二极管放电电流下降。如果PWM频率足够高电流纹波很小电机感受到的就是一个平滑的等效直流电压。这个等效电压决定了电机的转速。占空比越大等效电压越高转速越快。这就是PWM调速的基本原理。但实际调试时你会发现占空比低于某个阈值时电机根本不转只是发出嗡嗡声。这是因为占空比太低等效电压不足以克服电机的静摩擦力和负载转矩。这个阈值取决于电机本身和负载情况一般有刷电机在10%~20%左右。4.3 驱动电路不能省STM32的GPIO输出电流有限通常只有20mA左右根本驱动不了直流电机。电机启动瞬间的电流可能达到几安培直接接GPIO会烧掉芯片。所以必须加驱动电路常见的有MOSFET驱动适合大电流场合效率高但需要注意栅极驱动电压和续流二极管H桥驱动芯片如DRV8323、L298N等支持正反转和调速集成度高继电器驱动只能开关不能调速不适合PWM场合我用过DRV8323驱动无刷电机也用过简单的MOSFET驱动有刷电机。MOSFET方案成本低但需要自己搭续流回路PCB布局不好容易烧管。H桥芯片贵一点但保护功能完善调试省心。注意不管用哪种驱动电机两端一定要加续流二极管否则PWM关断瞬间电感产生的反电动势会击穿驱动管。这个坑我烧过好几个MOSFET才记住。4.4 背光干扰显示的问题热词里有个“pwm背光 干扰 显示”这个我在做ILI9341屏幕项目时遇到过。屏幕背光用PWM调光结果发现显示内容有横纹干扰。排查下来是PWM频率和SPI时钟产生了拍频或者PWM的开关噪声通过电源耦合到了显示电路。解决办法有几个一是提高PWM频率到20kHz以上超出音频范围也避开SPI的敏感频段二是在背光电源上加LC滤波减少纹波三是把PWM走线和SPI走线分开避免平行走线。我最后用的是20kHz加RC滤波横纹基本消失了。5. 常见问题与排查技巧实录5.1 PWM没输出的排查清单现象可能原因排查方法引脚完全没波形GPIO模式没设复用检查GPIO_InitStructure.GPIO_Mode引脚有波形但频率不对预分频或ARR算错重新计算定时器时钟和周期高级定时器无输出没开PWM输出使能调用TIM_CtrlPWMOutputs占空比不可调CCR没更新或预装载没使能检查TIM_OCPreloadConfig波形抖动严重时钟源不稳定或干扰检查晶振和电源滤波5.2 电机调速的常见坑第一个坑是死区时间。如果你用H桥驱动上下管不能同时导通否则电源直接短路。STM32的高级定时器支持死区插入配置TIM_DeadTimeConfig可以自动插入死区避免直通。第二个坑是续流回路。PWM关断时电机绕组的电流需要继续流动如果没有续流二极管电流会通过驱动管的反并联二极管流回电源可能导致电源电压升高。所以电源端要加足够的滤波电容。第三个坑是低频啸叫。PWM频率落在人耳听觉范围内时电机会发出啸叫。把频率提到20kHz以上就能解决但要注意驱动管的开关损耗会增加。5.3 调试工具的选择调试PWM最直接的工具是示波器。没有示波器的话可以用逻辑分析仪几十块钱的就能看波形和频率。我早期用过一个USB逻辑分析仪配合开源软件看PWM波形足够了。另外STM32的定时器支持输入捕获可以用另一个定时器去测量PWM的频率和占空比这样不用外部工具也能验证输出是否正确。这个方法在调试遥控信号解码时特别有用。6. 一些实操心得与扩展思路PWM这个东西原理不复杂但细节很多。我做了这么多项目最大的体会是参数计算要自己动手算一遍不要直接抄。抄来的代码换个芯片、换个频率就跑不起来因为你不知道那些数值是怎么来的。另外PWM的应用远不止LED和电机。舵机控制、开关电源、DAC模拟输出、音频放大甚至红外遥控发射底层都是PWM。你把定时器的PWM模式吃透了这些应用都能触类旁通。最后分享一个小技巧如果你需要多路PWM输出优先选同一个定时器的不同通道这样它们共享同一个时基频率一致相位关系固定。如果选不同定时器频率可能会有微小差异长时间运行会累积相位误差。这个在做RGB调光或者三相电机驱动时特别重要。
阅读完成 · 觉得有帮助?