【写在前面】最近在读《ESP32-C物联网开发实战》,个人感觉书在这一块讲的并不太适合初学者。这里反过来,先通过实践的方式实现功能,搞懂每一行代码的实现机制之后,再去一个个研究硬件的原理和机制。
如果还没搭建好环境,或者碰到找不到头文件的问题,以下指路:
目录
一点必要的准备知识
链接指路:
这是关于ESP32 C3最通用入门知识整理(此处先占坑,最近已经在写了)
从点灯开始入坑
level1:光速点亮一颗灯
一切就绪后,这里可以直接用例程,如果是默认安装路径的话,一般在C:\Espressif\frameworks\esp-idf-v4.3.2\examples\get-started,将blink文件用vscode打开即可,下面是blink.c的代码:
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <driver/gpio.h>
#include <sdkconfig.h>
#define BLINK_GPIO CONFIG_BLINK_GPIO//此处需要调整,下图有两种方法
void app_main(void)
{
gpio_reset_pin(BLINK_GPIO);
gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
while(1) {
printf("Turning off the LED\n");
gpio_set_level(BLINK_GPIO, 0);
vTaskDelay(1000 / portTICK_PERIOD_MS);
printf("Turning on the LED\n");
gpio_set_level(BLINK_GPIO, 1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
这段代码还是比较好阅读的,只采用了高低电平的方式控制LED,但是对于初学者仍有一些需要注意的地方,简单的模块也应当吃透。
然后连接好开发板,依次按照以下三个步骤,设置好COM口,选择对应的型号,在使用一键编译写入和显示按钮即可
成功后你将得到一个闪烁的灯^ ^,由于每块板子不一样,这里以我手上的亿研开发板为例
仅需要将blink.c中的CONFIG_BLINK_GPIO 选择10(代码注释那里我给了两种方法),便可以实现。
level2:点灯只是计划的一部分——利用定时器控制
肯定有细心的兄弟发现了Freertos中用来控制延时的函数 vTaskDelay() ,虽然vTaskDelay易于上手和理解,但它功能也很笨【但是比delay()还是要高级点,不是做空循环(持续消耗cpu),而是把任务挂起和唤醒(中间不消耗cpu)】,不是控制周期任务频率的好方法,其罪有二 qwq:
- 其它任务和中断的活动会影响vTaskDelay()的调用频率,甚至连其余模块运行时间都会干扰到vTaskDelay()下一次调用的间隔时间
- 在vTaskDelay()的时间内,程序无法向后运行,相当于被阻塞,除非只为了实现点灯,不然会影响整体的运行时间
所以此刻我们需要学习定时器来实现精准的控制,所以在这个模块中,我将分成led模块和定时器模块,因为上文主要拿红灯作为举例,所以为了简化,我将绿灯和蓝灯部分注释掉了,如果想要加入的话稍加调整就可以实现了
//这是需要自己创建的led.h文件,请在文件根目录下创建component文件夹并放入此文件
#ifndef _LED_H_
#define _LED_H_
#define LED_RED_IO 10 //这一块对gpio口进行宏定义(上文有写)
//#define LED_GREEN_IO 7
//#define LED_BLUE_IO 6
#define LED_ON 0
#define LED_OFF 1 //这一块主要是控制亮灭的(上文提到的,低电平为亮)
void led_red(int on);
//void led_green(int on);
//void led_blue(int on);
//void initLed();
#endif
/*——————————————————————————————————————————————————————————————————*/
//下面是需要自己创建的led.c文件,请在文件根目录下创建component文件夹并放入此文件
/*——————————————————————————————————————————————————————————————————*/
#include "driver/gpio.h"
#include "led.h"
void led_red(int on) //控制红灯
{
if(on==LED_ON) gpio_set_level(LED_RED_IO, LED_ON);//开灯
else gpio_set_level(LED_RED_IO, LED_OFF);//关灯
}
/*
void led_green(int on) //控制绿灯
{
if(on==LED_ON) gpio_set_level(LED_GREEN_IO, LED_ON);//开灯
else gpio_set_level(LED_GREEN_IO, LED_OFF);//关灯
}
void led_blue(int on) //控制蓝灯
{
if(on==LED_ON) gpio_set_level(LED_BLUE_IO, LED_ON);//开灯
else gpio_set_level(LED_BLUE_IO, LED_OFF);//关灯
}
*/
void initLed() //LED初始化
{
gpio_pad_select_gpio(LED_RED_IO);
//gpio_pad_select_gpio(LED_GREEN_IO);
//gpio_pad_select_gpio(LED_BLUE_IO);
gpio_set_direction(LED_RED_IO, GPIO_MODE_OUTPUT);
//gpio_set_direction(LED_GREEN_IO, GPIO_MODE_OUTPUT);
//gpio_set_direction(LED_BLUE_IO, GPIO_MODE_OUTPUT);
led_red(LED_OFF);
//led_green(LED_OFF);
//led_blue(LED_OFF);
}
灯的控制还是比较好理解的,还是原来的高低电平控制,只是将level1中提到的部分重新做成了一个头文件的形式。下面是定时器&主函数main.c部分的代码:
#include <stdio.h>
#include "esp_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "soc/timer_group_struct.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"
#include "esp_timer.h"
#include "led.h"
esp_timer_handle_t fw_timer_handle = 0; //定时器句柄,在创建任务之前,一般都指向0地址,下文会解释
unsigned char led_flag=0; //LED灯标志,用于闪灯
void fw_timer_cb(void *arg) //根据LED状态闪灯
{
if(led_flag>0)
{
led_red(LED_ON);
led_flag=0;
}
else
{
led_red(LED_OFF);
led_flag=1;
}
}
void app_main()
{
initLed();//LED初始化
//定时器结构体初始化↓↓
esp_timer_create_args_t fw_timer =
{
.callback = &fw_timer_cb, //回调函数
.arg = NULL, //参数
.name = "timer1" //定时器名称
};
esp_err_t err = esp_timer_create(&fw_timer, &fw_timer_handle); //周期定时器启动
err = esp_timer_start_periodic(fw_timer_handle, 1000 * 1000); //1秒回调一次
if(err == ESP_OK)
{
printf("timer1 cteate and start ok!\r\n");
}
}
其实代码还是挺短的,没有很复杂的地方,但是对初入门来说还是有很多问题存在的,比如:
定时器句柄
FreeRTOS经常会用到句柄,简而言之,句柄就是一个 void * 指针,指向了一块内存空间,这块内存才是我们需要的数据结构。
结构体初始化
部分API的实现过程梳理
层级确实太丰富了,很多函数相互内嵌,在整理的时候有点无从下手导致花了一些时间,其实对于初学者来说确实不容易定位和理解,这一块建议直接查官方的编程指南,先从了解基本用法就可以,后续遇到需要深入调整和定制的时候再逐级研究。
总之,还是多看,多想,单一的教程或者指导确实很难顾及到具体的每一个问题。
例程中遇到的函数基本在官方的编程指南中一些需要注意的地方和用法,可以在官网进行查询,还是挺方便的。
level3:实现彩色光污染——利用PWM控制
一般常见的RGB灯模组占用3-5个GPIO口(R G B “冷光CW” “暖光WW”),可以控制PWM的占空比来实现亮度、色温、色彩的调节。以我使用的为例,占用了3个GPIO口(具体上文已经介绍)。
这次咱们思路就比较清晰了,先了解主要使用的函数以及功能实现所需要的架构,这一块书中讲的还是不错的,所以节选了一段《ESP32-C物联网开发实战》的内容:
按照书中的思路通过占空比的调节,来实现渐变和交替显示,多是一件美事。
下面是main.c的代码,无需在component中添加.c.h文件,和书中的实现思路基本一致,由于是将例程修改和移植而来,所以如果是不同的板子或者想要实现不同的功能,还需要再做调整。
#include <stdio.h>
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "esp_log.h"
#include "esp_err.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_event.h"
//定义LED灯的IO口
#define LED_RED_IO 10 //对应红灯的LED,绿灯为7,蓝灯为6
#define LED_GREEN_IO 7 //对应绿灯的LED,红灯为10,蓝灯为6
#define LED_BLUE_IO 6 //对应蓝灯的LED,红灯为10,绿灯为7
#define LEDC_MAX_DUTY (8191) //2的13次方-1(13位PWM)
#define LEDC_FADE_TIME (1000) //渐变时间(ms)
#define PWM_RED_CHANNEL LEDC_CHANNEL_0 //定义红灯通道
#define PWM_GREEN_CHANNEL LEDC_CHANNEL_1 //定义绿灯通道
#define PWM_BLUE_CHANNEL LEDC_CHANNEL_2 //定义蓝灯通道
unsigned char pwm_mode=1; //PWM模块,如果为1表示通过库函数实现渐变功能
//ledc配置结构体
ledc_channel_config_t g_ledc_red,g_ledc_green,g_ledc_blue;
void PWM_init(void)
{
//定时器配置结构体
ledc_timer_config_t ledc_timer;
//定时器配置->timer0
ledc_timer.duty_resolution = LEDC_TIMER_13_BIT; //PWM分辨率
ledc_timer.freq_hz = 5000; //频率
ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE; //速度
ledc_timer.timer_num = LEDC_TIMER_0; // 选择定时器
ledc_timer.clk_cfg = LEDC_USE_APB_CLK;
ledc_timer_config(&ledc_timer); //设置定时器PWM模式
//PWM通道0配置->IO10->红色灯
g_ledc_red.channel = PWM_RED_CHANNEL; //PWM通道
g_ledc_red.duty = LEDC_MAX_DUTY; //占空比
g_ledc_red.gpio_num = LED_RED_IO; //IO映射
g_ledc_red.speed_mode = LEDC_LOW_SPEED_MODE; //速度
g_ledc_red.timer_sel = LEDC_TIMER_0; //选择定时器
ledc_channel_config(&g_ledc_red); //配置PWM
//PWM通道0配置->IO7->绿色灯
g_ledc_green.channel = PWM_GREEN_CHANNEL; //PWM通道
g_ledc_green.duty = LEDC_MAX_DUTY; //占空比
g_ledc_green.gpio_num = LED_GREEN_IO; //IO映射
g_ledc_green.speed_mode = LEDC_LOW_SPEED_MODE; //速度
g_ledc_green.timer_sel = LEDC_TIMER_0; //选择定时器
ledc_channel_config(&g_ledc_green); //配置PWM
//PWM通道0配置->IO6->蓝色灯
g_ledc_blue.channel = PWM_BLUE_CHANNEL; //PWM通道
g_ledc_blue.duty = LEDC_MAX_DUTY; //占空比
g_ledc_blue.gpio_num = LED_BLUE_IO; //IO映射
g_ledc_blue.speed_mode = LEDC_LOW_SPEED_MODE; //速度
g_ledc_blue.timer_sel = LEDC_TIMER_0; //选择定时器
ledc_channel_config(&g_ledc_blue); //配置PWM
ledc_fade_func_install(0); //使能ledc渐变功能
}
//设置红灯的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_LOW_SPEED_MODE, PWM_RED_CHANNEL, duty);//修改占空比
ledc_update_duty(LEDC_LOW_SPEED_MODE, PWM_RED_CHANNEL);//新的占空比生效
}
//设置绿灯的PWM级别
//输入level取值0~255
void CtrRBG_G(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_LOW_SPEED_MODE, PWM_GREEN_CHANNEL, duty);//修改占空比
ledc_update_duty(LEDC_LOW_SPEED_MODE, PWM_GREEN_CHANNEL);//新的占空比生效
}
//设置蓝灯的PWM级别
//输入level取值0~255
void CtrRBG_B(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_LOW_SPEED_MODE, PWM_BLUE_CHANNEL, duty);//修改占空比
ledc_update_duty(LEDC_LOW_SPEED_MODE, PWM_BLUE_CHANNEL);//新的占空比生效
}
//通过渐变功能演示PWM任务
void task_pwm1(void *pvParameter)
{
int i=1;
while(1)
{
if(1==pwm_mode)
{
printf("pwm mode1 has run %d times.\r\n",i);
//渐变功能演示PWM
i++;
///
//红灯占空比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);
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
///
//绿灯占空比100%-->0%-->100%,时间2*LEDC_FADE_TIME
//绿灯:灭-->亮-->灭,的过程
//
//绿灯占空比100%渐变至0%,时间LEDC_FADE_TIME
ledc_set_fade_with_time(g_ledc_green.speed_mode,
g_ledc_green.channel,
0,
LEDC_FADE_TIME);
//渐变开始
ledc_fade_start(g_ledc_green.speed_mode,
g_ledc_green.channel,
LEDC_FADE_NO_WAIT);
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
//绿灯占空比0%渐变至100%,时间LEDC_FADE_TIME
ledc_set_fade_with_time(g_ledc_green.speed_mode,
g_ledc_green.channel,
LEDC_MAX_DUTY,
LEDC_FADE_TIME);
//渐变开始
ledc_fade_start(g_ledc_green.speed_mode,
g_ledc_green.channel,
LEDC_FADE_NO_WAIT);
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
///
//蓝灯占空比100%-->0%-->100%,时间2*LEDC_FADE_TIME
//蓝灯:灭-->亮-->灭,的过程
//
//蓝灯占空比100%渐变至0%,时间LEDC_FADE_TIME
ledc_set_fade_with_time(g_ledc_blue.speed_mode,
g_ledc_blue.channel,
0,
LEDC_FADE_TIME);
//渐变开始
ledc_fade_start(g_ledc_blue.speed_mode,
g_ledc_blue.channel,
LEDC_FADE_NO_WAIT);
//延时LEDC_FADE_TIME,给LEDC控制时间
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
//蓝灯占空比0%渐变至100%,时间LEDC_FADE_TIME
ledc_set_fade_with_time(g_ledc_blue.speed_mode,
g_ledc_blue.channel,
LEDC_MAX_DUTY,
LEDC_FADE_TIME);
//渐变开始
ledc_fade_start(g_ledc_blue.speed_mode,
g_ledc_blue.channel,
LEDC_FADE_NO_WAIT);
//延时LEDC_FADE_TIME,给LEDC控制时间
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
vTaskDelay(LEDC_FADE_TIME / portTICK_PERIOD_MS);
}
else{
vTaskDelay(5);//延时一下
}
}
}
void app_main()
{
PWM_init();//PWM初始化
xTaskCreate(&task_pwm1, "task_pwm1", 4096, NULL, 9, NULL);
}
拓展
怎么将上面的程序修改,实现从单色光源控制到模拟256*256*256色彩的变换?(可以自己先试试,后续会更新完整代码和注释,也可以在评论区交流)
小结
通过将不同模块和点灯结合,也算是抛砖引玉,在整理的过程中获得了很多知识和灵感;本人新人一枚,之前在学STM32的时候,可以参考的教程和文章十分丰富,在CubeMx就可以实现所需要配置和调用的库,然后在keil直接调用初始化函数,自己写子函数和主函数就可以了,所以上手还是很快的;而在ESP32的学习过程中,却遇到了很多新的api、初始化设置、参数调用以及全新的编译环境。所以在写第一篇的时候也是一路摸黑,有时候一个问题看了很多资料仍然不太理解,但是摸着摸着也摸出了一些经验,点灯只是一个开始,下一章再和大家唠唠按键和中断~