【ESP32】打造全网最强esp-idf基础教程——5按键值获取逻辑

【ESP32】打造全网最强esp-idf基础教程——5.按键值获取逻辑

       假期攻略:上午睡觉中午吃饭,下午刷视频,晚上,当然是继续更新博客啦~宝子们我来啦~

  一、按键原理
       大家在使用一些智能家居家电时,可能都会有那么几个按键,然后看操作说明,按键的短按长按功能是不一样的,短按的话基本上是执行标准功能,比如说开灯关灯,那么长按可能就是执行一些不常用的功能了,比如说配网。因此按键的处理对物联网开发来说是必备的基础技能,要说简单,也很简单,要玩出花样,也能玩出很多花样。本例程中会对单个按键实现长按和短按功能的划分,同时也可以扩展出多个按键的应用。原理图如下

       按键在没按下的时候,GPIO口被上拉电阻拉高,读取引脚的电平就是高电平1,按键按下时,GPIO本短接到地,读取引脚的电平就是低电平0,原理图非常简单,我们只需在程序中对GPIO引脚设置成输入模式,实时读取电平即可

二、第一个例程
       这个例程实现的功能如下,我们实时的判断外部按键状态,当检测到低电平时,我们就把LED点亮,当检测到高电平时,我们就把LED熄灭。代码如下,代码位于esp32-board/button

void app_main(void)
{
   //初始化LED的GPIO管脚,设置成输出
    gpio_config_t gpio_t = 
    {
        .intr_type = GPIO_INTR_DISABLE,     //禁止中断
        .mode = GPIO_MODE_OUTPUT,           //输出模式
        .pin_bit_mask = (1ull<<LED_GPIO),   //GPIO引脚号
        .pull_down_en = GPIO_PULLDOWN_DISABLE,//下拉禁止
        .pull_up_en = GPIO_PULLUP_DISABLE,   //上拉禁止
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_t));

//简单按键例程
    xTaskCreatePinnedToCore(simple_btn_test,"btn1",2048,NULL,3,NULL,1);
}
/** 简单例程
 * @param 无
 * @return 无
*/
void simple_btn_test(void* arg)
{
    //初始化按键GPIO
    gpio_config_t gpio_t = 
    {
        .intr_type = GPIO_INTR_DISABLE,     //禁止中断
        .mode = GPIO_MODE_INPUT,            //输入模式
        .pin_bit_mask = (1ull<<BTN_GPIO),   //GPIO引脚号
        .pull_down_en = GPIO_PULLDOWN_DISABLE,  //禁止下拉
        .pull_up_en = GPIO_PULLUP_ENABLE,   //使能上拉
    };
    ESP_ERROR_CHECK(gpio_config(&gpio_t));
    while(1)
    {
        //检测到按键按下,就亮,否则熄灭
        if(gpio_get_level(BTN_GPIO) == 0)
            gpio_set_level(LED_GPIO,1);
        else
            gpio_set_level(LED_GPIO,0);
        vTaskDelay(pdMS_TO_TICKS(50));
    }
}

       在app_main()函数中,初始化了LED的GPIO口,设置成输出模式,同时启用了一个任务simple_btn_test,在这个新任务中处理LED和按键状态
     
 simple_btn_test函数中,对按键的GPIO口进行了初始化,并添加了内部上拉电阻(如果有外部上拉电阻,这里可以不设置)。然后在循环中不断读取按键状态,按键有按下,就输出LED的管脚为高电平点亮LED,按键没按下就输出LED的管脚为低电平熄灭LED。

三、第二个例程
       现在我们来考虑更复杂的场景,需求如下:
       1)按键按下之后,LED只闪亮一下,然后熄灭,不能一直亮
       2)按键长按超过3秒后,LED闪亮8下,然后熄灭,提示用户已经检测到长按事件。
       3)在处理完长按后,没有释放按键之前,不做任何动作

       对于按键事件的处理很简单,看如下代码 

EventBits_t ev;
    while(1)
    {
        //等待按键按下事件
        ev = xEventGroupWaitBits(s_pressEvent,SHORT_EV|LONG_EV,pdTRUE,pdFALSE,portMAX_DELAY);
        if(ev & SHORT_EV)
        {
            //短按事件,亮一下熄灭
            gpio_set_level(LED_GPIO,1);
            vTaskDelay(pdMS_TO_TICKS(200));
            gpio_set_level(LED_GPIO,0);
        }
        if(ev & LONG_EV)
        {
            //长按事件,闪烁8下,然后熄灭
            for(int i = 0;i < 8;i++)
            {
                gpio_set_level(LED_GPIO,1);
                vTaskDelay(pdMS_TO_TICKS(200));
                gpio_set_level(LED_GPIO,0);
                vTaskDelay(pdMS_TO_TICKS(200));
            }
        }
        xEventGroupClearBits(s_pressEvent,SHORT_EV);
        xEventGroupClearBits(s_pressEvent,LONG_EV);
    }

       我们通过一个事件组来标识短按和长按事件,然后在任务循环中实时监控这个事件,短按事件则控制LED点亮,200ms后熄灭。长按事件则用for循环来执行8次led的亮灭即可。在最后清除掉事件防止LED闪亮没处理完,又有事件重复触发。
       那么接下来关键的是如何检测到这两个事件。这边思路是建立一个定时器,定时器中实时检测按键的状态,当检测到按键按下时,跳转到按键按下状态,同时触发按键短按回调函数,然后开始计数,当计数值到达预设的长按值时,就触发长按回调函数,同时把状态转入LONG_HOLD状态,不做任何处理,直到检测到按键释放。上面说的短按回调函数和长按回调函数里面,主要实现就是设置了相应的短按和长按事件位,由于代码较多,我只贴一部分关键代码。大家要看详细代码,到目录esp32-board/button中可以看到所有代码。

** 定时器回调函数,本例中是5ms执行一次
 * @param cfg   配置结构体
 * @return ESP_OK or ESP_FAIL 
*/
static void button_handle(void *param)
{
    int increase_cnt = (int)param;  //传入的参数是5,表示定时器运行周期是5ms
    button_dev_t* btn_target = s_button_head;
    for(;btn_target;btn_target = btn_target->next)
    {
        switch(btn_target->state)
        {
            case BUTTON_RELEASE:             //按键没有按下
                if(gpio_get_level(btn_target->btn_cfg.gpio_num) == btn_target->btn_cfg.active_level)
                {
                    btn_target->press_cnt += increase_cnt;
                    btn_target->state = BUTTON_PRESS;   //调转到按下状态
                }
                break;
            case BUTTON_PRESS:               //按键按下了,等待一点延时(消抖),然后触发短按回调事件,进入BUTTON_HOLD
                if(gpio_get_level(btn_target->btn_cfg.gpio_num) == btn_target->btn_cfg.active_level)
                {
                    btn_target->press_cnt += increase_cnt;
                    if(btn_target->press_cnt >= FILITER_TIMER)  //过了滤波时间,执行短按回调函数
                    {
                        ESP_LOGI(TAG,"short press detect");
                        if(btn_target->btn_cfg.short_cb)
                            btn_target->btn_cfg.short_cb();
                        btn_target->state = BUTTON_HOLD;    //状态转入按下状态
                    }
                }
                else
                {
                    btn_target->state = BUTTON_RELEASE;
                    btn_target->press_cnt = 0;
                }
                break;
            case BUTTON_HOLD:                //按住状态,如果时间长度超过设定的超时计数,将触发长按回调函数,进入BUTTON_LONG_PRESS_HOLD
                if(gpio_get_level(btn_target->btn_cfg.gpio_num) == btn_target->btn_cfg.active_level)
                {
                    btn_target->press_cnt += increase_cnt;
                    if(btn_target->press_cnt >= btn_target->btn_cfg.long_press_time)  //已经检测到按下大于预设长按时间,执行长按回调函数
                    {
                        ESP_LOGI(TAG,"long press detect");
                        if(btn_target->btn_cfg.long_cb)
                            btn_target->btn_cfg.long_cb();
                        btn_target->state = BUTTON_LONG_PRESS_HOLD;
                    }
                }
                else
                {
                    btn_target->state = BUTTON_RELEASE;
                    btn_target->press_cnt = 0;
                }
                break;
            case BUTTON_LONG_PRESS_HOLD:     //此状态等待电平消失,回到BUTTON_RELEASE状态
                if(gpio_get_level(btn_target->btn_cfg.gpio_num) != btn_target->btn_cfg.active_level)    //检测到释放,就回到初始状态
                {
                    btn_target->state = BUTTON_RELEASE;
                    btn_target->press_cnt = 0;
                }
                break;
            default:break;
        }
    }
}

       button_handle 是定时器,每隔5ms执行一次, button_dev_t结构体是对于单个按键的描述,本例程考虑了有多个按键,将这些按键描述通过链表串起来,然后循环遍历,对应配套教程的开发板只有一个按键,因此这个循环只执行一次,button_dev_t链表只有一个内容。
在这个循环中,会判断按键当前的状态,btn_target->state状态类型分别有4个

       1)BUTTON_RELEASE,//此状态表示按键没有按下,但会一直检测,如果检测到有按键按下,会进入到BUTTON_PRESS状态
       2)BUTTON_PRESS,//按键按下了,如果计数值超过滤波时间,触发短按回调事件,然后进入BUTTON_HOLD
       3)BUTTON_HOLD, /按住状态,如果计数值超过预设长按超时,将触发长按回调函数,进入BUTTON_LONG_PRESS_HOLD
       4 )BUTTON_LONG_PRESS_HOLD,     //此状态等待电平消失,回到BUTTON_RELEASE状态

       btn_target->press_cnt成员就是记录按键按下的时间,如果按键按下,每个定时器周期都会增加(本例中是5ms)。 

       再看看button的相关初始化代码 

//按键配置结构体
typedef struct
{
    int gpio_num;           //gpio号
    int active_level;       //按下的电平
    int long_press_time;    //长按时间
    button_press_cb short_cb;   //短按回调函数
    button_press_cb long_cb;    //长按回调函数
}button_config_t;

/** 设置按键事件
 * @param cfg   配置结构体
 * @return ESP_OK or ESP_FAIL 
*/
esp_err_t button_event_set(button_config_t *cfg);

       这里的初始化需要新建一个按键配置,设定gpio_num、按下时的电平、长按时间、短按和长按的回调函数,然后通过button_event_set把按键配置添加到按键配置链表中。看看在主函数中,是如果应用这个函数的。 

/** 短按按键回调函数
 * @param 无
 * @return 无
*/
void short_press_handle(void)
{
    xEventGroupSetBits(s_pressEvent,SHORT_EV);
}

/** 长按按键回调函数
 * @param 无
 * @return 无
*/
void long_press_handle(void)
{
    xEventGroupSetBits(s_pressEvent,LONG_EV);
}
/** 完整的按键+LED演示程序
 * @param 无
 * @return 无
*/
void complete_btn_test(void* arg)
{
    s_pressEvent = xEventGroupCreate();
    button_config_t btn_cfg = 
    {
        .gpio_num = BTN_GPIO,       //gpio号
        .active_level = 0,          //按下的电平
        .long_press_time = 3000,    //长按时间
        .short_cb = short_press_handle,           //短按回调函数
        .long_cb = long_press_handle             //长按回调函数
    };
button_event_set(&btn_cfg);     //添加按键响应事件处理
……………………………………

       上述我们定义了短按和长按回调函数,然后填充了button_config_t结构体,最后通过button_event_set把按键处理添加到链表中。
       这样我们对于按键的处理就比较完善了。再次强烈建议大家去阅读一下源码,源码中的注释都比较详细。 

 

最后附上相关资料:

ESP32教程资料链接:
https://pan.baidu.com/s/1kCjD8yktZECSGmHomx_veg?pwd=q8er 
提取码:q8er 

配套源码下载地址:
esp32-board: esp32开发板配套的经典例程

鉴于实验需要开发板的支持,我也设计了一款ESP32开发板,包含部分传感器模块,1.69寸LCD高亮屏,Type-C一键下载,方便大家学习和做各种实验。开发板链接如下:

https://item.taobao.com/item.htm?ft=t&id=802401650392&spm=a21dvs.23580594.0.0.4fee645eXpkfcp&skuId=5635015963649
 

请大家多多支持。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值