概述
LED 控制器 (LEDC) 主要用于控制 LED,也可产生 PWM 信号用于其他设备的控制。该控制器有 8 路通道,可以产生独立的波形,驱动 RGB LED 等设备。
LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实现亮度和颜色渐变。
功能概览
设置 LEDC 通道分三步完成。注意,与 ESP32 不同,ESP32-S3 仅支持设置通道为低速模式。
- 定时器配置 指定 PWM 信号的频率和占空比分辨率。
- 通道配置 绑定定时器和输出 PWM 信号的 GPIO。
- 改变 PWM 信号 输出 PWM 信号来驱动 LED。可通过软件控制或使用硬件渐变功能来改变 LED 的亮度。
另一个可选步骤是可以在渐变终端设置一个中断。
备注
首次 LEDC 配置时,建议先配置定时器(调用函数
ledc_timer_config()
),再配置通道(调用函数ledc_channel_config()
)。这样可以确保 IO 脚上的 PWM 信号自有输出开始其频率就是正确的。
定时器配置
要设置定时器,可调用函数 ledc_timer_config()
,并将包括如下配置参数的数据结构 ledc_timer_config_t
传递给该函数:
- 速度模式(值必须为
LEDC_LOW_SPEED_MODE
) - 定时器索引
ledc_timer_t
- PWM 信号频率(Hz)
- PWM 占空比分辨率
- 时钟源
ledc_clk_cfg_t
频率和占空比分辨率相互关联。PWM 频率越高,占空比分辨率越低,反之亦然。如果 API 不是用来改变 LED 亮度,而是用于其它目的,这种相互关系可能会很重要。更多信息详见 频率和占空比分辨率支持范围 一节。
时钟源同样可以限制 PWM 频率。选择的时钟源频率越高,可以配置的 PWM 频率上限就越高。
ESP32-S3 LEDC 时钟源特性
时钟名称 | 时钟频率 | 时钟功能 |
---|---|---|
APB_CLK | 80 MHz | / |
RC_FAST_CLK | ~ 20 MHz | 支持动态调频(DFS)功能,支持 Light-sleep 模式 |
XTAL_CLK | 40 MHz | 支持动态调频(DFS)功能 |
备注
- 如果 ESP32-S3 的定时器选用了
RC_FAST_CLK
作为其时钟源,驱动会通过内部校准来得知这个时钟源的实际频率。这样确保了输出 PWM 信号频率的精准性。- ESP32-S3 的所有定时器共用一个时钟源。因此 ESP32-S3 不支持给不同的定时器配置不同的时钟源。
LEDC 驱动提供了一个辅助函数 ledc_find_suitable_duty_resolution()
。传入时钟源频率及期望的 PWM 信号频率,这个函数可以直接找到最大可配的占空比分辨率值。
当一个定时器不再被任何通道所需要时,可以通过调用相同的函数 ledc_timer_config()
来重置这个定时器。此时,函数入参的配置结构体需要指定:
ledc_timer_config_t::speed_mode
重置定时器的所属速度模式 (ledc_mode_t
)ledc_timer_config_t::timer_num
重置定时器的索引 (ledc_timer_t
)ledc_timer_config_t::deconfigure
将指定定时器重置必须配置此项为 true
通道配置
定时器设置好后,请配置所需的通道(ledc_channel_t
之一)。配置通道需调用函数 ledc_channel_config()
。
通道的配置与定时器设置类似,需向通道配置函数传递包括通道配置参数的结构体 ledc_channel_config_t
。
此时,通道会按照 ledc_channel_config_t
的配置开始运作,并在选定的 GPIO 上生成由定时器设置指定的频率和占空比的 PWM 信号。在通道运作过程中,可以随时通过调用函数 ledc_stop()
将其暂停。
改变 PWM 信号
通道开始运行、生成具有恒定占空比和频率的 PWM 信号之后,有几种方式可以改变该信号。驱动 LED 时,主要通过改变占空比来变化光线亮度。
以下两节介绍了如何使用软件和硬件改变占空比。如有需要,PWM 信号的频率也可更改,详见 改变 PWM 频率 一节。
备注
在 ESP32-S3 的 LED PWM 控制器中,所有的定时器和通道都只支持低速模式。对 PWM 设置的任何改变,都需要由软件显式地触发(见下文)。
使用软件改变 PWM 占空比
调用函数 ledc_set_duty()
可以设置新的占空比。之后,调用函数 ledc_update_duty()
使新配置生效。要查看当前设置的占空比,可使用 _get_
函数 ledc_get_duty()
。
另外一种设置占空比和其他通道参数的方式是调用 通道配置 一节提到的函数 ledc_channel_config()
。
传递给函数的占空比数值范围取决于选定的 duty_resolution
,应为 0
至 (2 ** duty_resolution)
。例如,如选定的占空比分辨率为 10,则占空比的数值范围为 0 至 1024。此时分辨率为 ~ 0.1%。
警告
在 ESP32-S3 上,当通道绑定的定时器配置了其最大 PWM 占空比分辨率( MAX_DUTY_RES
),通道的占空比不能被设置到 (2 ** MAX_DUTY_RES)
。否则,硬件内部占空比计数器会溢出,并导致占空比计算错误。
使用硬件改变 PWM 占空比
LED PWM 控制器硬件可逐渐改变占空比的数值。要使用此功能,需用函数 ledc_fade_func_install()
使能渐变,之后用下列可用渐变函数之一配置:
最后需要调用 ledc_fade_start()
开启渐变。渐变可以在阻塞或非阻塞模式下运行,具体区别请查看 ledc_fade_mode_t
。需要特别注意的是,不管在哪种模式下,下一次渐变或是单次占空比配置的指令生效都必须等到前一次渐变完成或被中止。中止一个正在运行中的渐变需要调用函数 ledc_fade_stop()
。
此外,在使能渐变后,每个通道都可以额外通过调用 ledc_cb_register()
注册一个回调函数用以获得渐变完成的事件通知。回调函数的原型被定义在 ledc_cb_t
。每个回调函数都应当返回一个布尔值给驱动的中断处理函数,用以表示是否有高优先级任务被其唤醒。此外,值得注意的是,由于驱动的中断处理函数被放在了 IRAM 中, 回调函数和其调用的函数也需要被放在 IRAM 中。 ledc_cb_register()
会检查回调函数及函数上下文的指针地址是否在正确的存储区域。
如不需要渐变和渐变中断,可用函数 ledc_fade_func_uninstall()
关闭。
改变 PWM 频率
LED PWM 控制器 API 有多种方式即时改变 PWM 频率:
- 通过调用函数
ledc_set_freq()
设置频率。可用函数ledc_get_freq()
查看当前频率。- 通过调用函数
ledc_bind_channel_timer()
将其他定时器绑定到该通道来改变频率和占空比分辨率。- 通过调用函数
ledc_channel_config()
改变通道的定时器。
控制 PWM 的更多方式
有一些较独立的定时器特定函数可用于更改 PWM 输出:
前两个功能可通过函数 ledc_timer_config()
在后台运行,在定时器配置后启动。
使用中断
配置 LED PWM 控制器通道时,可在 ledc_channel_config_t
中选取参数 ledc_intr_type_t
,在渐变完成时触发中断。
要注册处理程序来处理中断,可调用函数 ledc_isr_register()
。
频率和占空比分辨率支持范围
LED PWM 控制器主要用于驱动 LED。该控制器 PWM 占空比设置的分辨率范围较广。比如,PWM 频率为 5 kHz 时,占空比分辨率最大可为 13 位。这意味着占空比可为 0 至 100% 之间的任意值,分辨率为 ~0.012%(2 ** 13 = 8192 LED 亮度的离散电平)。然而,这些参数取决于为 LED PWM 控制器定时器计时的时钟信号,LED PWM 控制器为通道提供时钟(具体可参考 定时器配置 和 ESP32-S3 技术参考手册 > LED PWM 计时器 (LEDC) [PDF])。
LED PWM 控制器可用于生成频率较高的信号,足以为数码相机模组等其他设备提供时钟。此时,最大频率可为 40 MHz,占空比分辨率为 1 位。也就是说,占空比固定为 50%,无法调整。
LED PWM 控制器 API 会在设定的频率和占空比分辨率超过 LED PWM 控制器硬件范围时报错。例如,试图将频率设置为 20 MHz、占空比分辨率设置为 3 位时,串行端口监视器上会报告如下错误:
E (196) ledc: requested frequency and duty resolution cannot be achieved, try reducing freq_hz or duty_resolution. div_param=128
此时,占空比分辨率或频率必须降低。比如,将占空比分辨率设置为 2 会解决这一问题,让占空比设置为 25% 的倍数,即 25%、50% 或 75%。
如设置的频率和占空比分辨率低于所支持的最低值,LED PWM 驱动器也会反映并报告,如:
E (196) ledc: requested frequency and duty resolution cannot be achieved, try increasing freq_hz or duty_resolution. div_param=128000000
占空比分辨率通常用 ledc_timer_bit_t 设置,范围是 10 至 15 位。如需较低的占空比分辨率(上至 10,下至 1),可直接输入相应数值。
结构体
ledc_timer_config_t
typedef struct {
ledc_mode_t speed_mode; /*!< LEDC速度模式,高速模式()(仅ESP32支持)或低速模式 */
/*!< 高速模式-LEDC_LOW_SPEED_MODE,低速模式-LEDC_SPEED_MODE_MAX*/
ledc_timer_bit_t duty_resolution; /*!< LEDC通道占空比分辨率 */
ledc_timer_t timer_num; /*!< 通道的定时器源(0 - LEDC_TIMER_MAX - 1) */
uint32_t freq_hz; /*!< LEDC定时器频率(赫兹) */
ledc_clk_cfg_t clk_cfg; /*!< 根据ledc_clk_cfg_t配置LEDC源时钟。
请注意,LEDC_USE_RC_FAST_CLK和LEDC_USE_XTAL_CLK是非定时器特定的时钟源。
不能让一个LEDC定时器使用RC_FAST_CLK作为时钟源,
而另一个LEDC定时器使用XTAL_CLK作为其时钟源。
除ESP32和ESP32S2之外的所有芯片都没有定时器特定的时钟源,
这意味着所有定时器的时钟源必须相同。 */
bool deconfigure; /*!< 设置此字段以取消配置之前已配置的LEDC定时器。
请注意,它不会检查要取消配置的定时器是否绑定到任何通道。
此外,在取消配置之前,必须先暂停该定时器。
当设置此字段时,duty_resolution(占空比分辨率)、freq_hz(频率(赫兹))、clk_cfg(时钟配置)字段将被忽略。 */
} ledc_timer_config_t;
ledc_channel_config_t
typedef struct {
int gpio_num; /*!< LEDC输出的GPIO编号,如果你想使用GPIO16,那么gpio_num = 16 */
ledc_mode_t speed_mode; /*!< LEDC速度模式,高速模式(仅ESP32支持)或低速模式 */
ledc_channel_t channel; /*!< LEDC通道(0 - LEDC_CHANNEL_MAX - 1) */
ledc_intr_type_t intr_type; /*!< 配置中断,使能渐变中断或禁用渐变中断 */
ledc_timer_t timer_sel; /*!< 选择通道的定时器源(0 - LEDC_TIMER_MAX - 1) */
uint32_t duty; /*!< LEDC通道占空比,占空比设置范围是[0, (2^占空比分辨率)] */
int hpoint; /*!< LEDC通道水平点值,范围是[0, (2 ^ 占空比分辨率) - 1] */
ledc_sleep_mode_t sleep_mode; /*!< 为轻睡眠模式下的LEDC通道选择所需行为 */
struct {
unsigned int output_invert: 1;/*!< 启用(1)或禁用(0)GPIO输出反相 */
} flags; /*!< LEDC标志 */
} ledc_channel_config_t;
API
LEDC 通道配置
esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf)
LEDC通道配置 使用给定的通道/输出GPIO编号/中断/源定时器/频率(赫兹)/LEDC占空比来配置LEDC通道。
参数
ledc_conf – LEDC通道配置结构体的指针
返回
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
LEDC定时器配置
esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf)
LEDC定时器配置 使用给定的源定时器/频率(赫兹)/占空比分辨率来配置LEDC定时器。
参数
timer_conf – LEDC定时器配置结构体的指针
返回
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
ESP_FAIL 基于给定的频率和当前占空比分辨率,找不到合适的预分频器数值。
ESP_ERR_INVALID_STATE 无法取消配置定时器,因为定时器未配置或未暂停。
更新LEDC通道参数。
esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
注意
调用此函数以激活更新后的LEDC参数。在调用ledc_set_duty之后,我们需要调用此函数来更新设置。并且新的LEDC参数要到下一个PWM周期才会生效。
ledc_set_duty、ledc_set_duty_with_hpoint和ledc_update_duty并非线程安全的,请勿在不同任务中同时调用这些函数来控制同一个LEDC通道。具备线程安全性的API版本为ledc_set_duty_and_update。
参数
speed_mode – 选择具有指定速度模式的LEDC通道组。请注意,并非所有目标都支持高速模式。
channel – LEDC通道(0 - LEDC_CHANNEL_MAX - 1),从ledc_channel_t中选择。
返回
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
设置LEDC输出GPIO
esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t ledc_channel)
注意
此函数仅通过矩阵将LEDC信号路由到GPIO,不涉及其他LEDC资源初始化。请改用ledc_channel_config() 来全面配置一个LEDC通道。
参数:
gpio_num – LEDC输出的通用输入输出引脚(GPIO)
speed_mode – 选择具有指定速度模式的LEDC通道组。请注意,并非所有目标设备都支持高速模式。
ledc_channel – LEDC通道(0 - LEDC_CHANNEL_MAX - 1),从ledc_channel_t中选择
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
LEDC输出停止
esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idle_level)
LEDC停止。禁用LEDC输出,并设置空闲电平。
注意
- 如果启用了CONFIG_LEDC_CTRL_FUNC_IN_IRAM,此函数将由链接器放置在IRAM中,这样即使缓存被禁用也能够执行。
- 此函数允许在中断服务程序(ISR)上下文环境中运行。
参数:
speed_mode – 选择具有指定速度模式的LEDC通道组。请注意,并非所有目标设备都支持高速模式。
channel – LEDC通道(0 - LEDC_CHANNEL_MAX - 1),从ledc_channel_t中选择。
idle_level – 设置LEDC停止后输出的空闲电平。
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
LEDC设置通道频率
esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t freq_hz)
LEDC设置通道频率(赫兹)
参数:
speed_mode – 选择具有指定速度模式的LEDC通道组。请注意,并非所有目标都支持高速模式。
timer_num – LEDC定时器索引(0 - 3),从ledc_timer_t中选择。
freq_hz – 设置LEDC频率。
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
ESP_FAIL 基于给定频率和当前占空比分辨率无法找到合适的预分频系数。
LEDC获取通道频率
uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num)
LEDC获取通道频率(赫兹)
参数
- speed_mode – 选择具有指定速度模式的LEDC通道组。请注意,并非所有目标都支持高速模式。
- timer_num – LEDC定时器索引(0 - 3),从ledc_timer_t中选择。
返回值
- 0 出错
- 其他值 当前LEDC频率
LEDC设置占空比
esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty)
LEDC设置占空比。此函数不会改变该通道的水平点(hpoint)值。如有需要,请调用ledc_set_duty_with_hpoint。只有在调用ledc_update_duty后,占空比才会更新。
注意
- ledc_set_duty、ledc_set_duty_with_hpoint 以及 ledc_update_duty 并非线程安全的,请勿在不同任务中同时调用这些函数来控制同一个 LEDC 通道。具有线程安全性的 API 版本是 ledc_set_duty_and_update。
- 对于ESP32,当某个通道正在进行渐变操作时,硬件不支持对其占空比进行任何更改。其他占空比操作必须等待渐变操作完成。
参数:
speed_mode – 选择具有指定速度模式的LEDC通道组。请注意,并非所有目标设备都支持高速模式。
channel – LEDC通道(0 - LEDC_CHANNEL_MAX - 1),从ledc_channel_t中选择。
duty – 设置LEDC占空比,占空比设置范围是[0, (2^占空比分辨率)]
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
LEDC获取占空比
uint32_t ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel)
LEDC获取占空比。此函数返回当前PWM周期的占空比。你不应期望该函数在调用ledc_update_duty的同一周期内返回新的占空比,因为占空比直到下一个周期才会生效。
参数:
speed_mode – 选择具有指定速度模式的LEDC通道组。请注意,并非所有目标都支持高速模式。
channel – LEDC通道(0 - LEDC_CHANNEL_MAX - 1),从ledc_channel_t中选择。
返回值:
如果参数错误,返回LEDC_ERR_DUTY
其他情况返回当前LEDC占空比
应用实例
头文件
ledc_pwm.h
#ifndef __LEDC_PWM_H
#define __LEDC_PWM_H
#include "driver/ledc.h"
// 定义 PWM 通道和 GPIO 引脚
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_OUTPUT_IO (14) // 可根据实际情况修改 GPIO 引脚
#define LEDC_CHANNEL LEDC_CHANNEL_0
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT // 13 位分辨率
#define LEDC_DUTY_MAX (8191) // 2^13 - 1
#define LEDC_FREQUENCY (50) // PWM 频率为 50Hz
void ledc_pwm_init();
void set_pwm_duty(ledc_channel_t channel,float duty);
void ledc_pwm_test();
#endif
ledc_pwm.c
#include "ledc_pwm.h"
void ledc_pwm_init()
{
//定时器配置
ledc_timer_config_t ledc_timer={
.duty_resolution = LEDC_TIMER_13_BIT, // 13 位分辨率,既2^13-1
.freq_hz = LEDC_FREQUENCY, //频率,50Hz
.speed_mode = LEDC_LOW_SPEED_MODE, //低速
.timer_num = LEDC_TIMER, // 通道的定时器源
.clk_cfg = LEDC_AUTO_CLK, //配置LEDC源时钟,自动配置
};
ledc_timer_config(&ledc_timer); //定时器配置
//通道配置
ledc_channel_config_t ledc_channel = {
.channel = LEDC_CHANNEL, //LEDC 通道
.duty = 0, //LEDC通道占空比,占空比设置范围是[0, (2^占空比分辨率)]
.gpio_num = LEDC_OUTPUT_IO, //LEDC输出的GPIO编号
.speed_mode = LEDC_LOW_SPEED_MODE, //LEDC速度模式
.hpoint = 0, //LEDC通道水平点值,范围是[0, (2 ^ 占空比分辨率) - 1]
.timer_sel = LEDC_TIMER, //通道的定时器源
};
ledc_channel_config(&ledc_channel);
}
/// @brief 设置占空比
/// @param duty 0%~100%
void set_pwm_duty(ledc_channel_t channel,float duty)
{
if(duty < 0 || duty > 100){
printf("占空比超出范围(0~100)n");
return ;
}
//计算占空比
uint32_t set_duty = LEDC_DUTY_MAX * duty / 100;
ledc_set_duty(LEDC_LOW_SPEED_MODE,channel,set_duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE,channel);
}
void ledc_pwm_test()
{
static uint8_t dir=1;
static float duty = 2.5;
if(dir){
duty += 0.2;
if(duty>=12.5) dir=0;
}
else {
duty -= 0.5;
if(duty<2.5) dir=1;
}
set_pwm_duty(LEDC_CHANNEL,duty);
}
main.c
//....
#include "ledc_pwm.h"
//.......
void app_main(void)
{
//...
ledc_pwm_init();
while(1)
{
//...
ledc_pwm_test();
bsp_delay_ms(10);
}
}