引言
- 按键状态机在单片机开发中的重要性
- 传统按键检测方法的局限性(如延时消抖)
- 状态机方法的优势(实时性、可扩展性)
话不多说
1.添加自己的按键读取函数和触发回调函数

2.注册状态机

3.状态机轮询(ms)

4.源代码,可选择需要的功能(双击、长按),以节省内存
#include "key.h"
/* ---------------------------状态机结构体--------------------------- */
struct KEY_StateMachine
{
KeyState current_state; // 当前按键状态
uint32_t current_time; // 当前扫描时刻
uint32_t last_time; // 状态变化时刻
#if ENABLE_LONG_CLICK
uint8_t long_pressed; // 标记是否已经长按过
#endif
#if ENABLE_DOUBLE_CLICK
uint8_t click_count; // 单击计数(用于双击判断)
#else
uint8_t reserved;
#endif
Trigger_Action click_action; // 单击触发函数
#if ENABLE_DOUBLE_CLICK
Trigger_Action double_click_action; // 双击触发函数
#endif
Trigger_Action long_click_action; // 长按触发函数
Read_Key_Pin read_key; // 读取引脚电平函数
};
// 按键状态机实例(每个按键一个)
static KEY_StateMachine key_sms[KEY_NUM] =
{
KEY_SMS_INIT
};
#define KEYSMS_TABLE_SIZE (sizeof(key_sms)/sizeof(key_sms[0]))
/* ----------------------------------------------------------------- */
/* --------------------------辅助函数和工具-------------------------- */
// 时间差计算
inline static uint32_t time_diff(KEY_StateMachine* key_sm)
{
uint32_t curr = key_sm->current_time;
uint32_t last = key_sm->last_time;
if (curr < last)
return (0xFFFFFFFF - last) + curr + 1;
else
return curr - last;
}
/* ----------------------------------------------------------------- */
/* -----------------------------动作函数----------------------------- */
// 空事件,不做处理
static void action_none(KEY_StateMachine* key_sm)
{
/* --- */
}
// 按键开始按下,记录开始按下时间
static void action_press_start(KEY_StateMachine* key_sm)
{
key_sm->last_time = key_sm->current_time;
#if ENABLE_LONG_CLICK
key_sm->long_pressed = 0; // 新一次按下时清除长按标志
#endif
}
static void action_click(KEY_StateMachine* key_sm)
{
#if ENABLE_DOUBLE_CLICK
#if ENABLE_LONG_CLICK
// 只在双击超时后才触发单击
if (key_sm->click_action && key_sm->click_count == 1 && !key_sm->long_pressed)
{
key_sm->click_action();
key_sm->click_count = 0;
}
#else
// 只在双击超时后才触发单击
if (key_sm->click_action && key_sm->click_count == 1)
{
key_sm->click_action();
key_sm->click_count = 0;
}
#endif
#else
#if ENABLE_LONG_CLICK
// 直接触发单击
if (key_sm->click_action && !key_sm->long_pressed)
{
key_sm->click_action();
}
#else
// 直接触发单击
if (key_sm->click_action)
{
key_sm->click_action();
}
#endif
#endif
}
#if ENABLE_DOUBLE_CLICK
// 双击触发
static void action_double_click(KEY_StateMachine* key_sm)
{
#if ENABLE_LONG_CLICK
if (key_sm->double_click_action && key_sm->click_count == 2 && !key_sm->long_pressed)
{
key_sm->double_click_action();
key_sm->click_count = 0; // 重置单击计数
}
#else
if (key_sm->double_click_action && key_sm->click_count == 2)
{
key_sm->double_click_action();
key_sm->click_count = 0; // 重置单击计数
}
#endif
}
#endif
#if ENABLE_LONG_CLICK
// 长按触发
static void action_long_press(KEY_StateMachine* key_sm)
{
if (key_sm->long_click_action && !key_sm->long_pressed) // 只触发一次长按
{
key_sm->long_click_action();
key_sm->long_pressed = 1;
#if ENABLE_DOUBLE_CLICK
key_sm->click_count = 0; // 长按时重置单击计数,防止误触发单击
#endif
}
}
#endif
// 按键开始松开,记录开始松开时间
static void action_release_start(KEY_StateMachine* key_sm)
{
key_sm->last_time = key_sm->current_time;
}
#if ENABLE_DOUBLE_CLICK
// 第一次松开,等待双击
static void action_first_release(KEY_StateMachine* key_sm)
{
key_sm->last_time = key_sm->current_time;
key_sm->click_count = 1; // 标记为第一次按下
}
// 第二次按下,准备双击
static void action_second_press(KEY_StateMachine* key_sm)
{
key_sm->last_time = key_sm->current_time;
key_sm->click_count = 2; // 标记为第二次按下
}
#endif
#if ENABLE_LONG_CLICK
// 长按后松开,直接返回空闲状态
static void action_long_press_release(KEY_StateMachine* key_sm)
{
#if ENABLE_DOUBLE_CLICK
key_sm->click_count = 0; // 重置单击计数
#endif
key_sm->long_pressed = 0; // 重置长按标志
}
#endif
/* ----------------------------------------------------------------- */
/* ----------------------------状态转移表---------------------------- */
typedef struct
{
KeyState current_state; // 按键当前状态
KeyEvent event; // 事件
KeyState next_state; // 按键下一个状态
ActionFunc action; // 动作函数
}
StateTransition;
#if ENABLE_DOUBLE_CLICK
static const StateTransition state_table[] =
{
{ KEY_IDLE, EVENT_PRESS, KEY_PRESS_DEBOUNCE, action_press_start },
{ KEY_PRESS_DEBOUNCE, EVENT_PRESS, KEY_PRESSED, action_none },
{ KEY_PRESS_DEBOUNCE, EVENT_NONE, KEY_PRESS_DEBOUNCE, action_none },
{ KEY_PRESS_DEBOUNCE, EVENT_TIMEOUT, KEY_IDLE, action_none },
{ KEY_PRESSED, EVENT_RELEASE, KEY_RELEASE_DEBOUNCE, action_first_release },
#if ENABLE_LONG_CLICK
{ KEY_PRESSED, EVENT_LONG_PRESS, KEY_PRESSED, action_long_press },
#endif
{ KEY_RELEASE_DEBOUNCE, EVENT_RELEASE, KEY_WAIT_DOUBLE_CLICK, action_none },
{ KEY_RELEASE_DEBOUNCE, EVENT_NONE, KEY_RELEASE_DEBOUNCE, action_none },
{ KEY_RELEASE_DEBOUNCE, EVENT_TIMEOUT, KEY_PRESSED, action_none },
{ KEY_WAIT_DOUBLE_CLICK, EVENT_PRESS, KEY_SECOND_PRESS_DEBOUNCE, action_second_press },
{ KEY_WAIT_DOUBLE_CLICK, EVENT_TIMEOUT, KEY_IDLE, action_click },
{ KEY_WAIT_DOUBLE_CLICK, EVENT_NONE, KEY_WAIT_DOUBLE_CLICK, action_none },
{ KEY_SECOND_PRESS_DEBOUNCE, EVENT_PRESS, KEY_SECOND_PRESSED, action_none },
{ KEY_SECOND_PRESS_DEBOUNCE, EVENT_NONE, KEY_SECOND_PRESS_DEBOUNCE, action_none },
{ KEY_SECOND_PRESS_DEBOUNCE, EVENT_TIMEOUT, KEY_WAIT_DOUBLE_CLICK, action_none },
{ KEY_SECOND_PRESSED, EVENT_RELEASE, KEY_SECOND_RELEASE_DEBOUNCE, action_release_start },
#if ENABLE_LONG_CLICK
{ KEY_SECOND_PRESSED, EVENT_LONG_PRESS, KEY_IDLE, action_long_press },
#endif
{ KEY_SECOND_RELEASE_DEBOUNCE, EVENT_RELEASE, KEY_IDLE, action_double_click },
{ KEY_SECOND_RELEASE_DEBOUNCE, EVENT_NONE, KEY_SECOND_RELEASE_DEBOUNCE, action_none },
{ KEY_SECOND_RELEASE_DEBOUNCE, EVENT_TIMEOUT, KEY_SECOND_PRESSED, action_none },
#if ENABLE_LONG_CLICK
{ KEY_PRESSED, EVENT_RELEASE_AFTER_LONG, KEY_IDLE, action_long_press_release },
#endif
};
#else
static const StateTransition state_table[] =
{
{ KEY_IDLE, EVENT_PRESS, KEY_PRESS_DEBOUNCE, action_press_start },
{ KEY_PRESS_DEBOUNCE, EVENT_PRESS, KEY_PRESSED, action_none },
{ KEY_PRESS_DEBOUNCE, EVENT_NONE, KEY_PRESS_DEBOUNCE, action_none },
{ KEY_PRESS_DEBOUNCE, EVENT_TIMEOUT, KEY_IDLE, action_none },
{ KEY_PRESSED, EVENT_RELEASE, KEY_RELEASE_DEBOUNCE, action_release_start },
#if ENABLE_LONG_CLICK
{ KEY_PRESSED, EVENT_LONG_PRESS, KEY_PRESSED, action_long_press },
#endif
{ KEY_RELEASE_DEBOUNCE, EVENT_RELEASE, KEY_IDLE, action_click },
{ KEY_RELEASE_DEBOUNCE, EVENT_NONE, KEY_RELEASE_DEBOUNCE, action_none },
{ KEY_RELEASE_DEBOUNCE, EVENT_TIMEOUT, KEY_PRESSED, action_none },
#if ENABLE_LONG_CLICK
{ KEY_PRESSED, EVENT_RELEASE_AFTER_LONG, KEY_IDLE, action_long_press_release },
#endif
};
#endif
#define STATE_TABLE_SIZE (sizeof(state_table)/sizeof(state_table[0]))
/* ----------------------------------------------------------------- */
/* ------------------------按键状态机实现函数------------------------- */
// 初始化接口
#if ENABLE_DOUBLE_CLICK && ENABLE_LONG_CLICK
KEY_StateMachine* key_init(
KeyID key_id,
Read_Key_Pin read_key,
Trigger_Action click_action,
Trigger_Action double_action,
Trigger_Action long_action
)
#elif ENABLE_DOUBLE_CLICK
KEY_StateMachine* key_init(
KeyID key_id,
Read_Key_Pin read_key,
Trigger_Action click_action,
Trigger_Action double_action // 启用双击时,传入单击和双击动作
)
#elif ENABLE_LONG_CLICK
KEY_StateMachine* key_init(
KeyID key_id,
Read_Key_Pin read_key,
Trigger_Action click_action,
Trigger_Action long_action // 启用长按时,传入单击和长按动作
)
#else
KEY_StateMachine* key_init(
KeyID key_id,
Read_Key_Pin read_key,
Trigger_Action click_action // 只有单击时,传入单击动作
)
#endif
{
if (key_id < KEY_NUM)
{
key_sms[key_id].click_action = click_action;
#if ENABLE_DOUBLE_CLICK
key_sms[key_id].double_click_action = double_action;
#endif
#if ENABLE_LONG_CLICK
key_sms[key_id].long_click_action = long_action;
#endif
key_sms[key_id].read_key = read_key;
#if ENABLE_LONG_CLICK
key_sms[key_id].long_pressed = 0;
#endif
#if ENABLE_DOUBLE_CLICK
key_sms[key_id].click_count = 0;
#endif
return &key_sms[key_id];
}
return NULL;
}
// 获取当前按键事件函数,根据按键当前状态和硬件输入进行判断
KeyEvent get_current_event(KEY_StateMachine* key_sm)
{
KeyState state = key_sm->current_state;
uint32_t diff = time_diff(key_sm);
switch (state)
{
case KEY_PRESS_DEBOUNCE:
case KEY_RELEASE_DEBOUNCE:
#if ENABLE_DOUBLE_CLICK
case KEY_SECOND_PRESS_DEBOUNCE:
case KEY_SECOND_RELEASE_DEBOUNCE:
#endif
// 消抖状态检查
if (diff >= DEBOUNCE_DELAY)
{
switch (state)
{
case KEY_PRESS_DEBOUNCE:
return key_sm->read_key() ? EVENT_PRESS : EVENT_TIMEOUT;
case KEY_RELEASE_DEBOUNCE:
return !key_sm->read_key() ? EVENT_RELEASE : EVENT_TIMEOUT;
#if ENABLE_DOUBLE_CLICK
case KEY_SECOND_PRESS_DEBOUNCE:
return key_sm->read_key() ? EVENT_PRESS : EVENT_TIMEOUT;
case KEY_SECOND_RELEASE_DEBOUNCE:
return !key_sm->read_key() ? EVENT_RELEASE : EVENT_TIMEOUT;
#endif
default:
break;
}
}
return EVENT_NONE;
#if ENABLE_DOUBLE_CLICK
case KEY_WAIT_DOUBLE_CLICK:
// 等待双击状态超时检查
if (diff >= DOUBLE_CLICK_DELAY)
{
return EVENT_TIMEOUT;
}
// 等待双击状态的按下检测
if (key_sm->read_key())
{
return EVENT_PRESS;
}
return EVENT_NONE;
case KEY_PRESSED:
#if ENABLE_LONG_CLICK
// 第一次按下状态
if (!key_sm->long_pressed)
{
// 长按检查
if (diff >= LONG_PRESS_DELAY)
{
return EVENT_LONG_PRESS;
}
// 松开检测(非长按情况)
if (!key_sm->read_key())
{
return EVENT_RELEASE;
}
}
else
{
// 长按状态下的松开检测
if (!key_sm->read_key())
{
return EVENT_RELEASE_AFTER_LONG;
}
}
return EVENT_NONE;
#else
// 松开检测(非长按情况)
if (!key_sm->read_key())
{
return EVENT_RELEASE;
}
return EVENT_NONE;
#endif
case KEY_SECOND_PRESSED:
#if ENABLE_LONG_CLICK
// 第二次按下状态
if (!key_sm->long_pressed)
{
// 长按检查
if (diff >= LONG_PRESS_DELAY)
{
return EVENT_LONG_PRESS;
}
// 松开检测
if (!key_sm->read_key())
{
return EVENT_RELEASE;
}
}
#else
// 松开检测
if (!key_sm->read_key())
{
return EVENT_RELEASE;
}
#endif
return EVENT_NONE;
#else
case KEY_PRESSED:
#if ENABLE_LONG_CLICK
if (!key_sm->long_pressed)
{
if (diff >= LONG_PRESS_DELAY)
{
return EVENT_LONG_PRESS;
}
if (!key_sm->read_key())
{
return EVENT_RELEASE;
}
}
else
{
if (!key_sm->read_key())
{
return EVENT_RELEASE_AFTER_LONG;
}
}
return EVENT_NONE;
#else
// 直接判断按键是否释放
if (!key_sm->read_key()) // 按键松开
{
return EVENT_RELEASE;
}
return EVENT_NONE;
#endif
#endif
case KEY_IDLE:
// 空闲状态的按下检测
return key_sm->read_key() ? EVENT_PRESS : EVENT_NONE;
default:
// 其他状态(理论上不会执行到这里)
return EVENT_NONE;
}
}
// 主扫描函数
void key_scan(KEY_StateMachine* key_sm, uint32_t current_tick)
{
// 更新当前时间
key_sm->current_time = current_tick;
// 获取当前的按键事件
KeyEvent event = get_current_event(key_sm);
// 初始化动作为无操作
ActionFunc Action = action_none;
// 默认下一个状态与当前状态相同
KeyState next_state = key_sm->current_state;
// 查找状态转移表,寻找对应的当前状态和事件
for (int i = 0; i < STATE_TABLE_SIZE; ++i)
{
if ((state_table[i].current_state == key_sm->current_state) && (state_table[i].event == event))
{
// 找到匹配,设置下一个状态和响应动作
next_state = state_table[i].next_state;
Action = state_table[i].action;
break;
}
}
// 执行动作函数
Action(key_sm);
// 更新按键状态
key_sm->current_state = next_state;
}
/* ----------------------------------------------------------------- */

6416

被折叠的 条评论
为什么被折叠?



