3、PWM(脉宽调制)原理:用Python控制LED亮度

各位同学,今天我们来聊聊PWM。说白了,这就是个「假装模拟」的数字把戏。

你想想看,单片机引脚只能输出高电平(3.3V或5V)和低电平(0V)。那怎么让LED半亮呢?直接给一半电压?不行,数字电路没这功能。

嗯,这里有个巧妙的办法——快速开关。让LED在极短的时间内,一会儿全亮,一会儿全灭。只要开关频率够快,人眼就反应不过来,看到的亮度就是「平均亮度」。

我在项目中遇到过这种情况:用普通IO口控制LED呼吸灯,频率设低了,肉眼能看到闪烁,特别掉档次。后来把频率提到1kHz以上,效果就顺滑了。

3.1 PWM的三个核心参数

要理解PWM,记住三个词就够了:

  • 频率(Frequency):一秒内开关多少次。单位Hz。频率太低会闪烁,太高浪费算力。
  • 周期(Period):一次开关的总时间。周期 = 1 / 频率。
  • 占空比(Duty Cycle):高电平时间占整个周期的百分比。0%就是全灭,100%就是全亮。

举个例子:频率1kHz,周期就是1ms。如果占空比50%,那就是0.5ms高电平,0.5ms低电平。LED看起来就是半亮。

核心公式:

平均电压 = 高电平电压 × 占空比

比如5V系统,占空比30%,平均电压就是5 × 0.3 = 1.5V

3.2 用Python生成PWM信号

我建议用Python的 time 模块来模拟PWM。虽然实际嵌入式开发会用硬件PWM模块,但用软件模拟能帮你彻底搞懂原理。

来看代码:

import time
import RPi.GPIO as GPIO  # 树莓派GPIO库,没有硬件也能学原理

# 配置引脚
LED_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)

def software_pwm(pin, frequency, duty_cycle, duration):
    """
    软件模拟PWM
    :param pin: GPIO引脚号
    :param frequency: PWM频率(Hz)
    :param duty_cycle: 占空比(0-100)
    :param duration: 运行时间(秒)
    """
    period = 1.0 / frequency
    high_time = period * (duty_cycle / 100.0)
    low_time = period - high_time
    
    cycles = int(duration * frequency)
    for i in range(cycles):
        GPIO.output(pin, GPIO.HIGH)
        time.sleep(high_time)
        GPIO.output(pin, GPIO.LOW)
        time.sleep(low_time)

# 让LED以50%占空比呼吸5秒
software_pwm(LED_PIN, 1000, 50, 5)

GPIO.cleanup()

小提示: 实际用的时候,time.sleep() 精度有限。高频PWM(比如10kHz以上)建议用硬件PWM模块。我在做汽车尾灯项目时,用的就是STM32的硬件定时器,精度到纳秒级。

3.3 控制LED亮度:从暗到亮

理解了基础,我们来做个呼吸灯效果——LED从暗到亮,再从亮到暗,循环变化。

说白了就是不断改变占空比。从0%慢慢增加到100%,再降回来。

import time
import RPi.GPIO as GPIO

LED_PIN = 18
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)

def breathing_led(pin, frequency=1000, step=2, delay=0.02):
    """
    呼吸灯效果
    :param step: 每次占空比变化量
    :param delay: 每步间隔时间
    """
    while True:
        # 从暗到亮
        for duty in range(0, 101, step):
            software_pwm(pin, frequency, duty, delay)
        # 从亮到暗
        for duty in range(100, -1, -step):
            software_pwm(pin, frequency, duty, delay)

# 注意:这里复用了上面的software_pwm函数
# 实际项目中建议用线程或异步方式,避免阻塞

try:
    breathing_led(LED_PIN)
except KeyboardInterrupt:
    GPIO.cleanup()

避坑指南: 我曾经在调试时发现LED亮度变化不平滑,检查半天发现是 step 设太大了。步长设小一点(比如1或2),变化会更细腻。另外,delay 值决定了呼吸速度,别设太小,否则人眼跟不上。

3.4 硬件PWM vs 软件PWM

做课程设计时用软件PWM没问题,但实际产品中,我强烈建议用硬件PWM。为什么?

对比项 软件PWM 硬件PWM
精度 受系统调度影响,不稳定 定时器硬件控制,精准
频率上限 一般不超过10kHz 可达MHz级别
CPU占用 高,一直占用CPU 几乎不占CPU
多通道支持 难,需要多线程 轻松支持多路
适用场景 学习、低频控制 工业、汽车、高频

你想想看,汽车尾灯流水效果里,每个LED都要独立控制亮度。如果用软件PWM,CPU得忙死。硬件PWM模块一启动,CPU就可以去干别的事了。

3.5 实际项目中的PWM配置建议

以汽车尾灯为例,我一般这样配置:

  • 频率: 200Hz ~ 1kHz。太低会闪烁,太高LED驱动芯片可能响应不过来。
  • 占空比分辨率: 8位(0-255)或10位(0-1023)。8位够用,10位更细腻。
  • 渐变步长: 每次变化1-3个占空比单位,配合20-50ms的延时。

记住: PWM的精髓不是「调电压」,而是「调时间比例」。这个思路在电机控制、舵机控制、音频输出等领域都能用。

好了,这一章就到这里。下一章我们会把PWM和流水灯结合起来,做出真正的「呼吸流水」效果。到时候你就知道,为什么汽车尾灯看起来那么有质感了。