单片机学习经历和模板

引言
  • 按键状态机在单片机开发中的重要性
  • 传统按键检测方法的局限性(如延时消抖)
  • 状态机方法的优势(实时性、可扩展性)

话不多说

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;
}
/* ----------------------------------------------------------------- */


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值