按键处理 --- 状态机

按键原理图

在这里插入图片描述

软件原理

1.使用IO外部中断,当按键有动作时开启定时器进行按键扫描并产生按键事件

环境

1.硬件环境:此代码是在昂瑞微SOC芯片OM6621d上运行的。(因此芯片有睡眠机制,所以定时器需在空闲时进行关闭让芯片进入睡眠状态,需根据自身平台做调整)
2.编译环境:keil

代码

按键扫描(根据自身平台配置IO中断以及定时器)

/*按键扫描代码*/
//key_scan.c
static key_gnd_config_t m_key_cfg;//按键定义信息
static co_timer_t m_key_scan_timer;//定时器
static bool m_key_scan_enable = false;//按键扫描状态
static void key_wakeup_enable(bool wakeup_en);
void key_scan_disable(void);

// 获取按键是按下还是释放,保存到结构体
static void key_get_action(void)
{
    uint8_t i = 0;
  uint32_t pins_state = gpio_read(m_key_pin_mask);  
  for(i = 0; i < m_key_cfg.key.numb; i++){
    if(pins_state&BITMASK(m_key_cfg.key.p_pin[i])){//松开
      m_key_cfg.key.key_config[i].key_action = KS_ACT_RELEASE;
    }else{//按下
      m_key_cfg.key.key_config[i].key_action = KS_ACT_PRESS;
    }      
  }
}
// 获取按键动作
static bool key_get_status(void)
{
  static uint8_t key_timeout_time = 0;
  bool key_status = 0;
  uint8_t i= 0;
  key_get_action();
    
  for(i = 0; i < m_key_cfg.key.numb; i++){
    switch(m_key_cfg.key.key_config[i].key_status)
    {
      //状态:没有按键按下
      case KS_STATUS_IDLE:
        if(m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS)
        {
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_DEBOUNCE;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        else
        {
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        break;
        
      //状态:消抖
      case KS_STATUS_DEBOUNCE:
        if(m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS)
        {
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_PRESS;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        else
        {
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        break;    
      //状态:继续按下
      case KS_STATUS_PRESS:
        if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_long_press_time))//1s
        {
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        else if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && (m_key_cfg.key.key_config[i].key_count < m_key_cfg.key.key_long_press_time))
        {
          m_key_cfg.key.key_config[i].key_count++;
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_PRESS;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        else
        {
          m_key_cfg.key.key_config[i].key_count = 0;
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_WAIT_AGAIN_PRESS;// 按短了后释放
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;

        }
        break;        
      //状态:一直长按着
      case KS_STATUS_LONG_PRESS:
        if(m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) 
        {   
          if(m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_long_press_time){//
            m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;
            m_key_cfg.key.key_config[i].key_event = KEY_EVENT_LONGPRESS;
            m_key_cfg.key.key_config[i].key_count  = 0;
          }else{
            m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;
            m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
            m_key_cfg.key.key_config[i].key_count  = 0;
          }
        }
        else
        {
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;//KEY_EVENT_LONGPRESS;
          m_key_cfg.key.key_config[i].key_count  = 0;
        }
        break;         
      //状态:等待是否再次按下
      case KS_STATUS_WAIT_AGAIN_PRESS:
        if((m_key_cfg.key.key_config[i].key_action != KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_wait_double_time))
        {   // 第一次短按,且释放时间大于KEY_WAIT_DOUBLE_TIME
          m_keygnd_cfg.key.key_config[i].key_count = 0;
          m_keygnd_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;  
          m_keygnd_cfg.key.key_config[i].key_event = KEY_EVENT_SINGLECLICK;
        }
        else if((m_key_cfg.key.key_config[i].key_action != KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count < m_key_cfg.key.key_wait_double_time))
        {// 第一次短按,且释放时间还没到
          m_key_cfg.key.key_config[i].key_count ++;
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_WAIT_AGAIN_PRESS;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;    
        }
        else // 第一次短按,且还没到KEY_WAIT_DOUBLE_TIME 第二次被按下
        {
          m_key_cfg.key.key_config[i].key_count = 0;
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_DOUBLE_PRESS;// 第二次按下
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        break;        
      case KS_STATUS_DOUBLE_PRESS:
        if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count >= m_key_cfg.key.key_long_press_time))
        {
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_LONG_PRESS;// 第二次按的时间大于 KEY_LONG_PRESS_TIME
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_SINGLECLICK; // 先响应单击
          m_key_cfg.key.key_config[i].key_count = 0;
        }
        else if( (m_key_cfg.key.key_config[i].key_action == KS_ACT_PRESS) && ( m_key_cfg.key.key_config[i].key_count < m_key_cfg.key.key_long_press_time))
        {
          m_key_cfg.key.key_config[i].key_count ++;
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_DOUBLE_PRESS;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_NULL;
        }
        else 
        {// 第二次按下后在 KEY_LONG_PRESS_TIME内释放
          m_key_cfg.key.key_config[i].key_count = 0;
          m_key_cfg.key.key_config[i].key_status = KS_STATUS_IDLE;
          m_key_cfg.key.key_config[i].key_event = KEY_EVENT_DOUBLECLICK; // 响应双击
        }
        break;    
      default:
        break;
    }
    //如果不是IDLE且仍在计时中 则判断为按键动作还未结束返回1
    if(m_key_cfg.key.key_config[i].key_status != KS_GND_STATUS_IDLE && m_key_cfg.key.key_config[i].key_count != 0){
      key_status = 1;
    }
  }
  return key_status;
}
//按键事件处理
static void key_event_handler(void){
    uint8_t i = 0;
  for(i = 0; i < m_keygnd_cfg.key.numb; i++){
    if(m_keygnd_cfg.key.key_config[i].key_event != KEY_EVENT_NULL){
      m_keygnd_cfg.key_scan_code_handler_cb(i, m_keygnd_cfg.key.key_config[i].key_event);
    }
  }
}
//按键扫描定时器处理
static void key_scan_timer_handler(co_timer_t *timer, void *param)
{   
  static uint32_t key_timeout_time = 0;
  bool key_status = 0;
  //获取按键状态
  if(key_get_status() == 0){
    key_timeout_time ++;
  }else{
    key_timeout_time = 0;
  }
  //按键事件处理 --- 事件处理根据需求放置位置
  key_event_handler();
  //按键扫描超时
  if(key_timeout_time >= 300){//300*20 = 6s
    key_timeout_time = 0;
    key_wakeup_enable(true);
    co_timer_del(&m_key_scan_timer);
  }

}
//按键IO中断使能接口
static void key_wakeup_enable(bool wakeup_en)
{
    if(wakeup_en)
    {
      //设置上升沿和下降沿触发中断  
      gpio_set_interrupt(m_key_pin_mask, GPIO_BOTH_EDGE);
      //读取IO当前状态,再设置对应相反电平唤醒
      uint32_t pins_state=gpio_read(m_key_pin_mask);
      pmu_wakeup_pin_set(pins_state, PMU_PIN_WAKEUP_LOW_LEVEL);
      pmu_wakeup_pin_set((pins_state^(m_key_pin_mask)), PMU_PIN_WAKEUP_HIGH_LEVEL);  
    }
    else
    {
      //关闭IO中断以及睡眠唤醒中断
      gpio_set_interrupt(m_key_pin_mask, GPIO_TRIGGER_DISABLE);
      pmu_wakeup_pin_set(m_key_pin_mask, PMU_PIN_WAKEUP_DISABLE);  
    }
}
//IO中断处理接口
void key_gpio_interrupt_handler(uint32_t pin_mask){ 
  if(pin_mask&m_key_pin_mask){
      key_wakeup_enable(false);
      //获取按键状态
      key_get_status();
      co_timer_set(&m_key_scan_timer, m_key_cfg.scan_interval, TIMER_REPEAT, key_scan_timer_handler, NULL);     
  }
}

void key_scan_enable(void)
{
    if(m_key_pin_mask == 0)
      return;
    if(m_key_scan_enable == false)
    {
      m_key_scan_enable=true;
      key_wakeup_enable(false);
      co_timer_set(&m_key_scan_timer, m_key_cfg.scan_interval, TIMER_REPEAT, key_scan_timer_handler, NULL);    
    }
}
void key_scan_disable(void)
{  
    if(m_key_scan_enable)
    {
      key_wakeup_enable(true);
      co_timer_del(&m_key_scan_timer);
    }
    m_key_scan_enable = false;
}
void key_init(key_config_t *p_cfg)
{
  uint32_t i;    
  if(p_cfg == NULL)
    return;
  m_key_cfg=*p_cfg;
  if(m_key_cfg.key.numb==0)
    return;
  
  m_key_pin_mask=0;
  for(i=0; i < m_key_cfg.key.numb; ++i)
  {
    if(m_key_cfg.key.p_pin[i]<MAX_GPIO_PIN_NUMBER)//限制IO进行配置,需根据自身平台做调整
    {
      m_key_pin_mask |= BITMASK(m_key_cfg.key.p_pin[i]);
      pinmux_config(m_key_cfg.key.p_pin[i],  PINMUX_GPIO_MODE_CFG);
    } 
  }
  if(m_key_pin_mask==0)
    return;
  //设置按键IO上拉输入    
  pmu_pin_mode_set(m_key_pin_mask, PMU_PIN_MODE_PU);
  gpio_set_direction(m_key_pin_mask, GPIO_INPUT); 
}
//key_sacn.h
typedef enum
{
  KS_ACT_RELEASE,
  KS_ACT_PRESS,
}key_action_t;
typedef enum
{
  KS_STATUS_IDLE, // 空闲
  KS_STATUS_DEBOUNCE,//消抖
  KS_STATUS_PRESS,//按下
  KS_STATUS_LONG_PRESS,//长按
  KS_STATUS_WAIT_AGAIN_PRESS, //等待再次按下
  KS_STATUS_DOUBLE_PRESS,  //双击
}key_state_t;

typedef enum _KEY_EventList_TypeDef 
{
    KEY_EVENT_NULL            = 0x00,  // 无事件
    KEY_EVENT_SINGLECLICK = 0x01,  // 单击
    KEY_EVENT_DOUBLECLICK  = 0x02,  // 双击
    KEY_EVENT_LONGPRESS    = 0x04   // 长按
}key_event_t;

typedef struct
{
  key_action_t key_action;//按键动作
  key_state_t key_status;//按键状态
  key_event_t key_event;//按键事件
  uint32_t key_count;//按键计数
}ks_config_t;

typedef struct
{
  uint8_t numb;
  uint8_t *p_pin;
  uint32_t key_long_press_time;
  uint32_t key_wait_double_time;
  ks_config_t *key_config;
}ks_t;

typedef void (*ks_scan_code_handler_t)(uint8_t pin_num, key_event_t key_event);

typedef struct
{
  ks_t   key;
  uint8_t scan_interval; //unit: ms
  ks_scan_code_handler_t key_scan_code_handler_cb;
}key_config_t;

void key_gpio_interrupt_handler(uint32_t pin_mask);
void key_init(key_gnd_config_t *p_cfg); 
void key_scan_enable(void);
void key_scan_disable(void);

按键初始化代码(应用层)

//app_keyboard.c
//应用层代码
#define KS_PIN        {13}
#define KS_NUMB        1
static uint8_t m_key_pin[] = KS_PIN;
static ks_config_t m_key_config[KS_NUMB];
static void key_scan_code_handler(uint8_t pin_num, key_event_t key_event){
  switch (pin_num)
  {
    case 0:
      {
        switch(key_event){
                    case KEY_EVENT_NULL:
                    break;    
                    case KEY_EVENT_SINGLECLICK:
                    {
                         printf("KEY_EVENT_SINGLECLICK\n");
                    }
                    break;
                    case KEY_EVENT_DOUBLECLICK:
                    {
                         printf("KEY_EVENT_DOUBLECLICK\n");
                    }
                    break;
                    case KEY_EVENT_LONGPRESS:
                    {
                        printf("KEY_EVENT_LONGPRESS\n");
                    }
                    break;
                }
      }
      break;
    
    default:
      break;
  }
}

void keyboard_init(void)
{
    key_config_t ks_cfg;
    ks_cfg.scan_interval = 20;//20ms
    ks_cfg.key.key_long_press_time = 150;// 20ms*50 = 3s
    ks_cfg.key.key_wait_double_time = 25;// 20ms*25 = 500ms
    ks_cfg.key.p_pin = &m_key_pin[0];
    ks_cfg.key.numb = KS_NUMB;
    ks_cfg.key.key_config = &m_key_config[0];
    ks_cfg.key_scan_code_handler_cb = key_scan_code_handler;
    key_init(&ks_cfg);
}

上文仅个人见解,敬请批评指正!

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值