4. PWM输出与模拟执行器:LED呼吸灯、舵机控制、电机调速、PWM频率与分辨率设置

PWM,说白了就是通过快速开关信号,让设备“以为”自己收到了一个模拟电压。我刚开始接触嵌入式时,总觉得这玩意儿挺玄乎——一个方波怎么能控制亮度、角度、转速?后来亲手调了一次参数,才明白其中的门道。

这一章,咱们就围绕ESP32的PWM功能,把LED呼吸灯、舵机控制、直流电机调速这三个典型场景讲透。嗯,频率和分辨率这两个参数,很多人容易搞混,我也会结合项目经验给你避坑。

本章核心知识点

  • ESP32的PWM硬件架构:通道、定时器、占空比
  • LED呼吸灯:从亮到暗的平滑过渡
  • 舵机控制:50Hz频率下的角度映射
  • 直流电机调速:频率选择与驱动能力
  • PWM频率与分辨率:两者之间的权衡关系
ESP32 PWM 知识体系 ESP32 PWM 核心 LED 呼吸灯 舵机控制 直流电机调速 占空比渐变 50Hz / 角度映射 频率 / 驱动能力 ⚠ 频率与分辨率:两者不可兼得,需根据应用场景权衡 高频率 → 低分辨率 | 高分辨率 → 低频率

4.1 ESP32的PWM硬件架构

ESP32的PWM模块,其实是一个独立的硬件外设,叫LEDC(LED Controller)。名字虽然叫LED控制器,但它能干的事远不止控制LED。我个人习惯把它理解成:8个独立的PWM通道,挂在2个定时器上。

硬件资源 数量 说明
高速通道 4个 频率范围:1Hz ~ 40MHz
低速通道 4个 频率范围:1Hz ~ 40MHz
定时器 2个 每个定时器可绑定多个通道
分辨率 1 ~ 20位 位数越高,占空比调节越精细

这里有个关键点:同一个定时器下的所有通道,共享相同的频率和分辨率。我在项目中遇到过一个问题——想用两个通道分别输出50Hz和1000Hz,结果发现它们挂在同一个定时器上,频率只能一样。后来我把它们分到不同的定时器,才解决了。

小技巧:ESP32的LEDC库提供了 ledcSetup() 和 ledcAttachPin() 两个核心函数。配置时先 setup 定时器,再 attach 引脚。顺序别搞反了,否则会报错。

4.2 LED呼吸灯:从亮到暗的平滑过渡

呼吸灯是PWM最直观的应用。说白了,就是让占空比从0%逐渐增加到100%,再逐渐降回0%。人眼对亮度的感知不是线性的,但咱们先不管伽马校正,先把效果做出来。

// LED呼吸灯示例代码
const int ledPin = 2;      // GPIO2,板载LED
const int freq = 5000;     // 5kHz频率
const int ledChannel = 0;  // 通道0
const int resolution = 8;  // 8位分辨率,0-255

void setup() {
  ledcSetup(ledChannel, freq, resolution);
  ledcAttachPin(ledPin, ledChannel);
}

void loop() {
  // 渐亮
  for (int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
    ledcWrite(ledChannel, dutyCycle);
    delay(10);
  }
  // 渐暗
  for (int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) {
    ledcWrite(ledChannel, dutyCycle);
    delay(10);
  }
}

这段代码跑起来,LED会像呼吸一样慢慢变亮再慢慢变暗。delay(10) 控制的是过渡速度,你可以根据实际效果调整。我曾经把 delay 设成1ms,结果呼吸太快,看起来像在闪;设成50ms又太慢,像在打瞌睡。10ms是个不错的起点。

注意:ESP32的LEDC库在 ledcWrite() 时,占空比范围取决于分辨率。8位分辨率对应0-255,10位对应0-1023。如果你设了10位却只写0-255,那LED永远只能亮到25%——我踩过这个坑。

4.3 舵机控制:50Hz频率下的角度映射

舵机是PWM控制的经典案例。标准舵机要求PWM频率为50Hz(周期20ms),占空比在0.5ms到2.5ms之间,对应0°到180°。你想想看,这其实就是一个线性映射问题。

角度 脉宽 占空比(50Hz) 8位分辨率值
0° 0.5ms 2.5% 6
90° 1.5ms 7.5% 19
180° 2.5ms 12.5% 32

看到没?8位分辨率下,0°到180°只用了6到32这27个值。这意味着角度分辨率只有180/27 ≈ 6.7°,精度很差。我建议用10位或更高分辨率,让舵机转动更平滑。

// 舵机控制示例代码
const int servoPin = 13;
const int freq = 50;          // 50Hz
const int servoChannel = 1;
const int resolution = 10;    // 10位分辨率,0-1023

void setup() {
  ledcSetup(servoChannel, freq, resolution);
  ledcAttachPin(servoPin, servoChannel);
}

void setServoAngle(int angle) {
  // 将角度映射到占空比
  // 0° -> 0.5ms -> 占空比 2.5% -> 1023 * 0.025 ≈ 26
  // 180° -> 2.5ms -> 占空比 12.5% -> 1023 * 0.125 ≈ 128
  int duty = map(angle, 0, 180, 26, 128);
  ledcWrite(servoChannel, duty);
}

void loop() {
  setServoAngle(0);
  delay(1000);
  setServoAngle(90);
  delay(1000);
  setServoAngle(180);
  delay(1000);
}

避坑指南:我曾经用8位分辨率控制舵机,结果舵机在0°附近抖动得厉害。原因是占空比变化太粗,舵机内部的控制电路无法稳定锁定。换成10位分辨率后,问题立刻解决。所以,控制舵机时分辨率别低于10位。

4.4 直流电机调速:频率选择与驱动能力

直流电机调速,本质上也是PWM控制。但和LED、舵机不同,电机是感性负载,对PWM频率有额外要求。频率太低,电机会发出刺耳的啸叫声;频率太高,驱动管的开关损耗会增大。

我个人习惯这样选频率:

  • 小型直流电机:1kHz ~ 10kHz,兼顾噪音和效率
  • 带减速箱的电机:500Hz ~ 2kHz,低频扭矩更大
  • 无刷电机(BLDC):10kHz ~ 20kHz,需要配合专用驱动

这里要特别提醒:ESP32的GPIO直接驱动电机是不行的。GPIO输出电流只有12mA左右,而电机启动电流可能达到几百毫安。你需要加一个驱动电路,比如L298N、L9110S或者MOS管。

// 直流电机调速示例(使用L9110S驱动)
const int motorPinA = 12;   // 电机A控制引脚
const int motorPinB = 14;   // 电机B控制引脚(PWM)
const int freq = 5000;      // 5kHz
const int motorChannel = 2;
const int resolution = 8;

void setup() {
  pinMode(motorPinA, OUTPUT);
  digitalWrite(motorPinA, LOW);  // 方向控制,LOW为正转
  
  ledcSetup(motorChannel, freq, resolution);
  ledcAttachPin(motorPinB, motorChannel);
}

void loop() {
  // 从0加速到最大
  for (int speed = 0; speed <= 255; speed++) {
    ledcWrite(motorChannel, speed);
    delay(20);
  }
  delay(2000);
  
  // 减速到0
  for (int speed = 255; speed >= 0; speed--) {
    ledcWrite(motorChannel, speed);
    delay(20);
  }
  delay(2000);
}

重要警告:电机启动瞬间电流很大,如果电源供电不足,会导致ESP32复位。我遇到过好几次——电机一转,ESP32就重启。后来在电机电源端加了470μF电解电容,才稳住。另外,PWM频率不要低于1kHz,否则电机会发出人耳可闻的噪音,很烦人。

4.5 PWM频率与分辨率:两者之间的权衡

这是本章最核心的概念。ESP32的LEDC模块有一个硬性限制:频率越高,可用的分辨率越低。为什么?因为PWM的时钟源是固定的(通常80MHz),频率和分辨率共享这个时钟。

计算公式很简单:

PWM频率 = 时钟源频率 / (2^分辨率)

举个例子:

  • 80MHz时钟,8位分辨率(256级)→ 最大频率 = 80M / 256 ≈ 312.5kHz
  • 80MHz时钟,10位分辨率(1024级)→ 最大频率 = 80M / 1024 ≈ 78.1kHz
  • 80MHz时钟,16位分辨率(65536级)→ 最大频率 = 80M / 65536 ≈ 1.22kHz
应用场景 推荐频率 推荐分辨率 说明
LED呼吸灯 1kHz ~ 10kHz 8位 ~ 10位 人眼对高频不敏感
舵机控制 50Hz 10位 ~ 12位 频率固定,分辨率越高越好
直流电机 1kHz ~ 20kHz 8位 ~ 10位 兼顾噪音和扭矩
音频输出 20kHz ~ 40kHz 8位 ~ 10位 需要避开人耳听觉范围

我在做一个小型四轴飞行器时,需要同时输出4路PWM控制无刷电机。当时选了16kHz频率和10位分辨率,结果发现ESP32的LEDC模块最多只能支持到8位分辨率。后来查了手册才明白——频率和分辨率是反比关系。嗯,从那以后我每次配置PWM都会先算一下这个公式。

经验总结:配置PWM时,先确定应用需要的频率,再根据公式反推最大可用分辨率。如果分辨率不够,要么降低频率,要么换用更高精度的外部PWM芯片。ESP32的LEDC虽然灵活,但也有物理极限。

好了,这一章的内容就到这里。PWM看似简单,但频率和分辨率的权衡、不同负载的特性、驱动电路的选择,都是实际项目中容易出问题的地方。动手试试呼吸灯和舵机控制,你会发现——理论懂了,代码写了,但真正调通还是需要一点耐心的。