这个实验的功能是使用 PWM 去控制板子上的彩灯实现渐变效果。 这个实验的代码为工程“3_8_pwm”目录。
3.8.1. 实验内容
(1) 学习如何控制 LED 灯及硬件原理
(2) 学习 ESP32 的 PWM(ledc)功能的配置
(3) 掌握 PWM(ledc)控制 LED 彩灯渐变程序
3.8.2. 硬件设计和原理
LED 彩灯硬件设计在 3.3 节已经讲解过了,我们知道通过 IO15、IO16 和 IO32 输出高低电平就可以控制 彩灯亮灭了。
在这个实验里,我们是通过 IO15、IO16 和 IO32 输出 PWM 信号去控制彩灯的亮度。要注意的是我们 的彩灯是共阳极的,输出 100%占空比的时候,彩灯是灭的,输出 0%占空比的时候,彩灯处于最亮状态。 ESP32 的占空比支持手动和自动两种模块,我们的这个实验里启动了两个任务,用于实现手动设置占
空比和自动设置占空比,通过板上的按键 key1 去控制切换。
3.8.3. 函数介绍
ESP32 的 ESP-IDF 编程指南可以从官网上查询:
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/index.html
PWM(LEDC)定时器配置
函数定义如下:
esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf); 参数说明:
ledc_timer_config_t* timer_conf:定时器的参数,这是一个结构体,具体的定义请往下看 返回值:ESP_OK(成功)和 ESP_ERR_INVALID_ARG(参数错误) 。
typedef struct {
ledc_mode_t speed_mode; union {
ledc_timer_bit_t duty_resolution; //pwm 的分辨率,也就是最大的占空比
ledc_timer_bit_t bit_num attribute ((deprecated)); //
};
ledc_timer_t timer_num; //定时器选择,支持 0 ̄3
} ledc_timer_config_t;
PWM(LEDC)配置函数
函数定义如下:
esp_err_t ledc_channel_config(const ledc_channel_config_t* ledc_conf); 参数说明:
ledc_channel_config_t* ledc_conf:pwm 配置参数,这是一个结构体,具体的定义请往下看 返回值:ESP_OK(成功)和 ESP_ERR_INVALID_ARG(参数错误) 。
typedef struct {
int gpio_num; //具体的 IO 口定义 ledc_mode_t speed_mode; //速度选择,高速或者低速 ledc_channel_t channel; //通道选择,取值 0~7 ledc_intr_type_t intr_type; //中断类型 ledc_timer_t timer_sel; //定时器通道选择 uint32_t duty; //占空比
int hpoint; //LEDC channel hpoint value, the max value is 0xfffff
} ledc_channel_config_t;
PWM(LEDC)渐变安装函数
这个函数在系统 PWM 占空比自己变化的时候会使用到,函数定义如下:
esp_err_t ledc_fade_func_install(int intr_alloc_flags); 参数说明:
int intr_alloc_flags:是否分配中断标志 返回值:ESP_OK(成功)和 ESP_ERR_INVALID_ARG(参数错误) 。
PWM(LEDC)自动渐变
要实现 PWM 自动渐变,先要调用自动渐变设置函数 ledc_set_fade_with_time,接着调用自动渐变启 动函数 ledc_fade_start,分别说明。
函数 1:
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);
功能:设置自动渐变参数 参数说明:
ledc_mode_t speed_mode://速度选择,高速或者低速 ledc_channel_t channel://PWM 通道
uint32_t target_duty://目标占空比
int max_fade_time_ms://变化到目标占空比使用的时间 返回值:ESP_OK(成功)和 ESP_ERR_INVALID_ARG(参数错误) 。
函数 2:
esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel,ledc_fade_mode_t fade_mode); 功能:启动自动渐变功能
参数说明:
ledc_mode_t speed_mode://速度选择,高速或者低速 ledc_channel_t channel://PWM 通道 ledc_fade_mode_t fade_mode://wait_done:是否等待
返回值:ESP_OK(成功)和 ESP_ERR_INVALID_ARG(参数错误) 。
PWM(LEDC)手动修改占空比
手动修改占空比涉及到 2 个函数,使用函数 ledc_set_duty 设置占空比后,还要使用 ledc_update_duty函数让新占空比生效,分别说明。
函数 1:
esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty); 功能:设置新的占空比
参数说明:
ledc_mode_t speed_mode://速度选择,高速或者低速 ledc_channel_t channel://PWM 通道
uint32_t duty://要设置的占空比 返回值:ESP_OK(成功)和 ESP_ERR_INVALID_ARG(参数错误) 。
函数 2:
esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel); 功能:启用新的占空比
参数说明:
ledc_mode_t speed_mode://速度选择,高速或者低速 ledc_channel_t channel://PWM 通道
返回值:ESP_OK(成功)和 ESP_ERR_INVALID_ARG(参数错误) 。
3.8.4. 代码讲解
我们的这个实验,只有一个源文件 app_main.c,按键、任务和 PWM 控制都在这个文件里。在讲解代 码的前,先把这个实验的框图发出来,如下图:
通过框图可以看出,一共有三个任务,功能分别如下:
主任务,检测按键 key1 按下,然后修改 pwm_mode 值。,任务 1 和任务 2 根据 pwm_mode 的值去 决定是否执行渐变功能,
任务 1,pwm_mode 等于 1 时,红绿蓝三种灯各渐变 2 秒,一个周期约是 9 秒时间。
任务 2,pwm_mode 等于 0 时,根据 pwm_index 决定当前是设置红绿蓝三种的其中一种,每一个占 空比停留时间是 50ms。
对这个框图有了一定的认识之后,我们看程序的时候应该比较容易理解了。程序里关于按键初始化和 按键扫描部分不讲解了,如果不理解请往回看实验 3.5。下面开始讲解程序里的各个片段。
在源文件 app_main.c 的最前面,对灯的硬件 IO 口、占空比最大值、自动渐变的时间和 PWM 使用的
通道有定义,代码如下图:
//定义 LED 灯的 IO
口 32//对应红灯的 LED,绿灯为 15,
#define 蓝灯为 16
LED_RED_IO 15 //对应红灯的 LED,绿灯为 15,蓝灯为 16
#define LEDC_MAX_DUTY
#define (8191) //2 的 13 次方-1(13 位 PWM)
(1000) //渐变时间(ms)
#define
#define PWM_RED_CHANNEL LEDC_CHANNEL_0 //定义红灯通道
#define PWM_GREEN_CHANNEL LEDC_CHANNEL_1 //定义绿灯通道
#define PWM_BLUE_CHANNEL LEDC_CHANNEL_2 //定义蓝灯通道
unsigned char //PWM 模块,如果为 1 表示通过库函数实现渐变
首先我们讲解 PWM 初始化,是对 PWM 使用的定时器配置,三路 PWM 参数配置,以及是否启动渐变功能,代码里都是注释,用到的每一个函数在前面都有讲解,代码如下:
void PWM_init(void)
{
//定时器配置结构体 ledc_timer_config_t
ledc_timer.duty_resolution = LEDC_TIMER_13_BIT;
ledc_timer.freq_hz = 5000; ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE; ledc_timer.timer_num = LEDC_TIMER_0; ledc_timer_config(&ledc_timer);
//PWM 通道 0 配置->IO32->红色灯
g_ledc_red.channel = PWM_RED_CHANNEL; g_ledc_red.duty = LEDC_MAX_DUTY; g_ledc_red.gpio_num = LED_RED_IO; g_ledc_red.speed_mode = LEDC_HIGH_SPEED_MODE; g_ledc_red.timer_sel = LEDC_TIMER_0; ledc_channel_config(&g_ledc_red);
//PWM 通道 0 配置->IO15->绿
g_ledc_green.channel
g_ledc_green.duty g_ledc_green.gpio_num g_ledc_green.speed_mode g_ledc_green.timer_sel
//PWM 通道 0 配置->IO32->蓝
g_ledc_blue.channel
g_ledc_blue.duty g_ledc_blue.gpio_num g_ledc_blue.speed_mode g_ledc_blue.timer_sel
ledc_channel_config(&g_ledc_bl
//PWM 模式为 1 的时候,使能 ledc 渐变功能
if(pwm_mode==1)
{
ledc_fade_func_install(0);
接着我们看主任务,任务里选是初始化按键和 PWM,接着创建了任务 1 和任务 2,最后通过 while(1)
检测 key1 从而改变 pwm_mode,代码如下:
//用户函数入口,相当于 main 函数 void app_main()
{
initKey();//按键初始化 PWM_init();//PWM 初始化
//创建两个任务,用于执行不同的 PWM 模式
xTaskCreate(&task_pwm1, "task_pwm1", 4096, NULL, 9, NULL);
xTaskCreate(&task_pwm2, "task_pwm2", 4096, NULL, 9, NULL);
while(1)
{
//通过按键改变 PWM 模式
if(1==key_read_key1())
{
pwm_mode=(pwm_mode==1)?0:1; PWM_init();//重新初始化 ledc 初始化
}
vTaskDelay(10);//延时一下
}
}
任务 1 入口函数,如果 pwm_mode 等于 1 时,红绿蓝三种灯自动轮流渐变,代码里都是注释,用到的 每一个函数在前面都有讲解。代码如下,绿灯和蓝灯的代码和红灯的类似,固省略,完整的代码请看源文件:
//通过渐变功能演示 PWM 任务
void task_pwm1(void *pvParameter)
{
while(1)
{
if(1==pwm_mode)
{
printf("pwm mode1.\r\n");
//渐变功能演示 PWM
///
//红灯占空比 100%-->0%-->100%,时间 2*LEDC_FADE_TIME
//红灯:灭-->亮-->灭,的过程
///
//红灯占空比 100% 渐变至 0%,时间 LEDC_FADE_TIME ledc_set_fade_with_time(g_ledc_red.speed_mode,
g_ledc_red.channel, 0, LEDC_FADE_TIME);
//渐变开始 ledc_fade_start(g_ledc_red.speed_mode,
g_ledc_red.channel, LEDC_FADE_NO_WAIT);
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
//红灯占空比 0%渐变至 100%,时间 LEDC_FADE_TIME ledc_set_fade_with_time(g_ledc_red.speed_mode,
g_ledc_red.channel, LEDC_MAX_DUTY, LEDC_FADE_TIME);
//渐变开始 ledc_fade_start(g_ledc_red.speed_mode,
g_ledc_red.channel, LEDC_FADE_NO_WAIT);
vaskDelay(LEDCT_FADE_TIME / portTICK_PERIOD_MS); vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
///
///
//蓝灯占空比 100%-->0%-->100%,时间 2*LEDC_FADE_TIME
//蓝灯:灭-->亮-->灭,的过程
//
代码相似///略/
}
else{
vTaskDelay(5);//延时一下
}
}
}
任务 2 入口函数,如果 pwm_mode 等于 0 时,每 50ms 通过程序修改一次占空比,红绿蓝三种灯轮变
化:
//用户修改占空比,改变 LED 灯亮度任务 void task_pwm2(void *pvParameter)
{
int pwm_level=0;//0~255 占空比 int pwm_index=1;//红 1 绿 2 蓝 3
while(1)
{
if(0==pwm_mode)
{
printf("pwm mode2.\r\n"); if(pwm_index==1)
{
//修改红灯占空比 CtrRBG_R(pwm_level); pwm_level+=10;//占空比递增
if(pwm_level>255)
{
pwm_level=0; pwm_index=2; CtrRBG_R(255);
}
}
else if(pwm_index==2)
{
//修改绿灯占空比 CtrRBG_G(pwm_level); pwm_level+=10;//占空比递增
if(pwm_level>255)
{
pwm_level=0; pwm_index=3; CtrRBG_G(255);
}
}
else
if(pwm_level>255)
{
pwm_level=0; pwm_index=1; CtrRBG_B(255);
}
}
}
vTaskDelay(5);//延时 50ms
}
}
在上面的代码里,修改红绿蓝占空比是通过三个函数 CtrRBG_R()、CtrRBG_G()和 CtrRBG_B()进行的,
这三个函数类似。
我们选其中一个讲解,这类函数先是把最大的占空比 LEDC_MAX_DUTY 平均分为 255 等分,根据参数计 算出当前的占空比,然后通过 ledc_set_duty()和 ledc_update_duty()函数更新占空比。为什么我们使 用 255 等分呢?因为我们说的真彩色是 24 位色,他是由红绿蓝三种色混合起来的,每一种颜色就是 8 位 色,2 的 8 次方就是 256,而 0~255 就是 256 级颜色,关于这方面的知识,可以百度查询理解一下。
//设置红灯的 PWM 级别
//输入 level 取值 0~255
void CtrRBG_R(unsigned char level)
{
int duty=0;
if(level==255)
{
duty=LEDC_MAX_DUTY;
}
else if(level==0)
{
duty=0;
}
else
{
//计算占空比 duty=(level*LEDC_MAX_DUTY)/255;
}
ledc_set_duty(LEDC_HIGH_SPEED_MODE, PWM_RED_CHANNEL, duty);//修改占空比 ledc_update_duty(LEDC_HIGH_SPEED_MODE, PWM_RED_CHANNEL);//新的占空比生效
}
3.8.5. 实验过程
配置下载串口、波特率、编绎和程序下载的详细过程请往回看 3.1.4,在这个实验里都是一笔带过。 (1) 把开发板通过 USB 线接到电脑上,通过设备管理器查看生成的串口。开发板在我们演示电脑上生成的是 COM3。
(2) 在 menuconfig 菜单里配置下载程序串口。提供的例程配置的串口是 COM3,波特率为 921600。
(3) 通过 make all 编绎工程。
(4) 当编绎通过之后,使用命令 make flash 把程序下载到开发板上。或者参考 2.3.2 节,使用工具下载。
(5) 按下开发板的复位键,让程序运行起来,观察彩灯的渐变过程。
(6) 按下 key1 键,观察彩灯的渐变过程,然后和按键前进行对比,也就是彩灯的两种模式变化区别。 (7) 我们认为自动变化过程比较适合做呼吸灯。