脉冲宽度调制 (PWM)
本节介绍脉冲宽度调制 (PWM) 的基本概念,并演示如何使用 MicroPython 控制 ESP32 的 PWM 功能来调节 LED 的亮度以及实现呼吸灯效果。
1. 什么是 PWM?
PWM(Pulse Width Modulation,脉冲宽度调制) 是一种数字信号调制技术,用于“模拟”连续变化的电压。
当需要控制设备输出的“强度”(例如调节 LED 亮度或电机转速),而不仅仅是简单的开/关状态时,PWM (Pulse Width Modulation,脉冲宽度调制) 技术是一种有效的解决方案。
PWM 信号本质上仍然是数字信号,只有 HIGH 和 LOW 两种状态,但通过控制高电平持续的时间比例,可以实现类似模拟输出的效果。

PWM 的核心概念包括:
-
占空比 (Duty Cycle): 在一个 PWM 周期内,高电平(信号为 HIGH)持续的时间占整个周期的百分比。占空比直接影响输出的平均功率或强度。
不同占空比产生不同的平均电压效果:
- 占空比 0%:平均电压 ≈ 0V
- 占空比 25%:平均电压 ≈ 0.825V (3.3V * 0.25)
- 占空比 50%:平均电压 ≈ 1.65V (3.3V * 0.50)
- 占空比 75%:平均电压 ≈ 2.475V (3.3V * 0.75)
- 占空比 100%:平均电压 ≈ 3.3V
-
频率 (Frequency): PWM 信号每秒钟重复一个完整周期的次数,单位是赫兹 (Hz)。选择合适的频率对于应用至关重要:对于 LED 调光,频率需足够高以避免人眼察觉闪烁;对于电机控制,频率会影响效率和噪音。
2. ESP32 上的 PWM 实现
ESP32 集成了专门的硬件模块用于生成 PWM 信号:
- LEDC (LED Control) 外设: ESP32 的主要 PWM 生成器。虽然名为 LED 控制,但可产生通用的 PWM 信号。根据具体芯片型号,拥有 6 到 16 个独立通道,频率范围 1Hz-40MHz。
在 MicroPython 中,这一功能通过 machine.PWM 类进行封装和调用。
3. 搭建电路
需要使用的器件有:
- LED * 1
- 330Ω 电阻 * 1
- 面包板 * 1
- 导线
- ESP32 开发板
按照下面接线图连接电路:
ESP32-S3-Zero 引脚图


4. REPL 交互
在 REPL 中逐行输入以下命令进行测试:
-
导入库并初始化 PWM 对象:
from machine import Pin, PWM
# 将 GPIO 7 配置为 PWM 输出
led_pwm = PWM(Pin(7))查看默认的 PWM 参数,默认频率为 5000Hz,占空比为 32768(约 50%):
print(led_pwm)也可以通过
freq()和duty_u16()方法获取当前的 PWM 参数。 -
设置频率: 设置频率为 10Hz。可观察到 LED 闪烁。
led_pwm.freq(10)设置频率为 5000Hz。由于人眼暂留效应,可观察到 LED 亮度稳定。
led_pwm.freq(5000) -
设置占空比:
使用
duty_u16方法设置亮度。范围是 0 到 65535。-
设置为最亮 (65535):
led_pwm.duty_u16(65535) -
设置为微亮 (1000):
led_pwm.duty_u16(1000) -
关闭 LED (0):
led_pwm.duty_u16(0)
使用
duty方法设置亮度。范围是 0 到 1023。-
设置为最亮 (1023):
led_pwm.duty(1023) -
设置为中间亮度 (512):
led_pwm.duty(512) -
设置为关闭 (0):
led_pwm.duty(0)
-
5. 示例:LED 呼吸灯
下面的代码将实现“呼吸灯”效果:LED 亮度会从熄灭逐渐变亮,再从最亮逐渐变暗,循环往复。
import time
from machine import Pin, PWM
# 5000 Hz 对于 LED 调光是足够平滑的频率
FREQUENCY = 5000
# 定义 LED 连接的引脚 (GPIO 7)
LED_PIN = 7
# 创建 PWM 对象
led_pwm = PWM(Pin(LED_PIN),freq=FREQUENCY,duty_u16=0)
while True:
# 亮度逐渐增加 (渐亮)
for duty in range(0, 65536, 1000):
led_pwm.duty_u16(duty) # 应用当前的占空比
time.sleep_ms(10) # 短暂延时,控制呼吸速度
# 亮度逐渐减小 (渐暗)
for duty in range(65535, -1, -1000):
led_pwm.duty_u16(duty) # 应用当前的占空比
time.sleep_ms(10) # 短暂延时,控制呼吸速度
代码解析
-
PWM(Pin(LED_PIN)): 实例化 PWM 对象。MicroPython 会自动为该引脚分配 ESP32 内部可用的 LEDC 通道。 -
duty_u16(value): 这是控制亮度的关键函数。- 这里的
u16代表 "unsigned 16-bit integer"(无符号 16 位整数)。 - 无论底层的硬件定时器分辨率是多少(ESP32 通常配置为 13 位或 14 位),MicroPython 都会自动将 0-65535 的输入值缩放映射到硬件实际支持的范围内。
0对应 0% 占空比。65535对应 100% 占空比。
- 这里的
-
range()循环: 通过for循环配合time.sleep_ms(),实现了亮度的线性、平滑过渡。调整步长(如1000)或延时时间(如10ms)可以改变呼吸灯的节奏。
6. 拓展
请尝试实现:通过调节电位器旋钮来控制 LED 灯的亮度,使旋钮位置与灯光亮度相对应
接线图:

代码:
import time
from machine import Pin, PWM, ADC
# 5000 Hz 对于 LED 调光是足够平滑的频率
FREQUENCY = 5000
# 定义引脚
LED_PIN = 7 # LED 连接的引脚
POT_PIN = 8 # 电位器连接的引脚
# 创建 PWM 对象,关联到 GPIO 7
led_pwm = PWM(Pin(LED_PIN), freq=FREQUENCY, duty_u16=0)
# 初始化 ADC (电位器)
pot = ADC(Pin(POT_PIN))
while True:
# 读取电位器的模拟值
# read_u16() 返回 0 到 65535 之间的整数,由原始数值缩放而来
pot_value = pot.read_u16()
# 设置 LED 亮度
# ADC 读取值范围 (0-65535) 与 PWM 设置值范围 (0-65535) 一致
# 可以直接将读取到的值赋给 PWM
led_pwm.duty_u16(pot_value)
time.sleep_ms(20)
代码解析:
- 统一的 16 位接口 (
u16): MicroPython 的设计在此处得到了体现。MicroPython 在不同硬件平台上都提供read_u16()接口,返回统一的 16 位值(0-65535),无论底层 ADC 是 10 位、12 位还是其他位宽。这让代码更容易移植。ADC.read_u16()将模拟电压(0V-3.3V)读取为 16 位整数 (0-65535)。PWM.duty_u16()接受 16 位整数 (0-65535) 来设置占空比。- 因此,我们可以直接传递 ADC 的读数给 PWM。