1、IO口按键,就是我们比较常见的一个IO接一个按键,或者是一个矩阵键盘。很多新人的处理方法可能是采样延时的方法,当年我也是这样的,如下
if(GETIO==low) { delay_10ms() if(GETIO==low) { //得到按键值 } }
这种方法虽然简单,但是有很大弊端。首先 Delay浪费很多时间,影响系统。第二,无法判断长短按键,连续按键。第三,如果这个按键是开关机按键系统在低功耗状态下,需要中断唤醒,这种方法比较容易出问题,如STM8S系列的 halt 模式。
所以我们一般在产品开发的过程中,采用扫描的方法,就是每隔10ms 去检测IO的状态,看是否有按键,然后去抖动,判断按键功能。 参考代码如下,这段代码是之前在一个论坛看到的比我自己写的更加优秀,所以拿出来和大家分享一下,也顺便感谢一下作者。 这段代码,容易修改,可以根据自己的时间需要,进行长短按键,连续按键,还有组合按键的判断。/* 按键滤波时间50ms, 单位10ms *只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件 */ #define BUTTON_FILTER_TIME 5 #define BUTTON_LONG_TIME 300 /* 持续1秒,认为长按事件 */ /* 每个按键对应1个全局的结构体变量。 其成员变量是实现滤波和多种按键状态所必须的 */ typedef struct { /* 下面是一个函数指针,指向判断按键手否按下的函数 */ unsigned char (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */ unsigned char Count; /* 滤波器计数器 */ unsigned char FilterTime; /* 滤波时间(最大255,表示2550ms) */ unsigned short LongCount; /* 长按计数器 */ unsigned short LongTime; /* 按键按下持续时间, 0表示不检测长按 */ unsigned char State; /* 按键当前状态(按下还是弹起) */ unsigned char KeyCodeUp; /* 按键弹起的键值代码, 0表示不检测按键弹起 */ unsigned char KeyCodeDown; /* 按键按下的键值代码, 0表示不检测按键按下 */ unsigned char KeyCodeLong; /* 按键长按的键值代码, 0表示不检测长按 */ unsigned char RepeatSpeed; /* 连续按键周期 */ unsigned char RepeatCount; /* 连续按键计数器 */ }BUTTON_T; typedef enum { KEY_NONE = 0, /* 0 表示按键事件 */ KEY_DOWN_Power, /* 按键键按下 */ KEY_UP_Power, /* 按键键弹起 */ KEY_LONG_Power, /* 按键键长按 */ KEY_DOWN_Power_TAMPER /* 组合键,Power键和WAKEUP键同时按下 */ }KEY_ENUM; BUTTON_T s_Powerkey; //是否有按键按下接口函数 unsigned char IsKeyDownUser(void) {if (0==GPIO_ReadInputPin(POWER_KEY_PORT, POWER_KEY_PIN) ) return 1;return 0;} void PanakeyHard_Init(void) { GPIO_Init (POWER_KEY_PORT, POWER_KEY_PIN, GPIO_MODE_IN_FL_NO_IT);//power key } void PanakeyVar_Init(void) { /* 初始化USER按键变量,支持按下、弹起、长按 */ s_Powerkey.IsKeyDownFunc = IsKeyDownUser; /* 判断按键按下的函数 */ s_Powerkey.FilterTime = BUTTON_FILTER_TIME; /* 按键滤波时间 */ s_Powerkey.LongTime = BUTTON_LONG_TIME; /* 长按时间 */ s_Powerkey.Count = s_Powerkey.FilterTime / 2; /* 计数器设置为滤波时间的一半 */ s_Powerkey.State = 0; /* 按键缺省状态,0为未按下 */ s_Powerkey.KeyCodeDown = KEY_DOWN_Power; /* 按键按下的键值代码 */ s_Powerkey.KeyCodeUp =KEY_UP_Power; /* 按键弹起的键值代码 */ s_Powerkey.KeyCodeLong = KEY_LONG_Power; /* 按键被持续按下的键值代码 */ s_Powerkey.RepeatSpeed = 0; /* 按键连发的速度,0表示不支持连发 */ s_Powerkey.RepeatCount = 0; /* 连发计数器 */ } void Panakey_Init(void) { PanakeyHard_Init(); /* 初始化按键变量 */ PanakeyVar_Init(); /* 初始化按键硬件 */ } /* ********************************************************************************************************* * 函 数 名: bsp_DetectButton * 功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。 * 形 参:按键结构变量指针 * 返 回 值: 无 ********************************************************************************************************* */ void Button_Detect(BUTTON_T *_pBtn) { if (_pBtn->IsKeyDownFunc()) { if (_pBtn->Count < _pBtn->FilterTime) { _pBtn->Count = _pBtn->FilterTime; } else if(_pBtn->Count < 2 * _pBtn->FilterTime) { _pBtn->Count++; } else { if (_pBtn->State == 0) { _pBtn->State = 1; /* 发送按钮按下的消息 */ if (_pBtn->KeyCodeDown > 0) { /* 键值放入按键FIFO */ Pannelkey_Put(_pBtn->KeyCodeDown);// 记录按键按下标志,等待释放 } } if (_pBtn->LongTime > 0) { if (_pBtn->LongCount < _pBtn->LongTime) { /* 发送按钮持续按下的消息 */ if (++_pBtn->LongCount == _pBtn->LongTime) { /* 键值放入按键FIFO */ Pannelkey_Put(_pBtn->KeyCodeLong); } } else { if (_pBtn->RepeatSpeed > 0) { if (++_pBtn->RepeatCount >= _pBtn->RepeatSpeed) { _pBtn->RepeatCount = 0; /* 常按键后,每隔10ms发送1个按键 */ Pannelkey_Put(_pBtn->KeyCodeDown); } } } } } } else { if(_pBtn->Count > _pBtn->FilterTime) { _pBtn->Count = _pBtn->FilterTime; } else if(_pBtn->Count != 0) { _pBtn->Count--; } else { if (_pBtn->State == 1) { _pBtn->State = 0; /* 发送按钮弹起的消息 */ if (_pBtn->KeyCodeUp > 0) /*按键释放*/ { /* 键值放入按键FIFO */ Pannelkey_Put(_pBtn->KeyCodeUp); } } } _pBtn->LongCount = 0; _pBtn->RepeatCount = 0; } } //功能说明: 检测所有按键。10MS 调用一次 void Pannelkey_Polling(void) { Button_Detect(&s_Powerkey); /* USER 键 */ } void Pannelkey_Put(void) { // 定义一个队列 放入按键值 }
typedef struct { unsigned char count;// unsigned char LongkeyFlag;/*是否长按键,1代表是*/ unsigned char PreKeyValue;/*按键值的一个备份,用于释放按键值*/ }ScanKeyDef; #define SHORT_PRESS_TIME_IR 16 // 10ms #define SERIES_PRESS_TIME_IR 10 #define LONG_PRESS_TIME_IR 22 #define KEY_RELEASE_TIME_OUT_IR 12 // 10ms //提供5个接口函数,如下。 unsigned char get_irkey(void); unsigned char ISSeriesKey(unsigned char temp);//按键是否需要做连续按键 unsigned char changeSeriesKey(unsigned char temp);//转换连续按键值 unsigned char ISLongKey(unsigned char temp);//按键是否需要做连续按键 unsigned char changeToLongKey(unsigned char temp);//转换连续按键值 unsigned char KeyScan(void) { unsigned char KeyValue = KEY_NONE, KeyValueTemp = KEY_NONE; static unsigned char KeyReleaseTimeCount =0; KeyValueTemp = get_irkey(); if(KeyValueTemp != KEY_NONE) { ScanKeyDef.count++; KeyReleaseTimeCount =0; if(ScanKeyDef.count < LONG_PRESS_TIME_IR ) { ScanKeyDef.LongkeyFlag = 0; if((ScanKeyDef.count == SERIES_PRESS_TIME_IR) && ISSeriesKey(KeyValueTemp)) //处理连续按键 { KeyValue = changeSeriesKey ( KeyValueTemp ); ScanKeyDef.PreKeyValue = KEY_NONE; } else if ( ScanKeyDef.count < SHORT_PRESS_TIME_IR ) { ScanKeyDef.PreKeyValue = KeyValueTemp; } else { ScanKeyDef.PreKeyValue = KEY_NONE; // 无效按键 } } else if ( ScanKeyDef.count == LONG_PRESS_TIME_IR ) { if (ISLongKey(KeyValueTemp)) { { ScanKeyDef.LongkeyFlag = 1; KeyValue = changeToLongKey ( KeyValueTemp ); } } ScanKeyDef.PreKeyValue = KEY_NONE; } else if (ScanKeyDef.count > LONG_PRESS_TIME_IR ) { ScanKeyDef.PreKeyValue = KEY_NONE; //无效按键 } } else//release & no press { KeyReleaseTimeCount ++; if(KeyReleaseTimeCount >= KEY_RELEASE_TIME_OUT_IR) { if ( ScanKeyDef.PreKeyValue != KEY_NONE ) //释放按键值 { if ( ScanKeyDef.LongkeyFlag == 0 ) { KeyValue =ScanKeyDef.PreKeyValue ; } } ScanKeyDef.count = 0; ScanKeyDef.LongkeyFlag = 0; ScanKeyDef.PreKeyValue = KEY_NONE; } } return(KeyValue); }