☰
PWM从入门到精通:LED调光与直流电机调速实战
2026/10/27 1:01:23 网站建设 项目流程

1. 从一个LED灯说起:为什么调亮度不是调电压

很多人第一次接触PWM,都是从“让一颗LED变暗一点”开始的。你手里有STM32或者51单片机,GPIO口接了一颗LED,串了一个限流电阻,程序里写个GPIO_SetBits灯就亮,写GPIO_ResetBits灯就灭。这时候你想让它“半亮”,第一反应往往是——把电压降一半行不行?

我试过,不行。或者说,用GPIO直接降电压这条路根本走不通。GPIO只有高电平和低电平两种输出状态,你要么给3.3V,要么给0V,没有中间值。就算你用DAC输出一个1.65V的模拟电压去驱动LED,亮度确实会变,但问题一大堆:DAC资源稀缺、驱动能力弱、效率极低,而且对电机这种负载来说,半电压意味着半转矩,电机可能直接堵转发热。

PWM的思路完全不一样。它不改变电压幅值,而是改变“通电时间”的比例。还是3.3V,但我让LED在1毫秒内只亮0.5毫秒、灭0.5毫秒,循环下去。因为人眼的视觉暂留效应,你看到的不是闪烁,而是亮度减半。这就是PWM最朴素的定义:用数字信号的时间比例,去等效模拟量的幅度。

这个思路一旦打通,你会发现它能干的事情远不止调LED亮度。直流电机的转速、舵机的角度、开关电源的输出电压、甚至加热丝的温度,底层都是同一套逻辑。标题里说的“从LED亮度变化到直流电机转起来”,其实就是PWM从“视觉等效”走向“能量等效”的过程。LED是光输出,电机是机械输出,但控制手段是同一个。

这篇文章我打算把PWM这件事从头到尾讲透。不管你是刚学STM32、还在纠结GPIO八种模式怎么选的新手,还是已经能用定时器输出PWM、但说不清楚“模式1和模式2到底差在哪”的进阶玩家,都能从中找到对你有用的东西。我会从原理讲到寄存器配置,从LED讲到电机,中间穿插我实际调试时踩过的坑和验证过的参数。

2. PWM的核心原理:占空比、频率与等效电压

2.1 占空比到底在表达什么

占空比(Duty Cycle)是PWM里最核心的概念,没有之一。它的定义很简单:在一个周期内,高电平时间占总时间的比例。比如周期是1ms,高电平0.3ms,占空比就是30%。

但我想强调的是,占空比本身不是目的,它只是手段。真正起作用的是“等效值”。对于一个LED,等效亮度约等于占空比乘以满亮度;对于一个直流电机,等效电枢电压约等于占空比乘以电源电压。注意这里我用了“约等于”,因为实际系统中还有非线性、惯性、死区等因素。

我见过不少初学者把占空比和频率搞混。占空比决定“等效值多大”,频率决定“这个等效值稳不稳”。举个例子,你用1Hz的频率、50%占空比去驱动LED,看到的是明显的闪烁,不是半亮。因为1秒亮1秒灭,人眼根本来不及做视觉平均。频率提高到100Hz以上,闪烁感才消失。所以调亮度靠占空比,消闪烁靠频率,这两件事要分开理解。

2.2 频率选多少才合适

频率的选择没有万能答案,它取决于负载特性。我整理了一个实际项目中常用的频率范围表,你可以直接参考:

应用场景推荐频率范围选择理由
LED调光200Hz - 2kHz高于200Hz人眼无闪烁感,太高会增加开关损耗
直流有刷电机调速1kHz - 20kHz低于1kHz有啸叫,高于20kHz开关损耗明显
舵机控制50Hz标准舵机协议固定50Hz,脉宽0.5-2.5ms
开关电源50kHz - 500kHz频率越高电感电容越小,但开关损耗上升
加热丝控温0.5Hz - 10Hz热惯性大,不需要高频,低频减少开关次数

这张表是我自己项目里反复验证过的。特别说一下直流电机,我一开始用1kHz驱动,电机发出明显的“滋滋”声,后来提到8kHz,声音就基本听不到了。但频率也不是越高越好,我试过用20kHz驱动一个小功率电机,MOS管发热明显增加,因为开关次数多了,开关损耗上去了。最后定在8kHz,兼顾静音和效率。

2.3 等效电压的数学关系

对于直流电机,PWM驱动下的等效电枢电压可以用这个公式算:

V_eff = V_supply × Duty

比如12V电源,占空比60%,等效电压就是7.2V。电机的转速大致和电枢电压成正比,所以转速大约是满速的60%。但这里有个坑:电机不是纯电阻负载,它有反电动势。实际转速会比这个公式算出来的略高一点,因为反电动势会抵消一部分电枢电压。我在调试小车电机时发现,50%占空比下实际转速大约是满速的55%左右,不是精确的50%。

对于LED,等效亮度和占空比的关系更接近线性,但也不是完全线性。因为LED的亮度还受结温影响,大电流下发热会导致亮度下降。所以如果你要做精确的亮度控制,光靠PWM还不够,还得考虑温度补偿。

3. STM32上怎么把PWM输出出来

3.1 GPIO八种模式里,PWM该选哪个

STM32的GPIO有八种工作模式,初学者最容易在这里懵。我列一下这八种模式,然后告诉你PWM输出该选哪个:

  • 输入浮空
  • 输入上拉
  • 输入下拉
  • 模拟输入
  • 开漏输出
  • 开漏复用输出
  • 推挽输出
  • 推挽复用输出

PWM输出要选推挽复用输出(GPIO_Mode_AF_PP)。为什么?两个关键词:推挽、复用。

推挽的意思是,输出级有上下两个管子,能主动拉高也能主动拉低,驱动能力强。开漏输出只能主动拉低,拉高要靠外部上拉电阻,上升沿会变慢,PWM波形会畸变。我试过用开漏输出驱动LED,波形上升沿明显变缓,占空比都不准了。

复用的意思是,这个引脚的控制权交给片上外设(这里是定时器),而不是GPIO数据寄存器。你写GPIO_SetBits没用,因为引脚已经不归GPIO管了。这一点新手特别容易搞错,代码里明明配置了PWM,但引脚没反应,就是因为忘了配成复用模式。

3.2 定时器是怎么生成PWM的

STM32的定时器生成PWM,核心靠三个寄存器:ARR(自动重装载寄存器)、PSC(预分频器)、CCR(捕获比较寄存器)。

先说频率怎么来的。定时器的计数时钟是总线时钟经过PSC分频后的结果:

f_cnt = f_clk / (PSC + 1)

然后计数器从0数到ARR,数满一个来回就是一个PWM周期:

f_pwm = f_cnt / (ARR + 1) = f_clk / ((PSC + 1) × (ARR + 1))

举个例子,STM32F103的APB1定时器时钟是72MHz,我想要8kHz的PWM频率。先定PSC,比如取0,那ARR就是:

ARR = 72,000,000 / 8,000 - 1 = 8999

如果取PSC=71,那计数时钟变成1MHz,ARR就是:

ARR = 1,000,000 / 8,000 - 1 = 124

两种都行,区别在于分辨率。PSC=0时,ARR=8999,占空比可以调9000档;PSC=71时,ARR=124,占空比只能调125档。分辨率越高,调速越细腻。所以我一般优先用小的PSC、大的ARR,除非ARR太大导致计数溢出。

占空比由CCR决定:

Duty = CCR / (ARR + 1)

CCR可以在运行时动态改,改完下一个周期就生效。这就是PWM调速的软件接口——你只需要改CCR的值。

3.3 PWM模式1和模式2的区别

这是很多人搞不清的地方。STM32的定时器每个通道可以配成PWM模式1或模式2,区别在于有效电平的定义。

  • PWM模式1:计数器小于CCR时,输出有效电平;大于等于CCR时,输出无效电平。
  • PWM模式2:反过来,计数器小于CCR时输出无效电平,大于等于CCR时输出有效电平。

那什么是“有效电平”?这又取决于另一个配置位:输出极性。极性可以配成高有效或低有效。

所以实际上有四种组合。我一般用PWM模式1加高有效极性,这样CCR越大,高电平时间越长,占空比越大,符合直觉。如果你用PWM模式2,CCR越大反而高电平越短,容易把自己绕晕。

有个实际场景需要注意:如果你驱动的是低电平点亮的LED(比如共阳极接法),那用PWM模式1加低有效极性会更方便,CCR越大亮度越高。我一开始没注意这个,代码里CCR调大了灯反而变暗,查了半天才发现是极性配反了。

3.4 用CubeMX配置PWM的完整流程

我平时用STM32CubeMX配PWM,效率高而且不容易漏配置。流程大致是这样:

  1. 选好定时器和通道,比如TIM1_CH1对应PA8。
  2. 把对应引脚配成复用推挽输出。
  3. 在定时器配置里,Clock Source选Internal Clock。
  4. Channel选PWM Generation CH1。
  5. 设PSC和ARR,算出想要的频率。
  6. 设Pulse(就是CCR初值),决定初始占空比。
  7. 生成代码,在main里调用HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1)。

这里有个坑我要提醒:HAL库启动PWM必须调用HAL_TIM_PWM_Start,光配置不启动是没有波形的。我见过好几个新手配置都对了,但引脚就是没输出,最后发现是忘了调这个函数。

还有,如果你想在运行时改占空比,用这个函数:

__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, new_ccr);

这个宏直接写CCR寄存器,比调HAL库函数快,适合在中断里用。

4. 从LED到直流电机:负载变了,思路怎么变

4.1 LED驱动:限流电阻和驱动方式的选择

LED是最简单的PWM负载,但也不是随便接就行。先说限流电阻,这个必须要有,否则LED容易烧。计算方法:

R = (V_supply - V_led) / I_led

比如3.3V电源,红色LED正向压降约1.8V,想要10mA电流:

R = (3.3 - 1.8) / 0.01 = 150Ω

这里有个热词提到“51单片机驱动LED时为什么不能采用输出高电平的驱动方式”。原因是51单片机的GPIO是准双向口,输出高电平时驱动能力很弱(通常只有几十微安),拉电流能力远不如灌电流。所以标准做法是低电平点亮:LED阳极接VCC,阴极通过限流电阻接GPIO,GPIO输出低电平时LED亮。STM32的推挽输出高低电平驱动能力都还行,但灌电流通常也比拉电流强一点,所以低电平点亮仍然是更稳妥的选择。

如果你要驱动大功率LED或者灯带,GPIO直接驱动就不够了,需要加驱动芯片。常见的LED驱动芯片有恒压和恒流两种。恒流驱动更适合LED,因为LED亮度对电流敏感,对电压不敏感。有些驱动芯片有FB(反馈)脚和CS(电流采样)脚,通过调整反馈电压来设定输出电流。这种芯片也可以用PWM调光,PWM信号加到EN脚或者直接调制FB脚。

4.2 直流电机驱动:为什么不能直接接GPIO

直流电机绝对不能直接接GPIO,原因有三个:电流大、有反电动势、有感性尖峰。

小电机堵转电流可能就有1A,STM32的GPIO最大也就20mA左右,直接接上去轻则烧引脚,重则烧芯片。电机转动时会产生反电动势,断电瞬间电感还会产生反向尖峰电压,可能击穿GPIO。所以必须加驱动电路,常见方案有:

  • H桥驱动芯片:比如DRV8833、L298N,能正反转,适合小车。
  • MOS管低边驱动:适合单向调速,成本低。
  • 继电器:只能开关,不能调速,不适合PWM。

我用得最多的是DRV8833,双路H桥,单路持续电流1.5A,峰值2A,驱动小车轮子绰绰有余。它的控制逻辑很简单:一个PWM脚调速,一个方向脚控方向。PWM频率我一般设8kHz,占空比0到100%对应转速0到满速。

这里有个细节:电机启动时占空比不能太小。因为电机有静摩擦,占空比低于某个阈值时电机根本不转,只是发热。我实测一个小电机,占空比低于15%就不转了。所以软件上要做个死区补偿,比如把0-100%的输入映射到15%-100%的输出。

4.3 电机调速的闭环思路

开环PWM调速有个问题:电池电压下降时,同样的占空比转速会变慢。如果你要做恒速控制,就需要闭环。最简单的方法是加编码器测速,然后用PID调PWM占空比。

我做过一个小车项目,用霍尔编码器测轮速,PID周期10ms,PWM频率8kHz。调参的时候先调P,让系统有响应但不振荡;再加I,消除稳态误差;最后加少量D,抑制超调。最后转速波动能控制在±3%以内,比开环好太多。

但闭环不是必须的。如果你只是做个风扇调速、灯光调光,开环足够了。闭环的成本和复杂度都要高不少,要看项目需求决定。

5. 实操中踩过的坑和排查技巧

5.1 PWM没输出,先查这五个地方

PWM配置完没波形,是新手最常遇到的问题。我总结了一个排查顺序,按这个查基本能定位:

排查项检查方法常见问题
定时器时钟查RCC配置忘了使能定时器时钟
PWM启动查代码忘了调HAL_TIM_PWM_Start
GPIO模式查引脚配置配成了普通输出而非复用
引脚映射查数据手册用错了引脚或重映射没开
CCR值查占空比CCR=0或CCR>ARR

我印象最深的一次是调PA8输出PWM,配置全对就是没波形。最后发现是TIM1是高级定时器,除了HAL_TIM_PWM_Start,还需要额外使能主输出:

__HAL_TIM_MOE_ENABLE(&htim1);

高级定时器(TIM1、TIM8)有刹车和死区功能,默认主输出是关的。这个坑我踩过一次就记住了。

5.2 PWM背光干扰显示的问题

热词里有个“pwm背光干扰显示”,这个我遇到过。用PWM调LCD背光亮度时,屏幕出现横条纹干扰。原因是PWM频率和LCD刷新频率产生了拍频,或者PWM的开关噪声耦合到了显示数据线上。

解决办法有三个:一是提高PWM频率到20kHz以上,超出人耳和显示刷新范围;二是给背光电路加LC滤波,把PWM平滑成模拟电压;三是背光PWM走线远离数据线,减少耦合。我一般用第一种加第二种组合,频率提到20kHz,再加个100uF电容,干扰基本消失。

5.3 电机PWM的故障保护

电机堵转时电流会飙升,如果没有保护,驱动芯片和电机都可能烧。我在DRV8833的方案里加了电流采样电阻,用STM32的ADC监测电流,超过阈值就立刻把PWM占空比降到0。

这里有个细节:故障保护要快。软件轮询ADC太慢,我改成ADC注入通道加中断,采样到过流立刻在中断里关PWM。从过流发生到PWM关闭,响应时间控制在100微秒以内,驱动芯片就不会过热。

另外,PWM故障保护还可以用定时器的刹车输入功能。高级定时器有BKIN引脚,检测到低电平自动关闭PWM输出,完全硬件响应,比软件快得多。这个功能在电机驱动里非常实用,建议用高级定时器做电机PWM时一定要配上。

5.4 常见问题速查表

现象可能原因解决办法
LED闪烁而非调光PWM频率太低提高到200Hz以上
电机有啸叫PWM频率在人耳范围提高到8kHz以上
电机低速不转占空比低于启动阈值加死区补偿
PWM波形畸变GPIO配成开漏改成推挽复用
占空比不准时钟配置错误核对PSC和ARR
高级定时器无输出MOE未使能调__HAL_TIM_MOE_ENABLE
背光干扰显示PWM噪声耦合提高频率加滤波

这张表是我自己项目里积累的,基本覆盖了PWM调试中80%的问题。遇到问题先查表,能省不少时间。

6. 一些进阶玩法和个人体会

PWM玩到后面,你会发现它不只是“调亮度”和“调速”。我最近在做的一个项目,用PWM控制DC-DC反馈脚来调输出电压。原理是把PWM经过RC滤波变成模拟电压,加到DC-DC的FB脚,改变反馈比例,从而调整输出。这种用法比用DAC灵活,因为PWM通道多,几乎每个定时器都能出。

还有一个方向是PWM监控。热词里提到“pwm怎么监控硬盘的温度”,这个思路其实是用PWM占空比来编码温度信息。温度传感器输出PWM,占空比随温度变化,MCU测占空比就知道温度。这种传感器抗干扰能力强,适合长距离传输。

我个人在实际操作中的体会是,PWM看着简单,但要用好,关键在三点:频率选对、模式配对、保护做足。频率选对决定了系统稳不稳,模式配对决定了控制方不方便,保护做足决定了系统可不可靠。这三点做到了,PWM基本不会出大问题。

最后分享一个小技巧:调试PWM时,如果手头没有示波器,可以用另一个定时器配成输入捕获模式,测PWM的频率和占空比,通过串口打印出来。我用这个方法在没有示波器的情况下调过好几个项目,精度虽然不如示波器,但看个大概足够了。STM32的定时器捕获功能很强大,测频率、测占空比、测脉宽都能做,值得花时间研究一下。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询