第十八章 ESP32S3 HW_PWM 实验
本章使用 ESP32-S3 LED控制器(LEDC)。上一章节我们介绍了通过软件改变 PWM占空比。这一章学习硬件改变 PWM 占空比的运用。
本章分为如下几个小节:
18.1 PWM 简介
18.2 硬件设计
18.3 程序设计
18.4 下载验证
18.1 PWM 简介
关于 PWM 的一些知识,在第十七章已经介绍过,在此便不做赘述。使用硬件的方式改变 PWM 占空比与使用软件的方式改变 PWM 占空比的不同之处在于, LED PWM 控制器硬件可逐渐改变占空比的数值, 要使用此功能,需用函数 ledc_fade_func_install() 使能渐变, 之后用下列可用渐变函数之一配置:
(1) ledc_set_fade_with_time()
(2) ledc_set_fade_with_step()
(3) ledc_set_fade()
最后需要调用 ledc_fade_start() 开启渐变。
18.2 硬件设计
18.2.1 例程功能
通过硬件改变 PWM 的形式使得 LED 由亮到暗,再由暗到亮,依次循环。
18.2.2 硬件资源
1. LED
LED - IO1
2. 定时器1
通道 1 - IO1
18.2.3 原理图
本章实验使用的定时器1 为 ESP32-S3 的片上资源,因此没有对应的连接原理图。
18.3 程序设计
18.3.1 程序流程图
本实验的程序流程图:
图 18.3.1.1 HW_PWM 实验程序流程图
18.3.2 HW_PWM 函数解析
ESP-IDF 提供了一套 API 来配置 PWM。要使用此功能,需要导入必要的头文件:
#include "driver/ledc.h"
接下来,介绍一些常用的 HW_PWM 函数,这些函数的描述及其作用如下:
(1)使能渐变
LED PWM控制器硬件可逐渐改变占空比的数值。开启此功能,需要用函数ledc_fade_func_install()使能渐变,该函数原型如下所示:
esp_err_t ledc_fade_func_install(int intr_alloc_flags);
该函数的形参描述,如下表所示:
形参 | 描述 | 推荐值 |
---|---|---|
intr_alloc_flags | 用于分配中断的标志 | 0 (默认) 或 ESP_INTR_FLAG_IRAM |
表 18.3.2.1 函数 ledc_fade_func_install()形参描述
返回值: ESP_OK 表示配置成功,其他表示配置失败。
调用此函数后会启用以下关键组件:
1)渐变后台任务 (FreeRTOS任务)
- 优先级:LEDC_FADE_SERVICE_TASK_PRI
- 栈大小:LEDC_FADE_SERVICE_TASK_STACK
2)中断服务程序 (ISR)
3)内存资源分配
- 队列:用于传递渐变指令
- 缓冲区:存储渐变状态
(2)设置 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);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
speed_mode | 速度模式选择: LEDC_HIGH_SPEED_MODE LEDC_LOW_SPEED_MODE |
channel | LEDC 通道: (0 - LEDC_CHANNEL_MAX-1),从 ledc_channel_t 中选择 |
target_duty | 目标占空比,0 - (2ⁿ-1),n为分辨率位数 |
max_fade_time_ms | 最大渐变时间(ms),≥ LEDC_FADE_TIME_MIN_MS |
表 18.3.2.2 函数 ledc_set_fade_with_time()形参描述
返回值:ESP_OK 表示配置成功,其他表示配置失败。
(3)开启渐变
设置占空比以及渐变时长后,便可开启渐变功能,该函数原型如下所示:
esp_err_t ledc_fade_start(ledc_mode_t speed_mode,ledc_channel_t channel,ledc_fade_mode_t fade_mode);
该函数的形参描述,如下表所示:
形参 | 描述 |
---|---|
speed_mode | 速度模式选择: LEDC_HIGH_SPEED_MODE LEDC_LOW_SPEED_MODE |
channel | LEDC 通道: (0 - LEDC_CHANNEL_MAX-1),从 ledc_channel_t 中选择 |
fade_mode | 渐变模式, ledc_fade_mode_t 为索引,有几个模式可选: LEDC_FADE_NO_WAIT LEDC_FADE_WAIT_DONE LEDC_FADE_MAX |
表 18.3.2.3 函数 ledc_fade_start()形参描述
返回值: ESP_OK 表示配置成功,其他表示配置失败。
18.3.3 HW_PWM 驱动解析
在 IDF 版的 08-2_hw_pwm 例程中,在 08-2_hw_pwm \components\BSP 路径下新增了一个 PWM文件夹,用于存放 pwm.c和 pwm.h这两个文件。其中, pwm.h文件负责声明HW_PWM相关的函数和变量,而 pwm.c 文件则实现了 HW_PWM 的驱动代码。下面,我们将详细解析这两个文件的实现内容。
注意:这个08-2_hw_pwm示例在IDF 5.4.2,程序下载后并没有实现预期的呼吸灯,原因是ledc_timer_config(deconfigure参数) 和 ledc_channel_config(sleep_mode参数)校验失败了,需要显示初始化为正确值。
(1) pwm.h 文件
/* 引脚以及重要参数定义 */
#define LEDC_PWM_TIMER LEDC_TIMER_0 /* 使用定时器0 */
#define LEDC_PWM_MODE LEDC_LOW_SPEED_MODE /* 模式设定必须使用LEDC低速模式 */
#define LEDC_PWM_CH0_GPIO GPIO_NUM_1 /* LED控制器通道对应GPIO */
#define LEDC_PWM_CH0_CHANNEL LEDC_CHANNEL_0 /* LED控制器通道号 */
#define LEDC_PWM_DUTY 8000 /* 渐变的变大最终目标占空比 */
#define LEDC_PWM_FADE_TIME 3000 /* 完整呼吸周期 3秒 */
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT /* 13位分辨率 (8192级亮度) */
#define LEDC_FREQ 5000 /* PWM频率 5KHz *//* 函数声明 */
void pwm_init(uint8_t resolution, uint16_t freq); /* 初始化PWM */
void pwm_set_duty(uint32_t duty); /* PWM占空比设置 */
(2) pwm.c 文件
在 PWM 初始化函数中,配置好 LEDC 定时器的频率、占空比、定时器模式以及定时器通道,并在 pwm_set_duty()函数中, 调用函数 ledc_set_fade_with_time ()用以设置占空比和渐变时长。此时,我们需要再次调用该函数,将占空比配置为 0,最后调用函数 ledc_fade_start ()开启渐变。
static const char *TAG = "BreathingLED";/*** @brief 初始化PWM* @param resolution:PWM占空比分辨率* freq: PWM信号频率* @retval 无*/
void pwm_init(uint8_t resolution, uint16_t freq)
{ledc_timer_config_t ledc_timer; /* LEDC定时器句柄 */ledc_channel_config_t ledc_channel; /* LEDC通道配置句柄 *//* 配置LEDC定时器 */ledc_timer.duty_resolution = resolution; /* PWM占空比分辨率 */ledc_timer.freq_hz = freq; /* PWM信号频率 */ledc_timer.speed_mode = LEDC_PWM_MODE; /* 定时器模式 */ledc_timer.timer_num = LEDC_PWM_TIMER; /* 定时器序号 */ledc_timer.clk_cfg = LEDC_AUTO_CLK; /* LEDC时钟源 */ledc_timer.deconfigure = false;esp_err_t ret = ledc_timer_config(&ledc_timer); /* 配置定时器 */if (ret != ESP_OK) {ESP_LOGE(TAG, "定时器配置失败: %s", esp_err_to_name(ret));return;}/* 配置LEDC通道 */ledc_channel.gpio_num = LEDC_PWM_CH0_GPIO; /* LED控制器通道对应引脚 */ledc_channel.speed_mode = LEDC_PWM_MODE; /* LEDC睡眠模式 */ledc_channel.channel = LEDC_PWM_CH0_CHANNEL; /* LEDC控制器通道号 */ledc_channel.intr_type = LEDC_INTR_DISABLE; /* LEDC失能中断 */ledc_channel.timer_sel = LEDC_PWM_TIMER; /* 定时器序号 */ledc_channel.duty = 0; /* 占空比值 */ledc_channel.hpoint = 0;ledc_channel.sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD;ret = ledc_channel_config(&ledc_channel); /* 配置LEDC通道 */if (ret != ESP_OK) {ESP_LOGE(TAG, "通道配置失败: %s", esp_err_to_name(ret));return;}ret = ledc_fade_func_install(0); /* 使能渐变(该函数不可或缺) */if (ret != ESP_OK) {ESP_LOGE(TAG, "渐变功能安装失败: %s", esp_err_to_name(ret));return;}
}/*** @brief PWM占空比设置* @param duty:PWM占空比* @retval 无*/
void pwm_set_duty(uint32_t duty)
{// 渐亮 (0 -> 最大亮度)esp_err_t ret = ledc_set_fade_with_time(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, duty, LEDC_PWM_FADE_TIME/2); /* 设置占空比以及渐变时长 */if (ret != ESP_OK) {ESP_LOGE(TAG, "设置渐亮失败: %s", esp_err_to_name(ret));}ret = ledc_fade_start(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, LEDC_FADE_NO_WAIT); /* 开始渐变 */if (ret != ESP_OK) {ESP_LOGE(TAG, "启动渐亮失败: %s", esp_err_to_name(ret));}vTaskDelay(pdMS_TO_TICKS(LEDC_PWM_FADE_TIME / 2));// 渐暗 (最大亮度 -> 0)ret = ledc_set_fade_with_time(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, 0, LEDC_PWM_FADE_TIME/2); /* 设置占空比以及渐变时长 */if (ret != ESP_OK) {ESP_LOGE(TAG, "设置渐暗失败: %s", esp_err_to_name(ret));}ret = ledc_fade_start(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, LEDC_FADE_NO_WAIT); /* 开始渐变 */if (ret != ESP_OK) {ESP_LOGE(TAG, "启动渐暗失败: %s", esp_err_to_name(ret));}vTaskDelay(pdMS_TO_TICKS(LEDC_PWM_FADE_TIME / 2));
}
18.3.4 CMakeLists.txt 文件
打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:
set(src_dirsPWM)set(include_dirsPWM)set(requiresdriver)idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
18.3.5 实验应用代码
打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。
/*** @brief 程序入口* @param 无* @retval 无*/
void app_main(void)
{esp_err_t ret;// 计算最大占空比值 (2^13 - 1 = 8191)const uint32_t max_duty = (1 << LEDC_DUTY_RES) - 1;ret = nvs_flash_init(); /* 初始化NVS */if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND){ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}pwm_init(LEDC_DUTY_RES, LEDC_FREQ); /* 初始化PWM */while(1) {pwm_set_duty(max_duty); /* 设置占空比 */}
}
18.4 下载验证
在完成编译和烧录后,可以看到板子上的 LED 先由暗再逐渐变亮,以此循环,实现了呼吸
灯的效果。