当前位置: 首页 > news >正文

第十七章 ESP32S3 SW_PWM 实验

        本章将介绍使用 ESP32-S3 LED 控制器(LEDC)。 LEDC 主要用于控制 LED,也可产生PWM信号用于其他设备的控制。该控制器有 8 路通道,可以产生独立的波形,驱动 RGB LED 等设备。 LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比,实现亮度渐变。 ESP32-S3 IDF 提供了两种方式改变 PWM,一种是通过软件改变 PWM 占空比,另一种是通过硬件改变PWM 占空比,这两种方式都会学习。通过本章将学习到通过软件改变PWM 占空比的运用。本章分为如下几个小节:
17.1 PWM 简介
17.2 硬件设计
17.3 程序设计
17.4 下载验证

17.1 PWM 简介

(1) PWM 原理解析
PWM(Pulse Width Modulation),简称脉宽调制,是一种将模拟信号变为脉冲信号的计数。PWM 可以控制 LED 亮度、直流电机的转速等。
PWM 的主要参数如下:
① PWM 频率:PWM 频率是 PWM 信号在 1s 内从高电平到低电平再回到高电平的次数,
也就是说 1s 内有多少个 PWM 周期,单位为 Hz。
② PWM 周期:PWM 周期是 PWM 频率的倒数,即 T=1/f, T 是 PWM 周期, f 是 PWM 频率。如果 PWM 频率为 50Hz,也就是说 PWM 周期为 20ms,即 1s 由 50 个 PWM 周期。
③ PWM 占空比:PWM 占空比是指在一个 PWM 周期内,高电平的时间与整个周期时间的比例,取值范围为 0%~100%。例如,如果 PWM 周期是 10ms,而脉宽时间为 8ms,那么 PWM 占空比就是8/10=80%,此时的 PWM 信号就是占空比为 80%的 PWM 信号。 PWM 名为脉冲宽度调制,顾名思义,就是通过调节 PWM 占空比来调节 PWM 脉宽时间。PWM 占空比如下图所示。

图 17.1.1 PWM 占空比

        在使用 PWM 控制 LED 时,亮 1s 后灭 1s,往复循环,就可以看到 LED 在闪烁。如果把这个周期缩小到 200ms,亮 100ms 后灭 100ms,往复循环,就可以看到 LED 灯在高频闪烁。继续
把这个周期持续缩小,总有一个临界值使人眼分辨不出 LED在闪烁,此时 LED的亮度处于灭与
亮之间亮度的中间值,达到了 1/2 亮度。 PWM 占空比和亮度的关系如下图所示。

图 17.1.2 PWM 占空比和亮度的关系

(2)ESP32 的 LED PWM 控制器介绍
ESP32-S3 的 LED PWM 控制器,简写为 LEDC,用于生成控制 LED 的脉冲宽度调制信号。
LED PWM 控制器具有八个独立的 PWM 生成器(即八个通道)。每个 PWM 生成器会从四个通用定时器中选择一个,以该定时器的计数值作为基准生成 PWM 信号。 LED PWM 定时器如下图所示。

图 17.1.1.1 LED_PWM 的定时器

        为了实现 PWM 输出,先需要设置指定通道的 PWM 参数:频率、分辨率、占空比,然后将该通道映射到指定引脚,该引脚输出对应通道的 PWM 信号,通道和引脚的关系所下图所示。

图 17.1.1.2 LED PWM 输出示意图

        另外, LED PWM 控制器可在没有 CPU 干预的情况下自动改变占空比,实现亮度以及颜色
渐变。

17.2 硬件设计

17.2.1 例程功能
通过软件改变 PWM 的形式使得 LED 由亮到暗,再由暗到亮,依次循环。
17.2.2 硬件资源
1. LED
LED-IO1
2. 定时器 1
通道 1 - IO1

17.2.3 原理图

        本章实验使用的定时器 1 为 ESP32-S3 的片上资源,因此没有对应的连接原理图。

17.3 程序设计

17.3.1 程序流程图
本实验的程序流程图:

图 17.3.1.1 SW_PWM 实验程序流程图

17.3.2 SW_PWM 函数解析
ESP-IDF 提供了一套 API 来配置 PWM。要使用此功能,需要导入必要的头文件:

#include "driver/ledc.h"

        接下来,将介绍一些常用的 SW_PWM 函数,这些函数的描述及其作用如下:
(1)配置 LEDC 使用的定时器为定时器1
需要注意的一点是,在首次配置LEDC时,建议先配置定时器(调用函数ledc_timer_config()),再配置通道(调用函数ledc_channel_config())。这样可以确保IO引脚上的PWM信号自输出开始那一刻起,其频率就是正确的。
要设置定时器,可调用函数 ledc_timer_config(),需要将一些参数的数据结构传递给该函数,该函数原型如下所示:

esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf);

        该函数的形参描述,如下表所示:

形参描述
timer_conf指向配置 LEDC 定时器的结构体指针

表 17.3.2.1 函数 ledc_timer_config ()形参描述

        返回值: ESP_OK 表示配置成功,其他配置失败。
该函数使用 ledc_timer_config_t 类型的结构体变量传入,该结构体的定义如下所示:

结构体成员变量可选参数
ledc_timer_config_tspeed_mode :
速度模式。需要注意的是,ESP32-S3 仅支持低速模式。
LEDC_LOW_SPEED_MODE:
低速模式
LEDC_HIGH_SPEED_MODE:
高速模式
timer_num :
通道的定时器源, 定时器索引 ledc_timer_t
LEDC_TIMER_0
LEDC_TIMER_1
LEDC_TIMER_2
LEDC_TIMER_3
LEDC_TIMER_MAX
freq_hz :
PWM信号频率 , 表示LEDC模块的定时器时钟频率设置,单位为 Hz
duty_resolution :
PWM 占空比分辨率。 占空比分辨率通常用ledc_timer_bit_t 设置,范围是 10 至 15 位。如需较低的占空比分辨率(上至10,下至1),可直接输入相应数值。相关参数请参考ledc_timer_bit_t。

LEDC_TIMER_1_BIT // 1位分辨率 (2级) LEDC_TIMER_2_BIT // 2位分辨率 (4级) LEDC_TIMER_3_BIT // 3位分辨率 (8级) LEDC_TIMER_4_BIT // 4位分辨率 (16级) LEDC_TIMER_5_BIT // 5位分辨率 (32级) LEDC_TIMER_6_BIT // 6位分辨率 (64级) LEDC_TIMER_7_BIT // 7位分辨率 (128级) LEDC_TIMER_8_BIT // 8位分辨率 (256级) LEDC_TIMER_9_BIT // 9位分辨率 (512级) LEDC_TIMER_10_BIT // 10位分辨率 (1024级) LEDC_TIMER_11_BIT // 11位分辨率 (2048级) LEDC_TIMER_12_BIT // 12位分辨率 (4096级) LEDC_TIMER_13_BIT // 13位分辨率 (8192级) LEDC_TIMER_14_BIT // 14位分辨率 (16384级) LEDC_TIMER_15_BIT // 15位分辨率 (32768级) LEDC_TIMER_16_BIT // 16位分辨率 (65536级) // ESP32-S2/S3/C3 支持到 20位

clk_cfg:
LEDPWM 的时钟来源
LEDC_AUTO_CLK:
启动定时器时,将根据给定的分辨率和
占空率参数自动选择 ledc 源时钟;
LEDC_USE_APB_CLK:
选择 APB 作为源时钟;
LEDC_USE_RC_FAST_CLK:
选择“RC_FAST” 作为源时钟;
LEDC_USE_XTAL_CLK:
选择 XTAL 作为源时钟;
LEDC_USE_RTC8M_CLK:
” LEDC_USE_RC_FAST_CLK” 的别名

表 17.3.2.2 ledc_timer_config_t 结构体参数值描述

特别再介绍下duty_resolution(占空比分辨率):

        占空比分辨率是 PWM 控制中的核心概念,它决定了你能以多精细的级别控制输出信号的强度。在 ESP32 的 LEDC (LED PWM Controller) 中,这是一个关键配置参数。

分辨率级别与精度:

分辨率位数​

​占空比级数​

​最小调节步进​

​适用场景​

1 bit

2

50%

简单开关控制

8 bits

256

0.39%

通用 LED 调光

10 bits

1024

0.098%

电机速度控制

12 bits

4096

0.024%

精密调光

13 bits

8192

0.012%

专业灯光控制

16 bits

65536

0.0015%

高精度仪器

        表 17.3.2.3 分辨率级别与精度描述

分辨率与频率的关系:

分辨率、时钟频率和 PWM 频率之间存在严格的数学关系:

​计算公式​​:

  • f_{PWM}:PWM 输出频率;
  • f_{clk}:时钟源频率 (APB=80MHz, RTC8M=8.5MHz);
  • ​​resolution​​:占空比分辨率位数;
  • ​​divider​​:预分频系数 (1-1024)。

实际限制​​:

  • 更高分辨率 → 更低最大 PWM 频率;
  • 更高PWM频率 → 更低最大分辨率。

        完成上述结构体参数配置之后,可以将结构传递给 ledc_timer_config () 函数,用以实例化SW_PWM 并返回 SW_PWM 句柄。 另外时钟源同样可以限制 PWM 频率。选择的时钟源频率越高,可以配置的 PWM 频率上限就越高。

(2)通道配置函数
该函数原型如下所示:

esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf);

        该函数的形参描述,如下表所示:

形参描述
ledc_conf指向配置 LEDC 通道的结构体指针

表 17.3.2.4 函数 ledc_channel_config ()形参描述

        返回值: ESP_OK 表示配置成功,其他配置失败。
该函数使用 ledc_channel_config_t 类型的结构体变量传入,该结构体的定义如下所示:

结构体成员变量可选参数
ledc_channel_config_tgpio_num :
配置输出引脚
本例程使用的引脚是 GPIO_NUM_1
speed_mode :
速度模式
LEDC_HIGH_SPEED_MODE:
高速模式
LEDC_LOW_SPEED_MODE:
低速模式
channel :
LEDC 的输出通道(PWM
的输出通道)
0~7,本例程配置为通道 1
intr_type:
配置中断
使能中断: LEDC_INTR_FADE_END
失能中断: LEDC_INTR_DISABLE
timer_sel:
选择通道的定时器源。
定时器索引 ledc_timer_t
LEDC_TIMER_0
LEDC_TIMER_1
LEDC_TIMER_2
LEDC_TIMER_3
LEDC_TIMER_MAX
duty :
LEDC 通道的占空比设置
占空比设定范围为0~2^{duty\_resolution}
hpoint :
led 通道 hpoint 值。 表示
占空比对应的时钟计数值
output_invert:
启 用(1)或禁 用(0)gpio 输
出反相

表 17.3.2.5 ledc_channel_config_t 结构体参数值描述

        完成上述结构体参数配置之后,可以将结构传递给 ledc_channel_config() 函数,用以实例化
PWM 通道。

(3)改变 PWM 占空比
调用函数 ledc_set_duty()可以设置新的占空比。之后,调用函数 ledc_update_duty()使新配置生效。要查看当前设置的占空比,可使用 get 函数 ledc_get_duty(),该函数原型如下所示:

esp_err_t ledc_set_duty(ledc_mode_t speed_mode,ledc_channel_t channel,uint32_t duty);

        该函数的形参描述,如下表所示:

形参描述
speed_mode速度模式选择:
LEDC_HIGH_SPEED_MODE
LEDC_LOW_SPEED_MODE
channelLEDC 通道:
(0 - LEDC_CHANNEL_MAX-1),从 ledc_channel_t 中选择
duty设置 led 的负载,负载设置范围为:
0~[(2^{duty\_resolution}) – 1]

表 17.3.2.6 函数 ledc_set_dut ()形参描述

        返回值: ESP_OK 表示配置成功,其他配置失败。

(5)改变 PWM 占空比
在上一步调用 ledc_set_duty()设置新的占空比后,调用函数 ledc_update_duty()使新配置生效,该函数原型如下所示:

esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);

        该函数的形参描述,如下表所示:

形参描述
speed_mode速度模式选择:
LEDC_HIGH_SPEED_MODE
LEDC_LOW_SPEED_MODE
channelLEDC 通道:
(0 - LEDC_CHANNEL_MAX-1),从 ledc_channel_t 中选择

表 17.3.2.7 函数 ledc_update_duty()形参描述

        返回值: ESP_OK 表示配置成功,其他配置失败。

17.3.3 SW_PWM 驱动解析
在 IDF 版的 08-1_sw_pwm 例程中,作者在 08-1_sw_pwm \components\BSP 路径下新增了一个 PWM 文件夹,用于存放 pwm.c 和 pwm.h 这两个文件。其中, pwm.h 文件负责声明SW_PWM相关的函数和变量,而 pwm.c 文件则实现了 SW_PWM 的驱动代码。下面,我们将详细解析这两个文件的实现内容。
(1)pwm.h 文件

/* 引脚以及重要参数定义 */
#define LEDC_PWM_TIMER          LEDC_TIMER_1        /* 使用定时器0 */
#define LEDC_PWM_CH0_GPIO       GPIO_NUM_1          /* LED控制器通道对应GPIO */
#define LEDC_PWM_CH0_CHANNEL    LEDC_CHANNEL_1      /* LED控制器通道号 *//* 函数声明 */
void pwm_init(uint8_t resolution, uint16_t freq);   /* 初始化PWM */
void pwm_set_duty(uint16_t duty);                   /* PWM占空比设置 */

(2)pwm.c 文件

/*** @brief       初始化PWM* @param       resolution: PWM占空比分辨率*              freq: PWM信号频率* @retval      无*/
void pwm_init(uint8_t resolution, uint16_t freq)
{ledc_timer_config_t ledc_timer = {0};           /* LEDC定时器句柄 */ledc_channel_config_t ledc_channel = {0};       /* LEDC通道配置句柄 *//* 配置LEDC定时器 */ledc_timer.duty_resolution = resolution;        /* PWM占空比分辨率 */ledc_timer.freq_hz = freq;                      /* PWM信号频率 */ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE;    /* 定时器模式 */ledc_timer.timer_num = LEDC_PWM_TIMER;          /* 定时器序号 */ledc_timer.clk_cfg = LEDC_AUTO_CLK;             /* LEDC时钟源 */ledc_timer_config(&ledc_timer);                 /* 配置定时器 *//* 配置LEDC通道 */ledc_channel.gpio_num = LEDC_PWM_CH0_GPIO;      /* LED控制器通道对应引脚 */ledc_channel.speed_mode = LEDC_LOW_SPEED_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_config(&ledc_channel);             /* 配置LEDC通道 */
}/*** @brief       PWM占空比设置* @param       duty:PWM占空比* @retval      无*/
void pwm_set_duty(uint16_t duty)
{ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_PWM_CH0_CHANNEL, duty); /* 设置占空比 */ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_PWM_CH0_CHANNEL);    /* 更新占空比 */
}

        LEDC 定时器设置好后,请配置所需的通道(ledc_channel_t)。配置通道需调用函数ledc_channel_config()。通道的配置与定时器设置类似,需向通道配置函数传递相应的结构体ledc_channel_config_t。此时,通道会按照 ledc_channel_config_t 的配置开始运作,并在选定的
GPIO 上生成由定时器指定的频率和占空比的 PWM 信号。
调用函数 ledc_set_duty()可以设置新的占空比。之后,调用函数 ledc_update_duty()使新配置生效。为了方便使用,笔者将这两个函数进行了“封装”,通过传参的形式来配置 PWM 占空比。
关于配置过程中所涉及到的结构体成员变量的含义以及用法,请读者们回顾本章节前面的内容。

17.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)

17.3.5 实验应用代码
打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。

/*** @brief       程序入口* @param       无* @retval      无*/
void app_main(void)
{esp_err_t ret;uint8_t dir = 1;uint16_t ledpwmval = 0;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_TIMER_10_BIT, 1000);     /* 初始化PWM */while(1) {vTaskDelay(10);if (dir == 1){ledpwmval += 5; /* dir==1 ledpwmval递增 */}else{ledpwmval -= 5; /* dir==0 ledpwmval递减 */}if (ledpwmval > 1005){dir = 0;        /* ledpwmval到达1005后,方向为递减 */}if (ledpwmval < 5){dir = 1;        /* ledpwmval递减到5后,方向改为递增 */}/* 设置占空比 */pwm_set_duty(ledpwmval);}
}

        从上面的代码中可以看到,在初始化 LEDC 定时器,并输出 PWM 后,就不断地改变定时器 0 的值,以达到改变 PWM 占功比的目的。又因为 PWM 由 IO1 引脚输出, IO1 引脚连接至LED,所以 LED 的亮度也会随之发生变化,从而实现呼吸灯的效果。

17.4 下载验证

        在完成编译和烧录后,可以看到板子上的 LED 先由暗再逐渐变亮,以此循环,实现了呼吸灯的效果。

http://www.dtcms.com/a/359028.html

相关文章:

  • 深入解析Nginx常见模块2
  • web渗透之RCE漏洞
  • 针对 “TCP 会话维持与身份验证” 的攻击
  • (二)设计模式(Command)
  • SQL Server 临时表合并与数量汇总的实现方法
  • 大模型不听话?试试提示词微调
  • “可选功能“中找不到 OpenSSH, PowerShell 命令行来安装OpenSSH
  • windows 谷歌浏览器一直提示无法更新Chrome弹窗问题彻底解决
  • Learning Curve|学习曲线
  • 数据库攻略:“CMU 15-445”Project0:C++ Primer(2024 Fall)
  • 【开题答辩全过程】以 “与我同行”中华传统历史数字化平台的设计和分析-------为例,包含答辩的问题和答案
  • Linux软件定时器回顾
  • 本地部署开源媒体服务器 Komga 并实现外部访问( Windows 版本)
  • 容器存储驱动升级:美国VPS文件系统优化全指南
  • 上海我店模式的多维度探究
  • 对于STM32工程模板
  • CRM、ERP、HRP系统有啥区别?
  • 250830-Docker从Rootless到Rootful的Gitlab镜像迁移
  • 深刻理解软硬件链接
  • ubuntu24.04 qt6安装
  • 学习游戏制作记录(各种优化)
  • 复制VMware虚拟机后的网络配置
  • leetcode算法刷题的第二十二天
  • 论《运动战》
  • Linux查看有线网卡和无线网卡详解
  • UNet改进(36):融合FSATFusion的医学图像分割
  • Vue基础知识-单向绑定v-bind、双向绑定v-model、插值语法{{}}、Object.defineProperty实现数据代理
  • PostgreSQL数据类型一览(数值类型)
  • Spring和mybatis整合后事务拦截器TransactionInterceptor开启提交事务流程
  • 【Java实战⑧】Java常用类实战:解锁String、Object与包装类的奥秘