LED 控制器 (LEDC) 主要用于控制 LED,也可产生 PWM 信号用于其他设备的控制。该控制器有 8 路通道,可以产生独立的波形,驱动 RGB LED 等设备。

LEDC 通道共有两组,分别为 8 路高速通道和 8 路低速通道。高速通道模式在硬件中实现,可以自动且无干扰地改变 PWM 占空比。低速通道模式下,PWM 占空比需要由软件中的驱动器改变。每组通道都可以使用不同的时钟源。

LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实现亮度和颜色渐变。

本教程采用esp-idf-5.3版本,型号为esp32-s3,ESP32-S3 仅支持设置通道为低速模式

1.头文件

#include "driver/ledc.h"

2.LEDC常用配置

首次 LEDC 配置时,建议先配置定时器(调用函数 ledc_timer_config()),再配置通道(调用函数 ledc_channel_config())。这样可以确保 IO 脚上的 PWM 信号自有输出开始其频率就是正确的。

 //准备并应用led PWM定时器配置
    ledc_timer_config_t ledc_timer = {
        .speed_mode       = LEDC_LOW_SPEED_MODE, 	//速度模式(值必须为 LEDC_LOW_SPEED_MODE)
        .duty_resolution  = LEDC_DUTY_RES,			//PWM 占空比分辨率
        .timer_num        = LEDC_TIMER,				//定时器索引
        .freq_hz          = LEDC_FREQUENCY, 		// PWM 信号频率(Hz)
        .clk_cfg          = LEDC_AUTO_CLK			//时钟源
    };
    ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

    // 准备并应用LEDC PWM通道配置
    ledc_channel_config_t ledc_channel = {
        .speed_mode     = LEDC_LOW_SPEED_MODE,	//高速模式(仅在esp32上存在)或低速模式
        .channel        = LEDC_CHANNEL,			//LEDC通道
        .timer_sel      = LEDC_TIMER,			//选择通道的定时器源
        .intr_type      = LEDC_INTR_DISABLE,	//配置中断
        .gpio_num       = LEDC_OUTPUT_IO,  		//LEDC输出的GPIO
        .duty           = 0, 					// LEDC通道占空比
        .hpoint         = 0,					//LEDC通道hpoint值
      	.flags.output_invert = 0				//启用(1)或禁用(0)gpio输出反相
    };
    ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));

3.相关控制函数

//LEDC 通道配置 使用给定的通道/输出 gpio_num/中断/源定时器/频率(Hz)/LEDC 占空比配置 LEDC 通道。
esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf);

//辅助函数用于查找 ledc_timer_config() 的最大可能占空比分辨率(以位为单位)
uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq);

//LEDC 定时器配置 使用给定的源定时器/频率(Hz)/占空比分辨率配置 LEDC 定时器
esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf);

//LEDC更新通道参数
//调用此函数激活LEDC更新后的参数。在ledc_set_duty之后,我们需要调用此函数来更新设置
//新的LEDC参数直到下一个PWM周期才会生效
//ledc_set_duty、ledc_set_duty_with_hpoint 和 ledc_update_duty 不是线程安全的
//请勿同时在不同任务中调用这些函数来控制一个 LEDC 通道
esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);

//设置LEDC输出gpio
esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t ledc_channel);

//LEDC 停止。禁止 LEDC 输出,并设置空闲电平
esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idle_level);

//LEDC 设定通道频率(Hz)
esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t freq_hz);

//LEDC 获取通道频率(Hz)
uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num);

//LEDC 设置占空比和 hpoint 值 只有调用 ledc_update_duty 后占空比才会更新
//hpoint高电平起始点
esp_err_t ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, uint32_t hpoint);

//LEDC 获取 hpoint 值
int ledc_get_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel);

//LEDC 设置占空比 该函数不会改变该通道的 hpoint 值
//如果需要改变,请调用 ledc_set_duty_with_hpoint
//只有调用 ledc_update_duty 后占空比才会更新
esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty);

//LEDC 获取占空比 此函数返回当前 PWM 周期的占空比
uint32_t ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel);

// 设置 LEDC 渐变,函数调用 ledc_update_duty 函数后,函数才能生效
esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, ledc_duty_direction_t fade_direction, uint32_t step_num, uint32_t duty_cycle_num, uint32_t duty_scale);

//注册 LEDC 中断处理程序,该处理程序是一个 ISR。该处理程序将附加到运行此函数的同一 CPU 核心上。
esp_err_t ledc_isr_register(void (*fn)(void*), void *arg, int intr_alloc_flags, ledc_isr_handle_t *handle);

//配置 LEDC 设置
esp_err_t ledc_timer_set(ledc_mode_t speed_mode, ledc_timer_t timer_sel, uint32_t clock_divider, uint32_t duty_resolution, ledc_clk_src_t clk_src);

// 重置 LEDC 计时器
esp_err_t ledc_timer_rst(ledc_mode_t speed_mode, ledc_timer_t timer_sel);

//暂停 LEDC 定时器计数器
esp_err_t ledc_timer_pause(ledc_mode_t speed_mode, ledc_timer_t timer_sel);

//恢复 LEDC 定时器
esp_err_t ledc_timer_resume(ledc_mode_t speed_mode, ledc_timer_t timer_sel);

//将 LEDC 通道与选定的定时器绑定
esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_timer_t timer_sel);

//设置 LEDC 渐变功能
//调用此函数之前先调用一次 ledc_fade_func_install()。之后调用 ledc_fade_start() 开始渐变
esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, uint32_t scale, uint32_t cycle_num);

//设定 LEDC 淡入淡出功能,并限制时间
esp_err_t ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, int max_fade_time_ms);

//安装 LEDC 渐变功能。该函数会占用 LEDC 模块的中断
esp_err_t ledc_fade_func_install(int intr_alloc_flags);

//卸载 LEDC 渐变功能
void ledc_fade_func_uninstall(void);

//停止 LEDC 渐变。函数返回后,保证通道的占空比最多在一个 PWM 周期内固定
esp_err_t ledc_fade_stop(ledc_mode_t speed_mode, ledc_channel_t channel);

//一个线程安全的 API,用于设置 LEDC 通道的占空比并在占空比更新时返回
esp_err_t ledc_set_duty_and_update(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, uint32_t hpoint);

//一个线程安全的 API,用于设置和启动 LEDC 渐变功能,但有时间限制
esp_err_t ledc_set_fade_time_and_start(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, uint32_t max_fade_time_ms, ledc_fade_mode_t fade_mode);

//用于设置和启动 LEDC 渐变功能的线程安全 API
esp_err_t ledc_set_fade_step_and_start(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, uint32_t scale, uint32_t cycle_num, ledc_fade_mode_t fade_mode);

//LEDC 回调注册函数
esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg);

4.LEDC例程

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

/*
 * About this example
 *
 * 1. 从初始化LEDC模块开始:
 *    a. 先设置LEDC的定时器,这决定了频率和PWM的分辨率.
 *    b. 然后设置要使用的LEDC通道,并绑定其中一个计时器.
 *
 * 2.首先你需要安装一个默认的渐变功能,然后你可以使用渐变api.
 *
 * 3. 您还可以直接设置目标任务,而不会衰减.
 *
 * 4. 在ESP32上,GPIO18/19/4/5用作LEDC输出:
 *              GPIO18/19属于高速通道组
 *              GPIO4/5属于低速通道组
 *
 *    在其他目标上,GPIO8/9/4/5用作LEDC输出,
 *    他们都来自低速频道组.
 *
 * 5. 所有的LEDC输出都可以重复改变占空.
 *
 */

#define LEDC_LS_TIMER          LEDC_TIMER_1
#define LEDC_LS_MODE           LEDC_LOW_SPEED_MODE
#if !CONFIG_IDF_TARGET_ESP32
#define LEDC_LS_CH0_GPIO       (8)
#define LEDC_LS_CH0_CHANNEL    LEDC_CHANNEL_0
#define LEDC_LS_CH1_GPIO       (9)
#define LEDC_LS_CH1_CHANNEL    LEDC_CHANNEL_1
#endif
#define LEDC_LS_CH2_GPIO       (4)
#define LEDC_LS_CH2_CHANNEL    LEDC_CHANNEL_2
#define LEDC_LS_CH3_GPIO       (5)
#define LEDC_LS_CH3_CHANNEL    LEDC_CHANNEL_3

#define LEDC_TEST_CH_NUM       (4)
#define LEDC_TEST_DUTY         (4000)
#define LEDC_TEST_FADE_TIME    (3000)


static IRAM_ATTR bool cb_ledc_fade_end_event(const ledc_cb_param_t *param, void *user_arg)
{
    BaseType_t taskAwoken = pdFALSE;

    if (param->event == LEDC_FADE_END_EVT) {
        SemaphoreHandle_t counting_sem = (SemaphoreHandle_t) user_arg;
        xSemaphoreGiveFromISR(counting_sem, &taskAwoken);
    }

    return (taskAwoken == pdTRUE);
}

void app_main(void)
{
    int ch;

 
    ledc_timer_config_t ledc_timer = {
        .duty_resolution = LEDC_TIMER_13_BIT, // resolution of PWM duty
        .freq_hz = 4000,                      // frequency of PWM signal
        .speed_mode = LEDC_LS_MODE,           // timer mode
        .timer_num = LEDC_LS_TIMER,            // timer index
        .clk_cfg = LEDC_AUTO_CLK,              // Auto select the source clock
    };
   
    ledc_timer_config(&ledc_timer);

    ledc_channel_config_t ledc_channel[LEDC_TEST_CH_NUM] = {

        {
            .channel    = LEDC_LS_CH0_CHANNEL,
            .duty       = 0,
            .gpio_num   = LEDC_LS_CH0_GPIO,
            .speed_mode = LEDC_LS_MODE,
            .hpoint     = 0,
            .timer_sel  = LEDC_LS_TIMER,
            .flags.output_invert = 0			//启用(1)或禁用(0)gpio输出反相
        },
        {
            .channel    = LEDC_LS_CH1_CHANNEL,
            .duty       = 0,
            .gpio_num   = LEDC_LS_CH1_GPIO,
            .speed_mode = LEDC_LS_MODE,
            .hpoint     = 0,
            .timer_sel  = LEDC_LS_TIMER,
            .flags.output_invert = 0
        },
        {
            .channel    = LEDC_LS_CH2_CHANNEL,
            .duty       = 0,
            .gpio_num   = LEDC_LS_CH2_GPIO,
            .speed_mode = LEDC_LS_MODE,
            .hpoint     = 0,
            .timer_sel  = LEDC_LS_TIMER,
            .flags.output_invert = 1
        },
        {
            .channel    = LEDC_LS_CH3_CHANNEL,
            .duty       = 0,
            .gpio_num   = LEDC_LS_CH3_GPIO,
            .speed_mode = LEDC_LS_MODE,
            .hpoint     = 0,
            .timer_sel  = LEDC_LS_TIMER,
            .flags.output_invert = 1
        },
    };

    // Set LED Controller with previously prepared configuration
    for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
        ledc_channel_config(&ledc_channel[ch]);
    }

    //安装 LEDC 渐变功能.
    ledc_fade_func_install(0);
    ledc_cbs_t callbacks = {
        .fade_cb = cb_ledc_fade_end_event		//创建回调函数
    };
    SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(LEDC_TEST_CH_NUM, 0);
	//为所有通道注册回调函数
    for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
        ledc_cb_register(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, &callbacks, (void *) counting_sem);
    }

    while (1) {
        printf("1. LEDC fade up to duty = %d\n", LEDC_TEST_DUTY);
        for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
            ledc_set_fade_with_time(ledc_channel[ch].speed_mode,
                                    ledc_channel[ch].channel, LEDC_TEST_DUTY, LEDC_TEST_FADE_TIME);
            ledc_fade_start(ledc_channel[ch].speed_mode,
                            ledc_channel[ch].channel, LEDC_FADE_NO_WAIT);
        }

        for (int i = 0; i < LEDC_TEST_CH_NUM; i++) {
            xSemaphoreTake(counting_sem, portMAX_DELAY);
        }

        printf("2. LEDC fade down to duty = 0\n");
        for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
            ledc_set_fade_with_time(ledc_channel[ch].speed_mode,
                                    ledc_channel[ch].channel, 0, LEDC_TEST_FADE_TIME);
            ledc_fade_start(ledc_channel[ch].speed_mode,
                            ledc_channel[ch].channel, LEDC_FADE_NO_WAIT);
        }

        for (int i = 0; i < LEDC_TEST_CH_NUM; i++) {
            xSemaphoreTake(counting_sem, portMAX_DELAY);
        }

        printf("3. LEDC set duty = %d without fade\n", LEDC_TEST_DUTY);
        for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
            ledc_set_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, LEDC_TEST_DUTY);
            ledc_update_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel);
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);

        printf("4. LEDC set duty = 0 without fade\n");
        for (ch = 0; ch < LEDC_TEST_CH_NUM; ch++) {
            ledc_set_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel, 0);
            ledc_update_duty(ledc_channel[ch].speed_mode, ledc_channel[ch].channel);
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

Logo

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

更多推荐