一、背景
嵌入式按鍵一般鍵數(shù)不多,按鍵支持的頁(yè)面比較多,同一個(gè)按鍵的功能較為復(fù)雜,并且按鍵需要支持多種模式,比如短按、長(zhǎng)按,持續(xù)長(zhǎng)按等。由于產(chǎn)品需要設(shè)計(jì)了一種高可用的按鍵處理方案。使用按鍵狀態(tài)機(jī),通過(guò)定時(shí)掃描來(lái)檢測(cè)按鍵的狀態(tài)。并且設(shè)計(jì)了多種模式標(biāo)志位,當(dāng)掃描過(guò)程中發(fā)現(xiàn)相關(guān)事件觸發(fā)時(shí)標(biāo)志位置位,然后在時(shí)間處理中獲取標(biāo)志位再清除。
二、按鍵設(shè)計(jì)
2.1、按鍵狀態(tài)機(jī)設(shè)計(jì)
typedef struct {
// 硬件參數(shù)
Int8U id; // 按鍵ID
bool (*read)(void); // 按鍵讀取函數(shù)
// 狀態(tài)參數(shù)
struct
{
bool raw_state : 1; // 原始狀態(tài)
bool stable_state : 1; // 穩(wěn)定狀態(tài)
bool repeat_armed : 1; // 重復(fù)觸發(fā)就緒標(biāo)志
};
// 時(shí)間參數(shù)
Int32U timestamp; // 狀態(tài)變化時(shí)間戳
Int32U press_time; // 有效按下起始時(shí)間
Int32U repeat_time; // 重復(fù)觸發(fā)時(shí)間戳
// 事件標(biāo)志
Int8U event_flag; // 事件標(biāo)志位
}KeyState;
固定消抖時(shí)間為:20ms
每一個(gè)按鍵單獨(dú)初始化一個(gè)狀態(tài)機(jī),并且注冊(cè)每一個(gè)按鍵的單獨(dú)讀取函數(shù)。我這兒設(shè)計(jì)有三種事件標(biāo)志
短按:100ms觸發(fā)。對(duì)應(yīng)0x01
長(zhǎng)按:1000ms觸發(fā)。對(duì)應(yīng)0x02
長(zhǎng)按不釋放(類(lèi)似于音量鍵):超過(guò)1000ms之后每多按300ms觸發(fā)一次。對(duì)應(yīng)0x04
#define DEBOUNCE_TIME 20 // 消抖時(shí)間20ms
#define SHORT_PRESS_MS 100 // 短按時(shí)間閾值
#define LONG_PRESS_MS 1000 // 長(zhǎng)按時(shí)間閾值
#define REPEAT_INTERVAL_MS 300 // 持續(xù)觸發(fā)間隔
typedef enum
{
KEY_SHORT_PRESS = 0x01,
KEY_LONG_PRESS = 0x02,
KEY_CONTINUOUS_PRESS= 0x04, // 持續(xù)長(zhǎng)按標(biāo)志(首次達(dá)到閾值后周期觸發(fā))
}KeyEventFlag;
2.2、按鍵初始化和注冊(cè)
/* 按鍵對(duì)象數(shù)組 */
KeyState keys[8];
/**
* 按鍵狀態(tài)機(jī)初始化
*/
void key_init(void)
{
Int8U i;
for(i = 0; i < 8; i++)
{
keys[i].id = i;
switch (i)
{
case 0:
keys[i].read = key1_read_func;
break;
case 1:
keys[i].read = key2_read_func;
break;
case 2:
keys[i].read = key3_read_func;
break;
case 3:
keys[i].read = key4_read_func;
break;
case 4:
keys[i].read = key5_read_func;
break;
case 5:
keys[i].read = key6_read_func;
break;
case 6:
keys[i].read = key7_read_func;
break;
case 7:
keys[i].read = key8_read_func;
break;
}
keys[i].raw_state = 0;
keys[i].stable_state = 0;
keys[i].repeat_armed = 0;
keys[i].timestamp = 0;
keys[i].press_time = 0;
keys[i].repeat_time = 0;
keys[i].event_flag = 0;
}
}
/********************按鍵外設(shè)相關(guān)**************************/
bool key1_read_func(void)
{
return POWER_PRESS ? true:false;
}
bool key2_read_func(void)
{
return RIGHT_PRESS ? true:false;
}
bool key3_read_func(void)
{
return TEST_PRESS ? true:false;
}
bool key4_read_func(void)
{
return CONFIRM_PRESS ? true:false;
}
bool key5_read_func(void)
{
return CONN_PRESS ? true:false;
}
bool key6_read_func(void)
{
return LEFT_PRESS ? true:false;
}
bool key7_read_func(void)
{
return SEL_PRESS ? true:false;
}
bool key8_read_func(void)
{
return BACK_PRESS ? true:false;
}
我這兒一共有8個(gè)按鍵,所以初始化定義了一個(gè)大小為8的KeyState數(shù)組,用來(lái)存儲(chǔ)8個(gè)按鍵的狀態(tài)機(jī)變化。并初始化時(shí)除了讀取按鍵狀態(tài)的函數(shù)外都初始化為0。
2.3、按鍵掃描
#define GET_TICK() SYS_GetTick() // 系統(tǒng)時(shí)基(1ms一個(gè)tick)
/******************** 按鍵處理核心邏輯 ********************/
void key_scan_task(void)
{
static Int32U last_scan = 0;
Int32U now = GET_TICK();
// 固定周期掃描(建議5-10ms)
if(now - last_scan < 5) return;
last_scan = now;
for(Int8U i = 0; i < sizeof(keys)/sizeof(KeyState); i++)
{
KeyState* key = &keys[i];
bool current = key->read();
/* 狀態(tài)變化檢測(cè) */
if(current != key->raw_state)
{
key->raw_state = current;
key->timestamp = now;
}
/* 消抖處理 */
if((now - key->timestamp) > DEBOUNCE_TIME)
{
bool state_changed = (key->stable_state != current);
key->stable_state = current;
/* 按下事件處理 */
if(state_changed && current)
{
key->press_time = now;
key->repeat_armed = true;
}
/* 釋放事件處理 */
if(state_changed && !current)
{
if(key->press_time != 0)
{
// 常規(guī)事件處理
uint32_t duration = now - key->press_time;
if(duration >= LONG_PRESS_MS)
{
key->event_flag |= KEY_LONG_PRESS;
}
else
{
key->event_flag |= KEY_SHORT_PRESS;
}
// 釋放時(shí)清除持續(xù)按壓標(biāo)志
key->event_flag &= ~KEY_CONTINUOUS_PRESS;
key->press_time = 0;
key->repeat_armed = false;
key->repeat_time = 0;
}
}
}
/* 持續(xù)長(zhǎng)按檢測(cè) */
if(key->stable_state && key->press_time)
{
uint32_t duration = now - key->press_time;
// 首次達(dá)到長(zhǎng)按閾值
if(duration >= LONG_PRESS_MS && key->repeat_armed)
{
key->event_flag |= KEY_CONTINUOUS_PRESS;
key->repeat_time = now;
key->repeat_armed = false; // 防止重復(fù)首次觸發(fā)
}
// 后續(xù)周期觸發(fā)
if(!key->repeat_armed && (now - key->repeat_time) >= REPEAT_INTERVAL_MS)
{
key->event_flag |= KEY_CONTINUOUS_PRESS;
key->repeat_time = now;
}
}
}
}
按鍵掃描一般放在主程序邏輯,每隔5-10ms調(diào)用一次。使用的計(jì)時(shí)函數(shù)可以根據(jù)系統(tǒng)自定義。我這兒是自定義的函數(shù)。定義了一個(gè)全局變量,在系統(tǒng)systick計(jì)數(shù)器中上電即不停累加。
2.4、用戶接口
/******************** 用戶接口函數(shù) ********************/
// 獲取按鍵事件(自動(dòng)清除標(biāo)志)
Int8U key_get_event(Int8U key_id)
{
if(key_id > sizeof(keys)/sizeof(KeyState))
{
return 0;
}
Int8U flag = keys[key_id].event_flag;
keys[key_id].event_flag = 0; // 清除標(biāo)志
return flag;
}
// 檢查是否有事件(非清除方式)
Int8U key_peek_event(Int8U key_id)
{
if(key_id > sizeof(keys)/sizeof(KeyState))
{
return 0;
}
return keys[key_id].event_flag;
}
// 按鍵清除所有標(biāo)志位
void key_clear_flag(void)
{
Int8U i;
for(i = 0; i < 8; i++)
{
keys[i].raw_state = 0;
keys[i].stable_state = 0;
keys[i].repeat_armed = 0;
keys[i].timestamp = 0;
keys[i].press_time = 0;
keys[i].repeat_time = 0;
keys[i].event_flag = 0;
}
}
一般來(lái)說(shuō)如果是獲取標(biāo)志位的方法的話,用戶一般會(huì)處理完相關(guān)事件才會(huì)清除按鍵標(biāo)志位。這種按鍵狀態(tài)機(jī)設(shè)計(jì)不是通過(guò)按鍵時(shí)間觸發(fā)并調(diào)用回調(diào)函數(shù)處理,如果調(diào)用key_get_event函數(shù)在主程序中的話,會(huì)每次自動(dòng)清除標(biāo)志位。無(wú)法滿足需要,可以自主調(diào)用key_peek_event函數(shù)處理完事件后再手動(dòng)調(diào)用key_clear_flag清除標(biāo)志位
三、具體實(shí)現(xiàn)代碼
3.1、key.h
#ifndef __KEY_H
#define __KEY_H
#define POWER_PRESS (gpio_input_bit_get(GPIOA,GPIO_PIN_4) != SET)
#define RIGHT_PRESS (gpio_input_bit_get(GPIOA,GPIO_PIN_5) != SET)
#define TEST_PRESS (gpio_input_bit_get(GPIOA,GPIO_PIN_6) != SET)
#define CONFIRM_PRESS (gpio_input_bit_get(GPIOA,GPIO_PIN_7) != SET)
#define CONN_PRESS (gpio_input_bit_get(GPIOC,GPIO_PIN_4) != SET)
#define LEFT_PRESS (gpio_input_bit_get(GPIOC,GPIO_PIN_5) != SET)
#define SEL_PRESS (gpio_input_bit_get(GPIOB,GPIO_PIN_0) != SET)
#define BACK_PRESS (gpio_input_bit_get(GPIOA,GPIO_PIN_0) != SET)
#define GET_TICK() SYS_GetTick() // 系統(tǒng)時(shí)基(1ms一個(gè)tick)
#define DEBOUNCE_TIME 20 // 消抖時(shí)間20ms
#define SHORT_PRESS_MS 100 // 短按時(shí)間閾值
#define LONG_PRESS_MS 1000 // 長(zhǎng)按時(shí)間閾值
#define REPEAT_INTERVAL_MS 300 // 持續(xù)觸發(fā)間隔
typedef enum
{
KEY_SHORT_PRESS = 0x01,
KEY_LONG_PRESS = 0x02,
KEY_CONTINUOUS_PRESS= 0x04, // 持續(xù)長(zhǎng)按標(biāo)志(首次達(dá)到閾值后周期觸發(fā))
}KeyEventFlag;
typedef enum
{
KEY_POWER = 0,
KEY_RIGHT = 1,
KEY_TEST = 2,
KEY_CONFIRM = 3,
KEY_CONN = 4,
KEY_LEFT = 5,
KEY_SEL = 6,
KEY_BACK = 7,
}KEY_NO_ENUM;
typedef struct {
// 硬件參數(shù)
Int8U id; // 按鍵ID
bool (*read)(void); // 按鍵讀取函數(shù)
// 狀態(tài)參數(shù)
struct
{
bool raw_state : 1; // 原始狀態(tài)
bool stable_state : 1; // 穩(wěn)定狀態(tài)
bool repeat_armed : 1; // 重復(fù)觸發(fā)就緒標(biāo)志
};
// 時(shí)間參數(shù)
Int32U timestamp; // 狀態(tài)變化時(shí)間戳
Int32U press_time; // 有效按下起始時(shí)間
Int32U repeat_time; // 重復(fù)觸發(fā)時(shí)間戳
// 事件標(biāo)志
Int8U event_flag; // 事件標(biāo)志位
}KeyState;
bool key1_read_func(void);
bool key2_read_func(void);
bool key3_read_func(void);
bool key4_read_func(void);
bool key5_read_func(void);
bool key6_read_func(void);
bool key7_read_func(void);
bool key8_read_func(void);
void key_init(void);
void key_scan_task(void);
Int8U key_get_event(Int8U key_id);
Int8U key_peek_event(Int8U key_id);
void key_clear_flag(void);
#endif
3.2、key.c
#include "key.h"
/* 按鍵對(duì)象數(shù)組 */
KeyState keys[8];
/********************按鍵外設(shè)相關(guān)**************************/
bool key1_read_func(void)
{
return POWER_PRESS ? true:false;
}
bool key2_read_func(void)
{
return RIGHT_PRESS ? true:false;
}
bool key3_read_func(void)
{
return TEST_PRESS ? true:false;
}
bool key4_read_func(void)
{
return CONFIRM_PRESS ? true:false;
}
bool key5_read_func(void)
{
return CONN_PRESS ? true:false;
}
bool key6_read_func(void)
{
return LEFT_PRESS ? true:false;
}
bool key7_read_func(void)
{
return SEL_PRESS ? true:false;
}
bool key8_read_func(void)
{
return BACK_PRESS ? true:false;
}
/**
* 按鍵狀態(tài)機(jī)初始化
*/
void key_init(void)
{
Int8U i;
for(i = 0; i < 8; i++)
{
keys[i].id = i;
switch (i)
{
case 0:
keys[i].read = key1_read_func;
break;
case 1:
keys[i].read = key2_read_func;
break;
case 2:
keys[i].read = key3_read_func;
break;
case 3:
keys[i].read = key4_read_func;
break;
case 4:
keys[i].read = key5_read_func;
break;
case 5:
keys[i].read = key6_read_func;
break;
case 6:
keys[i].read = key7_read_func;
break;
case 7:
keys[i].read = key8_read_func;
break;
}
keys[i].raw_state = 0;
keys[i].stable_state = 0;
keys[i].repeat_armed = 0;
keys[i].timestamp = 0;
keys[i].press_time = 0;
keys[i].repeat_time = 0;
keys[i].event_flag = 0;
}
}
/******************** 按鍵處理核心邏輯 ********************/
void key_scan_task(void)
{
static Int32U last_scan = 0;
Int32U now = GET_TICK();
// 固定周期掃描(建議5-10ms)
if(now - last_scan < 5) return;
last_scan = now;
for(Int8U i = 0; i < sizeof(keys)/sizeof(KeyState); i++)
{
KeyState* key = &keys[i];
bool current = key->read();
/* 狀態(tài)變化檢測(cè) */
if(current != key->raw_state)
{
key->raw_state = current;
key->timestamp = now;
}
/* 消抖處理 */
if((now - key->timestamp) > DEBOUNCE_TIME)
{
bool state_changed = (key->stable_state != current);
key->stable_state = current;
/* 按下事件處理 */
if(state_changed && current)
{
key->press_time = now;
key->repeat_armed = true;
}
/* 釋放事件處理 */
if(state_changed && !current)
{
if(key->press_time != 0)
{
// 常規(guī)事件處理
uint32_t duration = now - key->press_time;
if(duration >= LONG_PRESS_MS)
{
key->event_flag |= KEY_LONG_PRESS;
}
else
{
key->event_flag |= KEY_SHORT_PRESS;
}
// 釋放時(shí)清除持續(xù)按壓標(biāo)志
key->event_flag &= ~KEY_CONTINUOUS_PRESS;
key->press_time = 0;
key->repeat_armed = false;
key->repeat_time = 0;
}
}
}
/* 持續(xù)長(zhǎng)按檢測(cè) */
if(key->stable_state && key->press_time)
{
uint32_t duration = now - key->press_time;
// 首次達(dá)到長(zhǎng)按閾值
if(duration >= LONG_PRESS_MS && key->repeat_armed)
{
key->event_flag |= KEY_CONTINUOUS_PRESS;
key->repeat_time = now;
key->repeat_armed = false; // 防止重復(fù)首次觸發(fā)
}
// 后續(xù)周期觸發(fā)
if(!key->repeat_armed && (now - key->repeat_time) >= REPEAT_INTERVAL_MS)
{
key->event_flag |= KEY_CONTINUOUS_PRESS;
key->repeat_time = now;
}
}
}
}
/******************** 用戶接口函數(shù) ********************/
// 獲取按鍵事件(自動(dòng)清除標(biāo)志)
Int8U key_get_event(Int8U key_id)
{
if(key_id > sizeof(keys)/sizeof(KeyState))
{
return 0;
}
Int8U flag = keys[key_id].event_flag;
keys[key_id].event_flag = 0; // 清除標(biāo)志
return flag;
}
// 檢查是否有事件(非清除方式)
Int8U key_peek_event(Int8U key_id)
{
if(key_id > sizeof(keys)/sizeof(KeyState))
{
return 0;
}
return keys[key_id].event_flag;
}
/**
* 按鍵清除標(biāo)志位
*/
void key_clear_flag(void)
{
Int8U i;
for(i = 0; i < 8; i++)
{
keys[i].raw_state = 0;
keys[i].stable_state = 0;
keys[i].repeat_armed = 0;
keys[i].timestamp = 0;
keys[i].press_time = 0;
keys[i].repeat_time = 0;
keys[i].event_flag = 0;
}
}
四、調(diào)用示例
// 短按
if(key_peek_event(KEY_SEL) & KEY_SHORT_PRESS)
{
key_clear_flag(); // 清除所有標(biāo)志位
// todo 用戶處理事件
}
else if(key_peek_event(KEY_SEL) & KEY_LONG_PRESS)
{
key_clear_flag(); // 清除所有標(biāo)志位
// todo 用戶處理事件
}
else if(key_peek_event(KEY_SEL) & KEY_CONTINUOUS_PRESS)
{
key_clear_flag(); // 清除所有標(biāo)志位
// todo 用戶處理事件
}