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基本原理 占空比概念 RPi.GPIO PWM类 软硬件PWM区别 0%-100%范围 平均电压计算 start()/ChangeDutyCycle() CPU占用对比 频率精度 LED呼吸灯效果实现 关键要点: • 占空比决定平均电压,频率决定控制平滑度 • 软件PWM适合简单应用,硬件PWM适合高精度场景

这张图把PWM的知识点都串起来了。从基本原理出发,分支出占空比、库使用、软硬件区别三个方向,最后汇聚到呼吸灯这个实际应用。我个人觉得,理解了这个结构,PWM就算入门了。

调试建议:先用万用表测GPIO引脚电压,确认PWM输出正常。占空比50%时,万用表读数应该是1.65V左右。如果偏差太大,检查频率设置是否合理。

好了,PWM这部分就讲到这里。呼吸灯只是开胃菜,后面我们会用PWM控制电机速度,那才是真正的实战。记住:占空比是灵魂,频率是骨架,两者配合好了,PWM就能玩出花来。