跳到主要内容

示例:LEDC 控制 LED 亮度

重要提示:关于开发板的兼容性

本教程的核心逻辑适用于所有 ESP32 开发板,但所有操作步骤均以 微雪 ESP32-S3-Zero 迷你开发板 为例进行讲解。如果您使用其他型号的开发板,请根据实际情况修改相应设置。

本教程将介绍如何使用乐鑫 ESP-IDF 框架,通过 LED PWM 控制器(LEDC)外设产生 PWM 信号来控制 LED 亮度。

1. LEDC 外设 (LED PWM 控制器)

LED 控制器(LEDC)主要用于控制 LED,也可产生 PWM 信号控制其他设备。根据具体芯片型号,拥有 6 到 16 个独立通道,可产生独立波形来驱动 RGB LED 等设备。

LED PWM 控制器通过精确控制 PWM 的占空比,可以调节 LED 的亮度。LEDC 还内置了硬件渐变功能,可在无需 CPU 持续干预的情况下自动平滑改变占空比。

大多数 ESP32 GPIO 引脚都可配置为 LEDC 外设,用于生成 PWM 信号控制 LED 亮度或其他需要占空比和频率控制的输出设备。注意,部分仅支持输入的 GPIO 引脚无法用于 LEDC 输出,具体可用引脚请参考对应芯片的技术参考手册。

在 ESP-IDF 中使用 LEDC(LED PWM 控制器)的通用步骤如下:

  1. 包含头文件

    首先需要包含 LEDC 驱动的头文件:

    #include "driver/ledc.h"

    并在 CMakeLists.txt 中添加依赖(如 REQUIRES esp_driver_ledc)。

  2. 定时器配置
    首先配置 LEDC 定时器,指定 PWM 信号的频率和占空比分辨率。使用 ledc_timer_config() 函数完成此步骤。

  3. 通道配置
    配置 LEDC 通道,将定时器与输出 PWM 信号的 GPIO 绑定,并设置初始占空比。使用 ledc_channel_config() 函数完成此步骤。先配置定时器,再配置通道,这样可以确保 PWM 信号一开始输出时频率就是正确的。

  4. 改变 PWM 信号
    通过 ledc_set_duty() 设置新的占空比,并用 ledc_update_duty() 应用到通道,实现 PWM 信号的实时调整。也可以使用硬件渐变功能实现亮度平滑变化。

  5. (可选)使用硬件渐变(fade)功能

    如需平滑调节亮度,可使用 LEDC 渐变功能,需先调用 ledc_fade_func_install(0) 使能渐变,然后配置渐变参数并启动(具体可参考 使用硬件改变 PWM 占空比)。

2. 示例项目

本示例将通过 LEDC 外设,驱动一个外部 LED,实现从灭到最亮、再从最亮到灭的平滑亮度变化。

2.1 电路

需要使用的器件有:

按照下面接线图连接电路:

ESP32-S3-Zero 引脚图

ESP32-S3-Zero-Pinout

接线图

2.2 创建项目

  1. 创建一个项目。如果不清楚如何操作,请参考 从模板创建项目

  2. 查看 LEDC API 参考。根据文档中的指引完成以下步骤。

    首先在 main.c 中包含头文件:

    #include "driver/ledc.h"

    然后在 main/CMakeLists.txt 中声明 esp_driver_ledc 组件:

    idf_component_register(SRCS "main.c"
    INCLUDE_DIRS "."
    REQUIRES esp_driver_ledc)

2.3 示例代码

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

static const char *TAG = "example"; // 定义日志标签

static const gpio_num_t LED_PIN = GPIO_NUM_7; // LED 连接的 GPIO 引脚
#define LEDC_CHANNEL LEDC_CHANNEL_0 // LEDC 通道
#define LEDC_TIMER LEDC_TIMER_0 // LEDC 定时器
#define LEDC_MODE LEDC_LOW_SPEED_MODE // LEDC 速度模式, 低速模式
#define LEDC_DUTY_RES LEDC_TIMER_10_BIT // PWM 占空比分辨率 (10-bit, 0-1023)
#define LEDC_FREQUENCY 1000 // PWM 频率 (1 kHz)

void app_main(void)
{
// 配置 LEDC 定时器
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE, // 设置速度模式
.duty_resolution = LEDC_DUTY_RES, // 设置占空比分辨率
.timer_num = LEDC_TIMER, // 选择定时器
.freq_hz = LEDC_FREQUENCY // 设置 PWM 频率
};
ledc_timer_config(&ledc_timer); // 应用定时器配置

// 配置 LEDC 通道
ledc_channel_config_t ledc_channel = {
.gpio_num = LED_PIN, // 通道输出的 GPIO 引脚
.speed_mode = LEDC_MODE, // 设置速度模式 (与定时器一致)
.channel = LEDC_CHANNEL, // 选择 LEDC 通道
.timer_sel = LEDC_TIMER, // 选择该通道使用的定时器
.duty = 0 // 设置初始占空比为 0
};
ledc_channel_config(&ledc_channel); // 应用通道配置

while (1)
{
// 渐亮
ESP_LOGI(TAG, "LED Fade in");
for (int duty = 0; duty <= 1023; duty += 16)
{
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty); // 设置占空比
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL); // 更新占空比使其生效
vTaskDelay(pdMS_TO_TICKS(20)); // 延时 20 毫秒
}

// 渐暗
ESP_LOGI(TAG, "LED Fade out");
for (int duty = 1023; duty >= 0; duty -= 16)
{
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty); // 设置占空比
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL); // 更新占空比使其生效
vTaskDelay(pdMS_TO_TICKS(20)); // 延时 20 毫秒
}
}
}

2.4 构建并烧录

  1. 配置烧录选项

    首先,在构建和烧录之前,请务必检查并设置正确的目标设备、串口和烧录方式。参考 第2节 运行示例 - 1.3 配置项目

    VS Code 工具栏

  2. 点击 VS Code 一键构建烧录监视图标 一键自动依次执行构建、烧录和监视这三个步骤。

  3. 烧录完成后,可以看到开发板上的 LED 开始平滑地渐明渐暗。同时,串口监视器会启动并输出如下日志信息:

    I (256) main_task: Started on CPU0
    I (266) main_task: Calling app_main()
    I (266) example: LED Fade in
    I (2826) example: LED Fade out
    I (5386) example: LED Fade in
    I (7946) example: LED Fade out
    ...

2.5 代码解析

1. 包含头文件

#include "driver/ledc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
  • driver/ledc.h: LEDC(LED PWM 控制器)驱动的核心头文件,提供了配置和控制 PWM 信号所需的所有函数和数据类型。
  • freertos/FreeRTOS.hfreertos/task.h: FreeRTOS 核心头文件,提供了任务管理功能,如本示例中用于延时的 vTaskDelay 函数。
  • esp_log.h: ESP-IDF 的日志库,用于在终端打印信息,方便调试。

2. 定义全局常量和宏

static const char *TAG = "example"; // 定义日志标签

static const gpio_num_t LED_PIN = GPIO_NUM_7; // LED 连接的 GPIO 引脚
#define LEDC_CHANNEL LEDC_CHANNEL_0 // LEDC 通道
#define LEDC_TIMER LEDC_TIMER_0 // LEDC 定时器
#define LEDC_MODE LEDC_LOW_SPEED_MODE // LEDC 速度模式(低速模式)
#define LEDC_DUTY_RES LEDC_TIMER_10_BIT // PWM 占空比分辨率 (10-bit, 0-1023)
#define LEDC_FREQUENCY 1000 // PWM 频率 (1 kHz)
  • TAG: 用于 esp_log 日志输出的标签,方便识别日志来源。
  • LED_PIN: 定义了连接外部 LED 的 GPIO 引脚。本示例中使用 GPIO_NUM_7
  • LEDC_CHANNEL: 选择要使用的 LEDC 通道。ESP32 有多个独立的通道(如 LEDC_CHANNEL_0LEDC_CHANNEL_1 等)。
  • LEDC_TIMER: 选择驱动通道的定时器。LEDC 的工作模式是“定时器产生基础波形,通道使用该波形并控制其占空比输出到 GPIO”。
  • LEDC_MODE: 设置 LEDC 的速度模式。与 ESP32 不同,ESP32-S3 仅支持设置通道为低速模式。
  • LEDC_DUTY_RES: 设置 PWM 的占空比分辨率。LEDC_TIMER_10_BIT 表示占空比可以从 0 变化到 2^10 - 1(即 1023),这决定了亮度调节的精细程度。
  • LEDC_FREQUENCY: 设置 PWM 信号的频率,单位是赫兹(Hz)。1000 Hz (1 kHz) 是一个常用值,可以确保人眼看不到 LED 闪烁。

3. 配置 LEDC 外设

这部分代码在 app_main() 函数的开头完成,是使用 LEDC 的关键初始化步骤。它分为两步:配置定时器和配置通道。

// 配置 LEDC 定时器
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_MODE,
.duty_resolution = LEDC_DUTY_RES,
.timer_num = LEDC_TIMER,
.freq_hz = LEDC_FREQUENCY
};
ledc_timer_config(&ledc_timer);

// 配置 LEDC 通道
ledc_channel_config_t ledc_channel = {
.gpio_num = LED_PIN,
.speed_mode = LEDC_MODE,
.channel = LEDC_CHANNEL,
.timer_sel = LEDC_TIMER,
.duty = 0
};
ledc_channel_config(&ledc_channel);
  • 配置 LEDC 定时器 (ledc_timer_config_t):

    • 这一步定义了 PWM 信号的基础特性。
    • speed_mode: 设置速度模式,必须与之后配置的通道模式一致。
    • duty_resolution: 设置占空比的分辨率,决定了亮度的级数。
    • timer_num: 指定要配置哪个 LEDC 定时器。
    • freq_hz: 设置 PWM 的频率。
    • ledc_timer_config(): 函数将上述配置应用到指定的定时器。
  • 配置 LEDC 通道 (ledc_channel_config_t):

    • 这一步将定时器与一个具体的 GPIO 引脚绑定起来。
    • gpio_num: 指定 PWM 信号将从哪个 GPIO 引脚输出。
    • speed_mode: 速度模式,必须与定时器配置的模式相同。
    • channel: 指定要配置哪个 LEDC 通道。
    • timer_sel: 指定该通道使用的定时器编号(如 0~3),需与已配置的定时器一致。
    • duty: 设置该通道的初始占空比。这里设为 0,表示 LED 初始状态为熄灭。
    • ledc_channel_config(): 函数将上述配置应用到指定的通道。

4. 主循环:控制 LED 亮度变化

app_main 函数中的 while(1) 循环是程序的主体,通过两个 for 循环实现了 LED 的渐亮和渐暗效果。

while (1)
{
// 渐亮
for (int duty = 0; duty <= 1023; duty += 16)
{
ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty);
ledc_update_duty(LEDC_MODE, LEDC_CHANNEL);
vTaskDelay(pdMS_TO_TICKS(20));
}

// 渐暗
for (int duty = 1023; duty >= 0; duty -= 16)
{
// ... (与渐亮逻辑类似)
}
}
  • 渐亮循环:

    • for (int duty = 0; duty <= 1023; duty += 16): 这个循环将 duty 变量从 0 增加到 1023(10-bit 分辨率的最大值),每次增加 16。步长 16 控制了渐变的速度。
    • ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty): 此函数用于设置新的目标占空比。它仅更新内部寄存器的值,并不会立即改变 PWM 输出。
    • ledc_update_duty(LEDC_MODE, LEDC_CHANNEL): 这个函数是真正使新占空比生效的步骤。它会加载 ledc_set_duty 设置的值,并更新到硬件输出,从而改变 LED 的实际亮度。
    • vTaskDelay(pdMS_TO_TICKS(20)): 在每次更新占空比后,延时 20 毫秒。这个延时使得亮度的变化过程能够被肉眼观察到,形成了平滑的渐变效果。
  • 渐暗循环:

    • 逻辑与渐亮循环完全相同,只是 duty 变量从 1023 递减到 0,从而实现 LED 从最亮到熄灭的渐变效果。

3. 参考链接