状态机理论:
每次只能执行状态机中的一个状态
注意:状态转移时有时会根据需要发生不同的动作(可根据不同条件发生不同的动作),此处的发生动作指返回按键码值(代表那个按键的对应状态)
如果是在长按状态在向释放状态迁移过程中,执行了获取长按的按键码值,则代表着是在长按结束时,系统响应长按。
分析该状态机:
四个状态:释放,消抖、短按、长按,三个动作:三个返回值:长按码值、单击按码值、双击码值。
关于长按在释放时执行还是在按下时执行分析:
如果是在长按状态在向释放状态迁移过程中,执行了获取长按的按键码值,则代表着是在长按结束时,系统响应长按。如果是在短按状态向长按状态迁移时,发生的动作(返回长按码值),则代表着是在长按开始时,执行了长按的动作。
在此代码中,按键状态主要有以下四种:
KEY_RELEASE (释放松开状态):表示按键处于未按下或已松开的状态。在这个状态下,代码检测是否有按键按下,如果按下,转移到消抖确认状态。
KEY_CONFIRM (消抖确认状态):用于处理按键的消抖过程。在这个状态下,代码检测按键是否稳定按下。如果按键稳定按下超过设定的消抖时间窗(CONFIRM_TIME),则转移到短按状态。如果按键在消抖时间窗内松开,返回释放状态。
KEY_SHORTPRESS (短按状态):表示按键已稳定按下,等待判断是短按、双击还是长按。在这个状态下,如果按键松开,转移回释放状态,并记录单击次数。如果按键持续按下超过长按时间窗(LONGPRESS_TIME),则转移到长按状态。
KEY_LONGPRESS (长按状态):表示按键已持续按下超过设定的长按时间窗。在这个状态下,如果按键松开,转移回释放状态,并返回长按的按键码值。
#include <stdint.h>
#include "gd32f30x.h"
#include "systick.h"
#include "delay.h"
// 定义按键引脚和RCU配置的结构体
typedef struct
{
rcu_periph_enum rcu; // 外设时钟
uint32_t gpio; // GPIO端口
uint32_t pin; // GPIO引脚
} Key_GPIO_t;
// 按键引脚配置表,只配置了一个按键
static Key_GPIO_t g_gpioList[] =
{
{RCU_GPIOC, GPIOC, GPIO_PIN_4}, // key1
};
// 定义按键的最大数量
#define KEY_NUM_MAX (sizeof(g_gpioList) / sizeof(g_gpioList[0]))
// 按键状态枚举
typedef enum
{
KEY_RELEASE = 0, // 释放松开
KEY_CONFIRM, // 消抖确认
KEY_SHORTPRESS, // 短按
KEY_LONGPRESS // 长按
} KEY_STATE;
// 定义时间窗口常量
#define CONFIRM_TIME 10 // 按键消抖时间窗10ms
#define DOUBLE_CLICK_INTERVAL_TIME 300 // 双击时间窗300ms
#define LONGPRESS_TIME 1000 // 长按时间窗1000ms
// 定义按键信息结构体
typedef struct
{
KEY_STATE keyState; // 按键当前状态
uint8_t singleClickNum; // 单击次数
uint64_t firstIoChangeSysTime; // 第一次按键状态改变的时间
uint64_t firstReleaseSysTime; // 第一次按键释放的时间
} Key_Info_t;
// 定义全局按键信息数组,保存所有按键的状态信息
static Key_Info_t g_keyInfo[KEY_NUM_MAX];
/**
***********************************************************
* @brief 按键硬件初始化
* @param 无
* @return 无
***********************************************************
*/
void KeyDrvInit(void)
{
for (uint8_t i = 0; i < KEY_NUM_MAX; i++)
{
// 启用对应GPIO端口的时钟
rcu_periph_clock_enable(g_gpioList[i].rcu);
// 配置GPIO引脚为上拉输入模式,速度为2MHz
gpio_init(g_gpioList[i].gpio, GPIO_MODE_IPU, GPIO_OSPEED_2MHZ, g_gpioList[i].pin);
}
}
/**
***********************************************************
* @brief 扫描按键状态并返回按键码值
* @param keyIndex 按键索引
* @return 按键码值,短按返回0x01,双击返回0x51,长按返回0x81
***********************************************************
*/
static uint8_t KeyScan(uint8_t keyIndex)
{
volatile uint64_t curSysTime;
uint8_t keyPress;
// 读取当前按键状态,0表示按下
keyPress = gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin);
switch (g_keyInfo[keyIndex].keyState)
{
case KEY_RELEASE: // 按键释放状态
if (!keyPress) // 如果按键被按下
{
// 切换到消抖状态,并记录按键按下的系统时间
g_keyInfo[keyIndex].keyState = KEY_CONFIRM;
g_keyInfo[keyIndex].firstIoChangeSysTime = GetSysRunTime();
break;
}
// 如果存在未处理的单击
if (g_keyInfo[keyIndex].singleClickNum != 0)
{
curSysTime = GetSysRunTime();
// 判断是否超出双击时间窗,如果超出,认为是单击
if (curSysTime - g_keyInfo[keyIndex].firstReleaseSysTime > DOUBLE_CLICK_INTERVAL_TIME)
{
g_keyInfo[keyIndex].singleClickNum = 0;
return (keyIndex + 1); // 返回单击按键码值
}
}
break;
case KEY_CONFIRM: // 按键消抖确认状态
if (!keyPress)
{
curSysTime = GetSysRunTime();
// 如果按键按下稳定超过消抖时间窗,则切换到短按状态
if (curSysTime - g_keyInfo[keyIndex].firstIoChangeSysTime > CONFIRM_TIME)
{
g_keyInfo[keyIndex].keyState = KEY_SHORTPRESS;
}
}
else // 如果按键松开,则回到释放状态
{
g_keyInfo[keyIndex].keyState = KEY_RELEASE;
}
break;
case KEY_SHORTPRESS: // 短按确认状态
if (keyPress)
{
g_keyInfo[keyIndex].keyState = KEY_RELEASE;
g_keyInfo[keyIndex].singleClickNum++; // 记录单击次数
// 第一次单击,记录按键释放时间
if (g_keyInfo[keyIndex].singleClickNum == 1)
{
g_keyInfo[keyIndex].firstReleaseSysTime = GetSysRunTime();
break;
}
else
{
curSysTime = GetSysRunTime();
// 如果双击间隔时间内按下第二次,认为是双击
if (curSysTime - g_keyInfo[keyIndex].firstReleaseSysTime <= DOUBLE_CLICK_INTERVAL_TIME)
{
g_keyInfo[keyIndex].singleClickNum = 0;
return (keyIndex + 0x51); // 返回双击按键码值
}
}
}
else
{
curSysTime = GetSysRunTime();
// 按键按下时间超过长按时间窗,认为是长按
if (curSysTime - g_keyInfo[keyIndex].firstIoChangeSysTime > LONGPRESS_TIME)
{
g_keyInfo[keyIndex].keyState = KEY_LONGPRESS;
}
}
break;
case KEY_LONGPRESS: // 长按状态
if (keyPress)
{
g_keyInfo[keyIndex].keyState = KEY_RELEASE;
return (keyIndex + 0x81); // 返回长按按键码值
}
break;
default:
g_keyInfo[keyIndex].keyState = KEY_RELEASE;
break;
}
return 0;
}
/**
***********************************************************
* @brief 获取按键码值
* @param 无
* @return 按键码值,短按返回0x01 0x02 0x03,长按返回0x81 0x82 0x83,没有按下返回0
***********************************************************
*/
uint8_t GetKeyVal(void)
{
uint8_t res = 0;
for (uint8_t i = 0; i < KEY_NUM_MAX; i++)
{
res = KeyScan(i);
if (res != 0)
{
break;
}
}
return res;
}
/**
***********************************************************
* @brief 带消抖的按键扫描
* @param keyIndex 按键索引
* @return 按键码值,按下返回keyIndex+1,否则返回0
***********************************************************
*/
static uint8_t KeyScanWithBlock(uint8_t keyIndex)
{
uint8_t keyPress;
keyPress = gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin);
if (keyPress) // 无按键按下
{
return 0;
}
DelayNms(CONFIRM_TIME); // 消抖延时
keyPress = gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin);
if (keyPress) // 无按键按下
{
return 0;
}
return (keyIndex + 1);
}
/**
***********************************************************
* @brief 带消抖的按键获取
* @param 无
* @return 按键码值,按下返回按键码值,否则返回0
***********************************************************
*/
uint8_t GetKeyValWithBlock(void)
{
uint8_t res = 0;
for (uint8_t i = 0; i < KEY_NUM_MAX; i++)
{
res = KeyScanWithBlock(i);
if (res != 0)
{
break;
}
}
return res;
}
方法二:一个状态对于一个返回值
#include <stdint.h> // 包含标准整数类型定义
#include "gd32f30x.h" // 包含GD32F30x系列微控制器的寄存器定义和相关功能
#include "systick.h" // 包含系统定时器相关的函数和定义
#include "delay.h" // 包含延时函数的声明和定义
// 定义按键的GPIO配置结构体
typedef struct {
rcu_periph_enum rcu; // 微控制器的时钟使能枚举
uint32_t gpio; // GPIO端口地址
uint32_t pin; // 引脚编号
} Key_GPIO_t;
// 定义按键GPIO列表,这里定义了一个按键连接到GPIOC的第4位
static Key_GPIO_t g_gpioList[] = {
{RCU_GPIOC, GPIOC, GPIO_PIN_4}, // key1
};
#define KEY_NUM_MAX (sizeof(g_gpioList) / sizeof(g_gpioList[0])) // 计算按键数量
// 定义按键状态枚举类型
typedef enum {
KEY_RELEASE = 0, // 按键未按下或已释放
KEY_DEBOUNCE, // 按键按下消抖状态
KEY_SHORTPRESS, // 按键短按
KEY_DOUBLECLICK_WAIT, // 等待判断是否双击状态
KEY_LONGPRESS // 按键长按
} KEY_STATE;
// 定义按键处理的时间参数
#define CONFIRM_TIME 10 // 消抖时间10ms
#define DOUBLE_CLICK_INTERVAL_TIME 300 // 双击间隔时间300ms
#define LONGPRESS_TIME 1000 // 长按时间1000ms
// 定义按键信息结构体,包含按键状态、点击次数和时间信息
typedef struct {
KEY_STATE keyState; // 当前按键状态
uint8_t clickCount; // 记录点击次数,用于区分单击和双击
uint64_t lastPressTime; // 记录按键最后一次按下的时间
uint64_t lastReleaseTime; // 记录按键最后一次释放的时间
} Key_Info_t;
// 初始化按键信息数组,数量为按键数量
static Key_Info_t g_keyInfo[KEY_NUM_MAX];
// 按键硬件初始化函数,配置GPIO为上拉输入模式
void KeyDrvInit(void) {
for (uint8_t i = 0; i < KEY_NUM_MAX; i++) {
rcu_periph_clock_enable(g_gpioList[i].rcu); // 使能GPIO时钟
gpio_init(g_gpioList[i].gpio, GPIO_MODE_IPU, GPIO_OSPEED_2MHZ, g_gpioList[i].pin); // 初始化GPIO
}
}
// 静态函数,用于扫描按键状态
static uint8_t KeyScan(uint8_t keyIndex) {
// 获取当前系统运行时间
uint64_t curSysTime = GetSysRunTime();
// 读取按键状态,这里假设按下为0
uint8_t keyPress = !gpio_input_bit_get(g_gpioList[keyIndex].gpio, g_gpioList[keyIndex].pin); // 按键逻辑可能需要根据实际硬件调整
// 根据按键当前状态和时间判断按键动作
switch (g_keyInfo[keyIndex].keyState) {
case KEY_RELEASE:
if (keyPress) { // 如果按键被按下
g_keyInfo[keyIndex].keyState = KEY_DEBOUNCE; // 迁移到消抖状态
g_keyInfo[keyIndex].lastPressTime = curSysTime; // 记录按下时间
}
break;
case KEY_DEBOUNCE:
if (keyPress) {
if (curSysTime - g_keyInfo[keyIndex].lastPressTime > CONFIRM_TIME) {
g_keyInfo[keyIndex].keyState = KEY_SHORTPRESS; // 迁移到短按状态
g_keyInfo[keyIndex].clickCount++; // 增加点击次数
}
} else {
g_keyInfo[keyIndex].keyState = KEY_RELEASE; // 如果按键释放,迁移到释放状态
}
break;
case KEY_SHORTPRESS:
if (!keyPress) { // 如果按键释放
g_keyInfo[keyIndex].lastReleaseTime = curSysTime; // 记录释放时间
if (g_keyInfo[keyIndex].clickCount == 1) {
g_keyInfo[keyIndex].keyState = KEY_DOUBLECLICK_WAIT; // 迁移到双击等待状态
} else {
// 如果不是双击,返回单击事件,并重置点击次数
g_keyInfo[keyIndex].keyState = KEY_RELEASE;
g_keyInfo[keyIndex].clickCount = 0;
return (keyIndex + 0x01); // 返回按键单击码值
}
} else if (curSysTime - g_keyInfo[keyIndex].lastPressTime > LONGPRESS_TIME) {
// 如果按下时间超过长按时间,迁移到长按状态
g_keyInfo[keyIndex].keyState = KEY_LONGPRESS;
}
break;
case KEY_DOUBLECLICK_WAIT:
if (keyPress) { // 如果在等待双击期间按键被按下
g_keyInfo[keyIndex].keyState = KEY_DEBOUNCE; // 重新进入消抖状态
} else if (curSysTime - g_keyInfo[keyIndex].lastReleaseTime > DOUBLE_CLICK_INTERVAL_TIME) {
// 如果超过双击间隔时间,确认为单击并返回
g_keyInfo[keyIndex].keyState = KEY_RELEASE;
g_keyInfo[keyIndex].clickCount = 0;
return (keyIndex + 0x51); // 返回按键双击码值
}
break;
case KEY_LONGPRESS:
if (!keyPress) { // 如果长按后按键释放
g_keyInfo[keyIndex].keyState = KEY_RELEASE; // 返回释放状态
return (keyIndex + 0x81); // 返回按键长按码值
}
break;
default:
g_keyInfo[keyIndex].keyState = KEY_RELEASE; // 任何未知状态都重置为释放状态
break;
}
return 0; // 如果没有按键事件或按键事件已处理,则返回0
}
// 获取按键码值的函数,遍历所有按键并调用KeyScan函数
uint8_t GetKeyVal(void) {
uint8_t res = 0; // 初始化结果为0
for (uint8_t i = 0; i < KEY_NUM_MAX; i++) {
res = KeyScan(i); // 调用KeyScan函数扫描按键
if (res != 0) {
break; // 如果有按键事件发生,返回结果并退出循环
}
}
return res; // 返回按键码值,如果没有按键事件则返回0
}
- 实现细节
KEY_RELEASE:初始状态或按键松开后的状态,等待按键按下。
KEY_DEBOUNCE:按键按下后,进入消抖状态,确保按键状态稳定。
KEY_SHORTPRESS:消抖结束后,进入短按状态,检测是否为双击或长按。
KEY_DOUBLECLICK_WAIT:短按后松开,等待双击的第二次按下,如果超时,返回单击事件。
KEY_LONGPRESS:在短按状态下,按键持续按住超过长按时间,进入长按状态,松开后返回长按事件。