.外设:一个io口、一个定时器。
 为了降低上手的门槛,本文仅使用一个IO口作演示。
实现思路
 使用定时器,定时20ms来读取简化的按键状态机。
 这里简化了状态机,大家只需明白三个概念。
状态:数量为有限个,记录按键状态。可根据条件切换。对应的代码中,用switch case来匹配不同的状态
 条件:就是一些简单的判断。对应的代码,用if来判断按键按下或释放,判断时间计数的长短
 事件:在特定的状态和条件下,产生的事件。代码只有三个事件:Null(空闲)SingleClick(短按)LongPress(长按)
 思路图解:

3. 长按、单击 定义
 长按事件:按下时间大于1s,释放后响应。(不支持连按,若需要连按,进行简单修改即可)
 单击事件:按下时间小于1s, 释放后响应。
4.代码分析
 4.1 外设准备
4.2 类型、变量定义
 4.2.1按键事件
 枚举型名称:KEY_EventList_TypeDef
 本文实现短按长按功能,故只有三种事件:无事件、短按事件、长按事件
 对应代码,即:空闲、单击、长按
typedef enum _KEY_EventList_TypeDef 
 {
     KEY_Event_Null            = 0x00, // 空闲
     KEY_Event_SingleClick  = 0x01, // 单击
     KEY_Event_LongPress    = 0x02 // 长按
 }KEY_EventList_TypeDef;
 4.2.2 按键电平、动作
 因为有的电路按下时,引脚为低电平;有的按下时,引脚却为高电平。这里将电平、动作分开,更方便移植。
 枚举型名称:KEY_PinLevel_TypeDef
 即:高、低电平。
// 按键引脚的电平
 typedef enum
 { 
     KKEY_PinLevel_Low = 0,
     KEY_PinLevel_High
 }KEY_PinLevel_TypeDef;
 枚举型名称:KEY_Action_TypeDef
 按键只有按下和没按下俩个动作:
 即:按下、释放
// 按键动作,
 typedef enum
 { 
     KEY_Action_Press = 0,
     KEY_Action_Release
 }KEY_Action_TypeDef;
 4.2.3 按键状态
 枚举型名称:KEY_StatusList_TypeDef
 思路图解的分析,分为如下几个状态。
 即:空闲、消抖、确认按下、确认长按
// 按键状态
 typedef enum _KEY_StatusList_TypeDef 
 {
     KEY_Status_Idle     = 0, // 空闲
     KEY_Status_Debounce ,    // 消抖
     KEY_Status_ConfirmPress    ,    // 确认按下    
     KEY_Status_ConfirmPressLong,    // 确认长按
 }KEY_StatusList_TypeDef;
 4.2.4 按键配置结构体
 按键配置信息的结构体名称:KEY_Configure_TypeDef
 打包好按键的基本属性。
typedef struct _KEY_Configure_TypeDef 
 {
     uint16_t                        KEY_Count;                 // 按键长按时长计数
     KEY_Action_TypeDef             KEY_Action;                // 按键动作,按下或释放
     KEY_StatusList_TypeDef         KEY_Status;                // 按键状态
     KEY_EventList_TypeDef          KEY_Event;                 // 按键事件
     KEY_PinLevel_TypeDef           (*KEY_ReadPin_Fcn)(void);  // 读引脚电平函数
 }KEY_Configure_TypeDef;
 成员解释:
KEY_Count:计数,记一个数为20ms。
 KEY_Action:按键动作,按下或者释放。
 KEY_Status:记录按键的状态值。
 KEY_Event:记录按键触发的事件。
 KEY_ReadPin_Fcn:读取按键电平值的函数指针。方便移植。
 4.3 变量、函数、宏定义
 4.3.1宏定义
 KEY_LONG_PRESS_TIME :
 短按、长按的时长分界线。大于–>长按,小于–>短按。
 KEY_PRESSED_LEVEL:
 按键被按下的实际电平,我的电路里,按键按下引脚接地。所以为低电平。
 /**************************************************************************************************** 
 *                             长按、单击 定义
 * 长按事件:按下时间大于 KEY_LONG_PRESS_TIME,释放后响应。(不支持连按,需要连按响应可自己配置)
 * 单击事件:按下时间小于 KEY_LONG_PRESS_TIME 释放后响应。
 ****************************************************************************************************/
 // 长按时长的宏定义
 #define KEY_LONG_PRESS_TIME    50  // 20ms*50 = 1s
 #define KEY_PRESSED_LEVEL      0   // 按键被按下时的电平
 4.3.2 变量定义
 KeyCfg:全局变量,打包了按键的各个属性。
/**************************************************************************************************** 
 *                            按键配置信息的全局结构体变量
 ****************************************************************************************************/
 KEY_Configure_TypeDef KeyCfg={
         
         0,                        // 按键长按时长计数
         KEY_Action_Release,        // 按键动作,按下或释放 
         KEY_Status_Idle,        // 按键状态
         KEY_Event_Null,         // 按键事件
         KEY_ReadPin             // 读引脚电平函数
 };
 4.3.3函数定义
 局部函数:
//读取引脚的电平
 static KEY_Action_TypeDef KEY_ReadPin(void) 
 {
   return (KEY_Action_TypeDef)GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);
 }
// 获取按键动作,按下或释放,保存到结构体
 static void KEY_GetAction(void) 
 {
     if(KeyCfg.KEY_ReadPin_Fcn() == KEY_PRESSED_LEVEL)
     {
         KeyCfg.KEY_Action = KEY_Action_Press;
     }
     else
     {
         KeyCfg.KEY_Action =  KEY_Action_Release;
     }
  
 }
 KEY_ReadPin:读取PA_0的电平状态。
 KEY_GetAction:将读取到的电平状态转换为实际的按下或释放的动作。
 状态处理函数
 KEY_ReadStateMachine:编写,思路完全按照前文手绘的思路图像
 首先读取按键的动作,再在switch case 里面匹配引脚的状态,case下用if判断按键动作或按下的时长,对状态、事件进行赋值。
void KEY_ReadStateMachine(void)
 {
    
     KEY_GetAction();
     
     switch(KeyCfg.KEY_Status)
     {
         //状态:没有按键按下
         case KEY_Status_Idle:
             if(KeyCfg.KEY_Action == KEY_Action_Press)
             {
                 KeyCfg.KEY_Status = KEY_Status_Debounce;
                 KeyCfg.KEY_Event = KEY_Event_Null;
             }
             else
             {
                 KeyCfg.KEY_Status = KEY_Status_Idle;
                 KeyCfg.KEY_Event = KEY_Event_Null;
             }
             break;
             
         //状态:消抖
         case KEY_Status_Debounce:
             if(KeyCfg.KEY_Action == KEY_Action_Press)
             {
                 KeyCfg.KEY_Status = KEY_Status_ConfirmPress;
                 KeyCfg.KEY_Event = KEY_Event_Null;
             }
             else
             {
                 KeyCfg.KEY_Status = KEY_Status_Idle;
                 KeyCfg.KEY_Event = KEY_Event_Null;
             }
             break;    
             
         //状态:确认按下
         case KEY_Status_ConfirmPress:
             if( (KeyCfg.KEY_Action == KEY_Action_Press) && ( KeyCfg.KEY_Count >= KEY_LONG_PRESS_TIME))
             {
                 printf("KEY_Status_ConfirmPressLong\r\n");
                 KeyCfg.KEY_Count = 0;
                 KeyCfg.KEY_Status = KEY_Status_ConfirmPressLong;
                 KeyCfg.KEY_Event = KEY_Event_Null;
                 
             }
             else if( (KeyCfg.KEY_Action == KEY_Action_Press) && (KeyCfg.KEY_Count < KEY_LONG_PRESS_TIME))
             {
                 printf("继续按下 %d\r\n",KeyCfg.KEY_Count);
                 KeyCfg.KEY_Count++;
                 KeyCfg.KEY_Status = KEY_Status_ConfirmPress;
                 KeyCfg.KEY_Event = KEY_Event_Null;
             }
             else
             {
                 printf("突然gg,按短了 %d\r\n",KeyCfg.KEY_Count);
                 KeyCfg.KEY_Count = 0;
                 KeyCfg.KEY_Status = KEY_Status_Idle;
                 KeyCfg.KEY_Event = KEY_Event_SingleClick;
            }
             break;    
            
         //状态:确认长按
         case KEY_Status_ConfirmPressLong:
             printf("KEY_Status_ConfirmPressLong\r\n");
             if(KeyCfg.KEY_Action == KEY_Action_Press) 
             {   // 一直等待其放开
                 printf("一直按着 KEY_Status_ConfirmPressLong\r\n");
                 KeyCfg.KEY_Status = KEY_Status_ConfirmPressLong;
                 KeyCfg.KEY_Event = KEY_Event_Null;
                 KeyCfg.KEY_Count = 0;
             }
             else
             {
                 KeyCfg.KEY_Status = KEY_Status_Idle;
                 KeyCfg.KEY_Event = KEY_Event_LongPress;
                 KeyCfg.KEY_Count = 0;
             }
             break;    
         default:
             break;
     }
}
 5.1定时器中断:
 直接调用KEY_ReadStateMachine()函数即可,将读取到的事件保存到KeyCfg.KEY_Event变量。
extern KEY_Configure_TypeDef KeyCfg;
 //定时器3中断服务程序
 void TIM3_IRQHandler(void)   //TIM3中断
 {
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
     {
         TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
             KEY_ReadStateMachine();  //调用状态机
             
             if(KeyCfg.KEY_Event == KEY_Event_SingleClick)
             {
                 printf("单击\r\n");//事件处理
             }
             if(KeyCfg.KEY_Event == KEY_Event_LongPress)
             {
                 printf("长按\r\n");//事件处理
             }
         }
 }
 5.2 main函数
 这里main函数十分简洁。初始化即可。
 实际应用时,事件处理的代码不建议放在定时器里面。
int main(void)
 {    
     uart_init(115200); // 用于查看输出
     TIM3_Int_Init(200-1,7200-1); //调用定时器使得20ms产生一个中断
     //按键初始化函数
     KEY_Init();
     
     while(1);
}



















