单片机开发笔记系列-按键扫描
单片机开发笔记系列-按键扫描

单片机开发笔记系列-按键扫描

分析单片机中的按键扫描

单片机中按键检测有多种方式,可以是程序定时去扫描按键的电平状态,也可以开启外部中断,用外部中断去做按键响应。在实际项目中,不推荐使用外部中断做按键检测。因为机械按键不可避免地会有抖动问题,很容易造成一次按下多次响应。如果用外部中断的话,需要实时记录按键响应的时间,根据当前时间与上一次响应的时间之差做为是否需要响应的依据。否则只能是在中断里做延时,这又很容易造成系统崩溃。

在本文中,默认按键按下时为低电平。外部中断做按键响应的中断服务函数如下所示。一般的,按键抖动在 100ms 以内,所以外部中断发生时,如果当前时间与上一次响应时间之差大于 100ms,则说明是一个新的按键事件。系统时间可以由一个定时器来记录,设置一个计数器,由定时器从零开始每隔 1ms 向上计数一次。

void EXTI0_IRQHandler(void)
{
  //上一次响应时间
  static unsigned int s_iLastTimeMs = 0;
  
  //当前系统时间
  unsigned int sysTime;

  //校验外部中断标志位
  if(SET == exti_flag_get(EXTI_0))
  {
    //获取当前系统时间
    sysTime = GetSysTime();

    //当前系统时间距离上一次响应超过了 100ms,说明是一次新的按键事件
    if((sysTime - s_iLastTimeMs) >= 100)
    {
      //按键响应
      ...

      //记录当前响应时间
      s_iLastTimeMs = sysTime;
    }

    //清除中断标志位
    exti_flag_clear(EXTI_0);
  }
}

当然,我们也可以让程序定时扫描按键电平状态,简单的按键扫描驱动如下所示。想要捕抓下降沿的话,需要拿当前按键状态和上一个按键状态做比较,如果上一个时刻按键为抬起而当前为按下状态,则说明发生了一个下降沿。最后别忘记了跟踪记录按键状态,保存当前键值。每隔 50ms 或 100ms 调用一次,可以起到不错的去抖效果。

int ScanKey(void)
{
  //按键上一次的电平状态
  static unsigned char s_iLastState = 1;
  
  //按键当前的电平状态
  unsigned char key;

  //返回值
  unsigned char ret = 0;

  //获取按键当前电平状态
  key = gpio_input_bit_get(GPIOA, GPIO_PIN_0);

  //检测到下降沿
  if((0 == key) && (1 == s_iLastState)
  {
    ret = 1;
  }

  //保存当前按键电平状态
  s_iLastState = key;

  //返回检测结果
  return ret;
}

带去抖的按键驱动如下所示,每隔 10ms 调用一次,即可起到 80ms 的软件去抖。如果记录下按键按下以及抬起的时间,还可以识别长按和段按。

int ScanKey(void)
{
  //按键上一次的电平状态
  static unsigned char s_iLastState = 0xFF;

  //当前按键状态
  static unsigned char s_iKeyValue = 0xFF;
  
  //返回值
  unsigned char ret = 0;

  //获取按键当前电平状态
  s_iKeyValue = (s_iKeyValue << 1) | gpio_input_bit_get(GPIOA, GPIO_PIN_0);

  //检测到下降沿
  if((0x00 == s_iKeyValue) && (0x00 != s_iLastState)
  {
    ret = 1;
  }

  //保存当前按键电平状态
  s_iLastState = s_iKeyValue;

  //返回检测结果
  return ret;
}