
很多玩单片机的朋友第一次接触 PWM往往是从一盏 LED 开始的。手上只有高电平和低电平想让灯暗一点脑子里的第一反应是“把电压调小”但单片机引脚输出的要么是 0 要么是 3.3V于是有人开始串电阻有人用 IO 口直接带负载折腾一圈发现亮度是变了可原理还是模模糊糊。等到做直流电机调速需要让电机从静止转到某个转速又发现“调电压”这条路同样绕不过 PWM。这篇文章就聊聊 PWM 为什么能调速从 LED 亮度变化到直流电机转起来中间到底发生了什么。无论你是刚学 51、STM32还是已经在用 HC32F460 这类国产单片机做产品把 PWM 的占空比、频率、模式和驱动能力搞清楚后面写代码、调硬件都会顺手很多。我会带上一些实际配置过程、参数计算和踩坑记录手头有板子的可以直接跟着试。1. PWM 到底在调什么从一盏 LED 灯说起1.1 人眼看到的“亮度”其实是时间上的平均PWM 的全称是脉宽调制简单说就是固定频率改变高电平持续的时间。一个周期 T 内高电平持续时间为 t_on占空比 D t_on / T用百分比表示。对纯电阻负载来说平均功率等于 D 乘以峰值功率也就是 D 乘以 V²/R。LED 不是纯电阻它的伏安特性和颜色、温度都有关系但只要开关频率足够快高于人眼闪烁融合频率通常 100Hz 以上人眼就不会感知到亮度突变而是感知到周期内的平均光输出。LED 的光通量和电流在很大范围内近似线性所以占空比从 1% 到 99%几乎能对应连续的亮度变化。这就是 PWM 调光的基本原理不是改变瞬时功率而是改变单位时间内的能量供给。很多初学者把 PWM 理解成“数字模拟电压”其实不够准确。PWM 输出的是完整的开关波形负载上要么是满电压要么是零电压只是两者的比例在变。LED 的亮、电机的转、蜂鸣器的响这些宏观结果都是时间平均效应叠加出来的。1.2 为什么不用模拟调压LED 的窄电压窗口和色偏问题有人会问既然平均电压决定亮度那把电压从 3.3V 降到 2V 不也一样吗对 LED 来说这条路很难走。LED 正向导通电压有一个比较窄的窗口比如黄绿色 LED 大约 2V蓝色、白色 LED 大约 3V。给它 2.5V可能电流很小给它 3.1V电流可能已经窜到几十毫安。有经验的硬件工程师都清楚LED 要稳定工作靠的是限流或恒流而不是精密调压。调压过程中你还会发现改变 LED 电流时色温、色坐标会漂移尤其白光 LED 低电流时偏黄高电流时偏蓝。PWM 调光则不同它让 LED 始终工作在同一个电流值由限流电阻或恒流驱动决定只是用开关方式快速通断。亮度变化时LED 的色坐标保持稳定寿命也更好。所以现在从手电筒到 LED 大屏调光方案基本默认用 PWM而不是模拟调节电流。这也是为什么你看到的 LED 芯片参数表里通常有“DIM 引脚”“PWM 调光”这类描述而不是建议你串一个电位器去调亮度。1.3 51 单片机驱动 LED为什么不能用输出高电平的方式这个问题在初学者群里几乎每周都出现。传统 51 单片机的 IO 口是准双向结构内部上拉电阻比较弱输出高电平时驱动能力非常有限输出低电平时引脚能灌入的电流往往比拉出的电流大不少。典型的老手册上IOL 能到十几毫安IOH 却只有几十到几百微安不同型号差异很大。如果你用高电平去点亮 LED10mA 可能把引脚电压拉垮还会影响其他引脚。所以最常见的做法是LED 正极接 VCC负极经过限流电阻接到 IO 口IO 输出低电平时点亮。这个细节和 PWM 调光直接相关。用 51 的 IO 口做软件 PWM 时如果采用低电平点亮你会发现“点亮时间”对应的是 IO 口的低电平时间而不是高电平时间。比如想让 LED 亮度为 70%周期 10ms点亮 7ms、熄灭 3ms用低电平点亮时IO 口输出低电平那 7ms 才是亮。很多新手在这里调反把占空比从 0 调到 100灯却从全亮变成全灭。解决办法一是把极性取反二是直接选支持硬件 PWM 和极性可配的 MCU。1.4 LED 闪灯驱动芯片和大屏灰度其实也是 PWM热词里还有“led 闪灯驱动芯片”和“led 大屏安装”这里顺带提一下。LED 闪灯驱动芯片内部的核心模块之一就是 PWM 发生器和恒流源通过内部寄存器控制每个通道的开关时间实现呼吸灯、流水灯、闪烁效果。LED 大屏就更典型了每个像素由红绿蓝三颗灯芯组成灰度等级靠的是在极短的时间片内用不同宽度的 PWM 脉冲驱动灯芯。常见做法是“扫描恒流PWM 灰度”利用人眼视觉暂留让 16 位灰度的画面看起来连续自然。所以 PWM 在 LED 领域不是可有可无的“调亮功能”而是整个显示系统的底层机制。理解好这一点再看后面电机调速思路就通了PWM 只是把能量分时投给负载的一种通用手段负载是 LED、电阻还是电机宏观表现不同但底层逻辑一致。2. 从 LED 搬到直流电机调速的本质没变2.1 直流电机的“阻力”反电动势和电感直流电机和 LED 看着差别很大但 PWM 能调速的原因是一样的让功率管周期性导通负载得到的平均功率可控。电机内部有电枢电阻 Ra 和电枢电感 La转子转动时还会产生反电动势 E Ke × ω。当 PWM 导通时电源电压加在电机上电流上升产生转矩PWM 关断时电感和续流二极管维持电流继续流电机依靠惯性继续转。只要开关频率远大于电机的电气时间常数电枢电流就基本连续电机两端平均电压近似等于 D × VCC。平均电压升高电枢电流平均值和转速都会上升平均电压降低转速下降。注意它不是完全的线性关系电机存在静摩擦和死区电压占空比太小时可能不转机械负载变化时同样占空比下的转速也会变化。所以 PWM 调速是“开环调压”的典型方式适合风扇、水泵、玩具马达这类要求不高的场景。如果要精确稳速必须加编码器反馈用 PID 去修正占空比后面我们会细说。2.2 占空比决定“平均电压”频率决定“电流平滑程度”很多教程只讲占空比不讲频率。占空比相当于油门踩多深频率相当于油门踏板抖多快。对电机来说频率太低电流纹波大电机会一顿一顿甚至转几毫秒停几毫秒听起来就是咔咔的抖动频率太高MOS 管开关损耗、驱动损耗和铁损都上来控制器发热电机还可能啸叫。经验值方面玩具直流电机和散热风扇PWM 频率在 1kHz 到 20kHz 之间比较常见。低于 1kHz 容易听到明显的换相噪声高于 20kHz 进入超声范围人耳听不见但开关损耗明显增加。如果你的电机在某个频率下发出刺耳的声音大概率是 PWM 频率落在音频范围内并且和电机的机械谐振点接近。把频率往上调到 20kHz 以上或者避开谐振点声音会消失。另外电机两端一定要并联续流二极管这个不能省否则关断瞬间电感产生的高压会击穿 MOS 管或单片机引脚。2.3 为什么线性调压不行必须用开关方式有人问我用一个可调电阻串在电源和电机之间或者用线性稳压器调压不也能调速吗能用但效率不行。可调电阻上分压的功率全部变成热量电机轻载时电阻发热尤其严重。线性稳压也一样压差越大损耗越大。PWM 的功率管只有导通和截止两个状态导通时电流从 MOS 管流过但导通压降很小截止时电流为零。理想情况下开关管本身不消耗能量所以效率可以达到 90% 以上。这就是电机驱动、LED 驱动普遍采用 PWM 而不是模拟调压的根本原因用高频开关换取极低的损耗。到这里LED 和电机两件事就统一了PWM 调速调的不是“电压幅值”而是“平均能量”。接下来进入实操看看怎么在 51、STM32、HC32F460 上把 PWM 真正发出来。3. 三种平台的 PWM 实操对照3.1 51 单片机定时器中断模拟 PWM也能调速增强型 51 单片机会有 PCA/CCP 硬件 PWM但基础款 51 大多没有只能用定时器中断模拟。思路并不复杂设置一个固定周期的定时中断比如 20us 一次用一个变量累计。假设累计到 50 为一个完整周期那么 PWM 频率就是 1/(20us × 50) ≈ 1kHz。占空比控制变量 duty每次中断计数器加一计数器小于等于 duty 时输出点亮电平否则输出熄灭电平。代码骨架大致是void Timer0_ISR(void) interrupt 1 { static unsigned char cnt 0; /* 重装定时器初值具体值取决于晶振 */ TH0 0xFF; TL0 0x38; cnt; if(cnt duty) LED 0; /* 低电平点亮 */ else LED 1; if(cnt period) /* period 设定 PWM 周期 */ cnt 0; }这是最朴素的方法缺点是中断频繁占用 CPU占空比变化会产生轻微抖动。但对你理解 PWM 有很大帮助肉眼看到的“占空比”就是单片机在中断里反复翻转引脚的结果。做产品时尽量选带硬件 PWM 的单片机STM32、HC32F460 甚至很多国产增强型 51 都有硬件 PWM 不需要 CPU 参与波形也更稳定。3.2 STM32CubeMX 配置 PWM模式 1 和模式 2 到底哪不同用 STM32CubeMX 配置 PWM 非常方便。选择定时器比如 TIM2勾选 Channel1Mode 设置为 PWM Generation CH1然后在 Parameter Settings 里设置 Prescaler、Counter Period也就是 ARR和 Pulse也就是 CCR。PWM 频率 定时器时钟 / ((Prescaler1) × (Counter Period1))。假设定时器时钟 80MHzPrescaler79ARR999频率 80MHz / (80 × 1000) ≈ 1kHz。占空比 Pulse / ARR × 100%Pulse 设置成 500 就是 50%。接着配置 GPIO引脚会自动复用为定时器输出。这里最容易搞混的是 PWM Mode 1 和 Mode 2。以向上计数和正极性为例模式 1 是 CNT CCR 时输出有效电平否则输出无效电平模式 2 刚好相反。所谓有效电平由 Polarity 决定可以是高电平或低电平。实际效果就是模式 1 和模式 2 输出的波形相差 180 度占空比互补。如果你要正逻辑的 50% 占空比用模式 1 High Polarity如果你想把波形反相就换成模式 2。CubeMX 生成代码后调用 HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1) 启动修改占空比就改 CCR1 寄存器比如__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, 600); /* 60% 占空比 */还有一个细节CubeMX 里 PWM 输出引脚默认配置为复用推挽输出。如果你的 LED 接法是低电平点亮就要对应选择低电平有效的 Polarity否则代码里看着占空比是对的实际灯却反着亮。3.3 HC32F460 单片机 PWM 配置历程PA8 和定时器复用这几年国产单片机在项目里越来越常见。HC32F460 是华大半导体的一颗 Cortex-M4 内核芯片主频可以做到 200MHz定时器资源多做电机控制很合适。它的 PWM 配置思路和 STM32 类似但库函数名和寄存器结构不同很多从 STM32 转过来的人第一反应是找 CubeMX结果发现没有只能看参考手册和官方例程。以官方库的定时器 PWM 输出为例大致流程是使能外设时钟配置引脚复用功能初始化定时器 PWM 通道设置周期寄存器、占空比寄存器和极性最后使能输出。比如你想让 PA8 输出 PWM要先在数据手册里查 PA8 对应的定时器复用功能然后在初始化函数里把引脚从普通 GPIO 切到复用模式。代码骨架大致是这样/* 伪代码具体寄存器名以 HC32F460 固件库为准 */ stc_pwm_init_t stcPwmInit; stcPwmInit.enClkDiv PwmClkDiv1; stcPwmInit.enPeriod 1000; /* 周期计数值 */ stcPwmInit.enDuty 500; /* 占空比计数值 */ stcPwmInit.enPolarity PwmHighPolarity; PWM_Init(stcPwmInit); PWM_Start();这里特意用伪代码是因为不同 SDK 版本命名差异很大直接照抄别人的代码容易抄错。我的建议是先跑通官方例程里带 PWM 或 TIMER 的工程做一次 LED 亮度渐变再改周期和占空比。HC32F460 的 PWM 时钟源可以和系统时钟不同步要注意分频系数计算别直接用主频去除。引脚复用必须查表PA8 能输出 PWM 不代表任意引脚都能输出 PWM这是和 STM32 一模一样的原则。另外HC32F460 的定时器 PWM 支持互补输出和死区配置做 H 桥电机驱动时用得着。配置死区时先确认驱动芯片的输入延时要求一般死区设置在几百纳秒到 2 微秒之间太小会直通太大会让波形畸变、效率下降。3.4 驱动能力单片机引脚不能直接拖电机PWM 波形只是控制信号。单片机的 IO 口直接驱动 LED 没问题直接驱动直流电机就是灾难。电机正常工作电流可能是几百毫安甚至几安培而单片机 GPIO 最大输出只有几个毫安到二十几个毫安强拉会导致引脚过流、芯片发热甚至烧毁。正确做法是加一级驱动电路用 MOS 管或者专用电机驱动芯片。以最简单的 N 沟道 MOS 管低边驱动为例PWM 引脚接栅极电机一端接电源正极另一端接 MOS 管漏极源极接地电机两端反并联续流二极管栅极到源极之间加 10k 到 100k 下拉电阻防悬空。MOS 管导通时电机得电PWM 占空比控制导通时间。选 MOS 管时注意栅极电压是否兼容 3.3V 逻辑很多单片机 IO 高电平只有 3.3V普通 MOS 管阈值电压 2 到 4V可能导通不完全导致管子发热。所以要么选逻辑电平 MOS 管要么用三极管或栅极驱动芯片抬压。做大功率 LED 产品时也一样最好用专门的 LED 驱动芯片而不是让 MCU 直接 PWM 大电流 LED。LED 驱动芯片自带恒流和调光引脚效率和一致性更好MCU 只需要给一个 PWM 调光信号功率部分交给专用芯片。4. 进阶用法死区、PID 和那些容易忽略的坑4.1 互补 PWM、死区和故障保护电机控制的生死线当你从单个 PWM 控制 LED升级到用 H 桥控制直流电机正反转事情就不只是调占空比了。H 桥四个开关管交替导通如果同侧上下管同时导通电源会直接短路电流瞬间冲到几百安培烧管冒烟只在几微秒之间。所以控制器输出的两路 PWM 必须严格互补并且在一路关闭到另一路开启之间插入死区时间。大多数 MCU 的定时器都支持死区插入STM32 用 Break 和 DeadTime 配置HC32F460 也有类似功能。另一个容易被忽略的是故障保护。电机过流、过压、堵转时最好让 PWM 输出立刻变成安全状态而不是等主程序反应过来。好的 MCU 支持 PWM 刹车或故障输入引脚比如把比较器输出接到 PWM 故障脚触发后硬件自动关断输出CPU 中断只是通知软件做记录。这个功能在英飞凌的 CCU6、STM32 的高级定时器上都有对应名字叫 PWM 刹车、FAIL 或 TRAP。做动力相关产品这个功能不能省软件置电平的速度远不如硬件刹车快。4.2 PID 调速PWM 只是执行器不是控制算法很多人做完开环调速后发现一个现象占空比设成 50%电机空载转 3000 转负载一加上去转速掉到 2000。这不是 PWM 失效了而是开环控制本来就没有修正能力。要让转速稳定必须闭环。常用方案是编码器测速把实际转速和目标转速比较误差送进 PID 控制器PID 输出一个合适的 PWM 占空比。也就是说PWM 是系统的“手”PID 是“大脑”。实际写代码时PID 输出往往是浮点数而占空比寄存器只能取整数要做限幅处理。比如输出限制在 0 到 ARR 之间防止积分饱和。参数整定我的经验是先只调 P从小到大看到转速开始震荡再加大 P 到震荡刚好消失然后加 I 消除稳态误差D 用于抑制超调。电机响应快PID 周期一般用 5ms 到 20ms比 PWM 周期慢很多。千万别在 PWM 中断里面做 PID 运算那样既浪费资源又会让控制周期不合理地变短用固定时间基准比如 systick 去更新占空比即可。4.3 LED 驱动器中的 FB、CS 引脚和 PWM 调光的关系热词里有个“led 驱动器芯片 fb cs 脚调整”这里简单说一下。FB 是反馈引脚通过外部电阻分压采样 LED 电流或输出电压芯片内部误差放大器根据 FB 电压决定内部开关的占空比实现恒流或恒压。CS 是电流采样引脚通常接采样电阻电阻两端电压与电流成正比到达阈值就关断开关管实现逐周期限流。所以 FB 和 CS 属于模拟反馈最终也在调整芯片内部 PWM 的占空比和 MCU 直接输出 PWM 调光是两条路径。如果用 MCU 给这类 LED 驱动芯片调光常见做法是找 DIM、EN 或 PWM 引脚把单片机的 PWM 接上去。低速 PWM几百赫兹直接改变平均电流如果 PWM 频率太高超过芯片的响应带宽就需要把 PWM 滤波成模拟电压送到 FB 或 REF 引脚。我看到不少人直接把几十 kHz 的 PWM 接到 DIM 引脚结果光通量变化不明显就是因为芯片内部把高频 PWM 滤平了。先查芯片手册的调光频率范围再决定用哪种方式这比盲目加大占空比更有效。4.4 用 PWM 监控硬盘温度其实是温控风扇调速搜索热词里有个问题叫“pwm 怎么监控硬盘的温度”。严格说PWM 不能直接监控温度它是一根控制线。在很多主板上风扇接口的第 4 针就是 PWM 控制线主板通过温度传感器比如贴在硬盘、CPU 散热器上的 NTC读到温度再用 PWM 控制风扇转速温度高就提高占空比温度低就降到 30% 左右。监控温度靠的是热敏电阻或数字温度传感器PWM 只是执行降温动作。如果你用单片机做温控风扇流程就是ADC 读 NTC 电压查表或计算温度然后映射到 PWM 占空比再加迟滞防止风扇在临界点反复启停。这里有个小技巧风扇 PWM 频率别选太低低于 1kHz 风扇内部可能会有明显噪音很多风扇模块要求 PWM 频率在 25kHz 左右用之前先看规格。所谓“监控硬盘温度”本质上是一个温度采集 PWM 调速的闭环系统。5. 常见问题与排查记录5.1 为什么我的 PWM 输出只有高电平或低电平最常见的原因有三个。第一定时器没启动STM32 里要调用 HAL_TIM_PWM_StartHC32 里要调用 PWM_Start不是初始化完就有波形。第二比较值 CCR 大于等于周期值 ARR占空比变成 100% 或 0%如果你中途把 CCR 改到超过 ARR波形就会锁死在全开或全关状态。第三引脚复用功能没配好GPIO 没有切到复用模式比如 PA8 还是普通 GPIO定时器信号根本没被引出来。排查方法不复杂先用示波器或逻辑分析仪看引脚波形。没有示波器的话可以先把频率调到很低比如 1Hz 左右用万用表看看平均电压或者直接听蜂鸣器的声音变化再逐步调高频率。不要上来就怀疑芯片坏八成是配置问题。5.2 电机用 PWM 调好后还是发热严重怎么查先量 MOS 管栅极波形如果上升沿和下降沿很缓说明驱动电流不足管子在中间线性区停留时间太长损耗全在这里。解决办法是降低栅极电阻、加推挽驱动或者换专门的栅极驱动 IC。再查 PWM 频率频率太高开关损耗增加频率太低电流纹波大铜损增加。发热往往是多种因素叠加不要只换管子不查驱动信号。还有一个常见情况是占空比有效但电机没转发热却很快。这是因为平均电压低于电机启动死区电压电机转子不动电枢电流一直很大能量全变成铜损。这时候要么提高启动占空比要么先用全速启动再切到低速 PWM。5.3 电机转起来有“吱吱”声是坏了吗大概率不是坏了是 PWM 频率落在音频范围内。把频率从 1kHz 改到 20kHz 以上声音通常会消失。如果电机还配了减速齿轮还要检查齿轮啮合和轴承润滑但 PWM 频率是第一嫌疑。另外避免让 PWM 占空比长期低于某个阈值比如 10% 以下电机可能因为死区电压不转但一直有功率消耗时间久了会过热。如果用的是有刷电机还要检查电刷和后盖是否有磨损痕迹。PWM 驱动有刷电机时电流纹波会在电刷处产生火花高频噪声也可能干扰控制电路。这时候可以在电机电源端加一个小容量的陶瓷电容并在电机外壳和控制地之间做好接地。5.4 问题速查表现象常见原因排查方向PWM 不输出定时器未启动、引脚复用错误、CCR 大于等于 ARR示波器量引脚检查定时器寄存器占空比和预期相反PWM Mode 1/2 配错、Polarity 没选对检查模式配置和有效电平LED 亮度变化非线性驱动器调光引脚频率超限查芯片手册的调光频率范围电机发热或异响频率不合适、死区不够、驱动不饱和测栅极波形调整频率加死区转速不稳开环控制、负载变化、占空比过小加编码器使用 PID 闭环最后分享一个我自己的调试习惯刚开始调 PWM哪怕只是点亮一个 LED也要先用示波器或逻辑分析仪看波形确认周期、占空比和极性是你想要的那一个再去看亮度、转速这些物理结果。因为软件里看到的寄存器值并不总是等于引脚上的波形极性问题、复用问题、启动顺序问题都可能让输出和预期相差一个反相或几微秒。把波形看明白LED 和电机的问题就解决了一大半。PWM 这套东西从原理到落地其实不复杂关键是每个细节都要有数据支撑不要靠猜。