按键驱动这是我早就想做的事情,想尽可能完善下各种不同的事件,以达到更少的按键实现更多的功能的目的。刚好最近有项目需要用到四个独立按键+LCD屏幕做各种界面的显示、切换、上下选择等操作,因此就捋了一下按键的驱动,后面如果有类似的需求,直接移植过去就好了。废话不多说,先看原理图。
这四个GPIO采用MCU内部的上拉输入,没有按键按下的时候全是1,按键按下变成0。
这个项目是使用32去做的,因此是基于32去开发的,移植到C51上也是一样适用的,逻辑都是一样的。
先来看下引脚的定义:
/* 按键1 GPIO配置 */ #define KEY1_GPIO_PORT GPIOA #define KEY1_GPIO_PIN GPIO_PIN_0 #define KEY1_GPIO_MODE GPIO_Mode_IPU #define KEY1_READ GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) /* 按键2 GPIO配置 */ #define KEY2_GPIO_PORT GPIOA #define KEY2_GPIO_PIN GPIO_PIN_1 #define KEY2_GPIO_MODE GPIO_Mode_IPU #define KEY2_READ GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN) /* 按键3 GPIO配置 */ #define KEY3_GPIO_PORT GPIOA #define KEY3_GPIO_PIN GPIO_PIN_2 #define KEY3_GPIO_MODE GPIO_Mode_IPU #define KEY3_READ GPIO_ReadInputDataBit(KEY3_GPIO_PORT, KEY3_GPIO_PIN) /* 按键4 GPIO配置 */ #define KEY4_GPIO_PORT GPIOA #define KEY4_GPIO_PIN GPIO_PIN_3 #define KEY4_GPIO_MODE GPIO_Mode_IPU #define KEY4_READ GPIO_ReadInputDataBit(KEY4_GPIO_PORT, KEY4_GPIO_PIN)
按键GPIO的初始化
/* * 函数名称:Key_GPIOConfig * 输入参数:None * 返 回 值:None * 作 者:Barry * 功能描述:初始化按键GPIO为输入上拉模式 * 修改记录:None */ void Key_GPIOConfig(void) { GPIO_InitType Key_GPIOType; /* Key1引脚初始化 */ Key_GPIOType.Pin = KEY1_GPIO_PIN; Key_GPIOType.GPIO_Mode = KEY1_GPIO_MODE; GPIO_InitPeripheral(KEY1_GPIO_PORT, &Key_GPIOType); /* Key2引脚初始化 */ Key_GPIOType.Pin = KEY2_GPIO_PIN; Key_GPIOType.GPIO_Mode = KEY2_GPIO_MODE; GPIO_InitPeripheral(KEY2_GPIO_PORT, &Key_GPIOType); /* Key3引脚初始化 */ Key_GPIOType.Pin = KEY3_GPIO_PIN; Key_GPIOType.GPIO_Mode = KEY3_GPIO_MODE; GPIO_InitPeripheral(KEY3_GPIO_PORT, &Key_GPIOType); /* Key4引脚初始化 */ Key_GPIOType.Pin = KEY4_GPIO_PIN; Key_GPIOType.GPIO_Mode = KEY4_GPIO_MODE; GPIO_InitPeripheral(KEY4_GPIO_PORT, &Key_GPIOType); log_i("Key gpio config complete.\r\n"); }
这些基本上使用过STM32的都很熟悉,就不详细赘述了。
四个按键为了好区分分别是哪个按键被按下或者同时被按下,定义一个KeyVal保存键值做如下拼装:
KeyVal = (KEY4_READ << 3) | (KEY3_READ << 2) | (KEY2_READ << 1) | KEY1_READ;
这样一来KeyVal的低四位每一位可以表示一个按键的状态,即便四个同时按下也能区分出来。也能更加简单的实现组合按键。
核心代码:划重点
keyscan.h
typedef enum { KEY_NONE = 0, KEY1 = 1, KEY2 = 2, KEY3 = 3, KEY4 = 4, }eKeyList; typedef enum { KEY_TYPE_NULL = 0, /*! 没有按键*/ KEY_TYPE_DOUBLE_CLICK = 1, /*! 双击按键 */ KEY_TYPE_DOWN = 2, /*! 按键按下*/ KEY_TYPE_LONG = 3, /*! 按键长按*/ KEY_TYPE_HOLD = 4, /*! 按键保持*/ KEY_TYPE_SHORT_UP = 5, /*! 按键短按弹起*/ KEY_TYPE_LONG_UP = 6, /*! 按键长按弹起*/ KEY_TYPE_HOLD_UP = 7, /*! 按键保持弹起*/ KEY_TYPE_ALL = 255, /*! 任意按键类型,不包含双击按键 */ }eKeyType; typedef enum { KEY_EVENT_NULL = 0, /*! 没有按键事件*/ SELECT_UP_EVENT = 1, /*!向上选择事件*/ SELECT_DOWN_EVENT = 2, /*!向下选择时间*/ ENTER_MENU_EVENT = 3, /*! 进入菜单事件*/ RETURN_MENU_EVENT = 4, /*!返回上一级菜单事件*/ }eKeyEvent; typedef struct { eKeyList Key; eKeyType KeyType; }stKeyFunc; #define DEBOUNCE_TIME 50 /*! 消抖时间50ms*/ #define CLICK_MAX_TIME 1200 /*! 单击最长时间(ms), 大于此时间被认为长按*/ #define DOUBLE_CLICK_TIME 220 /*! 双击间隔时间*/
keyscan.c
/* * 函数名称:KeyScan * 输入参数:None * 返 回 值:None * 作 者:Barry * 功能描述:按键扫描,检测单击、双击、长按、短按抬起事件 * 修改记录:None */ void KeyScan(void) { /* 按键按下的起始时间 */ static uint32_t KeyStartTime = 0; /* 按键的触发标志 */ static uint8_t TrigFlag = 0; /* 上一次按下的键值 */ static uint8_t PreKeyVal = 0; /* 等待按键释放 */ static uint8_t WaitKeyUp = 0; /* 单击起始时间 */ static uint32_t ClickStartTime = 0; /* 上次单击时间 */ static uint32_t LastClickTime = 0; /* 双击标志位 */ static uint8_t DoubleClickFlag = 0; /* 没有按键按下每个按键读取出来为1(配置为输入上拉),按下后为0 */ /* 没有按键按下时默认KeyVal = 0x0F */ KeyVal = (KEY4_READ << 3) | (KEY3_READ << 2) | (KEY2_READ << 1) | KEY1_READ; /* 按键在按下的状态--标记长按事件 */ if(WaitKeyUp) { if(KeyVal == PreKeyVal) { return; } WaitKeyUp = 0; KeyFunc.KeyType = KEY_TYPE_LONG_UP; //log_w("KEY_TYPE_LONG_UP.\r\n"); } /* 有按键被按下 */ if(KeyVal != 0x0F) { if(TrigFlag == 0) { TrigFlag = 1; /* 保存键值 */ PreKeyVal = KeyVal; /* 获取当前系统时钟计数值 */ KeyStartTime = GetSysTickVal(); } /* 之前已经按下该按键,并保持这个键按下 */ else if((TrigFlag == 1) && (PreKeyVal == KeyVal)) { uint32_t KeyDownTime = GetSysTickVal() - KeyStartTime; /* 按键按下时间小于消抖时间--消抖处理 */ if(KeyDownTime <= DEBOUNCE_TIME) return; /* 大于单击的最长时间--认定为长按走这个分支 */ else if(KeyDownTime > CLICK_MAX_TIME) { /* 清空标志位 */ TrigFlag = 0; /* 置位等待释放标志位 */ WaitKeyUp = 1; /* 标记按键按下状态 */ KeyFunc.KeyType = KEY_TYPE_LONG; //log_w("KEY_TYPE_LONG.\r\n"); } } } /* 没有按键按下或按键被释放 */ else { /* 单击标志位 */ static uint8_t ClickFlag = 0; if(TrigFlag != 0) { /* 按键按下到松开的时间 */ uint32_t KeyTime = GetSysTickVal() - KeyStartTime; /* 按键按下时间小于消抖时间--消抖处理 */ if(KeyTime <= DEBOUNCE_TIME) return; /* 按键按下的时间小于长按--短按(区分双击和单击) */ else if(KeyTime <= CLICK_MAX_TIME) { if(GetSysTickVal() - LastClickTime <= DOUBLE_CLICK_TIME) { /* 置位双击标志位 */ DoubleClickFlag = 1; /* 双击 */ KeyFunc.KeyType = KEY_TYPE_DOUBLE_CLICK; //log_w("KEY_TYPE_DOUBLE_CLICK.\r\n"); } /* 单击 */ else { /* 置位单击标志位 */ ClickFlag = 1; /* 清除双击标志位 */ DoubleClickFlag = 0; /* 更新单击释放的时间 */ ClickStartTime = GetSysTickVal(); } LastClickTime = GetSysTickVal(); } } /* 单击标志位 且 大于双击的时间内没有双击的标志位被触发被认为是单击事件 */ if((ClickFlag == 1) && ((GetSysTickVal() - ClickStartTime) > DOUBLE_CLICK_TIME) && (DoubleClickFlag == 0)) { /* 单击抬起 */ KeyFunc.KeyType = KEY_TYPE_SHORT_UP; /* 清除单击标志位 */ ClickFlag = 0; /* 清除双击标志位 */ DoubleClickFlag = 0; //log_w("KEY_TYPE_SHORT_UP.\r\n"); } TrigFlag = 0; } }
使用前实现一个获取当前软件运行的时间即可,我这里使用的是Systick实现的。如下:每1ms中断一次
/* * 函数名称:SystemTickConfig * 输入参数:None * 返 回 值:None * 作 者:Barry * 功能描述:初始化系统工作时钟 * 修改记录:None */ void SystemTickConfig(void) { uint32_t Reload = 0; uint16_t TickRate = 1000u; SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); /* 配置PCLK1时钟 */ RCC_ConfigPclk1(RCC_HCLK_DIV1); /* 配置HCLK时钟 */ RCC_ConfigHclk(RCC_SYSCLK_DIV1); /* 配置PCLK2时钟 */ RCC_ConfigPclk2(RCC_HCLK_DIV1); Reload = SystemCoreClock / TickRate; /* 开启SYSTICK中断 */ SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; /* 每1/TickRate Hz中断一次 */ SysTick->LOAD = Reload; /* 使能SysTick */ SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; } /* * 函数名称:SysTick_Handler * 输入参数:None * 返 回 值:None * 作 者:Barry * 功能描述:滴答定时器中断服务函数 * 修改记录:None */ void SysTick_Handler(void) { uwTick++; } /* * 函数名称:GetSysTickVal * 输入参数:None * 返 回 值:None * 作 者:Barry * 功能描述:获取当前SysTick计数值 * 修改记录:None */ uint32_t GetSysTickVal(void) { return uwTick; }
如果觉得本篇文章多少有点帮助的话大家不要忘了,点赞、关注、评论、转发哦,创作不易!你们的支持是小编创作的最大动力。