1. 从一个反直觉的现象说起LED 调光不是降压电机调速也不是降流很多人第一次接触 PWM 的时候脑子里会冒出一个很自然的想法要让 LED 变暗把电压调低不就行了要让电机转慢把电流减小不就行了这个思路在电阻分压的世界里确实成立但一旦你真正动手去调 LED 或者驱动直流电机就会发现事情完全不是这么回事。我最早做 LED 调光的时候试过用可调电阻串在 LED 回路里。灯确实暗下来了但电阻烫得厉害而且亮度调节范围很窄稍微调过头灯就灭了。后来换成用单片机 DAC 输出一个可变的模拟电压去驱动 LED理论上电压从 3.3V 降到 1.8V亮度应该平滑变化。实测下来LED 在某个电压阈值附近突然亮起来再往上加电压亮度变化又很迟钝整个调光曲线非常难看。更麻烦的是DAC 输出的模拟电压在长导线上很容易受干扰亮度会莫名其妙地抖动。直到我用上 PWM才真正理解了调光和调速的本质。PWM 不去改变电压的幅值而是改变电压开和关的时间比例。LED 在开的时候是全亮在关的时候是全灭但只要开关速度足够快人眼就分辨不出来看到的是平均亮度。电机也是一样全压供电时扭矩最大断电时靠惯性滑行快速切换之后电机感受到的是平均电压转速就跟着变了。这个思路的妙处在于功率器件始终工作在完全导通或完全截止两个状态不存在中间的高功耗区间。用可调电阻调光多余的能量全变成热耗散掉了用 PWM 调光开关管要么几乎不耗电要么几乎不压降效率高得离谱。这就是为什么现在几乎所有的 LED 驱动芯片、电机驱动芯片、甚至开关电源底层都在用 PWM。关键词里提到的 STM32、GPIO、定时器、CCU6、DRV8323、步进电机、伺服电机其实都围绕同一个核心问题怎么用数字引脚产生一个频率和占空比都可控的方波然后用这个方波去控制功率器件的通断。这篇文章就从这个最根本的问题出发把 PWM 调速的来龙去脉讲清楚顺带把 STM32 上配置 PWM 的实操细节、常见坑点、以及从 LED 到电机的完整链路都过一遍。2. 占空比、频率、平均电压PWM 三个参数到底谁在起作用2.1 占空比决定平均效果但不是简单的线性关系PWM 波形最直观的描述就是占空比也就是一个周期内高电平时间占总时间的比例。假设供电电压是 12V占空比 50%那么负载两端的平均电压就是 6V。这个平均是理解 PWM 的第一把钥匙。但这里有个容易被忽略的细节平均电压只是从能量角度看的等效值负载实际承受的是一连串的方波脉冲。对于纯电阻负载比如一个加热丝平均电压确实能直接换算成功率。但对于 LED亮度大致和平均电流成正比而 LED 的伏安特性是非线性的所以占空比和亮度之间并不是完美的直线关系。对于直流电机转速和平均电压的关系也受负载转矩、反电动势、摩擦力的影响空载和带载时的调速曲线完全不一样。我在实际调 LED 背光的时候发现占空比从 0% 加到 10%亮度变化非常明显但从 80% 加到 100%肉眼几乎看不出差别。这是因为人眼对亮度的感知近似对数关系同时也和 LED 本身的非线性有关。所以如果你要做精细的调光体验最好在软件里做一次伽马校正把线性占空比映射成感知上均匀的亮度曲线。2.2 频率选低了会闪选高了会发热中间有个甜区PWM 频率的选择是另一个关键决策。频率太低LED 会肉眼可见地闪烁电机也会听到明显的啸叫频率太高开关损耗会上升驱动芯片可能发热EMI 也会变差。对于 LED 调光人眼的闪烁融合频率大概在 60Hz 到 100Hz 之间。理论上超过 100Hz 人眼就感觉不到闪烁了但实际使用中如果 LED 或者摄像头在移动低频 PWM 会产生频闪条纹。我一般会把 LED 调光频率设在 1kHz 以上这样既不会闪也不会让驱动电路太吃力。有些高端背光会用到 20kHz 以上主要是为了避开音频范围防止电感或者陶瓷电容产生可听噪声。对于直流电机PWM 频率的选择更讲究。有刷直流电机在低频 PWM 下比如 1kHz 以下会听到明显的嗡嗡声因为电机绕组在脉冲电流下会产生机械振动。频率提高到 15kHz 到 20kHz 以上就超出了人耳听觉范围电机运行会安静很多。但频率太高也有代价MOSFET 的开关损耗和栅极驱动损耗都会增加如果驱动能力不足波形会变形电机反而转得不稳。下面这张表是我在实际项目中总结的常见负载对应的 PWM 频率范围可以作为选型参考负载类型推荐频率范围主要考虑因素LED 指示灯1kHz - 5kHz避免可见闪烁兼顾驱动简单LED 背光20kHz 以上避开音频噪声防止摄像头频闪有刷直流电机15kHz - 25kHz超出听觉范围减少啸叫步进电机20kHz - 40kHz配合细分驱动减少振动加热丝1Hz - 100Hz热惯性大低频即可开关电源100kHz - 1MHz减小电感体积提高动态响应2.3 平均电压只是表象电感电流连续性才是电机调速的核心很多人用平均电压来解释电机调速这个说法在宏观上没错但容易让人忽略一个关键问题电机绕组是电感性的电流不能突变。PWM 加在电机上电流并不是方波而是三角波或者近似直流。当 PWM 频率足够高且电机电感量足够大时电流纹波很小电机感受到的几乎是平滑的直流电流转速和占空比近似线性。但如果频率太低或者电感太小电流会断续电机的转矩会脉动低速时甚至会出现一顿一顿的现象。我在驱动一个小型有刷直流电机时一开始用 1kHz 的 PWM电机低速时抖得很厉害还伴随刺耳的噪声。后来把频率提到 20kHz同时确认驱动 MOS 的栅极电阻没有过大电机立刻就顺滑了。这个经历让我明白PWM 调速不是简单地给一个方波就行频率、驱动能力、负载特性三者必须匹配。3. 从 GPIO 到定时器STM32 上产生 PWM 的完整链路3.1 为什么不能直接用 GPIO 翻转来产生 PWM初学者最容易想到的办法是用 GPIO 配合延时函数手动把引脚拉高、延时、拉低、再延时循环往复。这个方法在 51 单片机上很常见但在 STM32 上用来做调速就力不从心了。问题出在 CPU 占用和精度上。手动翻转 GPIO 需要 CPU 全程参与一旦程序里有其他任务比如按键扫描、串口通信PWM 的周期就会抖动。而且用软件延时产生的 PWM 频率很低通常只有几百赫兹到几kHz占空比调节的分辨率也很粗糙。对于 LED 调光可能勉强够用但对于电机调速尤其是需要高频低噪声的场合完全不够看。STM32 的正确做法是使用定时器的 PWM 输出模式。定时器硬件自己计数、自己比较、自己翻转引脚CPU 只需要在初始化时配置好寄存器和占空比之后就可以去干别的事情。这就是硬件 PWM 和软件 PWM 的本质区别。3.2 定时器是怎么顺便输出 PWM 的STM32 的通用定时器和高级定时器都带有 PWM 输出功能。以最常见的 TIM1、TIM2、TIM3、TIM4 为例每个定时器有四个独立通道可以分别输出四路 PWM。定时器的核心是一个计数器它按照设定的时钟频率从 0 数到自动重装载值ARR然后归零重新开始。这个 ARR 决定了 PWM 的周期也就是频率。每个通道还有一个比较寄存器CCR当计数器的值小于 CCR 时输出高电平大于 CCR 时输出低电平。这样CCR 和 ARR 的比值就决定了占空比。用公式表示就是PWM 频率 定时器时钟 / ((ARR 1) * (PSC 1))占空比 CCR / (ARR 1)其中 PSC 是预分频器。举个例子如果定时器时钟是 72MHz想要 20kHz 的 PWM 频率可以设 PSC 0ARR 3599因为 72MHz / 3600 20kHz。如果想让占空比分辨率更高可以把 PSC 设大一点ARR 设得更大比如 PSC 71ARR 49999这样频率还是 20kHz但占空比可以细调 50000 个档位。这里有个经验ARR 越大占空比分辨率越高但频率会降低PSC 越大频率越低但分辨率不变。实际配置时先确定需要的频率再在频率和分辨率之间做权衡。对于 LED 调光1000 级分辨率足够了对于电机调速如果要做闭环控制可能需要 2000 级以上。3.3 PA8 输出 PWM 的完整配置过程PA8 是 STM32 上很常用的一个引脚它对应 TIM1 的通道 1。下面以 STM32F103 为例把 PA8 输出 PWM 的配置过程完整走一遍。第一步是使能时钟。TIM1 是高级定时器挂在 APB2 总线上PA8 作为 GPIO 也挂在 APB2 上所以需要同时使能 GPIOA 和 TIM1 的时钟。RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE);第二步是配置 GPIO。PA8 要设置为复用推挽输出模式因为 PWM 信号是由定时器外设产生的不是普通 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);第三步是配置定时器时基。这里设定 PSC 和 ARR决定 PWM 频率。TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period 3599; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure);第四步是配置 PWM 输出通道。这里要设置 PWM 模式、输出极性、使能输出。TIM_OCInitTypeDef TIM_OCInitStructure; TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 1800; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable);第五步是使能定时器。高级定时器 TIM1 还需要额外使能主输出否则引脚不会有波形。TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE);这套流程跑通之后PA8 上就会出现 20kHz、占空比 50% 的方波。如果你用的是 STM32CubeMX这些配置都可以在图形界面里点出来生成的 HAL 库代码结构类似只是函数名换成了 HAL_TIM_PWM_Init 和 HAL_TIM_PWM_ConfigChannel。3.4 PWM 模式 1 和模式 2 的区别以及什么时候该用哪个STM32 的 PWM 输出有两种模式PWM 模式 1 和 PWM 模式 2。很多教程只提模式 1导致有人遇到为什么我的波形反了的时候一头雾水。简单说PWM 模式 1 是计数器小于 CCR 时输出有效电平PWM 模式 2 是计数器小于 CCR 时输出无效电平。配合输出极性设置可以实现四种组合。实际使用中模式 1 加高极性是最常见的配置输出的是正逻辑 PWM占空比越大高电平时间越长。那什么时候需要模式 2 呢一种情况是驱动低电平有效的负载比如某些共阳极 LED 或者低边开关。另一种情况是做互补输出时需要两路波形严格反相。还有就是在调整死区时间的时候模式的选择会影响死区插入的位置。我个人的习惯是先用模式 1 加高极性把电路跑通如果发现逻辑反了优先检查硬件驱动电路的电平要求而不是急着改 PWM 模式。因为改模式会让代码可读性变差而硬件上的反相器或者上拉电阻往往更直观。4. LED 调光实战从闪灯到平滑调光的几个关键坎4.1 LED 驱动方式选错了PWM 再好也白搭PWM 调光的效果很大程度上取决于 LED 的驱动电路。常见的驱动方式有三种直接 GPIO 驱动、三极管或 MOS 低边驱动、专用 LED 驱动芯片。直接 GPIO 驱动最简单但 STM32 的 GPIO 输出电流有限通常只有 20mA 左右只能点亮小功率指示灯。而且 GPIO 有内阻多个 LED 并联时亮度不一致。我早期用 GPIO 直接驱动 LED发现不同颜色的 LED 亮度差异很大后来才知道是因为不同颜色的正向压降不同导致电流差异。三极管或 MOS 低边驱动是最常用的方案。PWM 信号加到三极管基极或者 MOS 栅极控制 LED 回路的通断。这里有个关键点三极管是电流驱动基极需要限流电阻MOS 是电压驱动栅极需要充放电回路。如果 PWM 频率较高MOS 的栅极电容会导致波形边沿变缓需要选择合适的栅极电阻。专用 LED 驱动芯片比如恒流驱动 IC内部集成了功率管和电流检测PWM 信号直接输入使能脚或者 FB 脚。这类芯片的好处是电流恒定亮度一致性好而且支持较高的 PWM 频率。关键词里提到的LED 驱动器芯片 FB CS 脚调整说的就是通过反馈脚或者电流检测脚来设定 LED 电流PWM 则用来调节平均电流。4.2 为什么 51 单片机驱动 LED 时不能采用输出高电平的方式这是一个很经典的问题关键词里也提到了。51 单片机的 GPIO 是准双向口内部结构决定了它拉低能力强、拉高能力弱。输出低电平时引脚可以吸收十几毫安电流输出高电平时只能提供很微弱的电流通常不足以点亮 LED。所以用 51 单片机驱动 LED标准做法是低电平点亮LED 阳极接 VCC阴极通过限流电阻接 GPIO。GPIO 输出低电平LED 导通输出高电平LED 熄灭。如果反过来接LED 要么不亮要么亮度极低。STM32 的 GPIO 是真正的推挽输出拉高和拉低能力对称所以高电平点亮和低电平点亮都可以。但在实际设计中我仍然倾向于低边驱动因为很多驱动芯片和 MOS 管的低边接法更简单而且共地设计更方便。4.3 调光曲线不平滑试试伽马校正前面提到占空比和 LED 亮度不是线性关系。如果你直接用线性占空比去调光会发现低亮度区域变化太快高亮度区域变化太慢。解决方法是做伽马校正把线性占空比映射成感知上均匀的亮度值。具体做法是建立一个查找表把 0 到 100 的亮度等级映射到 0 到 ARR 的占空比。映射公式可以用指数函数duty (uint16_t)(ARR * pow(brightness / 100.0, 2.2));其中 2.2 是常用的伽马值可以根据实际观感微调。如果单片机资源紧张也可以用分段线性近似把曲线分成几段每段用不同的斜率。我在做一款台灯调光的时候一开始没做伽马校正用户反馈低亮度调不了一调就太亮。加上伽马校正之后调光手感立刻好了很多从最暗到最亮整个过程都很顺滑。4.4 PWM 背光干扰显示的问题怎么排查关键词里有个PWM 背光干扰显示这是液晶屏背光调光时经常遇到的问题。现象是屏幕出现横向条纹、闪烁或者摄像头拍照时出现滚动条纹。根本原因通常是 PWM 频率和液晶屏的刷新率、行频产生了拍频或者 PWM 的快速边沿通过电源和地耦合到了显示信号上。排查的时候可以分几步走先确认干扰是传导还是辐射。把背光电源和显示电源分开供电如果干扰消失说明是电源耦合。如果还在可能是辐射耦合或者地弹。然后调整 PWM 频率。把频率从 1kHz 改到 20kHz或者反过来观察条纹是否变化。如果条纹滚动速度变了说明是拍频问题需要把 PWM 频率设成刷新率的整数倍或者远离刷新率。最后检查 PCB 布局。PWM 走线要远离模拟信号和高速信号背光驱动的地回路要短而粗必要时加磁珠或者 RC 滤波。我在一个项目里遇到背光干扰 SPI 屏的问题最后发现是背光 MOS 的源极和屏的地共用了一段细走线改成分开走线并在单点接地之后干扰就消失了。5. 直流电机调速从开环到闭环PWM 只是第一步5.1 有刷直流电机的 PWM 驱动电路怎么搭有刷直流电机的 PWM 驱动核心是一个 H 桥电路。H 桥由四个开关管组成通过控制对角开关管的通断可以实现正转、反转、刹车、滑行四种状态。PWM 信号加到其中一个对角管上另一个对角管常通就可以调速。常见的 H 桥驱动芯片有 L298N、DRV8833、DRV8323 等。关键词里提到的 DRV8323 是 TI 的一款三相栅极驱动器主要用于无刷电机但也支持有刷电机的半桥模式。这类芯片内部集成了栅极驱动、电流检测、保护电路使用起来比分立 MOS 方便很多。搭电路的时候有几个坑要注意。第一是死区时间H 桥的上下管不能同时导通否则会直通短路。专用驱动芯片内部通常有死区插入但如果用分立 MOS 自己搭必须在软件或者硬件上保证死区。第二是续流回路电机关断时电感电流需要续流否则会产生高压尖峰击穿开关管。H 桥的体二极管或者外部续流二极管就是干这个的。第三是电源去耦电机启动和换向时电流变化剧烈电源上要并大容量电解电容和陶瓷电容。5.2 开环调速为什么低速不稳闭环怎么补开环 PWM 调速就是给定一个占空比电机转到什么速度就是什么速度。空载时转速和占空比大致成正比但一旦加上负载转速就会下降。负载变化越大转速波动越明显。要解决这个问题需要引入闭环控制。最常见的是用编码器或者霍尔传感器测速把实际转速反馈给控制器控制器根据误差调整 PWM 占空比。这就是经典的 PID 控制。我在做一个小车项目时一开始用开环调速小车直行时左右轮转速不一致走不了直线。后来加了编码器做闭环用增量式 PID 调了两天参数小车终于能走直线了。PID 参数整定的经验是先调 P让系统有响应但不振荡再加 I消除稳态误差最后加 D抑制超调。如果电机响应慢P 可以大一点如果电机容易振荡D 要加一点。5.3 步进电机和伺服电机的 PWM 控制有什么不同步进电机和伺服电机的控制方式和有刷直流电机完全不同。步进电机是按步转动的PWM 通常用来做细分驱动也就是用 PWM 控制绕组电流的大小让电机在两个整步之间平滑过渡。关键词里提到的五线四相步进电机 STM32就是典型的步进电机应用。伺服电机内部有闭环控制器外部只需要给位置指令或者速度指令。PWM 在伺服系统里通常用来产生指令信号比如航模舵机就是用 PWM 的脉宽来表示角度1ms 对应最小角度2ms 对应最大角度。工业伺服更多用总线通信比如 CAN 或者 EtherCATPWM 反而用得少。关键词里还有STM32 控制伺服电机 485这说的是用 RS485 总线发送指令控制伺服驱动器。这种方案在工业现场很常见因为 RS485 抗干扰能力强传输距离远可以挂多个从站。5.4 PWM 故障保护别等电机烧了才想起来PWM 驱动电机尤其是大功率电机必须考虑故障保护。常见的故障有过流、过压、欠压、过热、堵转。过流保护最简单的方法是在电源回路上串一个采样电阻用 ADC 采集电压超过阈值就关闭 PWM。专用驱动芯片通常有电流检测输出可以直接接到单片机的比较器或者 ADC。STM32 的高级定时器 TIM1 和 TIM8 有刹车输入功能一旦刹车引脚被触发硬件会立刻关闭 PWM 输出不需要 CPU 干预。这个功能在过流保护里非常有用响应速度比软件快得多。堵转保护也很重要。电机堵转时电流会飙升到额定电流的好几倍如果保护不及时电机和驱动管都会烧。我见过一个案例一个小车电机被卡住操作者没注意几秒钟之后驱动 MOS 就冒烟了。后来加了电流检测和刹车功能再遇到堵转驱动器会立刻切断输出并报警。6. 那些年我在 PWM 上踩过的坑和总结出的经验6.1 占空比设了但没输出先查这三个地方PWM 配置完了示波器一测没波形这是新手最常遇到的问题。我总结下来九成以上的原因出在三个地方。第一是高级定时器的主输出没使能。TIM1、TIM8、TIM15、TIM16、TIM17 这些高级定时器除了常规的 TIM_Cmd 之外还需要调用 TIM_CtrlPWMOutputs 使能主输出。很多人只记得 TIM_Cmd结果引脚一直没波形。第二是 GPIO 模式配错了。PWM 输出引脚必须配成复用推挽如果配成普通推挽输出定时器的信号根本到不了引脚。如果配成复用开漏没有外部上拉的话也测不到高电平。第三是时钟没使能或者引脚映射不对。STM32 的定时器通道和 GPIO 引脚之间有固定的映射关系有些引脚还可以通过重映射切换。如果引脚选错了或者重映射没配置波形自然出不来。6.2 占空比改了但电机没反应可能是频率太低或者驱动不足有时候 PWM 波形在示波器上看是正常的但电机就是不转或者转速不变。这种情况通常不是 PWM 本身的问题而是驱动电路的问题。一种可能是 PWM 频率太低电机电流断续严重平均转矩不够。尤其是小电感电机低频 PWM 下电流纹波很大有效值反而下降。把频率提高到 15kHz 以上往往能明显改善。另一种可能是驱动管的栅极驱动能力不足。MOS 的栅极电容在每次开关时都需要充放电如果驱动电流太小开关过程会变慢MOS 工作在线性区的时间变长功耗增加甚至发热烧毁。这时候需要减小栅极电阻或者加专用栅极驱动芯片。还有一种可能是电源功率不够。电机启动瞬间电流很大如果电源限流或者内阻大电压会被拉低PWM 占空比再大也没用。这时候需要换更大功率的电源或者在电源端并大电容。6.3 用示波器看 PWM 波形时要注意的细节调试 PWM 的时候示波器是最好的朋友。但用示波器看 PWM 也有讲究。首先要注意探头的地线。示波器探头的地线夹要尽量短最好用弹簧地针否则长地线会引入振铃让你误以为波形有问题。尤其是在看高频 PWM 的边沿时地线环路的影响非常明显。其次要注意带宽限制。如果示波器带宽不够高频 PWM 的边沿会被平滑掉看起来上升沿很慢实际上可能是探头或者示波器的问题不一定是电路的问题。最后要注意测量点。测 GPIO 引脚和测负载两端的波形可能完全不同。GPIO 引脚上是干净的方波负载两端可能因为电感效应出现尖峰和振铃。判断驱动电路是否正常要看负载两端的波形而不是只看引脚。6.4 从 LED 到电机PWM 调速的通用思路回过头来看PWM 调速的核心逻辑其实很简单用数字方式控制平均功率。LED 和电机的区别只在于负载特性不同一个主要是电阻性加非线性一个是电感性加机械惯性。掌握了这个核心剩下的就是具体问题具体分析。LED 调光关注的是闪烁、亮度曲线、驱动方式电机调速关注的是频率、电流连续性、闭环控制、故障保护。但底层都是同一个定时器同一个占空比寄存器同一个开和关的哲学。我在实际项目中越来越觉得PWM 是嵌入式开发里性价比最高的技术之一。它不需要昂贵的 DAC不需要复杂的模拟电路只需要一个定时器和几个寄存器就能实现精确的功率控制。从调一个 LED 的亮度到控制一台电机的转速再到开关电源的稳压背后都是同一个原理在支撑。如果你刚开始学 STM32我建议从 PA8 输出 PWM 点亮一个 LED 开始把频率、占空比、模式这几个参数都改一改用示波器看一看波形变化。然后接一个电机驱动模块感受一下占空比和转速的关系。最后加上编码器和 PID做一个能稳速的小系统。这一套走下来PWM 的里里外外你就都摸透了。
阅读完成 · 觉得有帮助?