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

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


前言

单片机按键扫描是指利用微控制器(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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值