3. PWM脉宽调制:从呼吸灯理解占空比
PWM这东西,说白了就是通过快速开关信号,让设备感觉到的平均电压在变化。我最早接触PWM是在做LED调光项目时,那时候还不理解为什么灯能“呼吸”——后来才明白,这背后全是占空比在作怪。
3.1 PWM基本原理
PWM的全称是Pulse Width Modulation,脉宽调制。它的核心思想很简单:在一个固定周期内,控制高电平持续的时间比例。
你想想看,如果周期是1ms,高电平占了0.5ms,那平均电压就是3.3V × 0.5 = 1.65V。LED接上去,亮度就是一半。这就是PWM的精髓——用数字信号模拟模拟效果。
核心公式:
占空比 = 高电平时间 / 周期 × 100%
平均电压 = 峰值电压 × 占空比
我在项目中遇到过一个问题:用PWM驱动电机时,频率选得太低,电机就会“咔咔”响。后来才意识到,人耳能听到20Hz-20kHz,PWM频率低于20kHz就会产生可闻噪声。嗯,这个坑我踩过。
3.2 占空比概念详解
占空比是PWM最核心的参数。它决定了输出信号的有效值。我习惯用百分比来表示,0%就是一直低电平,100%就是一直高电平。
| 占空比 | 高电平时间 | 平均电压(3.3V系统) | LED亮度 |
|---|---|---|---|
| 0% | 0μs | 0V | 熄灭 |
| 25% | 250μs | 0.825V | 微亮 |
| 50% | 500μs | 1.65V | 半亮 |
| 75% | 750μs | 2.475V | 较亮 |
| 100% | 1000μs | 3.3V | 最亮 |
这里要注意:占空比变化时,频率保持不变。频率决定了PWM的“细腻程度”,频率越高,控制越平滑,但CPU开销也越大。
3.3 RPi.GPIO的PWM类使用
树莓派的RPi.GPIO库提供了PWM类,用起来非常方便。我个人习惯先初始化GPIO,再创建PWM对象,最后设置占空比和频率。
import RPi.GPIO as GPIO
import time
# 设置GPIO模式
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
# 创建PWM对象,频率1000Hz
pwm = GPIO.PWM(18, 1000)
# 启动PWM,初始占空比0%
pwm.start(0)
try:
while True:
# 从0%到100%渐变
for duty in range(0, 101, 5):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
# 从100%到0%渐变
for duty in range(100, -1, -5):
pwm.ChangeDutyCycle(duty)
time.sleep(0.1)
except KeyboardInterrupt:
pass
# 清理
pwm.stop()
GPIO.cleanup()
小技巧:ChangeDutyCycle()的参数是浮点数,可以精确到小数点后一位。但实际硬件精度有限,别太较真。
3.4 软件PWM与硬件PWM区别
这个问题我经常被问到。简单说:软件PWM靠CPU定时器中断模拟,硬件PWM由芯片内部专用模块生成。
| 特性 | 软件PWM | 硬件PWM |
|---|---|---|
| 实现方式 | CPU中断+GPIO翻转 | 专用PWM模块 |
| 频率精度 | 受系统负载影响 | 稳定,不受CPU影响 |
| 最大频率 | 通常<10kHz | 可达MHz级别 |
| CPU占用 | 高,频繁中断 | 几乎为零 |
| 同时输出路数 | 受定时器数量限制 | 取决于硬件设计 |
| 抖动 | 较大,不稳定 | 极小,稳定 |
我曾经在一个项目中用软件PWM驱动4个舵机,结果CPU负载飙到80%,系统响应变得很慢。后来换成硬件PWM,CPU负载直接降到5%以下。所以,能上硬件PWM就别用软件。
注意:树莓派的RPi.GPIO库默认使用软件PWM。如果需要硬件PWM,得用pigpio库或者直接操作寄存器。别指望RPi.GPIO能给你硬件级别的精度。
3.5 LED呼吸灯效果实现
呼吸灯是PWM最经典的应用。原理就是让占空比按正弦波或三角波变化,LED亮度随之平滑过渡。
我习惯用三角波方式,代码简单,效果也够用。上面那段代码就是三角波呼吸灯——占空比从0线性增加到100,再线性减少到0。
如果想更自然,可以用正弦波。人眼对亮度变化不是线性的,正弦波会让呼吸更柔和。
import RPi.GPIO as GPIO
import time
import math
GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.OUT)
pwm = GPIO.PWM(18, 1000)
pwm.start(0)
try:
while True:
for i in range(0, 360, 5):
# 正弦波映射到0-100
duty = 50 + 50 * math.sin(math.radians(i))
pwm.ChangeDutyCycle(duty)
time.sleep(0.02)
except KeyboardInterrupt:
pass
pwm.stop()
GPIO.cleanup()
效果对比:
- 三角波呼吸:亮度变化均匀,视觉上“机械感”较强
- 正弦波呼吸:亮度变化先快后慢,更接近自然呼吸
实际调试时,我建议先用示波器看PWM波形。确认频率和占空比正确后,再接LED。我曾经直接接LED,结果发现灯不亮——原来是GPIO引脚搞错了。嗯,这种低级错误谁都会犯。
3.6 PWM知识体系总览
下面这张图把PWM的核心知识点串起来了。从基本原理到实际应用,每一步都有对应关系。
这张图把PWM的知识点都串起来了。从基本原理出发,分支出占空比、库使用、软硬件区别三个方向,最后汇聚到呼吸灯这个实际应用。我个人觉得,理解了这个结构,PWM就算入门了。
调试建议:先用万用表测GPIO引脚电压,确认PWM输出正常。占空比50%时,万用表读数应该是1.65V左右。如果偏差太大,检查频率设置是否合理。
好了,PWM这部分就讲到这里。呼吸灯只是开胃菜,后面我们会用PWM控制电机速度,那才是真正的实战。记住:占空比是灵魂,频率是骨架,两者配合好了,PWM就能玩出花来。