2·ESP32-C3入门教程——按键基本法

【写在前面】经过了点灯→定时器点灯→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的用法,还有补自己的漏洞(真的很多)有的时候不知不觉时间就过去了,也算是心流状态;这让我保持了一个很好的心态,碰到问题就记录下自己的思路,学一段时间再回看,有时候忽然就理解了,再隔一段时间,甚至会觉得“怎么这么简单的东西当时都不会?”这是一个神奇的变化过程。

            下一篇就要聊聊WIFI模块了,毕竟还是要做物联网板块的,没有wifi怎么行(●ˇ∀ˇ●)

    物联沃分享整理
    物联沃-IOTWORD物联网 » 2·ESP32-C3入门教程——按键基本法

    发表评论