1. 从一个LED灯说起:PWM到底在干什么
很多人第一次接触PWM,都是在点亮一颗LED的时候。代码里写一句“设置占空比50%”,烧录进去,灯就变成了半亮。再改成20%,灯暗下去;改成80%,灯又亮起来。整个过程没有改电压,没有换电阻,只是让一个GPIO引脚以极快的速度在高低电平之间来回切换,LED的亮度就跟着变了。
这件事第一次看会觉得有点反直觉。电压没变,电流峰值也没变,凭什么亮度会变?答案藏在两个东西里:一是人眼的视觉暂留,二是LED本身的响应速度。人眼对光强的感知有一个积分效应,大概几十毫秒以内的闪烁,眼睛分辨不出来,只会感知到一个平均亮度。如果引脚以1kHz的频率翻转,高电平占一半时间、低电平占一半时间,LED实际导通的时间就是总时间的一半,平均功率也就是满功率的一半,看起来就是半亮。
同样的逻辑放到直流电机上,效果更直观。给电机加一个恒定的5V,它全速转;加2.5V,它慢速转。但很多单片机的GPIO只能输出高或低,没法输出2.5V这种中间电压。PWM就绕开了这个限制:让引脚快速地在5V和0V之间切换,电机因为自身的机械惯性和电感惯性,感受到的是一个平均电压。占空比50%,平均电压就是2.5V,转速就降下来了。
所以PWM的本质,是用时间上的“开关比例”去等效一个模拟量。它不需要DAC,不需要可调电源,只需要一个能快速翻转的GPIO和一个定时器。这也是为什么从LED调光、电机调速、舵机控制、开关电源,到背光调节、加热控制,PWM几乎无处不在。
这篇文章我会把PWM从原理到落地完整走一遍。先讲清楚它为什么能调速、为什么能调光,再拆解频率和占空比这两个核心参数怎么选,然后落到STM32上用定时器输出PWM的实际配置,最后把LED和直流电机两个场景的实操过程、常见坑和排查方法都摊开讲。不管你是刚点亮第一颗LED的新手,还是已经在调电机但总遇到抖动、啸叫、发热的老手,应该都能从里面找到能直接抄的东西。
2. PWM能调速调光的底层逻辑
2.1 平均电压与占空比的关系
PWM波形其实很简单,就是一个方波。一个周期T内,高电平持续的时间记作Ton,低电平持续的时间记作Toff,那么占空比D就是:
D = Ton / T
如果高电平电压是Vcc,低电平是0V,那么一个周期内的平均电压就是:
Vavg = Vcc × D
这个公式是PWM所有应用的根。占空比50%,平均电压就是Vcc的一半;占空比10%,平均电压就是Vcc的十分之一。对于LED,平均电压近似对应平均电流,平均电流对应平均功率,平均功率对应亮度。对于直流电机,平均电压对应电枢两端的等效电压,等效电压决定转速(在负载不变的前提下)。
这里有一个容易被忽略的点:平均电压成立的前提是负载对PWM频率“不敏感”。也就是说,负载的响应速度要慢于PWM频率。LED的响应在微秒级,所以PWM频率要到几百Hz以上,人眼才看不到闪烁;电机的机械时间常数通常在几十毫秒到几百毫秒,所以PWM频率只要几百Hz到几kHz,电机就感受不到逐个脉冲的冲击,只会感受到平均转矩。
反过来说,如果PWM频率太低,比如给LED用10Hz,人眼就会看到明显的闪烁;给电机用10Hz,电机会一顿一顿地转,还会发出明显的噪音。这就是频率选择的第一个约束。
2.2 为什么不是直接调电压
有人会问,既然要调平均电压,为什么不直接用DAC输出一个模拟电压,或者用可调电阻分压?
DAC的问题在于成本和效率。很多单片机没有DAC,或者DAC通道很少。而且DAC输出的是小信号电压,要驱动LED或电机,后面还得加功率放大级,电路复杂、效率低。用可调电阻分压更不现实,电阻上会消耗大量功率,发热严重,效率极低,而且只能手动调,没法用程序控制。
PWM的优势在于:它直接用数字引脚控制功率开关(MOSFET或三极管),开关要么完全导通、要么完全截止,导通时压降很小,截止时电流为零,所以开关本身的损耗非常低,效率可以做到90%以上。这也是为什么开关电源、电机驱动、LED驱动几乎都用PWM而不是线性调节。
2.3 频率和占空比各自决定什么
PWM有两个核心参数:频率和占空比。很多人会把它们混在一起,其实它们管的是完全不同的两件事。
占空比决定“平均量”的大小。占空比越大,平均电压越高,LED越亮,电机越快。这是调速调光的直接手段。
频率决定“开关的快慢”。频率太低,负载会表现出脉冲响应,LED闪、电机抖、有噪音;频率太高,开关损耗增加,驱动电路可能跟不上,EMI也会变差。所以频率的选择要匹配负载特性和驱动能力。
| 应用场景 | 典型PWM频率 | 原因 |
|---|---|---|
| LED调光 | 1kHz ~ 20kHz | 高于人眼闪烁感知,低于驱动电路带宽极限 |
| 直流电机调速 | 1kHz ~ 20kHz | 高于听觉范围可减少啸叫,低于电机驱动响应极限 |
| 舵机控制 | 50Hz | 舵机标准协议要求 |
| 开关电源 | 几十kHz ~ 几百kHz | 提高效率、减小电感体积 |
| 加热控制 | 0.1Hz ~ 10Hz | 热惯性大,不需要高频 |
这张表不是随便定的,背后是负载的时间常数和系统的损耗权衡。比如电机用1kHz以下,人耳能听到明显的啸叫;用20kHz以上,MOSFET开关损耗和驱动芯片的发热会明显上升。所以10kHz到16kHz是一个常见的折中区间。
3. 频率与占空比怎么选:参数背后的取舍
3.1 频率选择的三个约束
选PWM频率,本质上是在三个约束之间找平衡。
第一个约束是负载响应速度。负载能不能“跟上”PWM的开关。LED响应快,频率可以很高;电机机械惯性大,频率不能太低;热系统惯性更大,频率可以很低。如果频率低于负载的响应带宽,负载就会表现出脉冲行为,而不是平均行为。
第二个约束是人耳和人眼的感知范围。人眼对闪烁的感知大概在几十Hz到几百Hz,所以LED调光频率要高于这个范围,通常1kHz以上就很稳。人耳听觉范围是20Hz到20kHz,电机如果在这个范围内开关,绕组和机械结构会发出啸叫,所以要么低于20Hz(但电机会抖),要么高于20kHz(但损耗增加)。实际工程里常用10kHz到16kHz,刚好在听觉上限附近,既能减少啸叫,又不至于损耗太大。
第三个约束是驱动电路的开关能力。MOSFET有栅极电容,驱动芯片有上升下降时间,光耦有响应延迟。如果PWM频率太高,驱动波形还没建立起来就被切断了,实际占空比会失真,开关损耗也会急剧上升。所以频率不能超过驱动电路的能力上限。
3.2 占空比分辨率的计算
占空比不是无限可调的。它由定时器的计数位数决定。如果定时器是16位,ARR(自动重装载寄存器)最大值是65535,那么占空比的分辨率就是1/65536,约0.0015%。如果ARR设成999,那分辨率就是1/1000,即0.1%。
分辨率决定了你能把亮度或转速调得多细。对于LED调光,人眼对低亮度区的变化更敏感,所以低占空比区需要更高的分辨率。如果分辨率不够,低亮度区会出现明显的跳变,比如从0%直接跳到5%,中间没有过渡。
计算分辨率很简单:
分辨率 = 1 / (ARR + 1)
比如STM32定时器时钟72MHz,预分频PSC=71,那么计数时钟是1MHz。要得到1kHz的PWM频率,ARR = 1MHz / 1kHz - 1 = 999。分辨率就是1/1000,即0.1%。这个分辨率对LED调光够用,对电机调速也够用。
如果要更高分辨率,可以降低PWM频率或者提高定时器时钟。比如同样1MHz计数时钟,PWM频率降到100Hz,ARR = 9999,分辨率就是0.01%。但100Hz对LED来说可能偏低,会有闪烁风险。
3.3 死区与互补输出的场景
在一些电机驱动和开关电源场景里,会用到互补PWM输出,也就是两个通道输出相反的波形,用来驱动H桥的上管和下管。这时候必须插入死区时间,防止上下管同时导通造成直通短路。
死区时间的选择取决于MOSFET的开关速度和驱动电路的延迟。太短了起不到保护作用,太长了会损失有效占空比、增加谐波。一般从几百纳秒到几微秒不等,需要根据具体器件手册和实测波形来调。
STM32的高级定时器(如TIM1、TIM8)支持硬件死区插入,配置BDTR寄存器即可。通用定时器一般不支持互补输出和死区,所以驱动H桥时通常选高级定时器。
4. STM32上输出PWM的完整实操
4.1 定时器与GPIO的对应关系
STM32的PWM输出依赖定时器的比较输出功能。每个定时器有四个独立通道,每个通道可以映射到特定的GPIO引脚。以常见的STM32F103C8T6为例,TIM1的CH1可以映射到PA8或PA9(重映射),TIM2的CH1可以映射到PA0,TIM3的CH1可以映射到PA6。
这里有一个关键点:不是随便一个GPIO都能输出PWM。只有定时器通道对应的引脚才行。所以选引脚的时候要先查数据手册的“引脚复用表”,确认这个引脚有没有定时器通道的复用功能。
以PA8输出PWM为例,PA8对应TIM1_CH1。配置步骤大致是:使能TIM1时钟和GPIOA时钟,把PA8配置为复用推挽输出,配置TIM1的时基和输出比较通道,最后使能定时器。
4.2 时基配置:PSC和ARR怎么算
时基配置决定PWM的频率。公式是:
PWM频率 = 定时器时钟 / ((PSC + 1) × (ARR + 1))
假设STM32F103的系统时钟是72MHz,TIM1挂载在APB2上,时钟也是72MHz。要得到1kHz的PWM频率,可以这样算:
先定PSC。如果PSC=71,那么计数时钟 = 72MHz / 72 = 1MHz。然后ARR = 1MHz / 1kHz - 1 = 999。
所以PSC=71,ARR=999,得到的PWM频率就是1kHz,占空比分辨率是1/1000。
如果要10kHz,可以保持PSC=71,ARR改成99,分辨率变成1/100。也可以PSC=7,计数时钟9MHz,ARR=899,分辨率1/900。两种方案频率一样,但分辨率不同。这就是参数选择时的取舍:频率越高,同样计数时钟下分辨率越低。
4.3 输出比较模式:PWM模式1和模式2的区别
STM32的定时器输出比较有PWM模式1和PWM模式2两种。它们的区别在于有效电平的定义。
PWM模式1:计数器小于CCR时输出有效电平,大于等于CCR时输出无效电平。 PWM模式2:计数器小于CCR时输出无效电平,大于等于CCR时输出有效电平。
如果有效电平设为高,那么PWM模式1下,CCR越大,高电平时间越长,占空比越大。PWM模式2则相反。
实际用的时候,通常选PWM模式1,有效电平设高,这样CCR值直接对应高电平时间,占空比 = CCR / (ARR + 1),直观好算。如果驱动的是低电平有效的电路(比如某些LED驱动芯片的使能脚),可以把有效电平设成低,或者用PWM模式2,效果一样。
4.4 代码实现:从寄存器到HAL库
用寄存器配置TIM1_CH1输出PWM,核心代码大概是这样:
// 使能时钟 RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // 配置PA8为复用推挽输出 GPIOA->CRH &= ~(0xF << 0); GPIOA->CRH |= (0xB << 0); // 复用推挽,50MHz // 时基配置 TIM1->PSC = 71; TIM1->ARR = 999; TIM1->CCR1 = 500; // 50%占空比 // 输出比较配置 TIM1->CCMR1 &= ~TIM_CCMR1_OC1M; TIM1->CCMR1 |= (0x6 << 4); // PWM模式1 TIM1->CCMR1 |= TIM_CCMR1_OC1PE; // 预装载使能 TIM1->CCER |= TIM_CCER_CC1E; // 使能通道1输出 TIM1->BDTR |= TIM_BDTR_MOE; // 高级定时器需要使能主输出 TIM1->CR1 |= TIM_CR1_ARPE; // ARR预装载 TIM1->CR1 |= TIM_CR1_CEN; // 启动定时器如果用HAL库,配置会更简洁:
TIM_HandleTypeDef htim1; TIM_OC_InitTypeDef sConfigOC; htim1.Instance = TIM1; htim1.Init.Prescaler = 71; htim1.Init.CounterMode = TIM_COUNTERMODE_UP; htim1.Init.Period = 999; htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter = 0; HAL_TIM_PWM_Init(&htim1); sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 500; sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);改占空比的时候,直接改CCR1寄存器或者调用__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, value)。value的范围是0到ARR,对应0%到100%。
注意:高级定时器(TIM1、TIM8)必须使能MOE(主输出使能),否则通道不会有输出。这个坑很多人踩过,配置都对了但引脚就是没波形,最后发现是BDTR的MOE位没置1。
5. LED调光实操:从闪烁到平滑亮度
5.1 硬件连接与限流电阻计算
LED调光是最简单的PWM应用。硬件上,LED正极接限流电阻再到电源,负极接单片机引脚(低电平点亮),或者正极接引脚、负极接地(高电平点亮)。两种接法各有优劣。
低电平点亮的好处是单片机引脚的灌电流能力通常比拉电流强,能驱动更大的电流。比如STM32的GPIO灌电流最大25mA,拉电流最大20mA,所以低电平点亮能多带一点。但低电平点亮的逻辑是反的,占空比越大反而越暗,写代码时要注意。
限流电阻的计算:假设电源3.3V,LED正向压降2.0V,目标电流10mA,那么电阻 = (3.3 - 2.0) / 0.01 = 130Ω。取标准值130Ω或150Ω。如果目标电流20mA,电阻 = 1.3 / 0.02 = 65Ω,取68Ω。
这里要注意,PWM调光时LED是断续导通的,平均电流是峰值电流乘以占空比。所以如果按峰值电流算限流电阻,实际平均亮度会比预期低。通常按峰值电流略高于目标平均电流来选,或者直接用示波器测实际波形再调。
5.2 频率选择:为什么1kHz是甜点
LED调光的PWM频率,1kHz是一个很常用的甜点值。原因有几个:
第一,1kHz远高于人眼闪烁感知频率(约100Hz),所以不会看到闪烁。第二,1kHz远低于大多数LED和驱动电路的带宽极限,波形失真小。第三,1kHz下用1MHz计数时钟,ARR=999,分辨率0.1%,足够平滑。第四,1kHz的开关损耗对LED来说可以忽略。
如果频率太低,比如100Hz,虽然人眼可能勉强看不出闪烁,但在快速移动或者用手机摄像头拍的时候,会出现明显的条纹。如果频率太高,比如100kHz,驱动电路的上升下降时间占比变大,实际占空比会偏离设定值,而且EMI会变差。
所以1kHz到5kHz是LED调光的常见区间。如果用的是专用LED驱动芯片,频率还要看芯片的PWM调光脚支持范围,有些芯片支持几十kHz甚至上百kHz。
5.3 占空比渐变实现呼吸灯
呼吸灯的本质是让占空比随时间做周期性变化。最简单的做法是在主循环里写一个for循环,让CCR值从0加到ARR,再从ARR减到0,中间加一个延时。
while (1) { for (int i = 0; i <= 1000; i++) { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i); HAL_Delay(1); } for (int i = 1000; i >= 0; i--) { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i); HAL_Delay(1); } }这样呼吸周期大约是2秒。但用HAL_Delay会阻塞CPU,实际项目里更推荐用定时器中断或者RTOS任务来更新CCR值。
呼吸灯的平滑度取决于两个因素:一是占空比分辨率,二是更新频率。分辨率不够,低亮度区会跳变;更新频率太低,亮度变化会卡顿。一般每1ms到10ms更新一次CCR,配合0.1%以上的分辨率,效果就很顺滑。
实操心得:人眼对亮度的感知是非线性的,近似对数关系。所以线性增加占空比,人眼看到的亮度变化是先快后慢。如果想让呼吸灯看起来更均匀,可以对占空比做伽马校正,或者用查表法输出预校正的亮度曲线。这个技巧在高端LED调光里很常用。
6. 直流电机调速实操:从转起来到稳下来
6.1 电机驱动电路的基本结构
单片机GPIO的电流能力通常只有几十毫安,驱动不了直流电机。电机启动电流可能是额定电流的几倍甚至十几倍,所以必须加驱动电路。常见的方案有:
- 三极管驱动:适合小功率电机,一个NPN三极管加一个续流二极管就行。
- MOSFET驱动:适合中大功率电机,导通电阻小,效率高。
- H桥驱动:适合需要正反转的场景,比如L298N、DRV8833、DRV8323等驱动芯片。
不管哪种方案,续流二极管都是必须的。电机是感性负载,断电瞬间会产生反向电动势,没有续流回路的话,这个高压会击穿驱动管。所以二极管要反并联在电机两端,方向是截止方向。
6.2 PWM频率对电机噪音和发热的影响
电机调速的PWM频率选择,比LED要讲究一些。频率太低,电机会有可听啸叫,而且转矩脉动大,低速时可能转不起来。频率太高,MOSFET开关损耗增加,驱动芯片发热,而且电机绕组的集肤效应和涡流损耗也会上升。
实际经验是:1kHz到5kHz区间,电机会有明显的啸叫;10kHz到16kHz区间,啸叫基本听不到,但驱动芯片会温热;20kHz以上,啸叫完全没有,但驱动芯片发热明显,效率下降。
所以常见的折中是10kHz到16kHz。如果对噪音要求极高,可以用20kHz以上,但要选低栅极电荷的MOSFET和强驱动能力的驱动芯片。如果对发热要求极高,可以用1kHz以下,但要接受啸叫和转矩脉动。
| PWM频率 | 噪音 | 发热 | 低速性能 | 适用场景 |
|---|---|---|---|---|
| 1kHz以下 | 明显啸叫 | 低 | 一般 | 对噪音不敏感、大功率 |
| 1k~5kHz | 有啸叫 | 较低 | 较好 | 普通调速 |
| 10k~16kHz | 轻微 | 中等 | 好 | 常用折中 |
| 20kHz以上 | 无 | 较高 | 好 | 静音要求高 |
6.3 占空比与转速的非线性关系
理论上占空比和平均电压是线性的,但转速和平均电压不是严格线性的。原因有几个:
第一,电机有启动死区。占空比太低时,平均电压不足以克服静摩擦和负载转矩,电机不转。这个死区通常在5%到20%之间,取决于电机和负载。
第二,电机有反电动势。转速越高,反电动势越大,实际电流越小,转矩越小。所以占空比增加到一定程度后,转速上升变缓。
第三,负载特性。如果负载是风扇,转矩随转速平方增加,那么占空比和转速的关系会更陡。
所以实际调电机的时候,不能简单地认为占空比50%就是半速。要用编码器或者测速发电机做闭环,或者至少在实际负载下测一条占空比-转速曲线,用查表法来设定。
实操心得:调电机的时候,先在空载下测一条曲线,再在额定负载下测一条,两条曲线对比就能看出负载对转速的影响。如果应用对转速精度要求高,建议直接上闭环控制,用PID调占空比,而不是开环给固定占空比。
6.4 电机启动与制动中的PWM策略
电机启动时,如果直接给一个较大的占空比,启动电流会很大,可能触发驱动芯片的过流保护。所以常见的做法是软启动:占空比从0慢慢加到目标值,比如用100ms到500ms的斜坡。
制动的时候,如果直接切断PWM,电机会因为惯性继续转,产生反电动势。如果驱动电路支持,可以用短接制动或者反向PWM制动。短接制动是把电机两端短接,反电动势产生的电流形成制动转矩。反向PWM制动是给一个反向的占空比,产生反向转矩,制动更快但电流更大。
这些策略在电机驱动芯片的手册里通常有推荐,实际用的时候要根据电机参数和保护要求来选。
7. 常见问题与排查技巧实录
7.1 PWM没有输出:从时钟到引脚的排查顺序
PWM配置完了但引脚没波形,是最常见的问题。排查顺序建议从时钟开始,一路查到引脚。
第一步,确认定时器时钟使能了。STM32的定时器挂在不同总线上,TIM1在APB2,TIM2~TIM4在APB1。用HAL库的话,HAL_TIM_PWM_Init里会调用__HAL_RCC_TIMx_CLK_ENABLE,但如果是寄存器配置,要手动使能。
第二步,确认GPIO时钟使能了,而且引脚配置成了复用推挽输出。如果配成了普通推挽输出,定时器的波形出不来。
第三步,确认高级定时器的MOE位使能了。TIM1和TIM8需要TIM_BDTR_MOE置1,否则通道输出被强制关闭。
第四步,确认CCR值不是0或者ARR。CCR=0时占空比0%,CCR=ARR时占空比接近100%但可能有一个计数周期的低电平。如果CCR设成了0,引脚一直是无效电平,看起来就像没输出。
第五步,用示波器或者逻辑分析仪看波形。万用表测PWM只能测平均电压,看不到频率和占空比,排查时不够用。
7.2 电机抖动、啸叫、发热的归因
电机抖动通常和PWM频率太低有关。频率低于电机的机械响应带宽时,每个脉冲都会让电机加速减速一次,表现出来就是抖动。提高频率到10kHz以上通常能解决。
啸叫也是频率问题,但更多是听觉范围内的开关频率引起的。1kHz到5kHz最容易听到。提高到16kHz以上,或者降到20Hz以下(但会抖),可以避开。
发热要分情况。如果是驱动芯片发热,可能是PWM频率太高导致开关损耗大,或者驱动电流不够导致MOSFET工作在放大区。如果是电机发热,可能是占空比太高、电流过大,或者频率太低导致电流纹波大、铜损增加。
| 现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 电机抖动 | PWM频率太低 | 示波器看频率 | 提高到10kHz以上 |
| 电机啸叫 | 频率在听觉范围 | 改变频率试听 | 提到16kHz以上 |
| 驱动芯片发热 | 开关损耗大 | 测驱动波形上升下降时间 | 降频或换低Qg MOSFET |
| 电机发热 | 电流纹波大 | 测电流波形 | 提高频率或加电感 |
| 低速不转 | 启动死区 | 逐步增加占空比 | 软启动或闭环 |
7.3 占空比与实际亮度/转速不符的排查
有时候设了50%占空比,但LED亮度明显不是半亮,或者电机转速明显不是半速。原因可能有几个:
一是有效电平搞反了。如果电路是低电平点亮,而PWM配置的是高电平有效,那么占空比50%实际输出的是50%低电平,亮度关系就反了。
二是驱动电路的延迟。如果驱动芯片的上升下降时间占PWM周期的比例较大,实际占空比会偏离设定值。比如周期100us,上升时间10us,那么实际高电平时间会比设定值少10us,占空比偏低。
三是负载的非线性。LED的亮度和电流不是严格线性,电机转速和电压也不是严格线性。这个前面讲过,需要用查表或者闭环来校正。
四是测量方法的问题。万用表测PWM平均电压,如果万用表的带宽不够,读数会偏低。示波器测的时候,探头衰减和耦合方式也会影响读数。
7.4 PWM背光干扰显示的典型场景
用PWM调LCD背光的时候,有时候会出现屏幕上有横纹、闪烁或者颜色偏。这通常是PWM频率和LCD刷新率产生了拍频,或者PWM的开关噪声耦合到了显示信号线上。
解决办法有几个:一是把PWM频率提高到20kHz以上,远离LCD刷新率和人眼感知范围。二是把背光PWM的走线和显示信号线分开,减少耦合。三是在背光驱动电路上加LC滤波,把PWM波形平滑成近似直流。四是如果背光驱动芯片支持,用模拟调光代替PWM调光,彻底避开开关噪声。
这个坑在嵌入式显示项目里很常见,尤其是用ILI9341这类并口屏的时候,背光PWM和SPI或者并口信号线挨得近,很容易互相干扰。
8. 从能用到好用:几个进阶方向
8.1 用DMA自动更新占空比
如果要做复杂的PWM波形,比如呼吸灯的多段曲线、电机的S形加减速,手动在中断里改CCR会比较占CPU。STM32的定时器支持DMA请求,可以在每次更新事件时自动从内存搬一个CCR值到寄存器,实现无CPU干预的波形输出。
配置方法是把CCR的地址设为DMA目标,内存里放一个波形数组,DMA循环模式打开,定时器的更新事件触发DMA传输。这样CPU只需要准备波形数据,剩下的交给DMA和定时器硬件。
这个技巧在LED灯效、电机驱动、音频输出里都很有用,能大幅降低CPU占用。
8.2 互补PWM与死区在电机驱动中的应用
驱动H桥的时候,上下管不能同时导通。用高级定时器的互补输出功能,可以硬件生成一对互补波形,并自动插入死区。这样CPU只需要给一个占空比,硬件负责生成带死区的互补信号,安全又省事。
死区时间要按MOSFET的关断时间和驱动电路的延迟来算。一般取关断时间加一点余量,比如MOSFET关断时间100ns,死区取200ns到500ns。太短了保护不够,太长了损失有效占空比。
8.3 闭环调速:编码器加PID
开环PWM调速的精度受负载、温度、电压影响很大。要稳,就得加编码器做闭环。编码器测转速,和设定值比较,误差经过PID运算,输出PWM占空比。这样负载变化时,PID会自动调整占空比,保持转速稳定。
PID参数整定是个经验活。先调P,让系统有响应但不振荡;再加I,消除稳态误差;最后加D,抑制超调。如果系统惯量大,P和I要小一点;如果要求响应快,P和D可以大一点。实际调的时候用示波器看转速波形,边调边看。
这个方向展开能写很多,但核心就是:PWM提供执行手段,编码器提供反馈,PID提供决策。三者配合,才能从“能转”做到“转得稳”。
8.4 PWM故障保护与监控
在一些安全相关的应用里,PWM输出需要故障保护。比如电机堵转时,电流会急剧上升,如果不及时关断PWM,驱动管和电机都可能烧掉。STM32的高级定时器支持刹车输入(BKIN),检测到故障信号时硬件自动关闭PWM输出,不需要CPU干预。
配置方法是把BKIN引脚接到过流检测电路的输出,使能刹车功能,设置刹车后的输出状态(高阻、低电平或高电平)。这样一旦过流,硬件在几百纳秒内就能关断PWM,比软件中断快得多。
这个功能在电机驱动、开关电源里很关键,属于“平时用不到,用到时救命”的设计。
9. 我个人在实际操作中的几点体会
PWM这个东西,原理简单,但真正用好,细节都在参数选择和场景适配上。我刚开始调电机的时候,觉得占空比50%就是半速,结果实测发现低速区根本不转,高速区又非线性,后来老老实实测了曲线才搞明白。调LED的时候,觉得频率随便设,结果用手机一拍全是条纹,才知道频率和摄像头采样率还有关系。
踩过几次坑之后,我的习惯是:第一,任何PWM应用,先用示波器看波形,确认频率、占空比、上升下降时间都符合预期,再往下调。第二,电机和LED的参数不要凭感觉,实测一条曲线比什么都靠谱。第三,遇到抖动、啸叫、发热,先查频率,再查驱动,最后查负载,这个顺序能解决大部分问题。
还有一个小技巧:如果手头没有示波器,可以用单片机自己的定时器输入捕获功能来测PWM频率和占空比,虽然精度不如示波器,但排查基本问题够用了。这个方法在只有一块开发板的时候特别实用。
PWM的扩展方向很多,从简单的调光调速,到复杂的闭环控制、故障保护、DMA波形生成,每一个方向都有足够的深度可以挖。但不管走多远,核心始终是那个公式:平均量等于峰值乘以占空比。把这个想透了,剩下的都是工程实现的问题。