本章将介绍使用 ESP32-S3 LED 控制器(LEDC)。 LEDC 主要用于控制 LED,也可产生PWM信号用于其他设备的控制。该控制器有 8 路通道,可以产生独立的波形,驱动 RGB LED 等设备。 LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实现亮度渐变。 ESP32-S3 IDF 提供了两种方式改变 PWM,一种是通过软件改变 PWM 占空比,另一种是通过硬件改变PWM 占空比,这两种方式都会学习。通过本章将学习到通过软件改变PWM 占空比的运用。本章分为如下几个小节:
17.1 PWM 简介
17.2 硬件设计
17.3 程序设计
17.4 下载验证
17.1 PWM 简介
(1) PWM 原理解析
PWM(Pulse Width Modulation),简称脉宽调制,是一种将模拟信号变为脉冲信号的计数。PWM 可以控制 LED 亮度、直流电机的转速等。
PWM 的主要参数如下:
① PWM 频率:PWM 频率是 PWM 信号在 1s 内从高电平到低电平再回到高电平的次数,
也就是说 1s 内有多少个 PWM 周期,单位为 Hz。
② PWM 周期:PWM 周期是 PWM 频率的倒数,即 T=1/f, T 是 PWM 周期, f 是 PWM 频率。如果 PWM 频率为 50Hz,也就是说 PWM 周期为 20ms,即 1s 由 50 个 PWM 周期。
③ PWM 占空比:PWM 占空比是指在一个 PWM 周期内,高电平的时间与整个周期时间的比例,取值范围为 0%~100%。例如,如果 PWM 周期是 10ms,而脉宽时间为 8ms,那么 PWM 占空比就是8/10=80%,此时的 PWM 信号就是占空比为 80%的 PWM 信号。 PWM 名为脉冲宽度调制,顾名思义,就是通过调节 PWM 占空比来调节 PWM 脉宽时间。PWM 占空比如下图所示。
图 17.1.1 PWM 占空比
在使用 PWM 控制 LED 时,亮 1s 后灭 1s,往复循环,就可以看到 LED 在闪烁。如果把这个周期缩小到 200ms,亮 100ms 后灭 100ms,往复循环,就可以看到 LED 灯在高频闪烁。继续
把这个周期持续缩小,总有一个临界值使人眼分辨不出 LED在闪烁,此时 LED的亮度处于灭与
亮之间亮度的中间值,达到了 1/2 亮度。 PWM 占空比和亮度的关系如下图所示。
图 17.1.2 PWM 占空比和亮度的关系
(2)ESP32 的 LED PWM 控制器介绍
ESP32-S3 的 LED PWM 控制器,简写为 LEDC,用于生成控制 LED 的脉冲宽度调制信号。
LED PWM 控制器具有八个独立的 PWM 生成器(即八个通道)。每个 PWM 生成器会从四个通用定时器中选择一个,以该定时器的计数值作为基准生成 PWM 信号。 LED PWM 定时器如下图所示。
图 17.1.1.1 LED_PWM 的定时器
为了实现 PWM 输出,先需要设置指定通道的 PWM 参数:频率、分辨率、占空比,然后将该通道映射到指定引脚,该引脚输出对应通道的 PWM 信号,通道和引脚的关系所下图所示。
图 17.1.1.2 LED PWM 输出示意图
另外, LED PWM 控制器可在没有 CPU 干预的情况下自动改变占空比,实现亮度以及颜色
渐变。
17.2 硬件设计
17.2.1 例程功能
通过软件改变 PWM 的形式使得 LED 由亮到暗,再由暗到亮,依次循环。
17.2.2 硬件资源
1. LED
LED-IO1
2. 定时器 1
通道 1 - IO1
17.2.3 原理图
本章实验使用的定时器 1 为 ESP32-S3 的片上资源,因此没有对应的连接原理图。
17.3 程序设计
17.3.1 程序流程图
本实验的程序流程图:
图 17.3.1.1 SW_PWM 实验程序流程图
17.3.2 SW_PWM 函数解析
ESP-IDF 提供了一套 API 来配置 PWM。要使用此功能,需要导入必要的头文件:
#include "driver/ledc.h"
接下来,将介绍一些常用的 SW_PWM 函数,这些函数的描述及其作用如下:
(1)配置 LEDC 使用的定时器为定时器1
需要注意的一点是,在首次配置LEDC时,建议先配置定时器(调用函数ledc_timer_config()),再配置通道(调用函数ledc_channel_config())。这样可以确保IO引脚上的PWM信号自输出开始那一刻起,其频率就是正确的。
要设置定时器,可调用函数 ledc_timer_config(),需要将一些参数的数据结构传递给该函数,该函数原型如下所示:
esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
timer_conf | 指向配置 LEDC 定时器的结构体指针 |
表 17.3.2.1 函数 ledc_timer_config ()形参描述
返回值: ESP_OK 表示配置成功,其他配置失败。
该函数使用 ledc_timer_config_t 类型的结构体变量传入,该结构体的定义如下所示:
结构体 | 成员变量 | 可选参数 |
---|---|---|
ledc_timer_config_t | speed_mode : 速度模式。需要注意的是,ESP32-S3 仅支持低速模式。 | LEDC_LOW_SPEED_MODE: 低速模式 |
LEDC_HIGH_SPEED_MODE: 高速模式 | ||
timer_num : 通道的定时器源, 定时器索引 ledc_timer_t | LEDC_TIMER_0 | |
LEDC_TIMER_1 | ||
LEDC_TIMER_2 | ||
LEDC_TIMER_3 | ||
LEDC_TIMER_MAX | ||
freq_hz : PWM信号频率 , 表示LEDC模块的定时器时钟频率设置,单位为 Hz | 无 | |
duty_resolution : PWM 占空比分辨率。 占空比分辨率通常用ledc_timer_bit_t 设置,范围是 10 至 15 位。如需较低的占空比分辨率(上至10,下至1),可直接输入相应数值。相关参数请参考ledc_timer_bit_t。 | LEDC_TIMER_1_BIT // 1位分辨率 (2级) LEDC_TIMER_2_BIT // 2位分辨率 (4级) LEDC_TIMER_3_BIT // 3位分辨率 (8级) LEDC_TIMER_4_BIT // 4位分辨率 (16级) LEDC_TIMER_5_BIT // 5位分辨率 (32级) LEDC_TIMER_6_BIT // 6位分辨率 (64级) LEDC_TIMER_7_BIT // 7位分辨率 (128级) LEDC_TIMER_8_BIT // 8位分辨率 (256级) LEDC_TIMER_9_BIT // 9位分辨率 (512级) LEDC_TIMER_10_BIT // 10位分辨率 (1024级) LEDC_TIMER_11_BIT // 11位分辨率 (2048级) LEDC_TIMER_12_BIT // 12位分辨率 (4096级) LEDC_TIMER_13_BIT // 13位分辨率 (8192级) LEDC_TIMER_14_BIT // 14位分辨率 (16384级) LEDC_TIMER_15_BIT // 15位分辨率 (32768级) LEDC_TIMER_16_BIT // 16位分辨率 (65536级) // ESP32-S2/S3/C3 支持到 20位 | |
clk_cfg: LEDPWM 的时钟来源 | LEDC_AUTO_CLK: 启动定时器时,将根据给定的分辨率和 占空率参数自动选择 ledc 源时钟; | |
LEDC_USE_APB_CLK: 选择 APB 作为源时钟; | ||
LEDC_USE_RC_FAST_CLK: 选择“RC_FAST” 作为源时钟; | ||
LEDC_USE_XTAL_CLK: 选择 XTAL 作为源时钟; | ||
LEDC_USE_RTC8M_CLK: ” LEDC_USE_RC_FAST_CLK” 的别名 |
表 17.3.2.2 ledc_timer_config_t 结构体参数值描述
特别再介绍下duty_resolution(占空比分辨率):
占空比分辨率是 PWM 控制中的核心概念,它决定了你能以多精细的级别控制输出信号的强度。在 ESP32 的 LEDC (LED PWM Controller) 中,这是一个关键配置参数。
分辨率级别与精度:
分辨率位数 | 占空比级数 | 最小调节步进 | 适用场景 |
---|---|---|---|
1 bit | 2 | 50% | 简单开关控制 |
8 bits | 256 | 0.39% | 通用 LED 调光 |
10 bits | 1024 | 0.098% | 电机速度控制 |
12 bits | 4096 | 0.024% | 精密调光 |
13 bits | 8192 | 0.012% | 专业灯光控制 |
16 bits | 65536 | 0.0015% | 高精度仪器 |
表 17.3.2.3 分辨率级别与精度描述
分辨率与频率的关系:
分辨率、时钟频率和 PWM 频率之间存在严格的数学关系:
计算公式:
:PWM 输出频率;
:时钟源频率 (APB=80MHz, RTC8M=8.5MHz);
- resolution:占空比分辨率位数;
- divider:预分频系数 (1-1024)。
实际限制:
- 更高分辨率 → 更低最大 PWM 频率;
- 更高PWM频率 → 更低最大分辨率。
完成上述结构体参数配置之后,可以将结构传递给 ledc_timer_config () 函数,用以实例化SW_PWM 并返回 SW_PWM 句柄。 另外时钟源同样可以限制 PWM 频率。选择的时钟源频率越高,可以配置的 PWM 频率上限就越高。
(2)通道配置函数
该函数原型如下所示:
esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
ledc_conf | 指向配置 LEDC 通道的结构体指针 |
表 17.3.2.4 函数 ledc_channel_config ()形参描述
返回值: ESP_OK 表示配置成功,其他配置失败。
该函数使用 ledc_channel_config_t 类型的结构体变量传入,该结构体的定义如下所示:
结构体 | 成员变量 | 可选参数 |
---|---|---|
ledc_channel_config_t | gpio_num : 配置输出引脚 | 本例程使用的引脚是 GPIO_NUM_1 |
speed_mode : 速度模式 | LEDC_HIGH_SPEED_MODE: 高速模式 | |
LEDC_LOW_SPEED_MODE: 低速模式 | ||
channel : LEDC 的输出通道(PWM 的输出通道) | 0~7,本例程配置为通道 1 | |
intr_type: 配置中断 | 使能中断: LEDC_INTR_FADE_END 失能中断: LEDC_INTR_DISABLE | |
timer_sel: 选择通道的定时器源。 定时器索引 ledc_timer_t | LEDC_TIMER_0 | |
LEDC_TIMER_1 | ||
LEDC_TIMER_2 | ||
LEDC_TIMER_3 | ||
LEDC_TIMER_MAX | ||
duty : LEDC 通道的占空比设置 | 占空比设定范围为0~ | |
hpoint : led 通道 hpoint 值。 表示 占空比对应的时钟计数值 | 无 | |
output_invert: 启 用(1)或禁 用(0)gpio 输 出反相 | 无 |
表 17.3.2.5 ledc_channel_config_t 结构体参数值描述
完成上述结构体参数配置之后,可以将结构传递给 ledc_channel_config() 函数,用以实例化
PWM 通道。
(3)改变 PWM 占空比
调用函数 ledc_set_duty()可以设置新的占空比。之后,调用函数 ledc_update_duty()使新配置生效。要查看当前设置的占空比,可使用 get 函数 ledc_get_duty(),该函数原型如下所示:
esp_err_t ledc_set_duty(ledc_mode_t speed_mode,ledc_channel_t channel,uint32_t duty);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
speed_mode | 速度模式选择: LEDC_HIGH_SPEED_MODE LEDC_LOW_SPEED_MODE |
channel | LEDC 通道: (0 - LEDC_CHANNEL_MAX-1),从 ledc_channel_t 中选择 |
duty | 设置 led 的负载,负载设置范围为: 0~[( |
表 17.3.2.6 函数 ledc_set_dut ()形参描述
返回值: ESP_OK 表示配置成功,其他配置失败。
(5)改变 PWM 占空比
在上一步调用 ledc_set_duty()设置新的占空比后,调用函数 ledc_update_duty()使新配置生效,该函数原型如下所示:
esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
speed_mode | 速度模式选择: LEDC_HIGH_SPEED_MODE LEDC_LOW_SPEED_MODE |
channel | LEDC 通道: (0 - LEDC_CHANNEL_MAX-1),从 ledc_channel_t 中选择 |
表 17.3.2.7 函数 ledc_update_duty()形参描述
返回值: ESP_OK 表示配置成功,其他配置失败。
17.3.3 SW_PWM 驱动解析
在 IDF 版的 08-1_sw_pwm 例程中,作者在 08-1_sw_pwm \components\BSP 路径下新增了一个 PWM 文件夹,用于存放 pwm.c 和 pwm.h 这两个文件。其中, pwm.h 文件负责声明SW_PWM相关的函数和变量,而 pwm.c 文件则实现了 SW_PWM 的驱动代码。下面,我们将详细解析这两个文件的实现内容。
(1)pwm.h 文件
/* 引脚以及重要参数定义 */
#define LEDC_PWM_TIMER LEDC_TIMER_1 /* 使用定时器0 */
#define LEDC_PWM_CH0_GPIO GPIO_NUM_1 /* LED控制器通道对应GPIO */
#define LEDC_PWM_CH0_CHANNEL LEDC_CHANNEL_1 /* LED控制器通道号 *//* 函数声明 */
void pwm_init(uint8_t resolution, uint16_t freq); /* 初始化PWM */
void pwm_set_duty(uint16_t duty); /* PWM占空比设置 */
(2)pwm.c 文件
/*** @brief 初始化PWM* @param resolution: PWM占空比分辨率* freq: PWM信号频率* @retval 无*/
void pwm_init(uint8_t resolution, uint16_t freq)
{ledc_timer_config_t ledc_timer = {0}; /* LEDC定时器句柄 */ledc_channel_config_t ledc_channel = {0}; /* LEDC通道配置句柄 *//* 配置LEDC定时器 */ledc_timer.duty_resolution = resolution; /* PWM占空比分辨率 */ledc_timer.freq_hz = freq; /* PWM信号频率 */ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE; /* 定时器模式 */ledc_timer.timer_num = LEDC_PWM_TIMER; /* 定时器序号 */ledc_timer.clk_cfg = LEDC_AUTO_CLK; /* LEDC时钟源 */ledc_timer_config(&ledc_timer); /* 配置定时器 *//* 配置LEDC通道 */ledc_channel.gpio_num = LEDC_PWM_CH0_GPIO; /* LED控制器通道对应引脚 */ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE; /* LEDC模式 */ledc_channel.channel = LEDC_PWM_CH0_CHANNEL; /* LEDC控制器通道号 */ledc_channel.intr_type = LEDC_INTR_DISABLE; /* LEDC失能中断 */ledc_channel.timer_sel = LEDC_PWM_TIMER; /* 定时器序号 */ledc_channel.duty = 0; /* 占空比值 */ledc_channel_config(&ledc_channel); /* 配置LEDC通道 */
}/*** @brief PWM占空比设置* @param duty:PWM占空比* @retval 无*/
void pwm_set_duty(uint16_t duty)
{ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_PWM_CH0_CHANNEL, duty); /* 设置占空比 */ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_PWM_CH0_CHANNEL); /* 更新占空比 */
}
LEDC 定时器设置好后,请配置所需的通道(ledc_channel_t)。配置通道需调用函数ledc_channel_config()。通道的配置与定时器设置类似,需向通道配置函数传递相应的结构体ledc_channel_config_t。此时,通道会按照 ledc_channel_config_t 的配置开始运作,并在选定的
GPIO 上生成由定时器指定的频率和占空比的 PWM 信号。
调用函数 ledc_set_duty()可以设置新的占空比。之后,调用函数 ledc_update_duty()使新配置生效。为了方便使用,笔者将这两个函数进行了“封装”,通过传参的形式来配置 PWM 占空比。
关于配置过程中所涉及到的结构体成员变量的含义以及用法,请读者们回顾本章节前面的内容。
17.3.4 CMakeLists.txt 文件
打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:
set(src_dirsPWM)set(include_dirsPWM)set(requiresdriver)idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
17.3.5 实验应用代码
打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。
/*** @brief 程序入口* @param 无* @retval 无*/
void app_main(void)
{esp_err_t ret;uint8_t dir = 1;uint16_t ledpwmval = 0;ret = nvs_flash_init(); /* 初始化NVS */if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}pwm_init(LEDC_TIMER_10_BIT, 1000); /* 初始化PWM */while(1) {vTaskDelay(10);if (dir == 1){ledpwmval += 5; /* dir==1 ledpwmval递增 */}else{ledpwmval -= 5; /* dir==0 ledpwmval递减 */}if (ledpwmval > 1005){dir = 0; /* ledpwmval到达1005后,方向为递减 */}if (ledpwmval < 5){dir = 1; /* ledpwmval递减到5后,方向改为递增 */}/* 设置占空比 */pwm_set_duty(ledpwmval);}
}
从上面的代码中可以看到,在初始化 LEDC 定时器,并输出 PWM 后,就不断地改变定时器 0 的值,以达到改变 PWM 占功比的目的。又因为 PWM 由 IO1 引脚输出, IO1 引脚连接至LED,所以 LED 的亮度也会随之发生变化,从而实现呼吸灯的效果。
17.4 下载验证
在完成编译和烧录后,可以看到板子上的 LED 先由暗再逐渐变亮,以此循环,实现了呼吸灯的效果。