单片机按键扫描是指利用微控制器(MCU)的数字输入口,定期检测按键的电平状态并进行去抖动处理(防止误操作),判断按键是否被按下或松开。
常用的按键有两种:自复式按键和自锁式按键。自复式按键按下就通,松开就断,不会锁住。自锁式按键按下一次就通并锁住,再按一次就断并弹回,需要两次操作。
本文介绍一种用于自复式按键的扫描算法,它有软件消抖功能,可以检测按键的短按和长按检测。
针对每一个独立按键,使用三个bits来标志按键的各个状态的转换:
ReadBit = 0
,状态标志TrigFlag = 0
,IdleFlag = 0
,ContFlag = 0
;ReadBit = 1
,经计算处理TrigFlag = 1
,IdleFlag = 0
,ContFlag = 1
;ReadBit = 1
,经计算处理TrigFlag = 0
,IdleFlag = 0
,ContFlag = 1
,TrigFlag = 1
,IdleFlag = 0
,ContFlag = 1
变到TrigFlag = 0
,IdleFlag = 0
,ContFlag = 1
可以判断按键被按下;ReadBit = 1
,状态标志也将保持TrigFlag = 0
,IdleFlag = 0
,ContFlag = 1
不再变化;TrigFlag = 0
,IdleFlag = 0
,ContFlag = 1
的时间达到设置的按键长按时间,可以判断按键被长按;ReadBit = 0
,经计算处理TrigFlag = 0
,IdleFlag = 1
,ContFlag = 0
;ReadBit = 0
,经计算处理TrigFlag = 0
,IdleFlag = 0
,ContFlag = 0
,TrigFlag = 0
,IdleFlag = 1
,ContFlag = 0
变到TrigFlag = 0
,IdleFlag = 0
,ContFlag = 1
可以判断按键被松开;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
// 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;
}
}
}
注意:如果只使能按键短按检测,按键短按的回调函数将在按键被按下时调用;如果只使能按键长按检测,按键长按的回调函数将在按键被按下到达设定的时间后调用;如果同时使能按键短按和长按检测,按键短按的回调函数将在按键被松开时调用,按键长按的回调函数将在按键被按下到达设定的时间后调用。
使用方法
#include "bsp_key.h"
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_func
和pkey_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.");
}
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