本帖最后由 dw772 于 2025-2-14 09:32 编辑
1:搞单片机程序有好几年了,回想出入门时写程序时,对按键处理一直理解不太透彻,每做一个项目就需要写一遍按键函数,虽然能解决问题,但是一直存在一个问题,按键识别跟功能一直纠缠在一起,无法将功能与按键的键值解耦。 2:随着项目的增加和经验的积累,逐渐理解了按键状态机和函数指针后,参考一些前辈的思路,总结了一个比较好用的按键思路。
3:在裸机系统中,我们一般采用while(1){}大循环,所有功能按顺序执行。在while循环内一般有个定时器标志位,比如10ms执行一次控制函数执行节奏,同时也可以做一下非精确的定时,比如按键长按时间,指示灯闪烁周期。
先定义一个按键状态的枚举类型结构体
typedef enum
{
BT_EVENT_RELEASED = 0, /**< 按键已经释放 */
BT_EVENT_PRESSED, /**< 按键按下边沿 */
BT_EVENT_PRESSING, /**< 按键按住保持 */
BT_EVENT_SHORT_CLICKED, /**< 按键短按释放边沿 */
BT_EVENT_LONG_PRESSED, /**< 按键识别为长按边缘 press_cont ==最小长按时间 */
BT_EVENT_LONG_PRESSED_REPEAT, /**< 按键长按重复 press_cont -上次计数 == 重复时间 */
BT_EVENT_CLICKED, /**< 按键释放 长按/短按 */
BT_EVENT_DOUBLE_CLICKED, /**< 按键双击 两次间隙<= 500MS */
}bt_event_value_t;
再定义一个按键类型参数,用来存放按键的各个参数typedef struct
{
flag_status (*read_gpio)(); //读取按键GPIO的函数指针 返回值为TRUE & FALSE
bt_event_value_t btn_event; //枚举为 BT_EVENT_RELEASED ~ BT_EVENT_DOUBLE_CLICKED任意值
uint8_t btn_stat; //按键有限状态机
uint8_t btn_double_en; //双击是否使能
uint8_t btn_act_sta; //有效状态
uint32_t press_cont; //按下计数,判断长短按
uint32_t idl_cont; //空闲计数,判断双击 组合按键需要另外处理
}TypeButton;
根据需要几个按键定义几个按键变量,一个变量对应一个按键。该按键的所有数据都保存在这个变量内部。同时还需要定义一个数组存放这些变量的地址,方便扫描的时候顺序扫描按键。TypeButton *bt[BUTTON_MAX] = { NULL}; //定义一个数组存放按键的地址,方便扫描按键的时候按顺序执行。
TypeButton Power_key; //电源
TypeButton Light_key; //灯
TypeButton Funct_key; //功能
TypeButton Incre_key; //增加
TypeButton Decre_key; //减少
还需要一个注册按键的函数,注册按键并初始化按键对应的变量,并将读取按键IO的函数指针存放到按键结构体变量内,函数原型如下。
/**
* [url=home.php?mod=space&uid=247401]@brief[/url] bsp_button_register 按键注册,并初始化相应按键的参数
*
*
* @param id 按键的顺序
* @param (*read_cb)() 按键读取回调函数 返回值为1表示按键按下
* @param *btn 指向 存放按键数值的变量 指针
* [url=home.php?mod=space&uid=120043]@reval[/url] none
*/
void bsp_button_register( uint8_t id, flag_status (*read_cb)(), TypeButton *btn)
{
bt[id] = btn;
btn->read_gpio = read_cb;
btn->btn_stat = 0;
btn->idl_cont = 0;
btn->btn_double_en = 0;
btn->press_cont = 0;
btn->btn_event = BT_EVENT_RELEASED;
}
注册按键实例如下,在程序初始化时调用,注册之前先定义好读取按键GPIO的回调函数实体
<blockquote>void bsp_btn_register(void)
下面需要用到按键状态机,主要是按下,按下等待,短按识别,长按识别等状态的切换。代码如下:void bsp_read_key_value( TypeButton *btn)
{
switch(btn->btn_stat)
{
case 0://空闲
//btn->press_cont = 0;
btn->btn_event = BT_EVENT_RELEASED;
if( btn->read_gpio())
{
if(btn->press_cont ++ >=2)
{
btn->btn_stat = 1;
}
}
break;
case 1://按下
if( btn->read_gpio())
{
btn->btn_event = BT_EVENT_PRESSED; //BT_EVENT_PRESSED;
btn->btn_stat = 2;
}
else
{
btn->btn_stat = 0;
}
break;
case 2://长按等待
btn->btn_event = BT_EVENT_PRESSING ; //按住不放
if( btn->read_gpio())
{
if( btn->press_cont++ > SHORT_CLICK_TIME )
{
btn->btn_event = BT_EVENT_LONG_PRESSED;
btn->btn_stat = 3;
}
}
else
{
btn->btn_stat = 3;
btn->btn_event = BT_EVENT_SHORT_CLICKED; //短按弹起
}
break;
case 3://长按
btn->press_cont++;
btn->btn_event = BT_EVENT_PRESSING;
if( (btn->press_cont-SHORT_CLICK_TIME)%REPEAT_PRESS_TIME ==0)
{
btn->btn_event = BT_EVENT_LONG_PRESSED_REPEAT;
}
if(!( btn->read_gpio() ))
{
btn->press_cont = 0;
btn->btn_event = BT_EVENT_RELEASED;
btn->btn_stat = 0;
}
break;
default: btn->btn_stat = 0; break;
}
}
最后就是按键扫描函数,定义好按键的个数,每次扫描从第一个按键开始,用for循环,遍历每个按键变量内的读取回IO调函数,根据IO状态切换按键的状态,此函数放在主循环调用。
void bsp_btnton_scanf(void)
{
uint8_t i;
for(i=0;i< BUTTON_MAX;i++)
{
bsp_read_key_value(bt[i]);
}
}
在main函数中结构如下,在功能函数中,只要根据按键的event事件来执行不同的功能,这样就实现了功能与按键扫描的解耦:int main(void)
{
bsp_btn_register(); //注册按键
while(1)
{
if (Flag_10ms)
{
Flag_10ms = 0;
bsp_btnton_scanf(); //按键扫描,得到键值
Funct_1(Power_key.btn_event); //根据按键值执行功能 这样就可以实现功能跟按键扫描的解耦,不需要把功能写到按键扫描内部
}
}
}
|