【写在前面】经过了点灯→定时器点灯→PWM点灯的学习之后,逐渐开始对ESP32 C3整体的框架有了一定认识【 点灯模块链接指路:http://t.csdn.cn/xOBmI】也掌握了一些理解和学习代码的思路,这一章咱们聊一聊按键的控制。
目录
GPIO输出与按键控制
level 1:从一个朴实无华的点按开始
level 2:引入队列、中断——实现按键控制2.0
优化代码、引入队列,实现多按键控制
通过线程的方式完成中断
小结
GPIO输出与按键控制
level 1:从一个朴实无华的点按开始
首先咱们了解下按键的硬件板块(以我手上的板子为例),可以发现按键按下电路导通对应低电平。
于是咱们可以使用最好理解的方法,从点按开始,光速上手按键模块:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#define KEY1_IO 9
#define KEY2_IO 8
#define LED_RED_IO 10 //咱们借用红灯作为按键指示灯
#define LED_ON 0 //这个板子是共阳极,低电平亮
#define LED_OFF 1
void initKey() //按键初始化(选择和设置为IO输入)
{
gpio_pad_select_gpio(KEY1_IO);
gpio_pad_select_gpio(KEY2_IO);
gpio_set_direction(KEY1_IO, GPIO_MODE_INPUT);
gpio_set_direction(KEY2_IO, GPIO_MODE_INPUT);
}
void initLed() //因为灯咱们也用到了,所以也要初始化
{
gpio_pad_select_gpio(LED_RED_IO);
gpio_set_direction(LED_RED_IO, GPIO_MODE_OUTPUT);
}
int key_read_key1(void) //一个很笨的方法,直到松开才触发下一步,否则一直等待松开
{
if(gpio_get_level(KEY1_IO)==0)
{
while (gpio_get_level(KEY1_IO)==0)
{
vTaskDelay(1);
}
return 1;
}
return 0;
}
int key_read_key2(void) //同上
{
if(gpio_get_level(KEY2_IO)==0)
{
while (gpio_get_level(KEY2_IO)==0)
{
vTaskDelay(1);
}
return 1;
}
return 0;
}
void main()
{
initKey();
initLed();
while(1) //主函数一定要while(1),不然刚通电程序就结束了
{
if(key_read_key1()) gpio_set_level(LED_RED_IO, LED_ON);
if(key_read_key2()) gpio_set_level(LED_RED_IO, LED_OFF);
}
}
编译,烧录,监视一气呵成~ level 1轻松秒杀,那要不要试下长按呢?
想实现长按其实也挺简单的,基于level1的代码简单魔改就能实现,只需要引入"esp_system.h"中的函数esp_timer_get_time(),可以获取内置定时器当时的时间,我们可以用这个作为flag,判断低电平状态和高电平的时间差。代码如下:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
#include "esp_system.h"
#define KEY1_IO 9
#define KEY2_IO 8
#define LED_RED_IO 10
#define LED_ON 0
#define LED_OFF 1
void initKey() //和短按无变化,具体注释看短按
{
gpio_pad_select_gpio(KEY1_IO);
gpio_pad_select_gpio(KEY2_IO);
gpio_set_direction(KEY1_IO, GPIO_MODE_INPUT);
gpio_set_direction(KEY2_IO, GPIO_MODE_INPUT);
}
void initLed() //相比短按无变化,具体注释看短按
{
gpio_pad_select_gpio(LED_RED_IO);
gpio_set_direction(LED_RED_IO, GPIO_MODE_OUTPUT);
}
void key_read_key1(void) // //相比短按,引入了flag和
{
int flag = 0;
if(gpio_get_level(KEY1_IO)==0)
{
flag = esp_timer_get_time();
while(gpio_get_level(KEY1_IO)==0)
{
vTaskDelay(1);
}
flag = esp_timer_get_time() - flag;
if(flag > 500*1000) gpio_set_level(LED_RED_IO, LED_ON);
else gpio_set_level(LED_RED_IO, LED_OFF);
}
}
void app_main()
{
initKey();
initLed();
while (1)
{
key_read_key1();
}
}
恭喜,如果上面的代码基本理解了,就已经可以做出一些小的模块了。但,这就满足了吗?
level 2:引入队列、中断——实现按键控制2.0
上面的方法用于实现按键控制,虽然很好理解,但在功能上总是不大理想的,如果只是考试还好,在一个具体的项目工程中,这种代码比较冗余且不利于后续的拓展,此时就需要我们更进一步,引入队列、线程、中断、回调的概念,来实现框架化、模块化的按键控制2.0。
对于入门来说,需要注意的地方其实还是不少,但是有了level1 的信心,level2 开始上点难度总不过分吧qwq,本文的所有代码我都统一了风格,可以通过比较的方式看看为了实现新功能新增了什么,改动了什么。
优化代码、引入队列,实现多按键控制
- 怎么样实现按键不松手就能输出一个按键长按?
- 拒绝重复if else ,如何简化多个按键之间的代码?
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "freertos/queue.h"
#include "esp_system.h" //多用了几个新的头文件
#include "sdkconfig.h"
#define KEY1_IO 9
#define KEY2_IO 8
#define LED_RED_IO 10 //咱们借用红灯作为按键指示灯key1
#define LED_BLUE_IO 6 //再用一个蓝灯指示key2
#define LED_ON 0 //这个板子是共阳极,低电平亮
#define LED_OFF 1
static xQueueHandle gpio_evt_queue = NULL; //句柄地址先设置为0,点灯的文章中解释过
void IRAM_ATTR gpio_isr_handler(void *arg) //创建了一个中断回调函数
{
uint32_t gpio_num = (u_int32_t) arg;
xQueueSendFromISR(gpio_evt_queue,&gpio_num,NULL); //插入一个中断到队列中
}
void initLed() //因为灯咱们也用到了,所以也要初始化
{
gpio_pad_select_gpio(LED_RED_IO);
gpio_set_direction(LED_RED_IO, GPIO_MODE_OUTPUT);
gpio_set_level(LED_RED_IO, LED_OFF);
gpio_pad_select_gpio(LED_BLUE_IO);
gpio_set_direction(LED_BLUE_IO, GPIO_MODE_OUTPUT);
gpio_set_level(LED_BLUE_IO, LED_OFF);
}
void initKey() //这里和上面的代码变化很大,是另一种配置IO口的方法
{
gpio_config_t io_conf; //这边赋值的方式不够简洁,但是好理解
io_conf.intr_type = GPIO_INTR_ANYEDGE; //上升下降沿中断均可触发
io_conf.pull_up_en = 1; //上拉使能
io_conf.mode = GPIO_MODE_INPUT; //io口输入模式
io_conf.pin_bit_mask = (1<<KEY1_IO) | (1<<KEY2_IO); //通过操控寄存器的方式
gpio_config(&io_conf); //上面是结构体赋值,赋好值了这里就要调用了
gpio_evt_queue = xQueueCreate(2,sizeof(uint32_t)); //创建一个大小为2的队列
gpio_install_isr_service(0); //释放资源
gpio_isr_handler_add(KEY1_IO,gpio_isr_handler,(void*)KEY1_IO); //初始化,返回esp_ok
gpio_isr_handler_add(KEY2_IO,gpio_isr_handler,(void*)KEY2_IO);
}
void key_scan() //因为我们上面配置的是上升沿、下降沿均可触发,所以按键按下就会立刻进入中断
{
uint32_t io_num;
bool ret=0;
xQueueReceive(gpio_evt_queue,&io_num,portMAX_DELAY);
if(gpio_get_level(io_num)==0)
{
int flag=esp_timer_get_time();
while(gpio_get_level(io_num)==0)
{
vTaskDelay(1);
if(flag < esp_timer_get_time()-1000*1000)
{
gpio_set_level(LED_RED_IO, LED_ON);
printf("按键长按\n");
flag = esp_timer_get_time();
ret = 1;
}
}
if(ret == 0 && flag > esp_timer_get_time()-1000*1000)
{
gpio_set_level(LED_RED_IO, LED_OFF);
printf("按键短按\n");
ret = 0;
}
}
}
void app_main()
{
initKey();
initLed();
while(1)
{
vTaskDelay(1);
key_scan();
}
}
通过线程的方式完成中断
实际工程中,我们当然不可能把按键扫描放到主函数的循环中,因为按键的响应速度会受到前面任务的影响,当我们按下按键时,肯定希望的是立刻得到反馈,那么,怎样实现不论程序进行什么,只要按键按下就能立刻响应?
经过了前面的铺垫,只需要在最后加入一个gpio_task_example(void* arg),并通过xTaskCreate调用即可,需要增添和修改的两个部分如下图所示:
下载之后,我们在监控中可以发现,虽然while(1)中只做了日志打印,但是每当按下按键的时候,中断都会立刻响应。
这个框架下想要加入和调整新功能都非常方便了(0 0总算搞定了),可以做成key.h和key.c,在根目录下封装到component里面。
小结
前期学习的时候,代码一定不能CTRL C +V,最好理解之后自己手敲一遍,这一章全部都是手敲的,然后就发现了一些平常没有注意到的问题,思路更加严谨,也熟悉了整体的框架。
点灯和按键其实代表最基本的 input 和 output 的概念,而在我想要这个基础上加入一点好玩的东西,通过层层递进的方式学到更多的概念,也许更加符合项目的需要和拓展。
从着手写 "不止点亮一个灯" 到写 "按键基本法" ,刚好是7天,春节期间经常需要走亲访友,所以都是等到整块的时间做的思考,毕竟一个完整的程序思路打断了还是不好调整的。不过也挺好的,整段时间可以拿来研究一个完整的程序,零碎时间;可以学各种API的用法,还有补自己的漏洞(真的很多)有的时候不知不觉时间就过去了,也算是心流状态;这让我保持了一个很好的心态,碰到问题就记录下自己的思路,学一段时间再回看,有时候忽然就理解了,再隔一段时间,甚至会觉得“怎么这么简单的东西当时都不会?”这是一个神奇的变化过程。文章来源:https://www.toymoban.com/news/detail-418910.html
下一篇就要聊聊WIFI模块了,毕竟还是要做物联网板块的,没有wifi怎么行(●ˇ∀ˇ●)文章来源地址https://www.toymoban.com/news/detail-418910.html
到了这里,关于2·ESP32-C3入门教程——按键基本法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!