[APM32E0] 【APM32E030R Micro-EVB开发板评测】——实现按键单击,双击,三击,长按,短按,组合

[复制链接]
369|4
龙鳞铁碎牙 发表于 2025-8-29 19:36 | 显示全部楼层 |阅读模式
, , , ,
很多人用按键处理时,只会单击(短按)和长按这两种,这是非常不正确的想法,也不能展示出个人的实力!!!
真正的按键高手可以使用按键处理算法来实现 单击,双击,三击,长按,2个按键组合新按键。这是非常有用的。
向你们平时使用的电脑按键,是不是有个组合按键,按下Fn + F1
进行电脑快捷按键的开闭,其实MCU也可以使用组合按键,主要是你们的思想已经封闭了,弄不出来!!!!
本文就是我个人自创的按键最齐全的算法,实现了按键的所有功能,包括单击,双击,三击,短按,长按,组合按键。你想要的都有!!!!
好了,进入正题!!!
1.打开原理图
5675268b18e789aecb.png
可以看到,KE1和KE2分别对应PA0和PA1

2.核心代码如下


#include "ebtn_app.h"


/* -------------------------------- 初始化和处理函数 -------------------------------- */


/** ***************************************************************************
* @brief easy_button初始化,在主函数中调用
* @NOTE  如果使用组合键,则需要在ebtn_init之后调用ebtn_combo_btn_add_btn添加组合键
*/
void ebtn_APP_Key_INIT(void)
{
    // 初始化easy_button库
    ebtn_init(btn_array, btn_array_size,
              btn_combo_array, btn_combo_array_size,
              ebtn_Get_State, ebtn_Event_Handler);


    /* ---------------------------- 此处向组合键结构体数组静态添加按键 --------------------------- */
    // 结构体数组索引值与ebtn_custom_config.c中组合键结构体数组btn_combo_array中的索引值一致
    // 示例:四个组合键
    // 为组合键1添加按键KEY_1和KEY_2
    ebtn_combo_btn_add_btn(&btn_combo_array[0], KEY_1);
    ebtn_combo_btn_add_btn(&btn_combo_array[0], KEY_2);


    // // 为组合键2添加按键KEY_3和KEY_4
    // ebtn_combo_btn_add_btn(&btn_combo_array[1], KEY_3);
    // ebtn_combo_btn_add_btn(&btn_combo_array[1], KEY_4);


    // // 为组合键3添加按键KEY_1和KEY_3
    // ebtn_combo_btn_add_btn(&btn_combo_array[2], KEY_1);
    // ebtn_combo_btn_add_btn(&btn_combo_array[2], KEY_3);


    // // 为组合键4添加按键KEY_2和KEY_4
    // ebtn_combo_btn_add_btn(&btn_combo_array[3], KEY_2);
    // ebtn_combo_btn_add_btn(&btn_combo_array[3], KEY_4);
}


/* --------------------------------- 自定义配置部分结束 -------------------------------- */


/** ***************************************************************************
* @brief 处理按键事件,需要定期调用,建议以20ms为周期执行一次
* @note  Tick时基为1ms
*/
void ebtn_APP_Key_Process(void)
{
    ebtn_process(ebtn_custom_hal.Get_Tick()); // 获取时间处理按键事件
}


/* -------------------------------- 辅助部分 ------------------------------- */


/** ***************************************************************************
* @brief  检查按键是否激活
* @param  key_id: 按键ID
* @return uint8_t: 1-激活,0-未激活
*/
uint8_t ebtn_APP_Is_Key_Active(uint16_t key_id)
{
    ebtn_btn_t *btn = ebtn_get_btn_by_key_id(key_id);
    return ebtn_is_btn_active(btn);
}


/** ***************************************************************************
* @brief  检查是否有按键处于处理中
* @return uint8_t: 1-有按键处理中,0-无按键处理中
*/
uint8_t ebtn_APP_Is_Any_Key_In_Process(void)
{
    return ebtn_is_in_process();
}


/** ***************************************************************************
* @brief  获取指定按键的按键状态
* @param  key_id: 按键ID
* @return uint8_t: 1-按下,0-松开
*/
uint8_t ebtn_APP_Get_Key_State(uint16_t key_id)
{
    ebtn_btn_t *btn = ebtn_get_btn_by_key_id(key_id);
    if (btn == NULL)
    {
        return 0;
    }
    return ebtn_Get_State(btn);
}

#ifndef EBTN_APP_H
#define EBTN_APP_H


#include "ebtn.h"
#include "ebtn_app.h"
#include "ebtn_custom_callback.h"
#include "ebtn_custom_hal.h"
#include "ebtn_custom_config.h"


void ebtn_APP_Key_INIT(void);
void ebtn_APP_Key_Process(void);


uint8_t ebtn_APP_Is_Key_Active(uint16_t key_id);
uint8_t ebtn_APP_Is_Any_Key_In_Process(void);
uint8_t ebtn_APP_Get_Key_State(uint16_t key_id);


#endif /* EBTN_APP_H */


#include "ebtn_custom_callback.h"


volatile uint8_t key_count = 0;
/* ---------------------------- 此函数中可自定义按键状态检测方式 ---------------------------- */


/** ***************************************************************************
* @brief  获取按键状态回调函数
* 此函数默认采用了查表检测,免去需要手动为每个按键添加检测方式
* 也可为特殊按键自定义检测方法
* @note   查表检测需要配合ebtn_custom_config.c中的按键配置结构体数组使用
* @param  btn: easy_button按键结构体指针
* @return 按键状态,0表示未按下,1表示按下
*/
uint8_t ebtn_Get_State(struct ebtn_btn *btn)
{
    // 查表法检测
    for (int i = 0; i < key_list_size; i++)
    {
        if (key_list.key_id == btn->key_id)
        {
            uint8_t pin_state = ebtn_custom_hal.Read_Pin(key_list.gpio_port, key_list.gpio_pin);
            // 根据有效电平转换
            if (key_list.active_level == pin_state)
            {
                pin_state = 1;
            }
            else
            {
                pin_state = 0;
            }
            return pin_state;
        }
    }


    /* ----------------------------- 此处可自定义按键状态获取方式 ----------------------------- */
    // 可自定义特殊按键的检测方式,如矩阵按键的检测


    return 0; // 未找到按键ID,返回0
}


/* ------------------------------- 此函数可修改按键触发事件 ------------------------------- */


/** ***************************************************************************
* @brief  按键事件处理回调函数,在此定义按键触发事件
* 推荐将不同按键或事件的处理逻辑拆分为独立的函数,并参考原有的 switch(ebtn->key) 结构,
* 在对应按键编号的 case 分支中调用相应的处理函数,以提升代码的可读性和可维护性
* @param  btn: easy_button按键结构体指针
* @param  evt: 事件类型
*/
void ebtn_Event_Handler(struct ebtn_btn *btn, ebtn_evt_t evt)
{
    switch (btn->key_id) // 按键ID
    {
    /* ---------------------------------- KEY1 ---------------------------------- */
    case KEY_1:
        /* ---------------------------------- 按下按键时 --------------------------------- */
        if (evt == EBTN_EVT_ONPRESS)
        {
        }
        /* ---------------------------------- 松开按键时 --------------------------------- */
        else if (evt == EBTN_EVT_ONRELEASE)
        {
        }
        /* ----------------------------- 短按按键时(可获取连击次数) ----------------------------- */
        else if (evt == EBTN_EVT_ONCLICK)
        {
            /* ----------------------------------- 单击时 ---------------------------------- */
            if (btn->click_cnt == 1)
            {
                    printf("KEY_1 单击\r\n");
                               
                                key_count++;
                        if(key_count>=7)key_count=0;
                               
            }
            /* ----------------------------------- 双击时 ---------------------------------- */
            else if (btn->click_cnt == 2)
            {
                    printf("KEY_1 双击\r\n");
                       
                                key_count += 2;
                                if(key_count>=7)key_count=0;
            }
            /* ----------------------------------- 三击时 ---------------------------------- */
            else if (btn->click_cnt == 3)
            {
                    printf("KEY_1 三击\r\n");
                               
                                key_count += 3;
                                if(key_count>=7)key_count=0;
            }
        }
        /* ------------------------- 长按达到最短时间(配置默认600ms),触发长按计数时 ------------------------ */
        else if (evt == EBTN_EVT_KEEPALIVE)
        {
            /* ------------------------------- 长按计数到达指定值时 ------------------------------- */
            if (btn->keepalive_cnt == 1)
            {
                    key_count++;if(key_count>=7)key_count=0;
                                printf("KEY_1 长按\r\n");
            }
        }
        break;
        /* ----------------------------------- KEY2 ---------------------------------- */
    case KEY_2:
        /* ---------------------------------- 按下按键时 --------------------------------- */
        if (evt == EBTN_EVT_ONPRESS)
        {
               
        }
        /* ---------------------------------- 松开按键时 --------------------------------- */
        else if (evt == EBTN_EVT_ONRELEASE)
        {
        }
        /* ----------------------------- 短按按键时(可获取连击次数) ----------------------------- */
        else if (evt == EBTN_EVT_ONCLICK)
        {
            /* ----------------------------------- 单击时 ---------------------------------- */
            if (btn->click_cnt == 1)
            {
                    printf("KEY_2 单击\r\n");
                                key_count--;
                                if(key_count==0)key_count=0;
            }
            /* ----------------------------------- 双击时 ---------------------------------- */
            else if (btn->click_cnt == 2)
            {
                    printf("KEY_2 双击\r\n");
                       
                                key_count -= 2;
                                if(key_count==0)key_count=0;
            }
                        /* ----------------------------------- 三击时 ---------------------------------- */
                else if (btn->click_cnt == 3)
                {
                        printf("KEY_2 三击\r\n");
                                       
                                key_count -= 3;
                                if(key_count==0)key_count=0;
                }
        }
        /* ------------------------- 长按到达最最短时间(默认600ms),触发长按计数时 ------------------------ */
        else if (evt == EBTN_EVT_KEEPALIVE)
        {
            /* ------------------------------- 长按计数到达指定值时 ------------------------------- */
            if (btn->keepalive_cnt == 1)
            {
                    printf("KEY_2 长按\r\n");
                    key_count--;
                                if(key_count==0)key_count=0;
            }
        }
        break;


        /* ----------------------------------- 组合键1 ---------------------------------- */


    case COMBO_KEY_1:
        /* ---------------------------------- 按下按键时 --------------------------------- */
        if (evt == EBTN_EVT_ONPRESS)
        {
                printf("按组合按键KEY1 KEY2 \r\n");
        }
        /* ---------------------------------- 松开按键时 --------------------------------- */
        else if (evt == EBTN_EVT_ONRELEASE)
        {
                //printf("松开KEY_1 和 KEY_2 组合按键\r\n");
        }
        break;


        /* ---------------------------------- 组合键2 ---------------------------------- */


    case COMBO_KEY_2:
        /* ---------------------------------- 按下按键时 --------------------------------- */
        if (evt == EBTN_EVT_ONPRESS)
        {
        }
        /* ---------------------------------- 松开按键时 --------------------------------- */
        else if (evt == EBTN_EVT_ONRELEASE)
        {
        }
        break;


        /* ---------------------------------- 组合键3 ---------------------------------- */


    case COMBO_KEY_3:
        /* ---------------------------------- 按下按键时 --------------------------------- */
        if (evt == EBTN_EVT_ONPRESS)
        {
        }
        /* ---------------------------------- 松开按键时 --------------------------------- */
        else if (evt == EBTN_EVT_ONRELEASE)
        {
        }
        break;


        /* ---------------------------------- 组合键4 ---------------------------------- */


    case COMBO_KEY_4:
        /* ---------------------------------- 按下按键时 --------------------------------- */
        if (evt == EBTN_EVT_ONPRESS)
        {
        }
        /* ---------------------------------- 松开按键时 --------------------------------- */
        else if (evt == EBTN_EVT_ONRELEASE)
        {
        }
        break;
    }
}

#ifndef EBTN_CUSTOM_CALLBACK_H
#define EBTN_CUSTOM_CALLBACK_H


#include "ebtn_app.h"
#include "ebtn_custom_hal.h"
#include "ebtn_custom_config.h"


/* -------------------------- 此处添加所需的按键触发事件的函数声明的头文件 ------------------------- */
extern volatile uint8_t key_count;


/* -------------------------------- 自定义配置部分结束 ------------------------------- */


uint8_t ebtn_Get_State(struct ebtn_btn *btn);
void ebtn_Event_Handler(struct ebtn_btn *btn, ebtn_evt_t evt);


#endif

#include "ebtn_custom_config.h"


/** ***************************************************************************
* @brief 定义按键参数结构体
* @ref ebtn_Custom_x.h
*/
ebtn_btn_param_t buttons_parameters = EBTN_PARAMS_INIT(
    DEBOUNCE_TIME,            // 按下防抖超时
    RELEASE_DEBOUNCE_TIME,    // 松开防抖超时
    CLICK_AND_PRESS_MIN_TIME, // 按键最短时间
    CLICK_AND_PRESS_MAX_TIME, // 按键最长时间
    MULTI_CLICK_MAX_TIME,     // 连续点击最大间隔(ms)
    KEEPALIVE_TIME_PERIOD,    // 长按报告事件间隔(ms)
    MAX_CLICK_COUNT           // 最大连续点击次数
);


/* --------------------------------- 此处修改按键 --------------------------------- */
// 所需的按键ID定义在ebtn_custom_config.h中增添


/** ***************************************************************************
* @brief 按键列表结构体数组,用于将按键ID与GPIO引脚以及触发电平进行绑定,
* 同时使用查表检测,免去需要为每个按键手动添加检测方式
* @note 此处填入所需的按键ID及其GPIO信息和触发时电平
*/
ebtn_custom_config_key_list_t key_list[] = {
    // 示例:4个按键
    {KEY_1, GPIOA, GPIO_PIN_0, 0},  // KEY_1按键,PA4,低电平有效
    {KEY_2, GPIOA, GPIO_PIN_1, 0},  // KEY_2按键,PB13,低电平有效
    //{KEY_3, GPIOA, GPIO_PIN_1, 0},  // KEY_3按键,PB1,低电平有效
    //{KEY_4, GPIOA, GPIO_PIN_0, 0},  // KEY_4按键,PB12,低电平有效
};


/** ***************************************************************************
* @brief 按键结构体数组,用于将按键ID与按键参数进行绑定
* @note 此为静态注册方法,动态注册详见easy_button库Github仓库
*/
ebtn_btn_t btn_array[] = {
    EBTN_BUTTON_INIT(KEY_1, &buttons_parameters),
    EBTN_BUTTON_INIT(KEY_2, &buttons_parameters),
    //EBTN_BUTTON_INIT(KEY_3, &buttons_parameters),
    //EBTN_BUTTON_INIT(KEY_4, &buttons_parameters),
};


/** ***************************************************************************
* @brief 组合键结构体数组,用于将多个按键组合成一个按键进行事件判断
* @note 此为静态注册方法,动态注册详见easy_button库Github仓库
* @note 需要在ebtn_app.c的ebtn_APP_Key_INIT函数中使用ebtn_Combo_btn_add_btn向组合键结构体数组添加对应按键
* 其中结构体数组索引与此处结构体数组的索引顺序一致,详见ebtn_app.c
*/
ebtn_btn_combo_t btn_combo_array[] = {
    EBTN_BUTTON_COMBO_INIT(COMBO_KEY_1, &buttons_parameters),
    EBTN_BUTTON_COMBO_INIT(COMBO_KEY_2, &buttons_parameters),
    //EBTN_BUTTON_COMBO_INIT(COMBO_KEY_3, &buttons_parameters),
    //EBTN_BUTTON_COMBO_INIT(COMBO_KEY_4, &buttons_parameters),
};


/* -------------------------------- 自定义配置部分结束 ------------------------------- */


const uint8_t btn_array_size = EBTN_ARRAY_SIZE(btn_array);
const uint8_t btn_combo_array_size = EBTN_ARRAY_SIZE(btn_combo_array);
const uint8_t key_list_size = EBTN_ARRAY_SIZE(key_list);










#ifndef EBTN_CUSTOM_CONFIG_H
#define EBTN_CUSTOM_CONFIG_H


#include "ebtn.h"
#include "ebtn_custom_hal.h"


/* -------------------------------- 此处修改按键参数定义 -------------------------------- */


#define DEBOUNCE_TIME 20             // 防抖处理,按下防抖超时,配置为0,代表不启用
#define RELEASE_DEBOUNCE_TIME 20     // 防抖处理,松开防抖超时,配置为0,代表不启用
#define CLICK_AND_PRESS_MIN_TIME 20  // 按键超时处理,触发最短时间,小于该时间则不触发Click和Press,配置为0,代表不检查最小值。用于防误触
#define CLICK_AND_PRESS_MAX_TIME 200 // 按键超时处理,短按最长时间,配置为0xFFFF,代表不检查最大值。用于区分长按和短按事件,大于该时间则不触发Click而触发Press
#define MULTI_CLICK_MAX_TIME 200     // 多击处理超时结算,两个按键之间的时间是连击的超时时间(ms)
#define KEEPALIVE_TIME_PERIOD 600    // 长按处理,长按周期,每个周期增加keepalive_cnt计数(ms)
#define MAX_CLICK_COUNT 3            // 最大连续短击次数,配置为0,代表不进行连击检查


/* -------------------------------- 此处修改按键ID定义 -------------------------------- */


/** ***************************************************************************
* @brief 按键ID枚举
* @note 此处定义按键ID
*/
typedef enum
{
    // 示例:4个按键和四个组合键,包含最大按键,用于提供按键数量
    KEY_1,
    KEY_2,
    KEY_3,
    KEY_4,
    MAX_KEY, // 最大按键,用于提供按键数量
    COMBO_KEY_1,
    COMBO_KEY_2,
    COMBO_KEY_3,
    COMBO_KEY_4,
    MAX_COMBO_KEY // 最大组合键,用于提供组合键数量
} ebtn_custom_config_key_enum_t;


/* ---------------------------------- 自定义配置部分结束 ---------------------------------- */


/** ***************************************************************************
* @brief 自定义按键配置结构体宏定义
*/
typedef struct
{
    uint16_t key_id;         // 按键按键ID
    GPIO_T* gpio_port;       // GPIO端口
    uint16_t gpio_pin;       // GPIO引脚
    uint8_t active_level;    // 有效电平(0=低电平有效,1=高电平有效)
} ebtn_custom_config_key_list_t;


extern ebtn_custom_config_key_list_t key_list[]; // 按键配置数组
extern ebtn_btn_t btn_array[];                   // 按键结构体数组
extern ebtn_btn_combo_t btn_combo_array[];       // 组合键结构体数组


extern const uint8_t btn_array_size;       // 按键结构体数组大小
extern const uint8_t btn_combo_array_size; // 组合键结构体数组大小
extern const uint8_t key_list_size;        // 按键配置数组大小


#endif /* EBTN_CUSTOM_CONFIG_H */



#include "ebtn_custom_hal.h"

/* ------------------------------ 此处实现硬件抽象回调函数 ------------------------------ */

/** ***************************************************************************
* @brief 读取GPIO电平,在ebtn_app.c中被使用,用于读取按键引脚的电平
* @param GPIOx 指向GPIO端口的指针
* @param GPIO_Pin GPIO引脚号
* @return GPIO引脚的电平状态(0/1)
*/
uint8_t ebtn_HAL_Read_Pin(GPIO_T* p_ctrl, uint16_t pin)
{

}
/** ***************************************************************************
* @brief  获取系统滴答计数器值,用于为ebtn_process()函数提供时间基准
* @note   SysTick时基为1ms
* @return 系统滴答计数器ms时间值
*/
uint32_t ebtn_HAL_Get_Tick(void)
{
     return HAL_GetTick(); // 示例:STM32HAL库
}

/* -------------------------------- 自定义配置部分结束 ------------------------------- */

/** ***************************************************************************
* @brief  ebtn_Custom回调函数结构体实例化
*/

ebtn_custom_hal_t ebtn_custom_hal = {
    .Read_Pin = ebtn_HAL_Read_Pin,
    .Get_Tick = ebtn_HAL_Get_Tick,
};

#ifndef EBTN_CUSTOM_HAL_H
#define EBTN_CUSTOM_HAL_H


/* ------------------------------ 此处包含单片机平台的头文件 ----------------------------- */
// 包含单片机平台的声明了GPIO和SysTick相关的头文件
#include "main.h"
/* -------------------------- 此处修改硬件回调函数自定义声明(如有需求) ------------------------- */


/** ***************************************************************************
* @brief  自定义回调函数结构体声明
*/
typedef struct
{
    /** ***************************************************************************
     * @brief 读取GPIO电平,在ebtn_app.c中被使用,用于读取按键引脚的电平
     * @param GPIOx 指向GPIO端口的指针
     * @param GPIO_Pin GPIO引脚号
     * @return GPIO引脚的电平状态(0/1)
     */
    uint8_t (*Read_Pin)(GPIO_T* p_ctrl, uint16_t pin);


    /** ***************************************************************************
     * @brief  获取系统滴答计数器值,用于为ebtn_process()函数提供时间基准
     * @note   SysTick时基为1ms
     * @return 系统滴答计数器ms时间值
     */
    uint32_t (*Get_Tick)(void);
} ebtn_custom_hal_t;


/* -------------------------------- 自定义配置部分结束 ------------------------------- */


extern ebtn_custom_hal_t ebtn_custom_hal; // ebtn适配层回调函数结构声明


#endif /* EBTN_CUSTOM_HAL_H */


8294368b18f3a424bc.png


自己定义一个20ms定时器,在里面调用按键钩子函数
2233968b18f61ccbb0.png


3.编译代码,烧录到板子
9960068b18f7a65f5b.png


4.打开串口助手
根据写好的串口函数,来判断按键的多功能
9458768b18fb729adf.png 6423768b19012e5150.png

7191068b19010695e0.png

可以看到已经完美的实现了KEY1,LEY2按键的单击, 双击,三击,长按,短按,KEY1和KEY2的组合按键,非常的完美!!!!
f附件是按键hex文件,大家可以自行烧录测试!!!



USART_Interrupt.zip

4.37 KB, 下载次数: 2

SpiritSong 发表于 2025-8-31 16:21 | 显示全部楼层
居然把按键处理放在了中断里来实现。
FrostShimmer 发表于 2025-9-6 10:32 | 显示全部楼层
这个组合按键是如何实现的啊
霜之闪耀 发表于 2025-9-7 22:57 | 显示全部楼层
这种按键组合为什么不使用状态机的实现方式
旧时光放映机 发表于 2025-9-9 10:33 | 显示全部楼层
感谢分享,APM32E030R开发板的按键处理算法实现得很全面,这对于我们这些嵌入式开发者来说很有帮助。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

22

主题

54

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部