通用button代码模块(使用状态机+回调)

郎弘壮
2023-12-01

通常普通的按键扫描程序,网上一大堆,基于扫描延时防抖等简单的操作,这里要讲的的遇到复杂的按键处理程序,

普通按键扫描:基于 一个按键的短按长按释放

复杂的按键扫描:有组合按键,且按键也有长短按,以及释放

现在如何实现一个按键扫描模块去处理这种按键扫描程序呢?这里用到按键状态机为基础框架。对按键编码能较好的实现复杂按键处理。

一、对按键进行编码具体实现参考如下

/*
 * libbutton.h
 *
 *      Author: zc-kernel
 */

#ifndef LIBBUTTON_H_
#define LIBBUTTON_H_


typedef void (*user_button_event_callback)(void *arg);



typedef enum
{
	USER_BUTTON  = 0,       //定义按键个数
    USER_BUTTON_MAX      
} user_button_index_t;


#define UKEY_BUTTON_PRESSED_HIGH       1
#define UKEY_BUTTON_PRESSED_LOW        0

#define SCAN_INTERVAL_MS           50
#define DEBOUNCE_TICKS_CNT         3
#define SHORT_TICKS_CNT            (800 / SCAN_INTERVAL_MS)
#define LONG_TICKS_CNT             (2400 / SCAN_INTERVAL_MS)
#define LONG_HOLD_TICKS_CNT        (5000 / SCAN_INTERVAL_MS)
#define MAX_COMBOS_CLICK_SOLT      (300 / SCAN_INTERVAL_MS)       // Combos slot, default 300ms */

/****************************user define  Region*******************************************/



typedef enum
{
    KEY_BTN_PRESS_DOWN = 0,
    KEY_BTN_PRESS_CLICK,
    KEY_BTN_PRESS_DOUBLE_CLICK,
    KEY_BTN_PRESS_TRIPLE_CLICK,
    KEY_BTN_PRESS_FOUR_CLICK,
    KEY_BTN_PRESS_FIVE_CLICK,
    KEY_BTN_PRESS_REPEAT_CLICK =6,
    KEY_BTN_PRESS_UP,
    KEY_BTN_PRESS_SHORT_START,
    KEY_BTN_PRESS_SHORT_UP,
    KEY_BTN_PRESS_LONG_START,
    KEY_BTN_PRESS_LONG_UP,
    KEY_BTN_PRESS_LONG_HOLD,
    KEY_BTN_PRESS_LONG_HOLD_UP,
    KEY_BTN_EVENT_NONE,
    KEY_BTN_EVENT_MAX,
} user_button_event_t;



/*****************************user define  Region**************************************/


enum KEY_BTN_STAGE
{
    KEY_BTN_STAGE_DEFAULT = 0,
    KEY_BTN_STAGE_DOWN    = 1,
    KEY_BTN_STAGE_COMBOS  = 2
};

typedef void  (*start_btnscan_timer)();
typedef void  (*stop_btnscan_timer)();
typedef void  (*btnscan_timer_init)();


typedef struct
{
	btnscan_timer_init   btnscan_init;
	start_btnscan_timer  start_btnscan;
	stop_btnscan_timer   stop_btnscan;
}btnscan_timer_t;



typedef struct _user_button
{

    uint8_t  (*u_button_read)(void *);

    uint16_t  scan_cnt;
    uint16_t  click_cnt;
    uint16_t  max_combos_click_solt;

    uint16_t debounce_tick;
    uint16_t short_press_start_tick;
    uint16_t long_press_start_tick;
    uint16_t long_hold_start_tick;
    uint8_t  id;
    uint8_t  pressed_logic_level : 1;
    uint8_t  event               : 4;
    uint8_t  status              : 3;

    user_button_event_callback  CallBack_Function[KEY_BTN_EVENT_MAX];
    btnscan_timer_t btnscan_timer;
    struct _user_button* next;
} user_button_t;



 void user_button_scan_handle(void);
 void user_button_creat(user_button_index_t button_idx,uint8_t(*get_pin_level)(),uint8_t active_level,btnscan_timer_t btnscan_timer);
 void button_event_add(user_button_index_t button_idx,user_button_event_t btn_event,user_button_event_callback btn_callback);

 extern uint32_t g_btn_status_reg ;

 extern uint32_t g_logic_level;
 extern user_button_t user_button[USER_BUTTON_MAX];
#endif /* LIBBUTTON_H_ */

二、定义实现上部分就可以对按键检测编码了,按键检测,得到按键键值编码g_btn_status_reg 。

  

/**
 * @brief Read all key values in one scan cycle
 *
 * @param void
 * @return none
*/
static void my_user_button_read(void)
{
    uint8_t button_index;
    user_button_t* target;

    /* The button that was registered first, the button value is in the low position of raw_data */
    uint32_t raw_data = 0;

    for(target = btn_head, button_index = USER_BUTTON_MAX - 1;
        (target != NULL) && (target->u_button_read != NULL);
        target = target->next, button_index--)
    {
        raw_data = raw_data | ((target->u_button_read)(target) << (target->id));
    }
    g_btn_status_reg = (~raw_data) ^ g_logic_level;

    //my_printf_uart("g_btn_status_reg = %02x g_logic_level = %02x\n" ,g_btn_status_reg,g_logic_level);
}

三、最后也是最重要的一步就是按键状态扫描的实现。

/*
 * buttonlib.c
 *
 *      Author: zc-kernel
 */


#include "buttonlib.h"

#ifndef NULL
#define NULL 0
#endif



#define EVENT_SET_AND_EXEC_CB(btn, evt)                                        \
    do                                                                         \
    {                                                                          \
        btn->event = evt;                                                      \
        if(btn->CallBack_Function[evt])                                                            \
              btn->CallBack_Function[evt]((user_button_t*)btn);                                      \
    } while(0);





static user_button_t *btn_head = NULL;


 user_button_t user_button[USER_BUTTON_MAX];

/**
 * BTN_IS_PRESSED
 *
 * 1: is pressed
 * 0: is not pressed
*/
#define BTN_IS_PRESSED(i) (g_btn_status_reg & (1 << i))

/**
 * g_logic_level
 *
 * The logic level of the button pressed,
 * Each bit represents a button.
 *
 * First registered button, the logic level of the button pressed is
 * at the low bit of g_logic_level.
*/
uint32_t g_logic_level = (uint32_t)0;

/**
 * g_btn_status_reg
 *
 * The status register of all button, each bit records the pressing state of a button.
 *
 * First registered button, the pressing state of the button is
 * at the low bit of g_btn_status_reg.
*/
uint32_t g_btn_status_reg = (uint32_t)0;



/**
 * @brief Register a user button
 *
 * @param button: button structure instance
 * @return Number of keys that have been registered
*/
static uint8_t Add_Button(user_button_t *button)
{
    user_button_t *curr = btn_head;

    if (!button || (USER_BUTTON_MAX > sizeof(uint32_t) * 8))
    {
        return -1;
    }

    while (curr)
    {
        if(curr == button)
        {
            return -1;  //already exist.
        }
        curr = curr->next;
    }

    /**
     * First registered button is at the end of the 'linked list'.
     * btn_head points to the head of the 'linked list'.
    */
    button->next = btn_head;
    button->status = KEY_BTN_STAGE_DEFAULT;
    button->event = KEY_BTN_EVENT_NONE;
    button->scan_cnt = 0;
    button->click_cnt = 0;
    button->max_combos_click_solt = MAX_COMBOS_CLICK_SOLT;
    btn_head = button;

    /**
     * First registered button, the logic level of the button pressed is
     * at the low bit of g_logic_level.
    */
    g_logic_level |= (button->pressed_logic_level << (button->id));

    return 1;
}

/**
 * @brief Read all key values in one scan cycle
 *
 * @param void
 * @return none
*/
static void my_user_button_read(void)
{
    uint8_t button_index;
    user_button_t* target;

    /* The button that was registered first, the button value is in the low position of raw_data */
    uint32_t raw_data = 0;

    for(target = btn_head, button_index = USER_BUTTON_MAX - 1;
        (target != NULL) && (target->u_button_read != NULL);
        target = target->next, button_index--)
    {
        raw_data = raw_data | ((target->u_button_read)(target) << (target->id));
    }
    g_btn_status_reg = (~raw_data) ^ g_logic_level;

    //my_printf_uart("g_btn_status_reg = %02x g_logic_level = %02x\n" ,g_btn_status_reg,g_logic_level);
}


/**
 * @brief Handle all key events in one scan cycle.
 *        Must be used after 'user_button_read' API
 *
 * @param void
 * @return none
*/

static void user_button_process(void)
{
    uint8_t button_idx_num;
    user_button_t* target = NULL;

    for (target = btn_head, button_idx_num = USER_BUTTON_MAX - 1; target != NULL; target = target->next, button_idx_num --)
    {
          if (target->status > KEY_BTN_STAGE_DEFAULT)
          {
              target->scan_cnt ++;
          }

        switch (target->status)
        {
        case KEY_BTN_STAGE_DEFAULT: // stage: default(button up)

            if (BTN_IS_PRESSED(button_idx_num)) // is pressed
            {
            	//my_printf_uart("button_idx_num = %d, press\n",button_idx_num );
				target->scan_cnt = 0;
				target->click_cnt = 0;
				EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_DOWN);

				/* swtich to button down stage */
				target->status = KEY_BTN_STAGE_DOWN;


           }else
           {
        	    //my_printf_uart("button_idx_num = %d, no press and  scan end \n",button_idx_num );
                target->scan_cnt = 0;
                EVENT_SET_AND_EXEC_CB(target, KEY_BTN_EVENT_NONE);
           }
            break;

        case KEY_BTN_STAGE_DOWN: // stage: button down


            if (BTN_IS_PRESSED(button_idx_num)) // is pressed
            {

                if (target->click_cnt > 0) // combos
                {

                    if (target->scan_cnt > target->max_combos_click_solt)  //
                    {
                    	//my_printf_uart(" combos_click handle   end .... \n" );

                        EVENT_SET_AND_EXEC_CB(target,
                            target->click_cnt < KEY_BTN_PRESS_REPEAT_CLICK ?
                                target->click_cnt :
                                KEY_BTN_PRESS_REPEAT_CLICK);

                        /* swtich to button down stage */
                        target->status = KEY_BTN_STAGE_DOWN;
                        target->scan_cnt = 0;
                        target->click_cnt = 0;

                    }
                }
                else if (target->scan_cnt >= target->long_hold_start_tick)
                {
                    if (target->event != KEY_BTN_PRESS_LONG_HOLD)
                    {
                        EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_LONG_HOLD);
                    }
                }
                else if (target->scan_cnt >= target->long_press_start_tick)
                {
                    if (target->event != KEY_BTN_PRESS_LONG_START)
                    {
                        EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_LONG_START);
                    }
                }
                else if (target->scan_cnt >= target->short_press_start_tick)
                {
                    if (target->event != KEY_BTN_PRESS_SHORT_START)
                    {
                        EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_SHORT_START);
                    }
                }
            }
            else // is up
            {

                if (target->scan_cnt >= target->long_hold_start_tick)
                {
                    EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_LONG_HOLD_UP);
                    target->status = KEY_BTN_STAGE_DEFAULT;

                }
                else if (target->scan_cnt >= target->long_press_start_tick)
                {
                    EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_LONG_UP);
                    target->status = KEY_BTN_STAGE_DEFAULT;

                }
                else if (target->scan_cnt >= target->short_press_start_tick)
                {
                    EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_SHORT_UP);
                    target->status = KEY_BTN_STAGE_DEFAULT;


                }
                else
                {

                	//my_printf_uart("combos_click handle start...  \n" );
                    /* swtich to combos stage */
                    target->status = KEY_BTN_STAGE_COMBOS;
                    target->click_cnt ++;
                    EVENT_SET_AND_EXEC_CB(target, KEY_BTN_PRESS_UP);


                }
            }
            break;

        case KEY_BTN_STAGE_COMBOS: // stage: combos

            if (BTN_IS_PRESSED(button_idx_num)) // is pressed
            {
                /* swtich to button down stage */
                target->status = KEY_BTN_STAGE_DOWN;
                target->scan_cnt = 0;
            }
            else
            {

                if (target->scan_cnt > target->max_combos_click_solt)
                {
                	//my_printf_uart(" combos_click handle   end .... \n" );
                    EVENT_SET_AND_EXEC_CB(target,
                        target->click_cnt < KEY_BTN_PRESS_REPEAT_CLICK ?
                            target->click_cnt :
                            KEY_BTN_PRESS_REPEAT_CLICK);

                    /* swtich to default stage */
                    target->status = KEY_BTN_STAGE_DEFAULT;
                }
            }
            break;
        }

   }

}
/**
 * user_button_event_read
 *
 * @brief Get the button event of the specified button.
 *
 * @param button: button structure instance
 * @return button event
*/
user_button_event_t user_button_event_read(user_button_t* button)
{
    return (user_button_event_t)(button->event);
}

/**
 * user_button_scan
 *
 * @brief Start key scan.
 *        Need to be called cyclically within the specified period.
 *        Sample cycle: 5 - 20ms
 *
 * @param void
 * @return none
*/
void user_button_scan_handle(void)
{
	my_user_button_read();
    user_button_process();

}


void button_event_add(user_button_index_t button_idx,user_button_event_t btn_event,user_button_event_callback btn_callback)
{

  user_button[button_idx].CallBack_Function[btn_event] = btn_callback;
}


void user_button_creat(user_button_index_t button_idx,uint8_t(*get_pin_level)(),uint8_t active_level,btnscan_timer_t btnscan_timer)
{


    user_button[button_idx].id = button_idx;
    user_button[button_idx].u_button_read = get_pin_level;
    user_button[button_idx].pressed_logic_level = active_level;

    user_button[button_idx].debounce_tick = DEBOUNCE_TICKS_CNT;
    user_button[button_idx].short_press_start_tick = SHORT_TICKS_CNT;
    user_button[button_idx].long_press_start_tick = LONG_TICKS_CNT;
    user_button[button_idx].long_hold_start_tick = LONG_HOLD_TICKS_CNT;

	user_button[button_idx].btnscan_timer.btnscan_init = btnscan_timer.btnscan_init;
	user_button[button_idx].btnscan_timer.start_btnscan = btnscan_timer.start_btnscan;
	user_button[button_idx].btnscan_timer.stop_btnscan = btnscan_timer.stop_btnscan;

    Add_Button(&user_button[button_idx]);

}


四、最后就是在各个按键状态的基础上实现处理函数,这里就可以对编码的那就值进行解析,分别处理,对于按键释放有组合也很好处理。

void Button_Init()
{
	  btnscan_timer_t button0_timer = {

		.btnscan_init  = NULL,
		.start_btnscan = buttonScantimer_start,
		.stop_btnscan = buttonScantimer_stop,
		};

	  user_button_creat(USER_BUTTON,get_button_pin_level,UKEY_BUTTON_PRESSED_LOW,button0_timer);
	  button_event_add(USER_BUTTON,KEY_BTN_PRESS_LONG_START,BtnLongBttonDownCallBack);
	  button_event_add(USER_BUTTON,KEY_BTN_EVENT_NONE,BtnNoneCallBack);
}

 类似资料: