ESP-IDF ESP32按键检测(单击、长按、连击)

头文件,bsp_key.h

#define KEY_NUM     2    //按键数量

typedef enum{
    KEY1_RELEASE = 0x00,
    KEY1_LONGCLICKED,
    KEY1_LONGLONG,      
    KEY1_MULTICLICKED_1,
    KEY1_MULTICLICKED_2,
    KEY1_MULTICLICKED_3,
    KEY1_MULTICLICKED_4,
    KEY1_MULTICLICKED_5,
    KEY2_RELEASE,
    KEY2_LONGCLICKED,   
    KEY2_LONGLONG,    
    KEY2_MULTICLICKED_1,
    KEY2_MULTICLICKED_2,
    KEY2_MULTICLICKED_3,
    KEY2_MULTICLICKED_4,
    KEY2_MULTICLICKED_5,
};

#define LONG_PRESS_THRESHOLD 1000   //长长按时间
#define IDLE_COUNT_THRESHOLD 200    //空闲时间
#define CLICK_LONG_THRESHOLD 500    //长按时间
#define CLICK_COUNT_MAX 1500        
#define CONFIRM_COUNT 25            //消抖时间

typedef struct{
    int (*Scan)(void);
    void (*Init)(void);
    uint16_t KeyCnt;
    uint8_t IdleCnt;
    uint8_t ClickCnt;
    uint8_t LongPressed;
    uint8_t State;
}Key_t;

通过修改头文件中的宏定义可以修改按键检测的参数。

源文件,bsp_key.c

定义按键对象

Key_t g_Key_List[KEY_NUM];

初始化代码

/*************************************  USER CODE  *************************************/

/*
*********************************************************************************************************
*	函 数 名: Key1_Scan
*	功能说明: 扫描按键1的状态
*	形    参: 无
*	返 回 值: 按键1的状态
*********************************************************************************************************
*/
static int Key1_Scan(void)
{
    return gpio_get_level(9);
}

/*
*********************************************************************************************************
*	函 数 名: Key2_Scan
*	功能说明: 扫描按键2的状态
*	形    参: 无
*	返 回 值: 按键2的状态
*********************************************************************************************************
*/
static int Key2_Scan(void)
{
    return gpio_get_level(2);
}

/*
*********************************************************************************************************
*	函 数 名: Key1_Init
*	功能说明: 初始化按键1的配置
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void Key1_Init(void)
{
    gpio_config_t io_conf = {
        .intr_type = GPIO_INTR_DISABLE, //falling edge interrupt
        .mode = GPIO_MODE_INPUT, //set as input mode
        .pin_bit_mask = 1<<GPIO_NUM_9, //bit mask of the pins GPIO9
        .pull_down_en = 0, //disable pull-down mode
        .pull_up_en = 1 //enable pull-up mode
    };
    //configure GPIO with the given settings
    gpio_config(&io_conf);
}

/*
*********************************************************************************************************
*	函 数 名: Key2_Init
*	功能说明: 初始化按键2的配置
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void Key2_Init(void)
{
    gpio_config_t io_conf2 = {
        .intr_type = GPIO_INTR_DISABLE, //falling edge interrupt
        .mode = GPIO_MODE_INPUT, //set as input mode
        .pin_bit_mask = 1<<GPIO_NUM_2, //bit mask of the pins GPIO9
        .pull_down_en = 0, //disable pull-down mode
        .pull_up_en = 1 //enable pull-up mode
    };
    //configure GPIO with the given settings
    gpio_config(&io_conf2);
}
/*************************************  END  *************************************/

/*
*********************************************************************************************************
*	函 数 名: bsp_Key_Init
*	功能说明: 初始化按键列表,设置扫描和初始化函数指针,初始化按键状态和计数器
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_Key_Init(void)
{
    g_Key_List[0].Scan = Key1_Scan;
    g_Key_List[0].Init = Key1_Init;
    g_Key_List[1].Scan = Key2_Scan;
    g_Key_List[1].Init = Key2_Init;

    for (uint8_t i = 0; i < KEY_NUM; i++)
    {
        g_Key_List[i].KeyCnt = 0;
        g_Key_List[i].IdleCnt = 0;
        g_Key_List[i].ClickCnt = 0;
        g_Key_List[i].LongPressed = 0;
        g_Key_List[i].State = 0;
    }

    g_Key_List[0].Init();
    g_Key_List[1].Init();
}

按键扫描代码:

/*
*********************************************************************************************************
*	函 数 名: bsp_key_scan
*	功能说明: 扫描按键状态,处理长按、短按和松手事件
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void bsp_key_scan(void)
{
    uint8_t KeyVal = 0x00;

    for(uint8_t i = 0; i < KEY_NUM; i++)
    {
        if (g_Key_List[i].Scan() == 0)
        {
            if (g_Key_List[i].KeyCnt < CLICK_COUNT_MAX)
                g_Key_List[i].KeyCnt++;
            
            /* 长长按 */
            if (g_Key_List[i].KeyCnt > LONG_PRESS_THRESHOLD && !g_Key_List[i].LongPressed)
            {
                KeyVal = i * 8 + KEY1_LONGLONG;
                g_Key_List[i].State = KeyVal;
                // printf("长长按, KeyVal = 0x%x\r\n", KeyVal);
                xQueueSend(gpio_evt_queue, &KeyVal, NULL);
                g_Key_List[i].LongPressed = 1;
                g_Key_List[i].KeyCnt = 0;
            }
            g_Key_List[i].IdleCnt = 0;
        }
        else
        {
            /* Release */
            if (g_Key_List[i].State != i*8)
            {
                if (g_Key_List[i].KeyCnt == 0)
                {
                    KeyVal = i * 8;
                    g_Key_List[i].State = i*8;
                    xQueueSend(gpio_evt_queue, &KeyVal, NULL);
                    // printf("Release\r\n");
                }
                else
                    g_Key_List[i].KeyCnt--;
            }

            if (g_Key_List[i].LongPressed)
            {
                g_Key_List[i].KeyCnt = 0;
                g_Key_List[i].LongPressed = 0;
                g_Key_List[i].ClickCnt = 0;
            }

            /* 单击、多击松手 */
            if (g_Key_List[i].IdleCnt++ >= IDLE_COUNT_THRESHOLD)
            {
                g_Key_List[i].KeyCnt = 0;

                if (g_Key_List[i].ClickCnt)
                {
                    KeyVal = i * 8 + KEY1_MULTICLICKED_1 - 1 + g_Key_List[i].ClickCnt;
                    g_Key_List[i].State = KeyVal;
                    xQueueSend(gpio_evt_queue, &KeyVal, NULL);
                    // printf("按键%d击, KeyVal = 0x%x\r\n",g_Key_List[i].ClickCnt,KeyVal);
                }
                g_Key_List[i].ClickCnt = 0;
                g_Key_List[i].IdleCnt = 0;
            }
            /* long Press */
            if (g_Key_List[i].KeyCnt > CLICK_LONG_THRESHOLD)
            {
                KeyVal = i * 8 + KEY1_LONGCLICKED;
                g_Key_List[i].State = KeyVal;
                xQueueSend(gpio_evt_queue, &KeyVal, NULL);
                g_Key_List[i].KeyCnt = 0;
                // printf("按键长按, KeyVal = 0x%x\r\n", KeyVal);
                g_Key_List[i].ClickCnt = 0;
            }
            /* 单击、多击 */
            else if (g_Key_List[i].KeyCnt <= CLICK_LONG_THRESHOLD && g_Key_List[i].KeyCnt >= CONFIRM_COUNT)
            {
                g_Key_List[i].ClickCnt++;
                g_Key_List[i].KeyCnt = 0;
            }
        }
    }
}

代码中使用 xQueueSend(gpio_evt_queue, &KeyVal, NULL) 发送键值至其他线程,在裸机中使用可以替换为向自己的队列FIFO写键值。

示例:

扫描线程,周期2ms,在裸机环境中可使用systick定时器,在systick中断中调用bsp_key_scan函数。

void key_task_example(void* arg)
{
    for(;;)
    {
        bsp_key_scan();
        vTaskDelay(2);
    }
}
static void gpio_task_example(void* arg)
{
    uint32_t KeyVal;

    for(;;) {
        if(xQueueReceive(gpio_evt_queue, &KeyVal, portMAX_DELAY)) 
        {
            switch (KeyVal)
            {
                case KEY1_RELEASE :
                    printf("Release\r\n");
                break;

                case KEY1_LONGCLICKED :
                    printf("KEY1_LONGCLICKED\r\n");
                break;

                case KEY1_LONGLONG :
                    printf("KEY1_LONGLONGCLICKED\r\n");
                break;

                case KEY1_MULTICLICKED_1 :
                    printf("KEY1_MULTICLICKED_1\r\n");
                break;

                case KEY1_MULTICLICKED_2 :
                    printf("KEY1_MULTICLICKED_2\r\n");
                break;

                case KEY1_MULTICLICKED_3 :
                    printf("KEY1_MULTICLICKED_3\r\n");
                break;                     
                
                case KEY1_MULTICLICKED_4 :
                    printf("KEY1_MULTICLICKED_4\r\n");
                break;                     
                
                case KEY1_MULTICLICKED_5 :
                    printf("KEY1_MULTICLICKED_5\r\n");
                break;                

                default: break;
            }
        }
    }
}

运行结果:

### 在ESP32-S3上使用ESP-IDF框架检测按Boot按钮20秒事件以及单击事件的实现方法 为了实现在ESP32-S3上通过ESP-IDF框架检测按Boot按钮20秒事件和单击事件的功能,可以采用以下方案。这种方法结合了GPIO中断机制、定时器以及任务调度来分别处理两种不同的按键行为。 --- #### 1. 初始化GPIO引脚 首先需要初始化用于检测BOOT按钮的GPIO引脚。通常情况下,ESP32-S3上的BOOT按钮连接至特定的GPIO引脚(例如GPIO0),因此需将其配置为输入模式并启用内部上拉电阻[^1]。 ```c #include "driver/gpio.h" void init_boot_button_gpio(void) { gpio_reset_pin(GPIO_NUM_0); // 假设BOOT按钮连接到GPIO0 gpio_set_direction(GPIO_NUM_0, GPIO_MODE_INPUT); gpio_pullup_en(GPIO_NUM_0); // 启用内部上拉电阻 } ``` --- #### 2. 处理单击事件 对于单击事件的检测,可以通过记录按键按下和释放的时间差来判断是否满足单击条件。如果时间差小于某个阈值(例如500毫秒),则认为是一次有效的单击。 ```c #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #define SINGLE_CLICK_THRESHOLD_MS (500) uint64_t last_click_time = 0; void handle_single_click_event(uint64_t current_time) { if ((current_time - last_click_time) > SINGLE_CLICK_THRESHOLD_MS) { printf("Single click detected!\n"); last_click_time = current_time; } } ``` --- #### 3. 处理按事件 对于按事件的检测,可以在按键被按下的状态下启动一个计时器,当计时器达到预设的时间阈值(例如20秒)时触发相应的动作。 ```c #define LONG_PRESS_TIME_MS (20 * 1000) // 定义按时间为20秒 bool is_long_press_detected = false; void check_for_long_press(bool button_pressed, uint64_t start_time) { if (button_pressed && !is_long_press_detected) { uint64_t elapsed_time_ms = (esp_timer_get_time() - start_time) / 1000; if (elapsed_time_ms >= LONG_PRESS_TIME_MS) { is_long_press_detected = true; printf("Long press detected after %llu ms!\n", elapsed_time_ms); // 可在此处执行其他操作,比如重启设备或其他功能触发 } } } ``` --- #### 4. 综合任务逻辑 创建一个独立的任务来不断读取GPIO的状态,并根据当前状态调用相应的方法来处理单击按事件。 ```c void button_detection_task(void* arg) { uint64_t start_time = 0; bool button_pressed = false; while (true) { if (!gpio_get_level(GPIO_NUM_0)) { // 如果按键被按下 if (!button_pressed) { start_time = esp_timer_get_time(); // 记录开始时间 button_pressed = true; // 设置标志位表示已进入按键按下状态 } check_for_long_press(button_pressed, start_time); } else { if (button_pressed) { button_pressed = false; // 按键松开,重置标志位 if (!is_long_press_detected) { handle_single_click_event(esp_timer_get_time()); } is_long_press_detected = false; // 清除按标记以便下次重新检测 } } vTaskDelay(pdMS_TO_TICKS(10)); // 减少CPU占用率 } } ``` --- #### 5. 主程序入口 最后在`app_main()`函数中调用上述方法完成整个流程。 ```c #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" extern void app_main(void); void app_main(void) { init_boot_button_gpio(); xTaskCreate(button_detection_task, "button_detection_task", 2048, NULL, 5, NULL); } ``` --- ### 注意事项 - **GPIO编号**:上述示例假设BOOT按钮接到了GPIO0,请根据实际硬件设计调整对应的GPIO编号。 - **任务优先级**:确保分配给按键检测任务的优先级不会与其他高优先级任务冲突。 - **功耗优化**:适当增加`vTaskDelay()`中的延迟时间以降低CPU负载,尤其是在电池供电的应用场景下。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值