按键驱动设计
文章目录
- 1 按键驱动设计
1 按键驱动设计
硬件结构如下:
按键驱动综合使用了软定时器 + 状态机 + 邮箱数据通信。
- 软定时器负责周期性地调用按键检测扫描函数。
- 状态机负责处理按键按下、弹起等状态的判断与处理。
- 邮箱负责处理定时器扫描任务与调用驱动的任务之间的按键值数据通信。
驱动初始化后,软定时器会自动扫描按键,然后结合状态机判断哪个按键按下或弹起,一旦检查到按键按下,则将键值投递到邮箱中。需要读取键值的任务会在邮箱上等待键值。
实现如下:
appConfig.h:
// // Created by lishutong on 2018/2/5. //#ifndef PROJECT_APPCONFIG_H #define PROJECT_APPCONFIG_H#include "hal.h"#define EN_DEBUG_PRINT 1#if EN_DEBUG_PRINT == 0 #define DEBUG_PRINT(str, arg) #else #define DEBUG_PRINT(str, arg) xprintf(str, arg) #endif#endif //PROJECT_APPCONFIG_Hbutton.h:
/*** @brief 按键驱动* @details* @author 01课堂 李述铜 http://01ketang.cc* @date 2017-06-01* @version 1.0* @copyright 版权所有,禁止用于商业用途*/ #ifndef BUTTON_H #define BUTTON_H#include "appConfig.h"#define BUTTON_MSG_NR 10 // 按键消息缓存数量 #define BUTTON_SCAN_INTERVAL 20 // 安键扫描的时间间隔, ms为单位typedef enum {ButtonNone,ButtonStartStop,Button1,Button2,Button3, }ButtonId;typedef enum {ButtonDown,ButtonUp, }ButtonState;void ButtonInit (void); uint32_t ButtonWaitPress (ButtonId * id);#endif //BUTTON_Hbutton.c:
/*** @brief 按键驱动* @details* @author 01课堂 李述铜 http://01ketang.cc* @date 2017-06-01* @version 1.0* @copyright 版权所有,禁止用于商业用途*/ #include "tinyOS.h" #include "button.h" #include "stm32f10x.h"// 按键存储消息缓冲区 static tMbox buttonMbox; static void * buttonMsgBuffer[BUTTON_MSG_NR];static tTimer buttonTimer; // 按键扫描定时器/*** 初始化按键硬件*/ static void ButtonHalInit (void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_Init(GPIOB, &GPIO_InitStructure); }/*** 获取哪一个按键按下*/ static ButtonId ButtonGetWhichPress (void) {uint8_t state;ButtonId buttonId = ButtonNone;state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1);if (state == Bit_SET) {buttonId = Button1;}state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2);if (state == Bit_SET) {buttonId = Button2;}state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_3);if (state == Bit_SET) {buttonId = Button3;}state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0);if (state == Bit_SET) {buttonId = ButtonStartStop;}return buttonId; }/*** 按键定时器扫描处理, 按键处理译码* @param arg*/ static void timerFunc (void * arg) {static ButtonId prePressedButton = ButtonNone;static enum {NO_DOWN,RECHECK_DOWN,CONFIRMED_DOWN,RECHECK_UP} scanState = NO_DOWN;// 读取当前硬件状态ButtonId currPressedButton = ButtonGetWhichPress();switch (scanState) {case NO_DOWN:// 有按键按下,进入第1次确认检查状态if (currPressedButton != ButtonNone) {scanState = RECHECK_DOWN;DEBUG_PRINT("NO_DOWN -> RECHECK_DOWN:%d\n", currPressedButton);}break;case RECHECK_DOWN:if (currPressedButton == ButtonNone) {scanState = NO_DOWN;DEBUG_PRINT("RECHECK_DOWN -> NO_DOWN:%d\n", currPressedButton);} else if (prePressedButton == currPressedButton) {// 发消息通知,根据按键类型,决定如何处理. 可以考虑从某个配置表中查,这样可配置多个按键事件的通知行为uint32_t notifyOption = (currPressedButton == ButtonStartStop)? tMBOXSendFront : tMBOXSendNormal;tMboxNotify(&buttonMbox, (void *)currPressedButton, notifyOption);scanState = CONFIRMED_DOWN;DEBUG_PRINT("RECHECK_DOWN -> CONFIRMED_DOWN:%d\n", currPressedButton);}break;case CONFIRMED_DOWN:// 进入这个状态后,要做的是检查按键是否重复按下或者等待按键释.这里简单起见,等待按键释放if (currPressedButton == ButtonNone) {// 发现按键释放?进一步确认。但这里要return,否则prePressedButton会在后面设置NonescanState = RECHECK_UP;DEBUG_PRINT("CONFIRMED_DOWN -> RECHECK_UP:%d\n", currPressedButton);return;} else if (currPressedButton != prePressedButton) {// 发现不一样的按键按下,重新确认scanState = RECHECK_DOWN;DEBUG_PRINT("CONFIRMED_DOWN -> RECHECK_DOWN:%d\n", currPressedButton);}break;case RECHECK_UP:if (currPressedButton == ButtonNone) {// 确认没有按键按下,已经释放scanState = NO_DOWN;DEBUG_PRINT("RECHECK_UP -> NO_DOWN:%d\n", currPressedButton);} else if (currPressedButton != prePressedButton) {// 发现不一样的按键按下,重新确认是否按下scanState = RECHECK_DOWN;DEBUG_PRINT("RECHECK_UP -> scanState:%d\n", currPressedButton);} else if (currPressedButton == prePressedButton) {// 相同的键,噢,前一次可能是误触发,再次重新检查scanState = CONFIRMED_DOWN;DEBUG_PRINT("RECHECK_UP -> CONFIRMED_DOWN:%d\n", currPressedButton);}break;}// 记录当前结果prePressedButton = currPressedButton; }/*** 按键初始化*/ void ButtonInit (void) {ButtonHalInit();tMboxInit(&buttonMbox, buttonMsgBuffer, BUTTON_MSG_NR);tTimerInit(&buttonTimer, 0, BUTTON_SCAN_INTERVAL / TINYOS_SYSTICK_MS, timerFunc, 0, TIMER_CONFIG_TYPE_SOFT);tTimerStart(&buttonTimer); }/*** 等待任意按键按下* @param id 按键是否按下的状态* @return 非0,有错误发生;0无错误*/ uint32_t ButtonWaitPress (ButtonId * id) {uint32_t err = 0;err = tMboxWait(&buttonMbox, (void **)id, 0);return err; }app.c:
/*** @brief tOS应用示例* @details* @author 01课堂 李述铜 http://01ketang.cc* @date 2017-06-01* @version 1.0* @copyright 版权所有,禁止用于商业用途*/ #include "tinyOS.h" #include "app.h" #include "hal.h"#include "button.h"static tTaskStack task1Env[TASK1_ENV_SIZE]; // 任务1的堆栈空间 static tTask task1;void task1Entry (void * param) {ButtonId buttonId;for (;;) {ButtonWaitPress(&buttonId);xprintf("Button press: %d\n", buttonId);} }/*** App的初始化*/ void tInitApp (void) {halInit();ButtonInit();tTaskInit(&task1, task1Entry, (void *) 0x0, TASK1_PRIO, task1Env, sizeof(task1Env)); }参考资料: