脉冲宽度调制 (PWM)
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。
- MCPWM (Motor Control PWM) 外设: 专门用于电机控制,包含死区控制等高级功能。其接口未被包含在标准的 Arduino ESP32 核心库中。
在 Arduino ESP32 编程环境中,主要通过以下两种方式来利用 LEDC 外设产生 PWM 信号:
analogWrite(pin, value)
函数: 这是 Arduino 平台的标准 PWM 函数,ESP32 对其提供了良好支持,使用便捷。- LEDC API 函数: 一系列如
ledcAttach()
,ledcWrite()
,ledcAttachChannel()
的函数,允许对 PWM 参数进行更细致和灵活的配置。
本节将通过控制外部 LED 的亮度来演示这两种 PWM 实现方法。
3. 搭建电路
需要使用的器件有:
- LED * 1
- 330Ω 电阻 * 1
- 面包板 * 1
- 导线
- ESP32 开发板
按照下面接线图连接电路:
ESP32-S3-Zero 引脚图

4. 使用 analogWrite()
的 ESP32 PWM 示例代码
const int ledPin = 7; // LED 连接的引脚号
void setup() {
}
void loop() {
// 亮度增加 (占空比值从 0 到 255)
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
analogWrite(ledPin, dutyCycle); // 设置 PWM 占空比值
delay(10); // 延时以控制变化速度
}
// 亮度减少 (占空比值从 255 到 0)
for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) {
analogWrite(ledPin, dutyCycle); // 设置 PWM 占空比值
delay(10); // 延时以控制变化速度
}
}
代码解析
-
对于
analogWrite()
,ESP32 的实现会自动处理引脚的 PWM 初始化,因此通常不需要显式调用pinMode()
。 -
loop()
函数:-
第一个
for
循环:for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++)
: 变量dutyCycle
从 0 递增至 255。 -
第二个
for
循环:for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--)
变量dutyCycle
从 255 递减至 0,使 LED 亮度逐渐减弱。 -
analogWrite(ledPin, dutyCycle);
:将当前
dutyCycle
值(0-255)设为ledPin
的 PWM 占空比,使 LED 亮度逐渐增加。提示在使用
analogWrite()
时,ESP32 会自动管理 LEDC 通道的分配,并设置默认的频率 和 8 位分辨率。 -
delay(10);
:短暂延时,用于控制亮度变化速率。
-
5. 使用 LEDC API 的 ESP32 PWM 示例代码
const int ledPin = 7; // LED 连接的 GPIO 引脚
const int frequency = 5000; // PWM 频率 5kHz
const int resolution = 8; // PWM 分辨率 8 位(0-255)
const int ledChannel = 0; // PWM 通道号
void setup() {
// 初始化 LED PWM 功能
ledcAttach(ledPin, frequency, resolution);
// 如果你想指定通道
// ledcAttachChannel(ledPin, frequency,resolution,ledChannel)
}
void loop() {
// 逐渐增加占空比,LED 变亮
for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
ledcWrite(ledPin, dutyCycle); // 设置 PWM 占空比值
delay(10); // 延时 10ms,控制变化速度
}
// 逐渐减少占空比,LED 变暗
for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) {
ledcWrite(ledPin, dutyCycle); // 设置 PWM 占空比值
delay(10); // 延时 10ms,控制变化速度
}
}
代码解析
-
ledcAttach(ledPin, frequency, resolution);
ledPin
:指定要用作 PWM 输出的 GPIO 引脚frequency
:PWM 信号的频率,单位为赫兹 (Hz)resolution
:分辨率位数,决定占空比的精度
-
ledcWrite(ledPin, dutyCycle);
:- 设置 LEDC 引脚
ledPin
的占空比为dutyCycle
。 - 数值范围取决于分辨率设置:8 位为 0-255(2⁸-1)
- 设置 LEDC 引脚
6. 拓展
请尝试实现:通过调节电位器旋钮来控制 LED 灯的亮度,使旋钮位置与灯光亮度相对应
接线图:

代码:
const int ledPin = 7; // LED 连接的引脚
const int potentiometerPin = 8; // 电位器连接的引脚
int potentiometerValue; // 存储电位器读取值
int brightness; // 存储映射后的亮度值
void setup() {
}
void loop() {
// 1. 读取电位器的模拟值 (范围 0-4095)
potentiometerValue = analogRead(potentiometerPin);
// 2. 将 ADC 读数映射到 PWM 的范围 (0-255)
brightness = map(potentiometerValue, 0, 4095, 0, 255);
// 3. 用映射后的值设置 LED 亮度
analogWrite(ledPin, brightness);
delay(20);
}
代码解析:
-
map(potValue, 0, 4095, 0, 255);
:将电位器的 0-4095 范围线性映射到 PWM 的 0-255 范围,实现电位器旋转角度与 LED 亮度的对应关系。
语法为:
map(value, fromLow, fromHigh, toLow, toHigh)
value
:要转换的输入值(这里是电位器读数)fromLow, fromHigh
:输入范围(0 到 4095)toLow, toHigh
:输出范围(0 到 255)
工作原理:
map()
函数按比例转换数值。例如:- 电位器值 0(0%位置)→ LED 亮度 0(0% 亮度)
- 电位器值 2047(50%位置)→ LED 亮度 127(50% 亮度)
- 电位器值 4095(100%位置)→ LED 亮度 255(100% 亮度)
简单来说,电位器转到哪个位置,LED 就对应那个亮度。