现在,我们来讲解PWM的相关知识

一、LEDC库函数

LEDC(LED PWM Controller)是 ESP32-S3 专门用于生成 PWM(脉冲宽度调制)信号的硬件外设,核心特点:

  • 无需 CPU 干预:配置好后,硬件自动生成 PWM 波形,不占用 CPU 资源;
  • 多通道:有 8 个通道(0-7),可同时控制 8 路 PWM(比如 8 个 LED 渐变、3 路电机调速);
  • 高精度:占空比分辨率支持 1-16 位可调(比如 10 位对应 0-1023 级亮度);
  • 仅支持低速模式:注意!ESP32-S3 只有LEDC_LOW_SPEED_MODE,没有高速模式。

二、完善CMakeList.txt

idf_component_register(SRCS "main.c"
                    PRIV_REQUIRES driver freertos 
                    INCLUDE_DIRS ".")

三、完善mian.c

#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

// ===================== PWM配置宏定义 =====================
#define LEDC_HS_TIMER          LEDC_TIMER_0
#define LEDC_HS_MODE           LEDC_LOW_SPEED_MODE  // ESP32-S3仅支持低速模式
#define LEDC_HS_CH0_CHANNEL    LEDC_CHANNEL_0
#define LEDC_HS_CH0_GPIO       18 // LED引脚
#define LEDC_DUTY_RES          LEDC_TIMER_10_BIT // 占空比分辨率:0-1023
#define LEDC_FREQUENCY         5000 // PWM频率:5000Hz

// ===================== PWM初始化函数 =====================
void ledc_pwm_init(void) {
    // 1. 配置定时器
    ledc_timer_config_t ledc_timer = {
        .duty_resolution = LEDC_DUTY_RES,
        .freq_hz = LEDC_FREQUENCY,
        .speed_mode = LEDC_HS_MODE,
        .timer_num = LEDC_HS_TIMER,
        .clk_cfg = LEDC_AUTO_CLK,
    };
    ledc_timer_config(&ledc_timer);

    // 2. 配置通道
    ledc_channel_config_t ledc_channel = {
        .channel    = LEDC_HS_CH0_CHANNEL,
        .duty       = 0, // 初始占空比:0(LED灭)
        .gpio_num   = LEDC_HS_CH0_GPIO,
        .speed_mode = LEDC_HS_MODE,
        .hpoint     = 0,
        .timer_sel  = LEDC_HS_TIMER
    };
    ledc_channel_config(&ledc_channel);
}

// ===================== PWM LED渐变任务函数 =====================
void pwm_led_task(void *arg) {
    // 初始化PWM
    ledc_pwm_init();
    
    int duty = 0;
    int step = 5; // 占空比步长

    // 任务无限循环
    while(1) {
        // 设置占空比(0-1023)
        ledc_set_duty(LEDC_HS_MODE, LEDC_HS_CH0_CHANNEL, duty);
        ledc_update_duty(LEDC_HS_MODE, LEDC_HS_CH0_CHANNEL); // 必须调用,占空比才生效
        
        // 渐变逻辑:递增到最大值后递减
        duty += step;
        if(duty >= 1023 || duty <= 0) {
            step = -step;
        }
        
        vTaskDelay(pdMS_TO_TICKS(10)); // 渐变速度
    }
}

// ===================== 主函数(仅保留任务创建) =====================
void app_main(void) {
    // 创建PWM LED渐变任务
    xTaskCreate(
        pwm_led_task,    // 任务函数
        "pwm_led_task",  // 任务名称
        4096,            // 任务栈大小(字节,PWM操作足够)
        NULL,            // 任务参数(无)
        2,               // 任务优先级(普通优先级)
        NULL             // 任务句柄(无)
    );
}

3.1 核心宏定义详解

// PWM配置宏定义
#define LEDC_HS_TIMER          LEDC_TIMER_0       // 选择LEDC定时器
#define LEDC_HS_MODE           LEDC_LOW_SPEED_MODE // 速度模式(ESP32-S3仅支持低速)
#define LEDC_HS_CH0_CHANNEL    LEDC_CHANNEL_0      // 选择LEDC通道
#define LEDC_HS_CH0_GPIO       18                   // LED连接的GPIO引脚
#define LEDC_DUTY_RES          LEDC_TIMER_10_BIT   // 占空比分辨率
#define LEDC_FREQUENCY         5000                 // PWM频率(Hz)
  1. LEDC_HS_TIMER:选择 LEDC 定时器,指定用哪个硬件定时器生成 PWM 波形;可选值LEDC_TIMER_0~LEDC_TIMER_3(共四个定时器)。
  2. LEDC_HS_MODE:速度模式,ESP32-S3只支持低速模式,即LEDC_LOW_SPEED_MODE。
  3. LEDC_HS_CH0_CHANNEL:选择LEDC通道。用于指定用哪个通道输出 PWM 波形,可选值LEDC_CHANNEL_0~LEDC_CHANNEL_7(共8个通道)。每个通道必须绑定一个定时器,可以多个通道绑定同一个定时器,实现同频率不同占空比的 PWM。
  4. LEDC_HS_CH0_GPIO:PWM输出引脚。用于指定 PWM 波形从哪个 GPIO 引脚输出。并不是所有GPIO都支持LEDC输出,需要参考下图。要注意不要使用GPIO6~11,这些是SPI Flash 引脚,占用会导致系统崩溃。
  5. LEDC_DUTY_RES:占空比分辨率,用于设置占空比的 “精度等级”,决定了 PWM 有多少级可调。可选值LEDC_TIMER_1_BIT~LEDC_TIMER_16_BIT。这里我们使用了10位分辨率,占空比范围 0-1023(共 1024 级,对应 0%-100% 占空比)
  6. LEDC_FREQUENCY:PWM频率。用于设置 PWM 波形的 “每秒重复次数”,单位是赫兹(Hz)5000Hz(5kHz)即PWM 波形每秒重复 5000 次,LED 不会闪烁。

3.2 关键配置结构体讲解

LEDC 的配置分为两步:先配置定时器(决定 PWM 的频率和分辨率),再配置通道(决定 PWM 的输出引脚和初始占空比)。

3.2.1 定时器配置结构体:ledc_timer_config_t

ledc_timer_config_t ledc_timer = {
    .duty_resolution = LEDC_DUTY_RES, // 占空比分辨率(复用宏定义)
    .freq_hz = LEDC_FREQUENCY,         // PWM频率(复用宏定义)
    .speed_mode = LEDC_HS_MODE,         // 速度模式(低速)
    .timer_num = LEDC_HS_TIMER,         // 定时器编号(复用宏定义)
    .clk_cfg = LEDC_AUTO_CLK,           // 时钟源(自动选择)
};
ledc_timer_config(&ledc_timer); // 应用定时器配置

逐成员拆解:

分辨率与频率的限制:分辨率越高,最大频率越低。公式:最大频率 = 80MHz / 2^分辨率。ESP32-S3的LEDC时钟默认80MHz:

如果设置的频率超过最大频率,PWM会无法正常工作。

3.2.2 通道配置结构体:ledc_channel_config_t

ledc_channel_config_t ledc_channel = {
    .channel    = LEDC_HS_CH0_CHANNEL, // 通道编号(复用宏定义)
    .duty       = 0,                     // 初始占空比(0:LED灭)
    .gpio_num   = LEDC_HS_CH0_GPIO,     // PWM输出引脚(复用宏定义)
    .speed_mode = LEDC_HS_MODE,         // 速度模式(和定时器一致)
    .hpoint     = 0,                     // 高电平起始点(新手设为0即可)
    .timer_sel  = LEDC_HS_TIMER         // 绑定的定时器(和定时器一致)
};
ledc_channel_config(&ledc_channel); // 应用通道配置

逐成员拆解:

3.3 核心API讲解

3.3.1 ledc_timer_config():配置定时器

esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf);
  • 作用:将定时器配置结构体应用到硬件,设置 PWM 的频率和分辨率;
  • 参数:指向ledc_timer_config_t结构体的指针(传地址,加&);
  • 返回值esp_err_t(ESP32 通用错误码,返回ESP_OK表示配置成功);
  • 调用时机:必须在ledc_channel_config()之前调用(先有定时器,才能绑定通道)。

3.3.2 ledc_channel_config():配置通道

esp_err_t ledc_channel_config(const ledc_channel_config_t *channel_conf);
  • 作用:将通道配置结构体应用到硬件,设置 PWM 的输出引脚和初始占空比;
  • 参数:指向ledc_channel_config_t结构体的指针(传地址,加&);
  • 返回值esp_err_t(返回ESP_OK表示配置成功);
  • 调用时机:必须在ledc_timer_config()之后调用(先配置定时器,再绑定通道)。

3.3.3 ledc_set_duty():设置占空比

esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty);
  • 作用:将新的占空比写入 “缓存”,但不会立即生效
  • 参数:1.speed_mode:速度模式(LEDC_LOW_SPEED_MODE);2.channel:通道编号(LEDC_CHANNEL_0);3.duty:新的占空比(0-1023,对应 10 位分辨率);
  • 返回值esp_err_t(返回ESP_OK表示写入缓存成功);
  • 新手注意:仅调用这个函数,PWM 占空比不会变化,必须配合ledc_update_duty()使用。

3.3.4 ledc_update_duty():更新占空比

esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
  • 作用:将缓存中的占空比更新到硬件,PWM 占空比立即变化
  • 参数:1.speed_mode:速度模式(和ledc_set_duty()一致);2.channel:通道编号(和ledc_set_duty()一致);
  • 返回值esp_err_t(返回ESP_OK表示更新成功);
  • 新手必记:这是最容易漏写的函数!必须在ledc_set_duty()之后调用,否则占空比不会生效。

3.4 任务函数

// ===================== PWM LED渐变任务函数 =====================
void pwm_led_task(void *arg) {
    // 初始化PWM
    ledc_pwm_init();
    
    int duty = 0;
    int step = 5; // 占空比步长

    // 任务无限循环
    while(1) {
        // 设置占空比(0-1023)
        ledc_set_duty(LEDC_HS_MODE, LEDC_HS_CH0_CHANNEL, duty);
        ledc_update_duty(LEDC_HS_MODE, LEDC_HS_CH0_CHANNEL); // 必须调用,占空比才生效
        
        // 渐变逻辑:递增到最大值后递减
        duty += step;
        if(duty >= 1023 || duty <= 0) {
            step = -step;
        }
        
        vTaskDelay(pdMS_TO_TICKS(10)); // 渐变速度
    }
}

在任务函数中,我们先初始化PWM,之后定义了初始的占空比步长。在一个无限循环中,我们设置并更新占空比,同时让占空比每循环一次增加一个步长,当亮度≥1023或者≤0时,将步长取反,这样LED的亮度就会朝相反方向变化。

最后,每循环一次等待10MS。

3.5 创建任务

// ===================== 主函数(仅保留任务创建) =====================
void app_main(void) {
    // 创建PWM LED渐变任务
    xTaskCreate(
        pwm_led_task,    // 任务函数
        "pwm_led_task",  // 任务名称
        4096,            // 任务栈大小(字节,PWM操作足够)
        NULL,            // 任务参数(无)
        2,               // 任务优先级(普通优先级)
        NULL             // 任务句柄(无)
    );
}

四、编译烧录,查看现象

编译烧录成功后,可以看到LED在以规律的速度增加减少亮度,看起来就像在呼吸一样。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐