说明
本示例用的stm32 按键是接地有效
特别设置按键拉高
按键事件循环50ms一次调用
电路图
按键检测思路
- 按键按下检测:检测当前本状态是否和上一次状态一致,不一致消抖完成的情况下,当前电平和按下定义电平一致,按键状态就是 按下 down 否则就是 按键回弹 up-----只有按键在变化的一瞬间更新按键状态和按键上一次状态
- 长按按检测,按键一处处于按下的状态 down 的情况下,时间超过 X 时间后 开始发出长按事件;
- 单击、双击检测 在一定时间段内检测 按键按下的次数,这段时间内部如果检测到的次数是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);
}
}