4. PWM与模拟输出:LED呼吸灯、电机调速、PWM频率与占空比

说到PWM,我刚开始做嵌入式那会儿,总觉得它就是“让灯忽亮忽暗”的小把戏。直到有一次做直流电机项目,电机嗡嗡响就是不转,我才意识到——PWM这东西,玩不好真会翻车。

说白了,PWM就是通过快速开关信号,让负载“感觉”到的是一个平均电压。你开关得够快,负载就反应不过来,看到的、感受到的就是一个稳定的中间值。

4.1 PWM的核心参数:频率与占空比

这两个参数,是PWM的灵魂。我见过不少新手调呼吸灯,灯闪得跟警灯似的,其实就是频率没设对。

参数 定义 典型范围(ESP32) 影响
频率 每秒开关次数(Hz) 1 Hz ~ 40 MHz 频率太低会闪烁,太高会增加开关损耗
占空比 高电平时间占整个周期的比例 0% ~ 100% 决定等效输出电压的大小
我的经验:LED呼吸灯用1 kHz左右就够了,电机调速建议5 kHz~20 kHz。低于1 kHz人眼能感觉到闪烁,高于20 kHz电机铁损会明显增加。

4.2 ESP32的PWM硬件结构

ESP32的PWM模块叫LEDC(LED Controller),名字虽然叫LED,但驱动电机、舵机完全没问题。它内部有8个通道,每个通道可以独立设置频率和占空比。

ESP32 LEDC PWM 硬件架构 时钟源(APB/REF) 分频器(H/L定时器) 定时器组(0~3) PWM通道(0~7) GPIO输出 关键说明: • 4个定时器(Timer 0~3)共享给8个通道 • 每个定时器独立设置频率,通道只能使用绑定的定时器 • 占空比分辨率:10位(0~1023)或 8位(0~255) • 同一定时器的通道必须使用相同频率,但占空比可以不同
注意:ESP32的LEDC通道虽然多,但定时器只有4个。如果你需要多个不同频率的PWM信号,记得合理分配定时器资源。我踩过这个坑——8个通道全用同一个定时器,结果电机和LED的频率需求冲突,折腾了半天。

4.3 LED呼吸灯实战

呼吸灯是PWM入门的经典项目。原理很简单:让占空比从0逐渐增加到100%,再逐渐减小到0%,循环往复。

#include <driver/ledc.h>

#define LED_GPIO      2
#define LEDC_CHANNEL  LEDC_CHANNEL_0
#define LEDC_TIMER    LEDC_TIMER_0
#define LEDC_FREQ     1000  // 1 kHz
#define LEDC_RESOLUTION LEDC_TIMER_10_BIT  // 10位分辨率

void ledc_init(void) {
    ledc_timer_config_t timer_conf = {
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .timer_num = LEDC_TIMER,
        .duty_resolution = LEDC_RESOLUTION,
        .freq_hz = LEDC_FREQ,
        .clk_cfg = LEDC_AUTO_CLK
    };
    ledc_timer_config(&timer_conf);

    ledc_channel_config_t channel_conf = {
        .channel = LEDC_CHANNEL,
        .duty = 0,
        .gpio_num = LED_GPIO,
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .hpoint = 0,
        .timer_sel = LEDC_TIMER
    };
    ledc_channel_config(&channel_conf);
}

void breath_led(void) {
    for (int duty = 0; duty < 1024; duty++) {
        ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL, duty);
        ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL);
        vTaskDelay(pdMS_TO_TICKS(2));
    }
    for (int duty = 1023; duty >= 0; duty--) {
        ledc_set_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL, duty);
        ledc_update_duty(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL);
        vTaskDelay(pdMS_TO_TICKS(2));
    }
}
效果:LED从暗到亮再到暗,周期约4秒。调整vTaskDelay的延时可以改变呼吸速度。我个人习惯把延时控制在1~5ms之间,太短了CPU占用高,太长了呼吸不平滑。

4.4 直流电机调速实战

电机调速和LED呼吸灯原理一样,但有几个坑要注意。我曾经用PWM直接驱动一个12V电机,结果ESP32的GPIO直接烧了——因为电机启动电流太大。

正确的做法是:用PWM控制MOS管或电机驱动芯片(如L298N、TB6612),再由驱动芯片控制电机。

#define MOTOR_GPIO     4
#define MOTOR_CHANNEL  LEDC_CHANNEL_1
#define MOTOR_TIMER    LEDC_TIMER_1
#define MOTOR_FREQ     5000   // 5 kHz
#define MOTOR_RESOLUTION LEDC_TIMER_8_BIT  // 8位分辨率

void motor_init(void) {
    ledc_timer_config_t timer_conf = {
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .timer_num = MOTOR_TIMER,
        .duty_resolution = MOTOR_RESOLUTION,
        .freq_hz = MOTOR_FREQ,
        .clk_cfg = LEDC_AUTO_CLK
    };
    ledc_timer_config(&timer_conf);

    ledc_channel_config_t channel_conf = {
        .channel = MOTOR_CHANNEL,
        .duty = 0,
        .gpio_num = MOTOR_GPIO,
        .speed_mode = LEDC_HIGH_SPEED_MODE,
        .hpoint = 0,
        .timer_sel = MOTOR_TIMER
    };
    ledc_channel_config(&channel_conf);
}

void motor_set_speed(int percent) {
    if (percent < 0) percent = 0;
    if (percent > 100) percent = 100;
    int duty = (percent * 255) / 100;
    ledc_set_duty(LEDC_HIGH_SPEED_MODE, MOTOR_CHANNEL, duty);
    ledc_update_duty(LEDC_HIGH_SPEED_MODE, MOTOR_CHANNEL);
}
避坑指南:电机启动时,占空比不要从0直接跳到100%。我建议做一个软启动——每50ms增加5%的占空比。这样电机电流不会瞬间飙升,电源也不会被拉垮。

4.5 频率选择的实战经验

频率选不对,项目白费。我整理了一个速查表,你直接照着用就行。

应用场景 推荐频率 分辨率 说明
LED呼吸灯 1 kHz 10位 人眼无闪烁,平滑度高
直流电机调速 5~20 kHz 8位 避免人耳听到啸叫
舵机控制 50 Hz 10位 标准舵机周期20ms
加热器控制 1~10 Hz 8位 热惯性大,低频即可
注意:频率越高,分辨率越低。ESP32的LEDC在40 MHz时钟下,如果设置20 kHz频率,最大分辨率只有11位。你想想看,分辨率不够的话,电机低速时可能一卡一卡的。

4.6 常见问题与故障定位

做PWM项目,我遇到过不少奇奇怪怪的问题。这里列几个典型的,你遇到了可以直接对号入座。

  • LED不亮或一直亮:检查GPIO是否被其他外设占用,或者LEDC通道是否初始化成功。我习惯在初始化后读一下ledc_get_duty()确认值对不对。
  • 电机嗡嗡响但不转:频率太低,人耳能听到。把频率提高到10 kHz以上就好了。
  • 电机转速不稳:电源供电不足。电机启动瞬间电流可能是额定电流的3~5倍,建议在电机驱动板电源端加一个大电容(470 μF以上)。
  • PWM输出波形异常:用逻辑分析仪或示波器看波形。我遇到过定时器配置冲突导致波形错乱的情况,重新分配定时器就解决了。
我的调试习惯:先用一个LED做PWM输出测试,确认通道和定时器配置正确后,再接到电机或其它负载上。这样能快速排除软件配置问题。

嗯,PWM这部分内容就这些。记住频率和占空比这两个核心参数,再结合ESP32的LEDC硬件结构,大部分PWM应用你都能搞定。下次遇到PWM相关的问题,先检查定时器配置,再查通道绑定,基本就能定位到问题。