3. PWM呼吸灯

任务目标

使用ESP32内置的LEDC(LED Control)PWM模块,通过改变PWM占空比控制LED亮度,实现从暗到亮再到暗的呼吸灯效果。通过本案例理解PWM调光的基本原理与ESP32 LEDC的配置方法。

硬件与接线

  • ESP32开发板 × 1
  • LED(普通5mm或贴片均可)× 1
  • 限流电阻 220Ω × 1
  • 面包板与跳线若干

接线方式:将LED阳极(长脚)串联220Ω电阻后连接至GPIO2,LED阴极(短脚)接GND。GPIO2为ESP32板载LED常用引脚,也可换用其他可用GPIO。

PWM与LEDC原理简述

PWM(脉冲宽度调制)通过固定频率下改变高电平占空比来调节LED的平均电流,从而控制亮度。ESP32的LEDC模块提供16个通道(0~15),可绑定任意GPIO,支持配置PWM频率与分辨率(1~16位)。本案例使用8位分辨率(占空比0~255),频率5kHz(对LED无频闪感)。

完整代码

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"

#define LED_GPIO        2
#define LEDC_MODE       LEDC_LOW_SPEED_MODE
#define LEDC_CHANNEL    LEDC_CHANNEL_0
#define LEDC_TIMER      LEDC_TIMER_0
#define PWM_FREQ_HZ     5000
#define PWM_RES_BITS    8          // 8位分辨率,占空比0~255
#define DUTY_MAX        ((1 << PWM_RES_BITS) - 1)  // 255
#define STEP_DELAY_MS   10         // 每步延时(毫秒)

void app_main(void)
{
    // 1. 配置LEDC定时器
    ledc_timer_config_t timer_conf = {
        .speed_mode      = LEDC_MODE,
        .timer_num       = LEDC_TIMER,
        .duty_resolution = PWM_RES_BITS,
        .freq_hz         = PWM_FREQ_HZ,
        .clk_cfg         = LEDC_AUTO_CLK
    };
    ESP_ERROR_CHECK(ledc_timer_config(&timer_conf));

    // 2. 配置LEDC通道并绑定GPIO
    ledc_channel_config_t channel_conf = {
        .gpio_num   = LED_GPIO,
        .speed_mode = LEDC_MODE,
        .channel    = LEDC_CHANNEL,
        .intr_type  = LEDC_INTR_DISABLE,
        .timer_sel  = LEDC_TIMER,
        .duty       = 0,
        .hpoint     = 0
    };
    ESP_ERROR_CHECK(ledc_channel_config(&channel_conf));

    // 3. 呼吸灯主循环:渐亮→渐暗
    while (1) {
        // 渐亮:占空比从0增加到255
        for (int duty = 0; duty <= DUTY_MAX; duty++) {
            ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty));
            ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
            vTaskDelay(pdMS_TO_TICKS(STEP_DELAY_MS));
        }
        // 渐暗:占空比从255减小到0
        for (int duty = DUTY_MAX; duty >= 0; duty--) {
            ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty));
            ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
            vTaskDelay(pdMS_TO_TICKS(STEP_DELAY_MS));
        }
    }
}

编译与烧录

本案例基于ESP-IDF开发框架(v5.x)。在项目目录下创建上述代码为 main.c,并确保 CMakeLists.txt 中已注册main组件。然后执行:

idf.py set-target esp32
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

将 /dev/ttyUSB0 替换为实际串口设备名(Windows下为 COMx)。

验证方法

  • 观察LED是否呈现周期性由暗到亮、再由亮到暗的平滑渐变,周期约为 256×2×10ms ≈ 5.1秒。
  • 若LED不亮,检查GPIO2接线方向与电阻连接;若亮度跳变而非渐变,检查 STEP_DELAY_MS 是否过小导致变化过快。
  • 可通过调整 STEP_DELAY_MS 改变呼吸速度,或修改 PWM_RES_BITS 改变亮度级数(如改为10位则占空比上限为1023,需同步修改循环边界)。

常见问题

  • 编译报错找不到ledc头文件:确认在ESP-IDF环境下编译,且 driver/ledc.h 路径正确。
  • LED亮度无变化:检查GPIO是否被其他功能占用(如板载LED可能连接Flash),换用GPIO4、GPIO5等空闲引脚测试。
  • 闪烁而非渐变:PWM频率过低(如低于1kHz)会产生可见闪烁,确认 PWM_FREQ_HZ 设置为5000。