The best key scanning and debounce method for compound, long press, press or lift responsive keys

When I first started working, I saw the key scanning and anti-shake methods used by some colleagues, and compared the key processing found in the school and the Internet, and found that they were not perfect. There are the following points:

1. 消抖复杂,效率低。有人直接在电平判断后使用delay()函数,进行消抖,耽误时间;有人在按键电平中断中进行消抖和处理,导致其他的服务反应慢,不适合做实时系统;

2. 许多功能在不同界面下是不同的,把按键处理在中断进行,导致分支很多,业务流不清晰。

3. 特殊功能按键的处理麻烦。在需要长按作为特殊按键、复合按键响应、复合按键长按响应的时候,需要增加很多的标志位,反复使用if..else判断,流程看起来很乱。

4. 跟硬件设计或业务关联很深,不便于移植和修改,导致每个项目都要更改一次。

After thinking about it for a long time, I combined the keyboard processing method of the PC and wrote my own key function. After several revisions, I settled on it. In the past ten years, whether replacing the microcontroller, using the port/scanning method, or using the front-end or back-end or operating system, it has been used all the time, which is convenient for transplantation and relatively clear.

/****/

It mainly has several features:

  1. The key scan and the value are separated.

    In the interrupt, call keyScan() every 10ms to scan the keys, scan multiple times to debounce, and the obtained key value is not returned, but is placed in the global variable as a message;

    Use getKeyValue() to get the current key value and process it where the business layer needs to judge.

  2. Each button has its own flag and timing variables.

    Debounce timing:

    每调用一次10ms中断,如果按键按下,gucKeyOkTimer(以OK按键为例)增加;
    gucKeyOkTimer超过消抖的阀值(我一般10次,即100ms),则确认有按键了。
    
    任何一次扫描到按键没有按下,gucKeyOkTimer清零,重新开始;

    Flag bit:

    如果按下的电平时间超过阈值,一直按着,会有gfOkPressing的标志,表明按键一直有效中;
    
    如果按下过一次,需要响应,会有gfOkNeedAck,这个标志只置位一次;
  3. Response of compound keys:

    因为每个按键,都有自己的标志位和计时变量。复合按键的判断,使用多个按键pressing的标志判断是否有效。同样每个复合按键有自己pressing的标志,和NeedAck的标志;
  4. Response to long key press:

    按键超过指定时间,则作为新的按键,也会有pressing标志,和NeedAck标志。

I don't use quirky programming methods. There are many tricky ways to achieve key scanning, and some people even wrote three lines of code to achieve debounce. - I personally don't like this style of programming. I like a clear thinking approach to programming that is easy to maintain and port. Of course the cost is a bit more ROM and RAM usage, but I think time and code quality are more important.

If you have the same idea as me and have encountered such confusion, you can consider my key scanning method.

/ ** Hardware Description ** /

This is a commonly used key definition, four keys: up, down, confirm, cancel; long press to confirm is the switch button; after power on, press the up and down buttons at the same time, it is the menu button.

/* **** software code ** /

The first is the key scan, which needs to be called every 10ms. In the system using STM32, SysTick can be used directly to call the key scan function every 10 seconds.

In void SysTick_Handler(void), add the following code:

    //key sacn, each 10ms
    giKeyScanTimer++;
    if(giKeyScanTimer>=10)
    {
        giKeyScanTimer=0;
        keyScan();
    }

In the key scan file key.c, the following is the macro definition of the key port. The project uses the HAL library, but to save time, the port scan calls the GPIO registers directly.

#define PORT_KOK        ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR4)
#define PORT_KUP        ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR5)
#define PORT_KDOWN      ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR6)
#define PORT_KCANCEL        ((GPIOA->IDR)&(uint32_t)GPIO_IDR_IDR7)

Press the key to scan the desired variable. Because the RAM of the STM32 used is larger, the flag bit directly uses uint8_t. In the place where the RAM is tight, it can be changed to the bit definition.

uint32_t gucKeyOkTimer, gucKeyUpTimer,gucKeyDownTimer, gucKeyCancelTimer, gucKeyMenuTimer;  //按键消抖需要的扫描计时器
uint8_t gfOkPressing, gfOkNeedAck;        //OK按键的按下标志、需要响应的标志
uint8_t gfUpPressing, gfUpNeedAck;    //UP按键的按下标志、需要响应的标志;  
uint8_t gfDownPressing, gfDownNeedAck;    //DN按键的按下标志、需要响应的标志;
uint8_t gfCancelPressing, gfCancelNeedAck;  //CANCEL按键的按下标志、需要响应的标志;
uint8_t gfMenuPressing, gfMenuNeedAck;      //MENU按键(同时按下UP、DOWN)的按下标志、需要响应的标志;
uint8_t gfONOFFPressing, gfONOFFNeedAck;    //ONOFF按键(按下OK超过3秒)的按下标志、需要响应的标志;

The following is the keyScan function. I copied the codes of 1 button, 1 long-press button, and 1 compound button completely, and the rest will not take up space.

//Key scan time, based on 10ms
#define KEY_100MS       10
#define KEY_200MS       20
#define KEY_500MS       50
#define KEY_1S          100
#define KEY_2S          200
/*********************函数说明*********************
函数作用:按键扫描函数
注意事项:每10ms被中断调用一次,判断是否有按键按下
         消抖时间:100ms
**********************************************/
void keyScan()
{
  //OK key
  if(PORT_KOK==0)
    {
      gucKeyOkTimer++;
      //100ms消抖后,确认需要处理
      if(gucKeyOkTimer>KEY_100MS)
        {
          //gfOkPressing代表这个按键一直被按下中
          gfOkPressing=1;
          //确认按下后,置待响应标志,这个标志只置一次,防止业务流重复处理
          if(gfOkPressing==0)
            gfOkNeedAck=1;
        }
      //如果连续按下1s,则为ONOFF按键,同样有pressing标志,和needack标志
      if(gucKeyOkTimer>KEY_1S)
        {
          gfONOFFPressing=1;
          if(gfONOFFPressing==0)
            gfONOFFNeedAck=1;
        }
    }
  else
    {
      //如果没有被按下,定时器、pressing标志都清零。needack标志不能清。
      gucKeyOkTimer=0;
      gfOkPressing=0;
      gfONOFFPressing=0;
    } 

  //Up key ...
  //Dn key ...
  //Cancel key ...
  //三个按键的处理方法相同,只是没有长按的处理。

  //如果UP和DOWN按键同时按下超过1秒,则为Menu按键;
  if(gfUpPressing&&gfDownPressing)
    {
      gucKeyMenuTimer++;
      if(gucKeyMenuTimer>KEY_1S)
        {
          gfMenuPressing=1;
          if(gfMenuPressing==0)
            gfMenuNeedAck=1;
        }
    }
  else
    {
      gucKeyMenuTimer=0;
      gfMenuPressing=0;
    } 
}

In the program processing of the business flow, call getKeyValue() to obtain a valid key value. Usually in the loop of an interface.

/*********************函数说明*********************
函数作用:根据扫描结果,返回按键值
注意事项:需要判断按键的时候,调用此函数
**********************************************/
uint8_t getKeyValue()
{
  if(gfUpNeedAck) 
    {
      gfUpNeedAck=0;
      return KEY_UP;
    }

        ... ...

  if(gfMenuNeedAck)
    {
      gfMenuNeedAck=0;
      return KEY_MENU;
    }

  if(gfONOFFNeedAck)
    {
      gfONOFFNeedAck=0;
      return KEY_ONOFF;
    }

  return KEY_NONE;
}

Of course, before entering a certain interface, you need to clear the button logo, otherwise the button that did not respond on the previous interface will affect the next interface:

/*********************函数说明*********************
函数作用:清空按键缓冲区
注意事项:
**********************************************/
void flushKeyBuf(void)
{
  gfUpNeedAck=0;
  gfDownNeedAck=0;
  gfOkNeedAck=0;
  gfCancelNeedAck=0;
  gfMenuNeedAck=0;
  gfONOFFNeedAck=0;
}
OK了,这篇文章我在51hei发表过,但是没有说得这么详细。

/ ** Write at the back ** /

There are several special key processing requirements, I will briefly accept them:

  1. Whether to press the response or lift the response.

    Different business requirements will have different requirements. The above code is for pressing the response. If the response needs to be lifted, the needack flag is not processed in the code of if(PORT_KOK==0). In the else branch, if pressing was set before lifting, then needack is set.

  2. The sequence, or how many times the password is operated in a combo.

    It is recommended to put it in the business flow, there is no need to deal with it in the key scan.

  3. Press a button for different times to give different prompts to enter different hidden functions.

    In this case, it is not recommended to process in keyscan, because the function with short key-press time may be processed first. Please judge the pressing time directly in the business flow.

  4. Button row and column scan.

    It is easy to change, just change PORT_KOK==0.

  5. time issue

    10ms scan once, 100ms debounce is not necessary, you can modify it according to your own time base.

/**/

Others are not explained, welcome everyone to leave a message below and communicate with each other.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324891182&siteId=291194637