适用于所有单片机的通用按键扫描算法【软件模块】

适用于所有单片机的按键扫描算法

  • 前言
  • 一、算法设计
  • 二、代码实现
  • 参考资料

  • 前言

    单片机按键扫描是指利用微控制器(MCU)的数字输入口,定期检测按键的电平状态并进行去抖动处理(防止误操作),判断按键是否被按下或松开。

    常用的按键有两种:自复式按键和自锁式按键。自复式按键按下就通,松开就断,不会锁住。自锁式按键按下一次就通并锁住,再按一次就断并弹回,需要两次操作。

    本文介绍一种用于自复式按键的扫描算法,它有软件消抖功能,可以检测按键的短按和长按检测。


    一、算法设计

    针对每一个独立按键,使用三个bits来标志按键的各个状态的转换:

    1. TrigFlag:按键被按下的触发标志,仅在按键被第一次检测到按下时置位为1,其它状态保持为0
    2. IdleFlag:按键被松开的触发标志,仅在按键被第一次检测到松开时置位为1,其它状态保持为0
    3. ContFlag:按键实时的状态标志,按键被检测到按下时为1,被检测到松开时为0
    4. ReadBit:扫描检测到的按键状态,按下时为1,松开时为0

    以下一次按键过程中,这三个标志的状态变化情况:
    按键状态变化
    过程分析:

    1. 初始状态下,扫描按键状态ReadBit = 0,状态标志TrigFlag = 0,IdleFlag = 0,ContFlag = 0;
    2. 按下按键后,扫描按键状态ReadBit = 1,经计算处理TrigFlag = 1,IdleFlag = 0,ContFlag = 1;
    3. 再次扫描按键状态(消抖)ReadBit = 1,经计算处理TrigFlag = 0,IdleFlag = 0,ContFlag = 1
      标志由TrigFlag = 1,IdleFlag = 0,ContFlag = 1变到TrigFlag = 0,IdleFlag = 0,ContFlag = 1可以判断按键被按下;
    4. 保持按键按下,扫描按键状态ReadBit = 1,状态标志也将保持TrigFlag = 0,IdleFlag = 0,ContFlag = 1不再变化;
    5. 状态标志将保持为TrigFlag = 0,IdleFlag = 0,ContFlag = 1的时间达到设置的按键长按时间,可以判断按键被长按;
    6. 当松开按键后,扫描按键状态ReadBit = 0,经计算处理TrigFlag = 0,IdleFlag = 1,ContFlag = 0;
    7. 再次扫描按键状态(消抖)ReadBit = 0,经计算处理TrigFlag = 0,IdleFlag = 0,ContFlag = 0
      标志由TrigFlag = 0,IdleFlag = 1,ContFlag = 0变到TrigFlag = 0,IdleFlag = 0,ContFlag = 1可以判断按键被松开;
    8. 最后,按键状态将回到初始的松开状态ReadBit = 0,状态标志TrigFlag = 0,IdleFlag = 0,ContFlag = 0.

    根据上述过程的第3,5,7步的状态变化,就确定出按键的真实工作状态。

    二、代码实现

    根据上述原理,使用C语言实现了该按键扫描算法,完整代码如下:

    头文件:bsp_key.h

    /**
     * @file bsp_key.h
     * @author Sean
     * @brief The header file of key-scanning driver.
     * @version 0.1
     * @date 2023-02-28
     *
     * @copyright Copyright (c) 2023
     *
     */
    #ifndef __BSP_KEY_H__
    #define __BSP_KEY_H__
    
    #include <stdint.h>
    
    // typedef for interface: function for scanning the key, return 1 means the key is pressed, 0 means the key is not pressed.
    typedef uint8_t (*key_scan)(void);
    // typedef for interface: function for callback when short press the key.
    typedef void (*key_call_trig)(void);
    // typedef for interface: function for callback when long press the key.
    typedef void (*key_call_cont)(void);
    
    // typedef for key config bit struct
    typedef struct
    {
        // User set this bit for enable/disable the key keep press callback function.
        uint8_t bitEnableTrig : 1;
        // User set this bit for enable/disable the key long press callback function.
        uint8_t bitEnableCont : 1;
    
        // Handle task will use this bits, user not need to handle it.
        uint8_t bitTrigLast : 1;
        uint8_t bitIdleLast : 1;
        uint8_t bitContLast : 1;
        uint8_t bitTrig : 1;
        uint8_t bitIdle : 1;
        uint8_t bitCont : 1;
    } KeyConfig_b;
    
    // typedef for key handle struct
    typedef struct
    {
        // key config bit struct.
        KeyConfig_b bitKeyConfig;
        // Time for trigger long press = u16LongTrig * time between key_handle() calls.
        const uint16_t u16LongTrig;
    
        // Define the interface function.
        key_scan key_scan_func;
        key_call_trig key_call_trig_func;
        key_call_cont key_call_cont_func;
    } key_config_t;
    
    // Handle key task, this function should be called every 5-10ms generally.
    void key_handle(key_config_t *ctx);
    
    #endif // __BSP_KEY_H__
    
    

    源文件:bsp_key.c

    /**
     * @file bsp_key.c
     * @author Sean
     * @brief The source file of key-scanning driver.
     * @version 0.1
     * @date 2023-02-28
     * 
     * @copyright Copyright (c) 2023
     * 
     */
    
    #include "bsp_key.h"
    
    /**
     * @brief Handle key task.
     *
     * @warning This function should be called every 5-10ms generally, you can also make adjustments based on your keys.
     *
     * @param ctx Interface definitions.(ptr)
     *
     */
    void key_handle(key_config_t *ctx)
    {
        uint8_t u8ReadKey = 0;
        static uint8_t u8PressType = 0;
        static uint16_t u16LongTrig = 0;
        
        // Check if interface functions are set.
        if (ctx->key_scan_func == NULL || ctx->key_call_trig_func == NULL || ctx->key_call_cont_func == NULL)
        {
            return;
        }
        
        // Backup last states of keys.
        ctx->bitKeyConfig.bitTrigLast = ctx->bitKeyConfig.bitTrig;
        ctx->bitKeyConfig.bitIdleLast = ctx->bitKeyConfig.bitIdle;
        ctx->bitKeyConfig.bitContLast = ctx->bitKeyConfig.bitCont;
    
        // Scan the states of keys and handle the current states of the keys.
        u8ReadKey = (ctx->key_scan_func() == 0 ? 0 : 1);
        ctx->bitKeyConfig.bitTrig = u8ReadKey & (u8ReadKey ^ ctx->bitKeyConfig.bitCont);
        ctx->bitKeyConfig.bitIdle = u8ReadKey ^ (u8ReadKey | ctx->bitKeyConfig.bitCont);
        ctx->bitKeyConfig.bitCont = u8ReadKey;
    
        // Check if the key is pressed.
        if (ctx->bitKeyConfig.bitTrigLast == 1 && ctx->bitKeyConfig.bitIdleLast == 0 && ctx->bitKeyConfig.bitContLast == 1)
        {
            if (ctx->bitKeyConfig.bitTrig == 0 && ctx->bitKeyConfig.bitIdle == 0 && ctx->bitKeyConfig.bitCont == 1)
            {
                // check short press.
                if (ctx->bitKeyConfig.bitEnableTrig == 1)
                {
                    if(ctx->bitKeyConfig.bitEnableCont == 1)
                        u8PressType = 1;
                    else
                        ctx->key_call_trig_func();
                }
            }
        }
    
        // Check if the key is keep pressed.
        if (ctx->bitKeyConfig.bitTrigLast == 0 && ctx->bitKeyConfig.bitIdleLast == 0 && ctx->bitKeyConfig.bitContLast == 1)
        {
            if (ctx->bitKeyConfig.bitTrig == 0 && ctx->bitKeyConfig.bitIdle == 0 && ctx->bitKeyConfig.bitCont == 1)
            {
                // check long press.
                if (ctx->bitKeyConfig.bitEnableCont == 1)
                {
                    if (u16LongTrig < ctx->u16LongTrig)
                    {
                        if (++u16LongTrig >= ctx->u16LongTrig)
                        {
                            u8PressType = 0;
                            ctx->key_call_cont_func();
                        }
                    }
                }
            }
        }
    
        // Check if the key is released.
        if (ctx->bitKeyConfig.bitTrigLast == 0 && ctx->bitKeyConfig.bitIdleLast == 1 && ctx->bitKeyConfig.bitContLast == 0)
        {
            if (ctx->bitKeyConfig.bitTrig == 0 && ctx->bitKeyConfig.bitIdle == 0 && ctx->bitKeyConfig.bitCont == 0)
            {
                if(u8PressType == 1)
                    ctx->key_call_trig_func();
                
                //ctx->key_call_idle_func();
                u8PressType = 0;
                u16LongTrig = 0;
            }
        }
    }
    
    

    注意:如果只使能按键短按检测,按键短按的回调函数将在按键被按下时调用;如果只使能按键长按检测,按键长按的回调函数将在按键被按下到达设定的时间后调用;如果同时使能按键短按和长按检测,按键短按的回调函数将在按键被松开时调用,按键长按的回调函数将在按键被按下到达设定的时间后调用。

    使用方法

    1. 将代码文件拷贝到工程目录,并包含bsp_key.h头文件。
    #include "bsp_key.h"
    
    1. 先实现自己的按键状态检测函数uint8_t u8KeyScan(void), 按键短按回调函数void vKeyShort(void)和按键长按回调函数void vKeyLong(void),然后定义一个按键配置的结构体pkey_config,结构体中pkey_config.bitKeyConfig.bitEnableTrig为使能按键短按检测控制位,pkey_config.bitKeyConfig.bitEnableCont为使能按键长按检测控制位,pkey_config.u16LongTrig为设置按键长按检测时间(实际时间 = key_handle()函数调用间隔时间 * pkey_config.u16LongTrig),pkey_config.key_scan_func,pkey_config.key_call_trig_funcpkey_config.key_call_cont_func分别为注册自己实现的三个接口函数。
    // Function list
    uint8_t u8KeyScan(void);
    void vKeyShort(void);
    void vKeyLong(void);
    
    // Global variable
    key_config_t pkey_config = {
        .bitKeyConfig = {
            .bitEnableTrig = 1,  // Enable button short prass detection
            .bitEnableCont = 1,  // Enable button long prass detection
        },
        .u16LongTrig = 1000,  // Set long prass detection time = 1000 * 5ms(key_handle() is called every 5ms)
        .key_scan_func = u8KeyScan,  // Register interface function
        .key_call_trig_func = vKeyShort,  // Register callback function
        .key_call_cont_func = vKeyLong};  // Register callback function
    
    /**
     * @brief The interface of key scan.
     *
     * @return uint8_t The state of key, 1: key down, 0: key up.
     */
    uint8_t u8KeyScan(void)
    {
        return (get_boot_key() == 0 ? 0x01 : 0x00);
    }
    
    /**
     * @brief The callback interface of key short.
     *
     */
    void vKeyShort(void)
    {
        led_toggle(LED5);
        ESP_LOGI(TAG, "Key has been short pressed, Led5 have been toggle.");
    }
    
    /**
     * @brief The callback interface of key long.
     *
     */
    void vKeyLong(void)
    {
        led_toggle(LED4);
        ESP_LOGI(TAG, "Key has been long pressed, Led4 have been toggle.");
    }
    
    1. main()函数中定时调用key_handle()函数,注意具体间隔时间可能需要按照使用按键的类型做一定调整。
    /**
     * @brief Main function.
     *
     * @param pvParameters
     */
    void main(void)
    {
        // Initialize the gpio of key.
        gpio_init();
        while (1)
        {
            // put your main code here, to run Keepedly.
            key_handle(&pkey_config);
            vDelay_ms(5);
        }
    }
    

    参考资料

    1.新型的按键扫描程序,仅三行程序:https://www.amobbs.com/thread-4308630-1-1.html

    物联沃分享整理
    物联沃-IOTWORD物联网 » 适用于所有单片机的通用按键扫描算法【软件模块】

    发表评论