本帖最后由 lulugl 于 2023-10-25 09:34 编辑
#申请原创# #有奖活动#@21ic小跑堂
【按键驱动】
单片机在输入设备中,按键是最简单经济可靠的设备之一。单按键可以直接由GPIO来驱动,采集他的高低电平来实现按键状态的功能,但是在实际中,按键的状态获取,要考虑到消抖情况,如果单一按键要通过单击、双击、连击、长按等来实现结合功能,那就实现起来就非常复杂,效果也不是非常好。
在github中我找到了一个开源的按键驱动,Button_drive是一个小巧的按键驱动,支持单击、双击、长按、连续触发等(后续可以在按键控制块中添加触发事件),理论上可无限量扩展Button,Button_drive采用按键触发事件回调方式处理业务逻辑,支持在RTOS中使用,我目前仅在 HYPERLINK "https://github.com/RT-Thread/rt-thread" 上测试过。 写按键驱动的目的是想要将用户按键逻辑与按键处理事件分离,用户无需处理复杂麻烦的逻辑事件。https://github.com/jiejieTop/ButtonDrive
【实现方式】
下载github源码
复制一份示例的printf工程到一个新的文件夹中,重命名为printf_button。
解压源码后复制button.c/h到工程文件的middlewares下的button文件夹中。
在工程文件中新增button组,把button.c加入button组中。
添加头文件的引用。
在button.c中添加at32f423_board.h头文件的引用。
在button.c中实现按键电平的获取自定义函数,在模版工程中,官方在at32f423_board.c中实现的按键的初始化,我们直接引用就可以实现按键状态的获取:
/* 获取按键电平 */
uint8_t Read_KEY1_Level(void)
{
return at32_button_state();
}
在main.c中,我们实现用户的单击、双击、长按释放的功能回调函数,代码如下:
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");
}
void Btn1_ContinuosFree_CallBack(void *btn)
{
printf("Button1 连按释放!\r\n");
}
在主函中,我们注册需要实现的功能函数,我这里添加单击、双击、长按、长按释放,代码如下:
Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack); //单击
Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack); //双击
Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack); //长按BUTTON_LONG_FREE,
Button_Attach(&Button1,BUTTON_LONG_FREE,Btn1_ContinuosFree_CallBack);
在while中添加按键检测函数:
Button_Process(); //需要周期调用按键处理函数
到此,移植结束,下载到开发板上可以实现相应的功能,按下开发板的按键,就在串口打印出相应的状态:
button.c完整程序如下:
/************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url] 按键驱动
* @param NULL
* [url=home.php?mod=space&uid=266161]@return[/url] NULL
* [url=home.php?mod=space&uid=187600]@author[/url] jiejie
* [url=home.php?mod=space&uid=2757600]@Github[/url] https://github.com/jiejieTop
* [url=home.php?mod=space&uid=212281]@date[/url] 2018-xx-xx
* [url=home.php?mod=space&uid=895143]@version[/url] v1.0
* [url=home.php?mod=space&uid=536309]@NOTE[/url] button.c
***********************************************************/
#include "button.h"
#include "at32f423_board.h"
#include "stdio.h"
#include "string.h"
/*******************************************************************
* 变量声明
*******************************************************************/
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);
/************************************************************
* [url=home.php?mod=space&uid=247401]@brief[/url] 按键创建
* @param name : 按键名称
* @param btn : 按键结构体
* @param read_btn_level : 按键电平读取函数,需要用户自己实现返回uint8_t类型的电平
* @param btn_trigger_level : 按键触发电平
* [url=home.php?mod=space&uid=266161]@return[/url] NULL
* [url=home.php?mod=space&uid=187600]@author[/url] jiejie
* [url=home.php?mod=space&uid=2757600]@Github[/url] https://github.com/jiejieTop
* [url=home.php?mod=space&uid=212281]@date[/url] 2018-xx-xx
* [url=home.php?mod=space&uid=895143]@version[/url] v1.0
* [url=home.php?mod=space&uid=536309]@NOTE[/url] NULL
***********************************************************/
void Button_Create(const char *name,
Button_t *btn,
uint8_t(*read_btn_level)(void),
uint8_t btn_trigger_level)
{
if( btn == NULL)
{
printf("struct button is null!");
//ASSERT(ASSERT_ERR);
}
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->Button_Trigger_Event = NONE_TRIGGER; //按键触发事件
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;
printf("button create success!");
Add_Button(btn); //创建的时候添加到单链表中
Print_Btn_Info(btn); //打印信息
}
/************************************************************
* @brief 按键触发事件与回调函数映射链接起来
* @param btn : 按键结构体
* @param btn_event : 按键触发事件
* @param btn_callback : 按键触发之后的回调处理函数。需要用户实现
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @version v1.0
***********************************************************/
void Button_Attach(Button_t *btn,Button_Event btn_event,Button_CallBack btn_callback)
{
uint8_t i;
if( btn == NULL)
{
printf("struct button is null!");
//ASSERT(ASSERT_ERR); //断言
}
if(BUTTON_ALL_RIGGER == btn_event)
{
for(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 jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @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 NULL
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @version v1.0
***********************************************************/
void Get_Button_EventInfo(Button_t *btn)
{
uint8_t i;
//按键事件触发的回调函数,用于处理按键事件
for(i = 0 ; i < number_of_event-1 ; i++)
{
if(btn->CallBack_Function[i] != 0)
{
printf("Button_Event:%d",i);
}
}
}
uint8_t Get_Button_Event(Button_t *btn)
{
return (uint8_t)(btn->Button_Trigger_Event);
}
/************************************************************
* @brief 获取按键触发的事件
* @param NULL
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @version v1.0
***********************************************************/
uint8_t Get_Button_State(Button_t *btn)
{
return (uint8_t)(btn->Button_State);
}
/************************************************************
* @brief 按键周期处理函数
* @param btn:处理的按键
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @version v1.0
* @note 必须以一定周期调用此函数,建议周期为20~50ms
***********************************************************/
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_State == NONE_TRIGGER)||(btn->Button_State == BUTTON_DOUBLE))
{
btn->Button_State = BUTTON_DOWM;
}
//释放按键
else if(btn->Button_State == BUTTON_DOWM)
{
btn->Button_State = BUTTON_UP;
TRIGGER_CB(BUTTON_UP); // 触发释放
// printf("释放了按键");
}
}
switch(btn->Button_State)
{
case BUTTON_DOWM : // 按下状态
{
if(btn->Button_Last_Level == btn->Button_Trigger_Level) //按键按下
{
#if CONTINUOS_TRIGGER //支持连续触发
if(++(btn->Button_Cycle) >= BUTTON_CONTINUOS_CYCLE)
{
btn->Button_Cycle = 0;
btn->Button_Trigger_Event = BUTTON_CONTINUOS;
TRIGGER_CB(BUTTON_CONTINUOS); //连按
printf("连按");
}
#else
btn->Button_Trigger_Event = BUTTON_DOWM;
if(++(btn->Long_Time) >= BUTTON_LONG_TIME) //释放按键前更新触发事件为长按
{
#if LONG_FREE_TRIGGER
btn->Button_Trigger_Event = BUTTON_LONG;
#else
if(++(btn->Button_Cycle) >= BUTTON_LONG_CYCLE) //连续触发长按的周期
{
btn->Button_Cycle = 0;
btn->Button_Trigger_Event = BUTTON_LONG;
TRIGGER_CB(BUTTON_LONG); //长按
}
#endif
if(btn->Long_Time == 0xFF) //更新时间溢出
{
btn->Long_Time = BUTTON_LONG_TIME;
}
// printf("长按");
}
#endif
}
break;
}
case BUTTON_UP : // 弹起状态
{
if(btn->Button_Trigger_Event == BUTTON_DOWM) //触发单击
{
if((btn->Timer_Count <= BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State == BUTTON_DOUBLE)) // 双击
{
btn->Button_Trigger_Event = BUTTON_DOUBLE;
TRIGGER_CB(BUTTON_DOUBLE);
// printf("双击");
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = NONE_TRIGGER;
}
else
{
btn->Timer_Count=0;
btn->Long_Time = 0; //检测长按失败,清0
#if (SINGLE_AND_DOUBLE_TRIGGER == 0)
TRIGGER_CB(BUTTON_DOWM); //单击
#endif
btn->Button_State = BUTTON_DOUBLE;
btn->Button_Last_State = BUTTON_DOUBLE;
}
}
else if(btn->Button_Trigger_Event == BUTTON_LONG)
{
#if LONG_FREE_TRIGGER
TRIGGER_CB(BUTTON_LONG); //长按
#else
TRIGGER_CB(BUTTON_LONG_FREE); //长按释放
#endif
btn->Long_Time = 0;
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = BUTTON_LONG;
}
#if CONTINUOS_TRIGGER
else if(btn->Button_Trigger_Event == BUTTON_CONTINUOS) //连按
{
btn->Long_Time = 0;
TRIGGER_CB(BUTTON_CONTINUOS_FREE); //连发释放
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = BUTTON_CONTINUOS;
}
#endif
break;
}
case BUTTON_DOUBLE :
{
btn->Timer_Count++; //时间记录
if(btn->Timer_Count>=BUTTON_DOUBLE_TIME)
{
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = NONE_TRIGGER;
}
#if SINGLE_AND_DOUBLE_TRIGGER
if((btn->Timer_Count>=BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State != BUTTON_DOWM))
{
btn->Timer_Count=0;
TRIGGER_CB(BUTTON_DOWM); //单击
btn->Button_State = NONE_TRIGGER;
btn->Button_Last_State = BUTTON_DOWM;
}
#endif
break;
}
default :
break;
}
}
/************************************************************
* @brief 遍历的方式扫描按键,不会丢失每个按键
* @param NULL
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @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 jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @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)
{
printf("button node have %s",pass_btn->Name);
}
}
/************************************************************
* @brief 处理所有按键回调函数
* @param NULL
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @version v1.0
* @note 暂不实现
***********************************************************/
void Button_Process_CallBack(void *btn)
{
uint8_t btn_event = Get_Button_Event(btn);
switch(btn_event)
{
case BUTTON_DOWM:
{
printf("添加你的按下触发的处理逻辑");
break;
}
case BUTTON_UP:
{
printf("添加你的释放触发的处理逻辑");
break;
}
case BUTTON_DOUBLE:
{
printf("添加你的双击触发的处理逻辑");
break;
}
case BUTTON_LONG:
{
printf("添加你的长按触发的处理逻辑");
break;
}
case BUTTON_LONG_FREE:
{
printf("添加你的长按释放触发的处理逻辑");
break;
}
case BUTTON_CONTINUOS:
{
printf("添加你的连续触发的处理逻辑");
break;
}
case BUTTON_CONTINUOS_FREE:
{
printf("添加你的连续触发释放的处理逻辑");
break;
}
}
}
/**************************** 以下是内部调用函数 ********************/
/************************************************************
* @brief 拷贝指定长度字符串
* @param NULL
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @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 jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @version v1.0
* @note NULL
***********************************************************/
static void Print_Btn_Info(Button_t* btn)
{
printf("button struct information:\n\
btn->Name:%s \n\
btn->Button_State:%d \n\
btn->Button_Trigger_Event:%d \n\
btn->Button_Trigger_Level:%d \n\
btn->Button_Last_Level:%d \n\
",
btn->Name,
btn->Button_State,
btn->Button_Trigger_Event,
btn->Button_Trigger_Level,
btn->Button_Last_Level);
Search_Button();
}
/************************************************************
* @brief 使用单链表将按键连接起来
* @param NULL
* @return NULL
* @author jiejie
* @github https://github.com/jiejieTop
* @date 2018-xx-xx
* @version v1.0
* @note NULL
***********************************************************/
static void Add_Button(Button_t* btn)
{
btn->Next = Head_Button;
Head_Button = btn;
}
/* 获取按键电平 */
uint8_t Read_KEY1_Level(void)
{
return at32_button_state();
}
button.h完整代码:
#ifndef BUTTON_H
#define BUTTON_H
#include "at32f423_conf.h"
#define NULL 0
#define BTN_NAME_MAX 32 //名字最大为32字节
/* 按键消抖时间40ms, 建议调用周期为20ms
只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件
*/
#define CONTINUOS_TRIGGER 0 //是否支持连续触发,连发的话就不要检测单双击与长按了
/* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应,
因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。
而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击,
因为双击必须是有一次按下并且释放之后才产生的 */
#define SINGLE_AND_DOUBLE_TRIGGER 1
/* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按,
否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */
#define LONG_FREE_TRIGGER 0
#define BUTTON_DEBOUNCE_TIME 2 //消抖时间 (n-1)*调用周期
#define BUTTON_CONTINUOS_CYCLE 1 //连按触发周期时间 (n-1)*调用周期
#define BUTTON_LONG_CYCLE 1 //长按触发周期时间 (n-1)*调用周期
#define BUTTON_DOUBLE_TIME 15 //双击间隔时间 (n-1)*调用周期 建议在200-600ms
#define BUTTON_LONG_TIME 50 /* 持续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,
BUTTON_DOUBLE,
BUTTON_LONG,
BUTTON_LONG_FREE,
BUTTON_CONTINUOS,
BUTTON_CONTINUOS_FREE,
BUTTON_ALL_RIGGER,
number_of_event, /* 触发回调的事件 */
NONE_TRIGGER
}Button_Event;
/*
每个按键对应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; /* 按键当前电平 */
uint8_t Button_Trigger_Event; /* 按键触发事件,单击,双击,长按等 */
Button_CallBack CallBack_Function[number_of_event];
uint8_t Button_Cycle; /* 连续按键周期 */
uint8_t Timer_Count; /* 计时 */
uint8_t Debounce_Time; /* 消抖时间 */
uint8_t Long_Time; /* 按键按下持续时间 */
struct button *Next;
}Button_t;
/* 供外部调用的函数声明 */
void Button_Create(const char *name,
Button_t *btn,
uint8_t(*read_btn_level)(void),
uint8_t btn_trigger_level);
void Button_Attach(Button_t *btn,Button_Event btn_event,Button_CallBack btn_callback);
void Button_Cycle_Process(Button_t *btn);
void Button_Process(void);
void Button_Delete(Button_t *btn);
void Search_Button(void);
void Get_Button_EventInfo(Button_t *btn);
uint8_t Get_Button_Event(Button_t *btn);
uint8_t Get_Button_State(Button_t *btn);
void Button_Process_CallBack(void *btn);
uint8_t Read_KEY1_Level(void);
#endif
main.c完整代码:
/**
**************************************************************************
* [url=home.php?mod=space&uid=288409]@file[/url] main.c
* @brief main program
**************************************************************************
* Copyright notice & Disclaimer
*
* The software Board Support Package (BSP) that is made available to
* download from Artery official website is the copyrighted work of Artery.
* Artery authorizes customers to use, copy, and distribute the BSP
* software and its related documentation for the purpose of design and
* development in conjunction with Artery microcontrollers. Use of the
* software is governed by this copyright notice and the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED ON "AS IS" BASIS WITHOUT WARRANTIES,
* GUARANTEES OR REPRESENTATIONS OF ANY KIND. ARTERY EXPRESSLY DISCLAIMS,
* TO THE FULLEST EXTENT PERMITTED BY LAW, ALL EXPRESS, IMPLIED OR
* STATUTORY OR OTHER WARRANTIES, GUARANTEES OR REPRESENTATIONS,
* INCLUDING BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT.
*
**************************************************************************
*/
#include "at32f423_board.h"
#include "at32f423_clock.h"
#include "button.h"
/** @addtogroup AT32F423_periph_examples
* @{
*/
/** @addtogroup 423_USART_printf USART_printf
* @{
*/
__IO uint32_t time_cnt = 0;
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");
}
void Btn1_ContinuosFree_CallBack(void *btn)
{
printf("Button1 连按释放!\r\n");
}
/**
* @brief main function.
* @param none
* @retval none
*/
int main(void)
{
system_clock_config();
at32_board_init();
uart_print_init(115200);
Button_Create("Button1",
&Button1,
Read_KEY1_Level,
1);
Button_Attach(&Button1,BUTTON_DOWM,Btn1_Dowm_CallBack); //单击
Button_Attach(&Button1,BUTTON_DOUBLE,Btn1_Double_CallBack); //双击
Button_Attach(&Button1,BUTTON_LONG,Btn1_Long_CallBack); //长按BUTTON_LONG_FREE,
Button_Attach(&Button1,BUTTON_LONG_FREE,Btn1_ContinuosFree_CallBack);
/* output a message on hyperterminal using printf function */
printf("usart printf example: retarget the c library printf function to the usart\r\n");
Get_Button_Event(&Button1);
while(1)
{
Button_Process(); //需要周期调用按键处理函数
// printf("usart printf counter: %u\r\n",time_cnt++);
// delay_sec(1);
delay_ms(20);
}
}
/**
* @}
*/
/**
* @}
*/
【试用心得】
1、此次是我第一次使用按键的驱动代码,实现了很多单按键的功能,这在我们以后面的编程工作中会有很大的帮助。
2、能够顺利的移植,也得益于雅特力的官方示例给了开发板的常规驱动,比如Uart\printf重定向、按键驱动、LED灯驱动都写好了,我们只要调用board.c中的就可以了。
3、在github上有很多优秀的开源软件,能向大佬们学习,也是提高自身技术水平的重要方式之一,这里要感觉这些无私奉献的大佬。
|