独立按键扫描实现单击、长按、组合单击事件(可重注册或取消按键事件)

编译器平台及例程说明

编译器平台说明

1. Toolchain : MDK-ARM Community  Version: 5.30.0.0(Kile V5)
2. Encoding  : Encode in UTF-8 without signature
3. Device Specific Packs : Keil.STM32H7xx_DFP.2.7.0.pack
4. Preprocessor Symbols Define : USE_HAL_DRIVER, STM32H743xx
    USE_HAL_DRIVER : 允许使用HAL库驱动
    STM32H743xx    : STM32H743xx芯片
5. Chip Type : STM32H743VIT6, Flash:2MByte, ROM:1MByte
6. 在stm32h7xx_hal_conf.h文件中HSE_VALUE为外部高速振荡器,默认25MHZ。此值必须与硬件保持一致。

例程说明

1. 独立按键扫描实现单击、长按、组合单击事件(可重注册或取消按键事件)

一、宏定义说明

// PE3  -- User Key1 -- 用户按键1
// PC5  -- User Key2 -- 用户按键2
#define USER_KEY1_PIN                                               USER_KEY1_Pin                             
#define USER_KEY2_PIN                                               USER_KEY2_Pin                                    
#define Read_Key1_State()                                           HAL_GPIO_ReadPin(USER_KEY1_GPIO_Port, USER_KEY1_PIN)
#define Read_Key2_State()                                           HAL_GPIO_ReadPin(USER_KEY2_GPIO_Port, USER_KEY2_PIN)


typedef void (* KeyFunc)(void);


#define KEY_VALID_COUNT_MAX                                         (220)    /* 按键有效次数最大值 */
#define KEY_NONE_ACTION                                             (0x0003) /* 两个按键 */

#define KEY_COUNT_CLICK                                             (10) /* 按键单击的有效次数 */
#define KEY_COUNT_LONG_PRESS                                        (80) /* 按键长按的有效次数 */


#define LONG_PRESS(key)                                             ((key) | 0x8000)
#define IS_KEY_LONG_PRESS(key , code)                               (((code) & 0x8000) && ((key) == ((code) & 0x7FFF)))


typedef enum
{
    KEY_USER1_CLICK_EVENT      = 0, /* 按键1单击事件 */
    KEY_USER2_CLICK_EVENT      = 1, /* 按键2单击事件 */
    KEY_USER_CLICK_EVENT       = 2, /* 按键12 组合单击事件 */
    KEY_USER1_LONG_PRESS_EVENT = 3, /* 按键1长按事件 */
    KEY_USER2_LONG_PRESS_EVENT = 4, /* 按键1长按事件 */
    KEY_EVENT_MAX
}KeyEvent;


#pragma pack(1) /* 字节对齐 */
typedef struct UserKeyInput
{
    u8 ubKeyValidCount; /* 按键有效次数 */
    KeyEvent eKeyEvent; /* 按键事件 */
}UserKey_TypeDef;
#pragma pack()

二、变量的定义

volatile KeyFunc KEY_FUNC_TAB[KEY_EVENT_MAX] = {0};


UserKey_TypeDef AppKey = 
{
	.ubKeyValidCount = 0,
	.eKeyEvent       = KEY_EVENT_MAX /* 避免第一次触发事件 */
};


/* 按键值 */
const u16 KeyMapCode[] = 
{
    0x0001, //Key1 单击
    0x0002, //Key2 单击
    0x0003, //Key12 组合 单击
    0x8001, //Key1 长按
    0x8002, //Key2 长按
};

三、按键GPIO初始化及按键默认事件

/* 初始化键盘回调 */
static void vInit_KeyBoard_CallBack(void)
{
    vSetKey_Handler_Event(KEY_USER1_CLICK_EVENT,      vUserKey1_ClickEvent);
    vSetKey_Handler_Event(KEY_USER2_CLICK_EVENT,      vUserKey2_ClickEvent);
    vSetKey_Handler_Event(KEY_USER_CLICK_EVENT,       vUserKey12_ClickEvent);
    vSetKey_Handler_Event(KEY_USER1_LONG_PRESS_EVENT, vUserKey1_LongPressEvent);
    vSetKey_Handler_Event(KEY_USER2_LONG_PRESS_EVENT, vUserKey2_LongPressEvent);
}


/* 初始化按键输入 */
void vInit_Key_Input(void)
{
    // PC5  -- User Key1 -- 用户按键1
    // PE3  -- User Key2 -- 用户按键2

    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin   = USER_KEY1_Pin;
    GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(USER_KEY1_GPIO_Port, &GPIO_InitStruct);

    GPIO_InitStruct.Pin  = USER_KEY2_Pin;
    HAL_GPIO_Init(USER_KEY2_GPIO_Port, &GPIO_InitStruct);

    vInit_KeyBoard_CallBack();

    dprintf("4-STM32H743xx_CubeMx_KeyInput......\r\n");
}

四、读取按键值及键盘扫描

/* 读取键盘值 */
static u16 usRead_KeyBoard_Value(void)
{
    u16 kVal = 0;

    /* 每Bit代表一个按键 低电平有效 */
    /* 目前只有两个按键 只有Bit0和Bit1有用 */
    kVal  = Read_Key1_State();        /* 0000 0010, Bit0 valid */
    kVal |= (Read_Key2_State() << 1); /* 0000 0001, Bit1 valid */

    return kVal;
}



/* 键盘的扫描 */
void vKeyBoard_Scan(void)
{
    static u16 oldVal = 0;
    u16 curVal = 0;


    /* 获取按键有效值 将低电平有效转为高电平有效 */
    curVal = usRead_KeyBoard_Value() ^ KEY_NONE_ACTION;

    /* 按键有效 Bit0或Bit1为1的情况 */
    if(curVal)
    {
        if(curVal == oldVal)/* 按键相同 */
        {
            /* 累计有效次数 */
            if(KEY_VALID_COUNT_MAX > AppKey.ubKeyValidCount)
            {
                AppKey.ubKeyValidCount++;
            }
        }
        else
        {
            /* 新按键 */
            oldVal = curVal;
            AppKey.ubKeyValidCount = 0;
        }


        if(KEY_COUNT_CLICK == AppKey.ubKeyValidCount)/* 单击 */
        {
            /* 组合单击 */
            if(oldVal == KeyMapCode[KEY_USER_CLICK_EVENT])/* Key12 单击 */
            {
                AppKey.eKeyEvent = KEY_USER_CLICK_EVENT;
            }
        }
        else if(KEY_COUNT_LONG_PRESS == AppKey.ubKeyValidCount)/* 长按 */
        {
            if(IS_KEY_LONG_PRESS(oldVal, KeyMapCode[KEY_USER1_LONG_PRESS_EVENT]))/* Key1 长按*/
            {
                AppKey.eKeyEvent = KEY_USER1_LONG_PRESS_EVENT;
            }
            else if(IS_KEY_LONG_PRESS(oldVal, KeyMapCode[KEY_USER2_LONG_PRESS_EVENT]))/* Key2 长按*/
            {
                AppKey.eKeyEvent = KEY_USER2_LONG_PRESS_EVENT;
            }
        }
    }
    else/* 无按键按下或已释放按键 */
    {
        /* 之前按键有效 */
        if(oldVal)
        {
            if((AppKey.ubKeyValidCount >= KEY_COUNT_CLICK) && (AppKey.ubKeyValidCount < KEY_COUNT_LONG_PRESS))/* 单击 */
            {
                if(oldVal == KeyMapCode[KEY_USER1_CLICK_EVENT])/* Key1 单击 */
                {
                    AppKey.eKeyEvent = KEY_USER1_CLICK_EVENT;
                }
                else if(oldVal == KeyMapCode[KEY_USER2_CLICK_EVENT])/* Key2 单击 */
                {
                    AppKey.eKeyEvent = KEY_USER2_CLICK_EVENT;
                }
            }

            /* 清空 */
            AppKey.ubKeyValidCount = 0;
            oldVal = 0;
        }
    }	
}

五、设置或取消按键事件

/* 设置按键的处理事件 */
void vSetKey_Handler_Event(KeyEvent evt, KeyFunc func)
{
    if(evt >= KEY_EVENT_MAX)
    {
        return;
    }

    KEY_FUNC_TAB[evt] = func;
}



/* 取消按键的处理事件 */
void vCancelKey_Handler_Event(KeyEvent evt)
{
    if(evt >= KEY_EVENT_MAX)
    {
        return;
    }

    KEY_FUNC_TAB[evt] = NULL;
}

六、键盘处理

/* 键盘扫描处理 */
void vKeyBoard_HandlerService(void)
{
    u8 event = 0;
    KeyFunc keyFunc = NULL;

    event = AppKey.eKeyEvent;
    if(event >= KEY_EVENT_MAX)
    {
        return;
    }

    AppKey.eKeyEvent = KEY_EVENT_MAX;
    keyFunc = KEY_FUNC_TAB[event];
    if(keyFunc != NULL)
    {
        keyFunc();
    }
}

七、按键事件的测试

static void vUserKey1_ClickEvent(void)
{
    dprintf("vUserKey1_ClickEvent......\r\n");
}

static void vUserKey2_ClickEvent(void)
{
    dprintf("vUserKey2_ClickEvent......\r\n");
}

static void vUserKey1_LongPressEvent(void)
{
    dprintf("vUserKey1_LongPressEvent......\r\n");
}

static void vUserKey2_LongPressEvent(void)
{
    dprintf("vUserKey2_LongPressEvent......\r\n");
}

static void vUserKey12_ClickEvent(void)
{
    dprintf("vUserKey12_ClickEvent......\r\n");
}

八、键盘的定时扫描及处理事件

/* This function handles System tick timer */
void SysTick_Handler(void)
{
    static u8 i = 0;

    HAL_IncTick();

    if((++i) >= 10)
    {
        i = 0;
        vKeyBoard_Scan();
    }
}





int main(void)
{
    static u8 i = 0;

    vStartUp_System();
    vInit_System_BSP();
    vShow_PlatformDevice_Info();

    while (1)
    {
        vKeyBoard_HandlerService();
        HAL_Delay(10);

        if((++i) > 50)
        {
            i = 0;
            USER_LED_CPL();
        }
    }
}

九、运行测试

00> Segger Rtt Init Ok...
00> STM32H743VIT6 Start Running...
00> 4-STM32H743xx_CubeMx_KeyInput......
00> Debug Versions......
00> Compile Date : Mar 27 2022 22:01:03
00> 
00> vUserKey1_ClickEvent......
00> vUserKey1_ClickEvent......
00> vUserKey2_ClickEvent......
00> vUserKey2_ClickEvent......
00> vUserKey1_LongPressEvent......
00> vUserKey1_LongPressEvent......
00> vUserKey2_LongPressEvent......
00> vUserKey2_LongPressEvent......
00> vUserKey12_ClickEvent......
00> vUserKey12_ClickEvent......
00> vUserKey12_ClickEvent......
00> vUserKey1_ClickEvent......
00> vUserKey2_ClickEvent......

在这里插入图片描述

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一份基于状态机的FreeRTOS独立按键实现单击双击按功能的程序,代码如下: ```c // 定义按键状态机的状态 typedef enum { IDLE, // 空闲状态 PRESSED, // 按下状态 RELEASED, // 松开状态 DOUBLE, // 双击状态 LONG // 按状态 } key_state_t; // 定义按键状态机的事件 typedef enum { PRESS_EVENT, // 按下事件 RELEASE_EVENT // 松开事件 } key_event_t; // 定义按键状态机的状态转移表 key_state_t key_state_transition[key_state_t][2] = { /* PRESS_EVENT RELEASE_EVENT */ /*IDLE*/{PRESSED, IDLE}, /*PRESSED*/{PRESSED, RELEASED}, /*RELEASED*/{DOUBLE, IDLE}, /*DOUBLE*/{IDLE, IDLE}, /*LONG*/{IDLE, IDLE} }; // 定义按键状态机的状态转移函数 key_state_t key_state_transfer(key_state_t current_state, key_event_t event) { return key_state_transition[current_state][event]; } // 定义按键状态结构体 typedef struct { key_state_t state; // 当前状态 TickType_t press_time; // 按下时间 uint8_t count; // 计数器 } key_t; // 定义按键检测任务函数 void key_task(void *pvParameters) { key_t key = {IDLE, 0, 0}; // 初始化按键状态 while (1) { if (gpio_get_level(KEY_PIN) == KEY_DOWN_LEVEL) { // 如果按键被按下 key.state = key_state_transfer(key.state, PRESS_EVENT); // 状态转移 key.press_time = xTaskGetTickCount(); // 记录按下时间 } else { // 如果按键被松开 key.state = key_state_transfer(key.state, RELEASE_EVENT); // 状态转移 } switch (key.state) { case PRESSED: if (xTaskGetTickCount() - key.press_time >= DOUBLE_CLICK_TIME) { key.count = 0; key.state = LONG; printf("long press\n"); } break; case RELEASED: if (key.count == 0) { key.count++; key.press_time = xTaskGetTickCount(); } else if (key.count == 1) { if (xTaskGetTickCount() - key.press_time <= DOUBLE_CLICK_TIME) { key.count++; printf("double click\n"); } else { key.count = 0; } } break; default: break; } vTaskDelay(10 / portTICK_PERIOD_MS); // 任务挂起10ms } } ``` 在这个程序,我们使用了一个状态机来实现按键单击、双击和按功能。按键状态机有五个状态:空闲状态、按下状态、松开状态、双击状态和按状态。按键状态机有两种事件:按下事件和松开事件按键状态机的状态转移表定义了每个状态在接收不同事件时的下一个状态。按键状态机的状态转移函数根据当前状态和事件来确定下一个状态。按键状态结构体包含了当前状态、按下时间和计数器。按键检测任务会不断地检测按键状态,并根据当前状态和计数器来判断单击、双击和事件。 需要注意的是,在这个程序,我们使用了FreeRTOS的延时函数vTaskDelay()来挂起任务。如果您的FreeRTOS版本不支持延时函数,可以使用vTaskDelayUntil()函数来实现任务的延时。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值