嵌入式key狀態(tài)機(jī)設(shè)計(jì)(通用)

一、背景

嵌入式按鍵一般鍵數(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 用戶處理事件
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容