原创 10. ESP32开发之LED闪烁和呼吸的实现

2025-6-4 09:07 504 0 3 分类: MCU/ 嵌入式 文集: ESP32
  • 硬件电路介绍
  • 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. 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 定时器未配置或未暂停
*/


  1. 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 [0, (2**duty_resolution)-1] */
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);


  1. 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:占空比,取值范围 [0, (2**duty_resolution)]
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)


  1. 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呼吸的效果。


  1. 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:用户注册时的数据,用于给回调函数传参
*/


  1. 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;
}


  1. 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(ev&LED_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(ev&LED_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);
}
}


  1. 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 (1<<0)//事件组bit0设置为关灯事件

//此时为开灯状态
#define LED_ON_EV (1<<1)//事件组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(ev&LED_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(ev&LED_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();
}

整体流程

作者: 二月半, 来源:面包板社区

链接: https://mbb.eet-china.com/blog/uid-me-1862109.html

版权声明:本文为博主原创,未经本人允许,禁止转载!

PARTNER CONTENT

文章评论2条评论)

登录后参与讨论

二月半 2025-6-9 08:30

eeNick: 厉害厉害,必须奖励E币,100个
感谢感谢

eeNick 2025-6-6 15:44

厉害厉害,必须奖励E币,100个
相关推荐阅读
二月半 2025-06-17 16:39
13. ESP32开发之定时器中断
概述相关API函数举例:定时发送一个事件总结概述ESP32有一组外设--定时器组。它可以选择不同的时钟源和分配系数。该定时器应用灵活,超时报警可以自动更新计数值。相关API函数1.定时器配置结构体ty...
二月半 2025-06-12 14:32
【拆解】一款远程控制开关
七年前买了个远程控制开关,想想那个时候应该物联网才兴起的时候吧。如今因为控制麻烦且经常出现连接掉线问题,于是给淘汰了。这个设备我是拿来控制吊灯,特别麻烦的是,当晚上关灯后,会有一点灯点亮着,掉线的时候...
二月半 2025-06-12 10:11
ESP32开发之GPIO中断
电路图GPIO的中断类型相关API函数应用举例总结电路图在ESP32中内部有完整的控制电路,比如上下拉以及滤波器等,所以我们这里可以直接用一个微动开关连接到地。GPIO的中断类型GPIO_INTR_D...
二月半 2025-06-09 22:37
ESP32开发之WS2812B控制
WS2812B数据手册重点摘录硬件电路Remote Control Transceive(RMT)概念RMT的相关API函数一段简单的控制WS2812B的应用举例总结WS2812B数据手册重点摘录WS...
二月半 2025-06-02 21:24
【Milk-V Duo 开发板】+初用体验
许久未在面包板社区申请板卡评测了。这一次偶然最近的这款Milk-V Duo开发板正在评测。首次看到如此简单切功能强大的嵌入式平台:· 支持linux、rtos· 可接一路Camera,做人脸检测、目标...
我要评论
2
0
关闭 站长推荐上一条 /4 下一条