ESP32开发之LED闪烁和呼吸的实现

  • 硬件电路介绍
  • 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函数进行输出电平控制

闪烁灯的源码

c 复制代码
/**
 * 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. 定时器的配置过程
  • 创建定时器配置结构体

    c 复制代码
    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
  • 使用相关函数将结构体完成配置

c 复制代码
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. 配置通道以及指定GPIO
  • 创建配置通道结构体
c 复制代码
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;
  • 使用函数完成配置
c 复制代码
esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf);
  1. 配置占空比改变PWM信号
  • 使能硬件PWM
c 复制代码
//参数intr_alloc_flags为分配的中断优先级
esp_err_t ledc_fade_func_install(int intr_alloc_flags)
  • 配置渐变参数
c 复制代码
/*
参数:
 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)
  • 开启渐变
c 复制代码
/*
参数:
 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. 第一阶段实例:实现LED缓慢亮灯
c 复制代码
/**
 * 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. 渐变回调函数

LEDC控制器在使能渐变后,每个通道都可以有一个回调函数,通过ledc_cb_register()进行注册

c 复制代码
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. 通过事件组的方式将此时LED的状态发送出去,即设置事件值

在中断中避免处理复杂的内容,所以在渐变回调函数中只使用事件组方式发送相关事件。不了解这块的知识可以参考我之前的文章

《ESP32开发之freeRTOS的事件组》

c 复制代码
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. 创建一个任务来接收事件并做渐变过程的改变
c 复制代码
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. 第二阶段实例:完整实现渐变的循环
c 复制代码
/**
 * 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();
}

整体流程

相关推荐
智者知已应修善业1 小时前
【51单片机用数码管显示流水灯的种类是按钮控制数码管加一和流水灯】2022-6-14
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
智商偏低7 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen8 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
白鱼不小白11 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D11 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术14 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt15 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘15 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
myloveasuka16 小时前
信号操作集函数
linux·运维·服务器·c语言·c++·vscode
几个几个n17 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件