硬件电路介绍 GPIO输出模式 GPIO配置过程 闪烁灯的源码 LED PWM的控制器(LEDC)概述 LEDC配置过程及现象 整体流程 硬件电路介绍 电路图如下: 只要有硬件基础的应该都知道上图中,当GPIO4的输出电平为高时,LED灯亮,反之则熄灭。如果每间隔一段时间进行一次电平的反转,则将使LED产生闪烁的效果。 GPIO模式 在进行GPIO控制之前,需要熟悉一下ESP32的GPIO几种模式: GPIO模式 模式宏定义 说明 输入模式 GPIO_MODE_INPUT 可以通过配置项pull_up_en或pull_down_en配置上拉或者下拉 推挽输出模式 GPIO_MODE_OUTPUT 高低电平输出 开漏输出模式 GPIO_MODE_OUTPUT_OD 通常用于I2C 中断 可通过intr_type配置项配置触发方式:上升沿/下降沿/双沿/电平触发等 禁用 GPIO_MODE_DISABLE 禁用GPIO,不作为输入也不作为输出 输入输出模式 GPIO_MODE_INPUT_OUTPUT 输入及开漏输出 GPIO_MODE_INPUT_OUTPUT_OD 注意: 使用中断时,将GPIO模式设置为输入模式 如果GPIO用于I2C的SDA,设置模式为 GPIO_MODE_INPUT_OUTPUT_OD ,且需要配置上拉,也可在芯片相关引脚增加上拉电路 GPIO配置过程 配置GPIO 使用结构体gpio_config_t对GPIO相关参数进行配置 注册GPIO 通过函数 gpio_config 函数将以上配置注册进系统 通过GPIO相关API函数对GPIO进行控制 比如此次实验是控制LED闪烁,那么则是使用 gpio_set_level 函数进行输出电平控制 闪烁灯的源码 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-05-20 * @version v0.1 * * @revision history: * 2025-05-20 - Initial version. */ # include stdio.h # include "freertos/FreeRTOS.h" # include "freertos/task.h" # include "driver/gpio.h" # include "esp_log.h" # define LED_GPIO GPIO_NUM_4 //根据实际的连接方式更改 void ledCtlTask ( void *pvParam) { while ( 1 ){ gpio_set_level (LED_GPIO, 1 ); // 设置为高电平(点亮 LED) vTaskDelay ( pdMS_TO_TICKS ( 500 )); // 延时 0.5 秒 gpio_set_level (LED_GPIO, 0 ); // 设置为低电平(熄灭 LED) vTaskDelay ( pdMS_TO_TICKS ( 500 )); // 延时 0.5 秒 } } void app_main ( void ) { // 配置 GPIO gpio_config_t io_conf = { .pin_bit_mask = ( 1ULL LED_GPIO), // 选择 GPIO .mode = GPIO_MODE_OUTPUT, // 设置为输出模式 .pull_up_en = GPIO_PULLUP_DISABLE, // 不启用上拉 .pull_down_en = GPIO_PULLDOWN_DISABLE, // 不启用下拉 .intr_type = GPIO_INTR_DISABLE // 不启用中断 }; gpio_config (io_conf); xTaskCreatePinnedToCore (ledCtlTask, "ledCtlTask" , 2048 , NULL , 3 , NULL , 1 ); } LED PWM的控制器(LEDC)概述 从以上案例可以看出,对于通用GPIO的控制要么是高电平,要么是低电平。所以只能控制LED的闪烁现象。而对于ESP32-S3却有专用控制LED的控制器,称之LED PWM。它有8路低速通道。专用于控制LED。当然也可以产生PWM控制电机等。ESP32有两组LED PWM控制器,一组为8路高速通道,另一组为8路低速通道。 LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实现亮度渐变,如果是RGB LED,还能实现颜色的渐变。 LEDC的配置过程及现象 1. 定时器的配置过程 创建定时器配置结构体 typedef struct { ledc_mode_t speed_mode; /* LEDC速度模式, high-speed mode (only exists on esp32) or low-speed mode */ ledc_timer_bit_t duty_resolution; /* LEDC占空比分辨率 */ ledc_timer_t timer_num; /* The timer source of channel (0 - LEDC_TIMER_MAX-1) */ uint32_t freq_hz; /* LEDC 的时钟频率 */ ledc_clk_cfg_t clk_cfg; /*配置LEDC的时钟源. */ bool deconfigure; /*是否取消此配置之前的配置,取消之前先要关闭定时器 */ } ledc_timer_config_t 使用相关函数将结构体完成配置 esp_err_t ledc_timer_config ( const ledc_timer_config_t *timer_conf) ; //参数为以上定义的结构体 /*返回值: * ESP_OK 成功 * ESP_ERR_INVALID_ARG 参数错误 * ESP_FAIL 无法根据给定频率和当前占空比分辨率找到合适的预分频器编号 *ESP_ERR_INVALID_STATE 定时器未配置或未暂停 */ 2. 配置通道以及指定GPIO 创建配置通道结构体 typedef struct { int gpio_num; /* LEDC的输出GPIO*/ ledc_mode_t speed_mode; /* LEDC 速度模式,ESP32S3只能配置为低速 */ ledc_channel_t channel; /*LED PWM的控制器(LEDC) LEDC的通道 */ ledc_intr_type_t intr_type; /*是否开启渐变中断 */ ledc_timer_t timer_sel; /*选择定时器l (0 - LEDC_TIMER_MAX-1) */ uint32_t duty; /*! LEDC 通道占空比*/ int hpoint; /*! LEDC channel hpoint value, the range is */ struct { unsigned int output_invert: 1 ; /*! Enable (1) or disable (0) gpio output invert */ } flags; /*! LEDC 标志 */ } ledc_channel_config_t ; 使用函数完成配置 esp_err_t ledc_channel_config ( const ledc_channel_config_t *ledc_conf) ; 3. 配置占空比改变PWM信号 使能硬件PWM //参数intr_alloc_flags为分配的中断优先级 esp_err_t ledc_fade_func_install ( int intr_alloc_flags) 配置渐变参数 /* 参数: speed_mode:LEDC的速度模式,只有ESP32有高速模式 channel:通道,0-7 target_duty:占空比,取值范围 max_fade_time_ms:最大的渐变时间 */ 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) 开启渐变 /* 参数: speed_mode:LEDC的速度模式 channel:通道,0-7 fade_mode:是否阻塞直到渐变完成,如果设置成LEDC_FADE_WAIT_DONE模式,则不渐变到预定值则不返回 */ esp_err_t ledc_fade_start ( ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t fade_mode) 4. 第一阶段实例:实现LED缓慢亮灯 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-05-27 * @version v0.1 * * @revision history: * 2025-05-27 - Initial version. */ # include stdio.h # include "freertos/FreeRTOS.h" # include "freertos/task.h" # include "driver/gpio.h" # include "esp_log.h" # include driver/ledc.h # define LEDC_MODE LEDC_LOW_SPEED_MODE # define LEDC_DUTY_RES LEDC_TIMER_13_BIT # define LEDC_TIMER_NUM LEDC_TIMER_0 # define LEDC_FREQ 5000 # define LEDC_CHANNEL LEDC_CHANNEL_0 # define LEDC_GPIO GPIO_NUM_4 # define LEDC_DUTY 4095 //2^13-1 void ledc_init ( void ) { ledc_timer_config_t timer_config={ .speed_mode= LEDC_MODE, .duty_resolution= LEDC_DUTY_RES, .timer_num= LEDC_TIMER_NUM, .clk_cfg=LEDC_AUTO_CLK, .freq_hz=LEDC_FREQ }; ledc_timer_config (timer_config); ledc_channel_config_t ledc_channel={ .speed_mode = LEDC_MODE, .channel = LEDC_CHANNEL, .gpio_num = LEDC_GPIO, .intr_type = LEDC_INTR_DISABLE, .duty = 0 , .hpoint = 0 }; ledc_channel_config (ledc_channel); } void app_main ( void ) { ledc_init (); ledc_fade_func_install ( 0 ); ledc_set_fade_with_time (LEDC_MODE,LEDC_CHANNEL, 4095 , 10000 ); ledc_fade_start (LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT); } 波形说明:可以明显的看到PWM的占空比的变化,LED也缓慢的亮起。 那么接下来就是实现从亮起再缓慢的熄灭,以此循环则实现了LED呼吸的效果。 5. 渐变回调函数 LEDC控制器在使能渐变后,每个通道都可以有一个回调函数,通过ledc_cb_register()进行注册 esp_err_t ledc_cb_register ( ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg) /*参数: speed_mode:速度模式,只有ESP32有高速模式 channel: LEDC通道,低速模式有8个通道 cbs:回调函数原型定义在 ledc_cbs_t 结构体中 user_arg:用户注册时的数据,用于给回调函数传参 */ 6. 通过事件组的方式将此时LED的状态发送出去,即设置事件值 在中断中避免处理复杂的内容,所以在渐变回调函数中只使用事件组方式发送相关事件。不了解这块的知识可以参考我之前的文章 《ESP32开发之freeRTOS的事件组》 bool IRAM_ATTR ledc_fade_cb ( const ledc_cb_param_t *param, void *user_arg) { BaseType_t pxHigherPriorityTaskWoken; //如果当前LEDC占空比最大,说明此时LED为开灯状态,反之为关灯状态 if (param-duty){ xEventGroupSetBitsFromISR (s_ledc_ev,LED_ON_EV,pxHigherPriorityTaskWoken); } else { xEventGroupSetBitsFromISR (s_ledc_ev,LED_OFF_EV,pxHigherPriorityTaskWoken); } return pxHigherPriorityTaskWoken; } 7. 创建一个任务来接收事件并做渐变过程的改变 void ledc_fade_task ( void * param) { EventBits_t ev; while ( 1 ){ ev= xEventGroupWaitBits (s_ledc_ev,LED_OFF_EV|LED_ON_EV,pdTRUE,pdFALSE,portMAX_DELAY); if (ev){ if (evLED_OFF_EV){ ledc_set_fade_with_time (LEDC_MODE,LEDC_CHANNEL,LEDC_DUTY, 1000 ); ledc_fade_start (LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT); } if (evLED_ON_EV){ ledc_set_fade_with_time (LEDC_MODE,LEDC_CHANNEL, 0 , 1000 ); ledc_fade_start (LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT); } } //处理完成需要再次注册回调函数,产生循环 ledc_cbs_t cbs={.fade_cb=ledc_fade_cb}; ledc_cb_register (LEDC_MODE,LEDC_CHANNEL,cbs, NULL ); } } 8. 第二阶段实例:完整实现渐变的循环 /** * Copyright (C) 2024-2034 HalfMoon2. * All rights reserved. * * @file Filename without the absolute path * @brief Brief description * @author HalfMoon2 * @date 2025-05-27 * @version v0.1 * * @revision history: * 2025-05-27 - Initial version. */ # include stdio.h # include "freertos/FreeRTOS.h" # include "freertos/task.h" # include "driver/gpio.h" # include "esp_log.h" # include driver/ledc.h # define LEDC_MODE LEDC_LOW_SPEED_MODE # define LEDC_DUTY_RES LEDC_TIMER_13_BIT # define LEDC_TIMER_NUM LEDC_TIMER_0 # define LEDC_FREQ 5000 # define LEDC_CHANNEL LEDC_CHANNEL_0 # define LEDC_GPIO GPIO_NUM_4 # define LEDC_DUTY 4095 //2^13-1 //通知渐变完成 static EventGroupHandle_t s_ledc_ev = NULL ; //此时为关灯状态 # define LED_OFF_EV (10) //事件组bit0设置为关灯事件 //此时为开灯状态 # define LED_ON_EV (11) //事件组bit1设置为开灯事件 /** * @brief 渐变结束回调函数 * @param *param:LEDC callback parameter * @param *user_arg:User registered data * @return 返回是否唤醒高优先级任务 * @note 此函数为中断服务函数,所以不应处理过多的操作,那么在此函数中通过发送事件的方式,由渐变任务函数处理事件 */ bool IRAM_ATTR ledc_fade_cb ( const ledc_cb_param_t *param, void *user_arg) { BaseType_t pxHigherPriorityTaskWoken; //如果当前LEDC占空比最大,说明此时LED为开灯状态,反之为关灯状态 if (param-duty){ xEventGroupSetBitsFromISR (s_ledc_ev,LED_ON_EV,pxHigherPriorityTaskWoken); } else { xEventGroupSetBitsFromISR (s_ledc_ev,LED_OFF_EV,pxHigherPriorityTaskWoken); } return pxHigherPriorityTaskWoken; } /** * @brief led渐变任务 * @param 任务参数 * @note 接收事件并做LED操作 */ void ledc_fade_task ( void * param) { EventBits_t ev; while ( 1 ){ ev= xEventGroupWaitBits (s_ledc_ev,LED_OFF_EV|LED_ON_EV,pdTRUE,pdFALSE,portMAX_DELAY); if (ev){ if (evLED_OFF_EV){ ledc_set_fade_with_time (LEDC_MODE,LEDC_CHANNEL,LEDC_DUTY, 1000 ); ledc_fade_start (LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT); } if (evLED_ON_EV){ ledc_set_fade_with_time (LEDC_MODE,LEDC_CHANNEL, 0 , 1000 ); ledc_fade_start (LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT); } } //处理完成需要再次注册回调函数,产生循环 ledc_cbs_t cbs={.fade_cb=ledc_fade_cb}; ledc_cb_register (LEDC_MODE,LEDC_CHANNEL,cbs, NULL ); } } void ledc_init ( void ) { ledc_timer_config_t timer_config={ .speed_mode= LEDC_MODE, .duty_resolution= LEDC_DUTY_RES, .timer_num= LEDC_TIMER_NUM, .clk_cfg=LEDC_AUTO_CLK, .freq_hz=LEDC_FREQ }; ledc_timer_config (timer_config); ledc_channel_config_t ledc_channel={ .speed_mode = LEDC_MODE, .channel = LEDC_CHANNEL, .gpio_num = LEDC_GPIO, .intr_type = LEDC_INTR_DISABLE, .duty = 0 , .hpoint = 0 }; ledc_channel_config (ledc_channel); //创建事件组,用于接收和发送渐变事件 s_ledc_ev = xEventGroupCreate (); //开启硬件PWM ledc_fade_func_install ( 0 ); //设置渐变参数 ledc_set_fade_with_time (LEDC_MODE,LEDC_CHANNEL,LEDC_DUTY, 1000 ); //启动渐变 ledc_fade_start (LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT); //注册渐变回调函数 ledc_cbs_t cbs={.fade_cb=ledc_fade_cb,}; ledc_cb_register (LEDC_MODE,LEDC_CHANNEL,cbs, NULL ); xTaskCreatePinnedToCore (ledc_fade_task, "ledc_fade_task" , 2048 , NULL , 3 , NULL , 1 ); } void app_main ( void ) { ledc_init (); } 整体流程