按键检测框架单击-双击-连按

 说明

本示例用的stm32 按键是接地有效

特别设置按键拉高

按键事件循环50ms一次调用

电路图

按键检测思路

  1. 按键按下检测:检测当前本状态是否和上一次状态一致,不一致消抖完成的情况下,当前电平和按下定义电平一致,按键状态就是 按下 down 否则就是 按键回弹 up-----只有按键在变化的一瞬间更新按键状态和按键上一次状态
  2. 长按按检测,按键一处处于按下的状态 down 的情况下,时间超过 X 时间后 开始发出长按事件;
  3. 单击、双击检测 在一定时间段内检测 按键按下的次数,这段时间内部如果检测到的次数是1,就是单击,多次就是双击或连击---单击事件会延迟响应

//核心检测代码
void Button_Cycle_Process(Button_t *btn)
{

    uint8_t current_level = (uint8_t)btn->Read_Button_Level(); //获取当前按键电平
    if ((current_level != btn->Button_Last_Level)//检测电平变化
       && (++(btn->Debounce_Time) >= BUTTON_DEBOUNCE_TIME)) //按键电平发生变化,消抖
    {
        btn->Button_Last_Level = current_level; //更新当前按键电平
        btn->Debounce_Time = 0;                 //确定了是按下
        if (btn->Button_Trigger_Level == current_level)
        {
            btn->Button_State = BUTTON_DOWM; //按键状态
            btn->Timer_Count = 0;//按键按下后开始计数清零
            
        }
        //释放按键
        else 
        {
            btn->Button_State = BUTTON_UP;
        }
    }

    btn->Timer_Count++;
    switch (btn->Button_State)
    {
    case BUTTON_DOWM: // 按下状态
    {
        if (btn->Timer_Count > BUTTON_LONG_TIME && //大于长按时间
        btn->Button_Last_Level == current_level)
        {
            if (btn->Timer_Count % 4 == 0)//减少长按放的事件
                btn->event = EV_LONG_CLICK;
        }
        break;
    }
    case BUTTON_UP: // 弹起状态
    {
        if (btn->event == EV_LONG_CLICK)//长按事件结束
        {
            btn->event = EV_NULL;
            DEBUG_PRINTF("BUTTON_CONTINUOS_FREE\r\n");
        }
        if (btn->Timer_Count < BUTTON_DOUBLE_TIME)//时间在双击时间范围内
        {
            if (btn->Button_Last_State == BUTTON_DOWM)//按键按下上一次是按下状态,当次是松开按键状态 也就是上升沿
            {
                btn->Button_trigger_cnt++;
                btn->Timer_Count = 0;
            }
        }
        if (btn->Timer_Count > BUTTON_DOUBLE_TIME) //到达时间后开始检测事件
        {
            btn->Timer_Count = 0;
            if (btn->Button_trigger_cnt == 1)//点击一次是单击
            {
                DEBUG_PRINTF("button onc clicki\r\n");
                btn->event = EV_CLICK;
            }
            else if (btn->Button_trigger_cnt > 1)//大于一次就是双击
            {
                DEBUG_PRINTF("button double clicki\r\n");
                btn->event = EV_DOUBLE_CLICK;
            }
            btn->Button_trigger_cnt = 0; //触发次数清零
            btn->Timer_Count = 0;
        }
        break;
    }
    }
    btn->Button_Last_State = btn->Button_State; //保存当次状态为下一轮比较
    //处理事件
    if (btn->event < EV_CNT && btn->CallBack_Function[btn->event] != NULL)
    {
        btn->CallBack_Function[btn->event](btn);
        btn->event = EV_NULL;
    }
}

 源代码

h文件

#ifndef __BUTTON_H__
#define __BUTTON_H__

#include <stdint.h>
#include <string.h>

#define DEBUG 0
#if DEBUG
#define DEBUG_PRINTF(fmt, args...)                      \
    do                                                  \
    {                                                   \
        (printf("\n[DEBUG] >> "), printf(fmt, ##args)); \
    } while (0)
#else
#define DEBUG_PRINTF(fmt, args...)
#endif
#define BTN_NAME_MAX 32 //按键名字最大为32字节

/* 按键消抖时间40ms, 建议调用周期为20ms
 只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件
*/

#define BUTTON_DEBOUNCE_TIME 2 //消抖时间      (n-1)*调用周期
#define BUTTON_DOUBLE_TIME 7   //双击间隔时间  (n-1)*调用周期  建议在200-600ms
#define BUTTON_LONG_TIME 30    /* 持续n秒((n-1)*调用周期 ms),认为长按事件 */

#define TRIGGER_CB(event)              \
    if (btn->CallBack_Function[event]) \
    btn->CallBack_Function[event]((Button_t *)btn)

typedef void (*Button_CallBack)(void *); /* 按键触发回调函数,需要用户实现 */

typedef enum
{
    BUTTON_DOWM = 0,
    BUTTON_UP,
    NONE_TRIGGER

} Button_Event;

typedef enum
{
    EV_CLICK = 0,    //单击
    EV_DOUBLE_CLICK, //双击
    EV_LONG_CLICK,   //长按
    EV_CNT,          //辅助用于统计事件个数
    EV_NULL,         //无事件发生
} ButtonEvent;
/*
	每个按键对应1个全局的结构体变量。
	其成员变量是实现滤波和多种按键状态所必须的
*/
typedef struct button
{
    /* 下面是一个函数指针,指向判断按键手否按下的函数 */
    uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */
    char Name[BTN_NAME_MAX];            //按键名称
    uint8_t Button_State : 4;           /* 按键当前状态(按下还是弹起) */
    uint8_t Button_Last_State : 4;      /* 上一次的按键状态,用于判断双击 */
    uint8_t Button_Trigger_Level : 2;   /* 按键触发电平 */
    uint8_t Button_Last_Level : 2;      /* 按键当前电平 */

    ButtonEvent event; /* 按键触发事件,单击,双击,长按等 */

    Button_CallBack CallBack_Function[EV_CNT]; //按键触发的回调函数
    uint8_t Button_trigger_cnt;                //单位双击周期内部触发的次数
    uint16_t Timer_Count;                      /* 计时 */
    uint16_t Debounce_Time;                    /* 消抖时间计数器 */
    struct button *Next;                       //用于形成按键链表的指针

} Button_t;

/* 供外部调用的函数声明 */
/************************************************************
  * @brief   按键创建
	* @param   name : 按键名称
	* @param   btn : 按键结构体
  * @param   read_btn_level : 按键电平读取函数,需要用户自己实现返回uint8_t类型的电平
  * @param   btn_trigger_level : 按键触发电平
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Button_Create(const char *name,
                   Button_t *btn,
                   uint8_t (*read_btn_level)(void),
                   uint8_t btn_trigger_level);

/************************************************************
  * @brief   按键触发事件与回调函数映射链接起来
	* @param   btn : 按键结构体
	* @param   btn_event : 按键触发事件
  * @param   btn_callback : 按键触发之后的回调处理函数。需要用户实现
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  ***********************************************************/
void Button_Attach(Button_t *btn, ButtonEvent btn_event, Button_CallBack btn_callback);

/************************************************************
  * @brief   遍历的方式扫描按键,不会丢失每个按键
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    此函数要周期调用,建议50ms调用一次
  ***********************************************************/
void Button_Process(void);
/************************************************************
  * @brief   删除一个已经创建的按键
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Button_Delete(Button_t *btn);

/************************************************************
  * @brief   遍历按键
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Search_Button(void);



#endif

 C文件

/************************************************************
  * @brief   按键驱动
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    button.c
  ***********************************************************/
#include "button.h"
#include "stdio.h"
/*******************************************************************
 *                          变量声明                               
 *******************************************************************/
static void Button_Cycle_Process(Button_t *btn);
static struct button *Head_Button = NULL;

/*******************************************************************
 *                         函数声明     
 *******************************************************************/
static char *StrnCopy(char *dst, const char *src, uint32_t n);
static void Print_Btn_Info(Button_t *btn);
static void Add_Button(Button_t *btn);

/************************************************************
  * @brief   按键创建
	* @param   name : 按键名称
	* @param   btn : 按键结构体
  * @param   read_btn_level : 按键电平读取函数,需要用户自己实现返回uint8_t类型的电平
  * @param   btn_trigger_level : 按键触发电平
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Button_Create(const char *name,
                   Button_t *btn,
                   uint8_t (*read_btn_level)(void),
                   uint8_t btn_trigger_level)
{
    if (btn == NULL)
    {
        DEBUG_PRINTF("btn IS nullptr\r\n");
    }

    memset(btn, 0, sizeof(struct button)); //清除结构体信息,建议用户在之前清除

    StrnCopy(btn->Name, name, BTN_NAME_MAX); /* 创建按键名称 */

    btn->Button_State = NONE_TRIGGER;                  //按键状态
    btn->Button_Last_State = NONE_TRIGGER;             //按键上一次状态
    btn->event = EV_NULL;                              //按键触发事件
    btn->Read_Button_Level = read_btn_level;           //按键读电平函数
    btn->Button_Trigger_Level = btn_trigger_level;     //按键触发电平
    btn->Button_Last_Level = btn->Read_Button_Level(); //按键当前电平
    btn->Debounce_Time = 0;
    btn->Timer_Count = 0;
    btn->Button_trigger_cnt = 0;

    DEBUG_PRINTF("button create success!");

    Add_Button(btn); //创建的时候添加到单链表中

    Print_Btn_Info(btn); //打印信息
}

/************************************************************
  * @brief   按键触发事件与回调函数映射链接起来
	* @param   btn : 按键结构体
	* @param   btn_event : 按键触发事件
  * @param   btn_callback : 按键触发之后的回调处理函数。需要用户实现
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  ***********************************************************/
void Button_Attach(Button_t *btn, ButtonEvent btn_event, Button_CallBack btn_callback)
{
    if (btn == NULL)
    {
        //PRINT_ERR("struct button is null!");
        //ASSERT(ASSERT_ERR);       //断言
        DEBUG_PRINTF("btn IS nullptr\r\n");
    }
    btn->CallBack_Function[btn_event] = btn_callback;
    //    if (BUTTON_ALL_RIGGER == btn_event)
    //    {
    //        for (uint8_t i = 0; i < number_of_event - 1; i++)
    //            btn->CallBack_Function[i] = btn_callback; //按键事件触发的回调函数,用于处理按键事件
    //    }
    //    else
    //    {
    //        btn->CallBack_Function[btn_event] = btn_callback; //按键事件触发的回调函数,用于处理按键事件
    //    }
}

/************************************************************
  * @brief   删除一个已经创建的按键
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Button_Delete(Button_t *btn)
{
    struct button **curr;
    for (curr = &Head_Button; *curr;)
    {
        struct button *entry = *curr;
        if (entry == btn)
        {
            *curr = entry->Next;
        }
        else
        {
            curr = &entry->Next;
        }
    }
}

/************************************************************
  * @brief   按键周期处理函数
  * @param   btn:处理的按键
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    必须以一定周期调用此函数,建议周期为20~50ms
  ***********************************************************/
void Button_Cycle_Process(Button_t *btn)
{

    //DEBUG_PRINTF("hello button\r\n");
    uint8_t current_level = (uint8_t)btn->Read_Button_Level(); //获取当前按键电平

    if ((current_level != btn->Button_Last_Level) && (++(btn->Debounce_Time) >= BUTTON_DEBOUNCE_TIME)) //按键电平发生变化,消抖
    {
        btn->Button_Last_Level = current_level; //更新当前按键电平
        btn->Debounce_Time = 0;                 //确定了是按下

        //如果按键是没被按下的,改变按键状态为按下(首次按下/双击按下)
        //if((btn->Button_State == NONE_TRIGGER)||(btn->Button_State == BUTTON_DOUBLE))
        if (btn->Button_Trigger_Level == current_level)
        {
            btn->Button_State = BUTTON_DOWM;
            btn->Timer_Count = 0;
            DEBUG_PRINTF("dowon\r\n");
        }
        //释放按键
        else //if(btn->Button_State == BUTTON_DOWM)
        {
            btn->Button_State = BUTTON_UP;
            //TRIGGER_CB(BUTTON_UP); // 触发释放
            DEBUG_PRINTF("up\r\n");
        }
    }
    //    if (btn->Timer_Count > 50)
    //        btn->Timer_Count = 0;
    btn->Timer_Count++;
    switch (btn->Button_State)
    {

    case BUTTON_DOWM: // 按下状态
    {
        //DEBUG_PRINTF("Last_Level = %d count = %d\r\n", btn->Button_Last_Level, btn->Timer_Count);
        if (btn->Timer_Count > BUTTON_LONG_TIME && btn->Button_Last_Level == current_level)
        {

            if (btn->Timer_Count % 4 == 0)
                btn->event = EV_LONG_CLICK;
            //DEBUG_PRINTF("long clicking\r\n");
        }

        break;
    }

    case BUTTON_UP: // 弹起状态
    {

        if (btn->event == EV_LONG_CLICK)
        {
            btn->event = EV_NULL;

            DEBUG_PRINTF("BUTTON_CONTINUOS_FREE\r\n");
        }

        if (btn->Timer_Count < BUTTON_DOUBLE_TIME)
        {
            if (btn->Button_Last_State == BUTTON_DOWM)
            {
                btn->Button_trigger_cnt++;
                btn->Timer_Count = 0;
                //DEBUG_PRINTF("trigger_cnt = %d\r\n",btn->Button_Trigger_Event);
            }
            else
            {
            }
        }

        if (btn->Timer_Count > BUTTON_DOUBLE_TIME)
        {
            btn->Timer_Count = 0;

            if (btn->Button_trigger_cnt == 1)
            {
                DEBUG_PRINTF("button onc clicki\r\n");
                btn->event = EV_CLICK;
            }
            else if (btn->Button_trigger_cnt > 1)
            {
                DEBUG_PRINTF("button double clicki\r\n");
                btn->event = EV_DOUBLE_CLICK;
            }
            btn->Button_trigger_cnt = 0;
            btn->Timer_Count = 0;
        }

        break;
    }
    }
    //DEBUG_PRINTF("trigger_cnt = %d  last_state = %d cnt =%d\r\n", btn->Button_trigger_cnt, btn->Button_Last_State,btn->Timer_Count);
    btn->Button_Last_State = btn->Button_State;
    if (btn->event < EV_CNT && btn->CallBack_Function[btn->event] != NULL)
    {
        btn->CallBack_Function[btn->event](btn);
        btn->event = EV_NULL;
    }
}

/************************************************************
  * @brief   遍历的方式扫描按键,不会丢失每个按键
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    此函数要周期调用,建议20-50ms调用一次
  ***********************************************************/
void Button_Process(void)
{
    struct button *pass_btn;
    for (pass_btn = Head_Button; pass_btn != NULL; pass_btn = pass_btn->Next)
    {
        Button_Cycle_Process(pass_btn);
    }
}

/************************************************************
  * @brief   遍历按键
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Search_Button(void)
{
    struct button *pass_btn;
    for (pass_btn = Head_Button; pass_btn != NULL; pass_btn = pass_btn->Next)
    {
        DEBUG_PRINTF("button node have %s\r\n", pass_btn->Name);
    }
}

/**************************** 以下是内部调用函数 ********************/

/************************************************************
  * @brief   拷贝指定长度字符串
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
static char *StrnCopy(char *dst, const char *src, uint32_t n)
{
    if (n != 0)
    {
        char *d = dst;
        const char *s = src;
        do
        {
            if ((*d++ = *s++) == 0)
            {
                while (--n != 0)
                    *d++ = 0;
                break;
            }
        } while (--n != 0);
    }
    return (dst);
}

/************************************************************
  * @brief   打印按键相关信息
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
static void Print_Btn_Info(Button_t *btn)
{

    DEBUG_PRINTF("button struct information:\r\n\
              btn->Name:%s \r\n\
              btn->Button_State:%d \r\n\
              btn->Button_Trigger_Event:%d \r\n\
              btn->Button_Trigger_Level:%d \r\n\
              btn->Button_Last_Level:%d \r\n\
              ",
                 btn->Name,
                 btn->Button_State,
                 btn->event,
                 btn->Button_Trigger_Level,
                 btn->Button_Last_Level);
    Search_Button();
}
/************************************************************
  * @brief   使用单链表将按键连接起来
	* @param   NULL
  * @return  NULL
  * @author  car
  * @github  https://blog.csdn.net/u010261063?spm=1000.2115.3001.5343
  * @date    2021-8-7
  * @version v1.0
  * @note    NULL
  ***********************************************************/
static void Add_Button(Button_t *btn)
{
    btn->Next = Head_Button;
    Head_Button = btn;
}

示例代码

int main()
{
    /*
   1、用户需要提供一个按键检测函数,用于检测按键当前电平
   2、告知Button 按键按下的电平
   */
    /*定义一个按键*/
    Button_t Button1;
    /*单击回调函数*/
    void Btn1_Dowm_CallBack(void *btn)
    {
        printf("Button1 单击!\r\n");
    }

    /*双击回调函数*/
    void Btn1_Double_CallBack(void *btn)
    {
        printf("Button1 双击!\r\n");
    }

    /*长按回调函数*/
    void Btn1_Long_CallBack(void *btn)
    {
        printf("Button1 长按!\r\n");
    }

    /*按键检测函数*/
    uint8_t Read_KEY1_Level()
    {
        return HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin);
    }

    Button_Create("Button1",                                         //按键名字
                  &Button1,                                          //按键指针
                  Read_KEY1_Level,                                   //按键检测函数
                  0);                                                //按键按下有效电平
    Button_Attach(&Button1, EV_CLICK, Btn1_Dowm_CallBack);           //订阅单击事件
    Button_Attach(&Button1, EV_DOUBLE_CLICK, Btn1_Double_CallBack);  //订阅双击事件
    Button_Attach(&Button1, EV_LONG_CLICK, Btn1_Continuos_CallBack); //订阅连按事件

    while (1)
    {
        Button_Process(); //需要周期调用按键处理函数
        HAL_Delay(50);
    }
}

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Car12

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值