说明

本示例用的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);}
}

按键检测框架单击-双击-连按相关推荐

  1. 单个按键,实现单击+双击+长按

    按键函数:实现单击.双击.长按 按键按下时长超过1.5S,认为是长按事件. 连续两次按下时间间隔不超过350ms,认为是双击事件. 除以上两条,按键按键都属于单击事件. 废话不多说,直接上代码.如有问 ...

  2. STC15单片机-按键检测单击、双击和长按(状态机)

    按键检测(状态机) 传统的按键检测 在单片机的应用中,利用按键实现与用户的交互功能是相当常见的,同时按键的检测也是很讲究的,众所周知,在有键按下后,数据线上的信号出现一段时间的抖动,然后为低,当按键释 ...

  3. STM32-蓝桥杯嵌入式之三行按键检测(按键的长、短,单击、双击)

    STM32-蓝桥杯嵌入式之三行按键检测(按键的长.短,单击.双击) 目录 STM32-蓝桥杯嵌入式之三行按键检测(按键的长.短,单击.双击) 一.检测按键下降沿分析 二.检测按键上升沿分析 三.按键检 ...

  4. STM32独立按键实现单击双击长按功能

    目录 前言 一.按键功能定义 二.使用步骤 1.按键初始化 2.按键扫描函数(重点) 总结 前言 在使用STM32或其他单片机开发项目时,经常需要用到独立按键进行控制. 通常一个独立按键需要使用一个I ...

  5. 多功能按键设计——利用一个I/O口,接一个按键,实现3功能操作:单击 + 双击 + 长按

    看了一个晚上,结合了马潮老师的书,才搞懂这个程序,确实经典!原文: 题目:多功能按键设计.利用一个I/O口,接一个按键,实现3功能操作:单击 + 双击 + 长按. (amobbs.com 阿莫电子论坛 ...

  6. 应广单片机-按键状态机(单击、双击、长按)

    在正常0.5s内无按键操作为启始按键扫描条件下,扫描按键将产生以下3种按键事件: 1.长按事件:任何1次出现的长按操作都属于长按事件 2.单击事件:1次短按操作后,间隔2s内没有短按操作 3.双击事件 ...

  7. 【按键】[独立按键] - 1: 单击,双击,三击以及N击

    此按键程序的实现的功能是单个独立按键的[单击],[长按],[双击],[三击]以及[多击].本文分为三个部分, 第一个部分是说[单击],[长按]的程序: 第二部分是讲[双击]: 第三部分是讲[三击],[ ...

  8. 一个学妹写的按键检测函数把我秀翻了!

    摘要:今年实验室来了三个学妹,其中一个学妹以前是物联网专业的,进了实验室老师二话没说:先把STM32单片机过一遍 上来第一个例程就是使用按键点亮一个LED灯,好家伙.点灯小师弟比较在行,毕竟32.FP ...

  9. 我用树莓派Pico学Python (5) - 按键检测 以及 新买的开发板底板

    因为觉得之前买的开发板底板的引出接口使用起来不方便(标识不清晰,以及3.3V和GND引出的插针太少),我又买了一个开发板底板,这个板子的引出插针很多,且每个引出的GPIO都"配备" ...

最新文章

  1. Percy Liang、李飞飞等百余位学者联名发布:「基础模型」的机遇与挑战
  2. java的for循环嵌套_优化Java的for循环嵌套的高效率方法
  3. ios图像处理第2部分:核心图形,核心图像,GPUImage
  4. MongoDB-数据类型
  5. AJPFX实例集合嵌套之ArrayList嵌套ArrayList
  6. python用pip安装wordcloud_如何在python3.7中使用pip安装wordcloud
  7. 纯CSS: hover特效
  8. Netty 基本原理
  9. 怎么把cad的图导入ps_CAD图如何导入Photoshop的方法
  10. Redis_常用数据类型及实践案例
  11. 分享11款Steam推理游戏
  12. 在线下单系统think php,昱杰订单管理系统(ThinkPHP版) v19.0
  13. log是什么文件可以删除吗?log文件被删怎么恢复?
  14. 微信小程序 生成UUID
  15. 文献阅读三—Deep Text Classification Can be Fooled
  16. java毕业设计KTV点歌系统mybatis+源码+调试部署+系统+数据库+lw
  17. 崩坏3服务器维护2月8号,崩坏3 3.9版本「逐暗星辉」更新维护通知
  18. 基于JavaWeb JSP SSM架构的网上求职招聘系统
  19. 【JSP】jQuery Deferred exception successed is not defined
  20. Language Modeling with Gated Convolutional Networks ( GLU )理解

热门文章

  1. php 求 相似 比,php比较相似字符串的方法
  2. linux中c语言延时毫秒函数,linux下写个C语言程序,要求有0.5微秒以下的延时,要怎样写...
  3. 电磁工程计算机辅助设计,《ANSYS工程应用教程-热与电磁学篇》
  4. 计算机维修法宝,计算机的三大法宝
  5. java web 启动顺序_JavaWeb开发Servlet过滤器链执行顺序详解
  6. python计算函数转公式(转Latex公式)
  7. 解决sqlalchemy连接mysql报错ModuleNotFoundError: No module named ‘pymysql‘
  8. deepin20安装mysql-workbench
  9. 麦克风阵列树莓派python_使用Python代码进行树莓派上的麦阵列声源定位
  10. vue php tree,Vue 实现树形视图数据功能