1.按键阻塞

/**
  * @brief  基础检测按键
  * @param  GPIOx:x 可以是 A,B,C等
  * @param  GPIO_Pin:待操作的pin脚号
  * @param  key_pressstatus:按键按下时的IO电平状态
  * @retval KEY_UP(没有触发按键)、KEY_DOWN(触发按键)
  */
KEY_Status KEY_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, KEY_TriggerLevel key_pressstatus)
{
    if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == key_pressstatus)
    {
        Rough_Delay_Ms(20);
        while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == key_pressstatus);
        Rough_Delay_Ms(20);
        return KEY_DOWN;
    }
    else
    {
        return KEY_UP;
    }
}

可以看到这个程序,如果按键按着不放会一直阻塞到while循环里面停止不动。

2. readme

/********************************************************************************************/
】程序简介

-工程名称 :非阻塞程序结构与扫描按键
-实验平台 :野火 STM32F103C8T6-STM32开发板

【 !】功能简介:

1-程序正常运行指示灯:核心板绿灯500ms翻转一次
2-外界光照环境感应灯:获取光敏传感器状态切换扩展白灯状态(100ms扫描一次)
3-按键长短按触发灯: 短按KEY1核心板蓝灯亮,长按KEY1红灯亮,释放按键灭灯;
注意:每一个功能不可阻塞过多影响其他功能

【 !】实验操作:

1.编译并下载程序到开发板即可观察现象;
2.观察板载绿灯以500ms翻转状态。
3.改变光敏传感器周围的光照,来切换LED灯状态。
4.短按KEY1核心板蓝灯亮,释放按键灭灯 ; 长按KEY1红灯亮,释放按键灭灯。

/*********************************************************************************************/

【*】 引脚分配

KEY1->PA0 按键按下为高电平 核心板板载默认下拉且硬件有消抖
NRST->NRST 按键按下为低电平 核心板板载默认上拉且硬件有消抖

LED_R->LED1->PA1 核心板板载LED灯低电平亮
LED_G->LED2->PA2 核心板板载LED灯低电平亮
LED_B->LED3->PA3 核心板板载LED灯低电平亮

LIGHT_DO->PA11 光敏传感器数字电平输入,强光照环境传感器模块DO输出低电平

LED4->PB5 用户自定义LED灯低电平亮

/*********************************************************************************************/

【*】程序描述:

< main.c >

1.初始化LED和LIGHT、KEY对应的GPIO,初始化对应的端口
2.使用一个绿灯作为程序正常运行的心率灯
3.检测光敏传感器反馈的信号,并对根据反馈信息来控制LED灯状态。
4.短按KEY1核心板蓝灯亮,释放按键灭灯 ; 长按KEY1红灯亮,释放按键灭灯。

/*********************************************************************************************/

核心部分是按键部分,短按KEY1蓝灯亮,长按KEY1红灯亮,释放灯灭。
其中短按和长按给一个取值时间,比如1秒钟,超过1秒钟就是长按,否则就是短按。

3.按键的非阻塞模式

3.1 基本框架

3.1.1 定时器部分

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
  /*每次进入定时器中断时间是1ms*/
void SysTick_Handler(void)
{
    SysTick_CountPlus();  //用来统计进入次数
    if (key_task.timer > 0x00) 
    {
        key_task.timer--;
        if (key_task.timer == 0) 
        {
            key_task.flag = 1;
        }
    }
}

每次20ms时间到达会将flag设置1,表示20ms时间到了。

3.1.2 按键部分


/**
  ******************************************************************************
  * @file    App_key.c
  * @author  作者
  * @version v1.0
  * @date    2026.4.3
  * @brief   按键应用层功能接口
  ******************************************************************************
  * @attention
  *
  * 版权声明
  *
  ******************************************************************************
  */

#include "key/app_key.h"
#include "led/bsp_gpio_led.h"
#include "key/bsp_gpio_key.h"

/*定义连接GPIO端口,用户只需要修改下面代码即可改变控制的LED引脚 */
typedef struct
{
    uint32_t cycle;     //周期
    int32_t timer;      //时间
    uint8_t flag;       //标志
}Key_TaskInfo;          /*按键任务信息*/

/*初始化 按键任务 变量*/
Key_TaskInfo key_task = { 0 }; 

/**
  * @brief  按键 计数复位
  * @param  无
  * @retval 无
  */
void Key_TaskReset(void)
{
    key_task.timer = key_task.cycle;
    key_task.flag = 0;
}

/**
  * @brief  按键 任务初始化
  * @param  key_task_cycle: 任务周期 单位ms(可修改系统节拍定时器)
  * @retval 无
  */
void Key_TaskInit(uint32_t key_task_cycle)
{
    key_task.cycle = key_task_cycle;
    Key_TaskReset();
}

/**
  * @brief  按键 任务
  * @param  无
  * @retval 无
  */
void Key_Task(void)
{
    if (key_task.flag == 1) 
    {
       //做事情
  
        Key_TaskReset();
    }
}
/***************************************END OF FILE *********************************************************/

接着当每次20ms时间到会在void Key_Task(void)函数中对flag进行判断,判断是否设置1,if判断成立说明时间到了,可以执行按键要做的事情了。

3.1.3 main.c

/**
  ******************************************************************************
  * @file    main.c
  * @author  作者
  * @version v1.0
  * @date    2026.4.28
  * @brief   非阻塞程序结构与扫描按键标准库写法
  ******************************************************************************
  * @attention
  *
  * 版权声明
  *
  ******************************************************************************
  */
#include "main.h"
#include "led/bsp_gpio_led.h"
#include "systick/bsp_systick.h" 
#include "idr/bsp_gpio_idr.h" 
#include "key/bsp_gpio_key.h"
#include "stm32f10x.h"


/*
   ************************程序设计要求*****************************************
   *                                                                           *
   *         1.编译并下载程序到开发板即可观察现象;                            *
   *         2.观察板载绿灯以500ms翻转状态。                                   *
   *         3.改变光敏传感器周围的光照,来切换LED灯状态。                     *
   *         4.短按KEY1核心板蓝灯亮,长按KEY1红灯亮,释放按键灭灯              *
   *         注意:每一个功能不可阻塞过多影响其他功能                          *
   *
   *                                                                           *
   *****************************************************************************

*/
    
/**
  * @brief  主函数
  * @param  无
  * @note   无
  * @retval 无
  */

int main (void)
{   
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);    
    KEY_Init(&key1_info);  
    Key_TaskInit(20);
    SysTick_Init();
    
    while(1) 
    {
        /* 按键长短触发灯 */
        Key_Task();
    }
}


在main.c中主要看whilel里面和 Key_TaskInit(20);这部分,每过20ms会进入一次中断函数。在while里面会不断扫描按键的任务

3.2 定时器非阻塞按键扫描核心逻辑部分

头文件定义一些结构体变量函数声明部分

#ifndef __BSP_GPIO_KEY_H
#define __BSP_GPIO_KEY_H

#include "stm32f10x.h"

/*定义连接GPIO端口,用户只需要修改下面代码即可改变控制的LED引脚 */

/**********************核心板载按键****************************************/
//KEY1
#define KEY1_GPIO_PORT      GPIOA                       /* GPIO 端口 */
#define KEY1_GPIO_CLK_PORT  RCC_APB2Periph_GPIOA        /* GPIO 端口时钟 */
#define KEY1_GPIO_PIN       GPIO_Pin_0                  /* 对应PIN脚 */

//KEY2
#define KEY2_GPIO_PORT      GPIOC                       /* GPIO 端口 */
#define KEY2_GPIO_CLK_PORT  RCC_APB2Periph_GPIOC        /* GPIO 端口时钟 */
#define KEY2_GPIO_PIN       GPIO_Pin_13                 /* 对应PIN脚 */


/**********************用户自定义按键****************************************/
//KEY3
#define KEY3_GPIO_PORT      GPIOB                       /* GPIO 端口 */
#define KEY3_GPIO_CLK_PORT  RCC_APB2Periph_GPIOB        /* GPIO 端口时钟 */
#define KEY3_GPIO_PIN       GPIO_Pin_15                 /* 对应PIN脚 */

//KEY4
#define KEY4_GPIO_PORT      GPIOB                       /* GPIO 端口 */
#define KEY4_GPIO_CLK_PORT  RCC_APB2Periph_GPIOB        /* GPIO 端口时钟 */
#define KEY4_GPIO_PIN       GPIO_Pin_6                 /* 对应PIN脚 */

//KEY5
#define KEY5_GPIO_PORT      GPIOB                       /* GPIO 端口 */
#define KEY5_GPIO_CLK_PORT  RCC_APB2Periph_GPIOB        /* GPIO 端口时钟 */
#define KEY5_GPIO_PIN       GPIO_Pin_7                 /* 对应PIN脚 */

//KEY6
#define KEY6_GPIO_PORT      GPIOB                       /* GPIO 端口 */
#define KEY6_GPIO_CLK_PORT  RCC_APB2Periph_GPIOB        /* GPIO 端口时钟 */
#define KEY6_GPIO_PIN       GPIO_Pin_8                 /* 对应PIN脚 */

/* 按键按下时的IO电平 */
typedef enum {
    KEY_LOW_TRIGGER = 0,
    KEY_HIGH_TRIGGER = 1,
    KEY_GENERAL_TRIGGER = 2,
}KEY_TriggerLevel;                          

/* 按键的状态 */
typedef enum {
    KEY_UP = 0,
    KEY_DOWN = 1,
    KEY_INIT = 2,
}KEY_Status;


/* 按键的事件类型 */
typedef enum {
    EVENT_ATTONITY      = 0,  /* 空闲状态 */
    EVENT_PRESS         = 1,  /* 按下状态 */
    EVENT_SHORT         = 2,  /* 短按状态 */
    EVENT_SHORT_RELEASE = 3,  /* 短按释放状态 */
    EVENT_LONG          = 4,  /* 长按状态 */
    EVENT_LONG_RELEASE  = 5,  /* 长按释放状态 */
}KEY_Evnet;

/* 按键的点击类型 */
typedef enum {
    KEY_NONE_CLICK       = 0,
    KEY_SINGLE_CLICK     = 1,   /*单击*/
    KEY_DOUBLE_CLICK     = 2,   /*双击*/
    KEY_LONG_CLICK       = 3,   /*按键长按*/
}KEY_ClickType;


/* 按键结构体 */
typedef struct
{
    GPIO_TypeDef*    GPIOx;
    uint16_t         GPIO_Pin;
    KEY_TriggerLevel triggerLevel;      /* 按下时电平状态 */
    KEY_Status       status;            /* 按键的状态 */
    uint64_t         press_time;        /* 按下时间 */
    uint64_t         release_time;      /* 释放时间 */
    KEY_Evnet        evnet;             /* 按键事件 */
    KEY_ClickType    clicktype;         /* 按键点击类型 */
}KEY_Info;


extern KEY_Info key1_info;

void KEY_GPIO_Config(void);
KEY_Status KEY_Scan(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, KEY_TriggerLevel key_pressstatus);
void KeyLevel_Init(KEY_Info* key_info);
void KEY_Init(KEY_Info* key_info);
KEY_Evnet KEY_SystickScan(KEY_Info* key_info);
#endif /*__BSP_GPIO_KEY_H*/



3.2.1 做事情部分

/**
  * @brief  按键 任务
  * @param  无
  * @retval 无
  */
void Key_Task(void)
{
    if (key_task.flag == 1) 
    {
        KEY_Evnet current_event;
        current_event = KEY_SystickScan(&key1_info);
        
        /* 短按蓝灯亮*/
        if (current_event == EVENT_SHORT)
        {
            LED_GPIO_ON(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
        }
        /* 短按释放 蓝灯灭*/
        else if (current_event == EVENT_SHORT_RELEASE) 
        {
            LED_GPIO_OFF(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
        }
         
        /* 长按红灯亮 */
        if (current_event == EVENT_LONG)
        {
            LED_GPIO_ON(R_LED1_GPIO_PORT, R_LED1_GPIO_PIN, LED_LOW_TRIGGER);
        }
        /* 长按释放 红灯灭*/
        else if (current_event == EVENT_LONG_RELEASE) 
        {
            LED_GPIO_OFF(R_LED1_GPIO_PORT, R_LED1_GPIO_PIN, LED_LOW_TRIGGER);
        }
        
//        /*单击事件*/
//        if (key1_info.clicktype == KEY_SINGLE_CLICK) 
//        {
//            LED_TOGGLE(R_LED1_GPIO_PORT, R_LED1_GPIO_PIN);
//            key1_info.clicktype = KEY_NONE_CLICK;
//        }
        Key_TaskReset();
    }
}

接着flag的逻辑继续往下走,当定时20ms事件到了,if判断的flag就会成立,开始做事情。

KEY_Evnet current_event;
current_event = KEY_SystickScan(&key1_info);

定义一个事件结构体变量。
通过按键定时器扫描函数返回的事件去执行对应的事件的功能。

/* 短按蓝灯亮*/
if (current_event == EVENT_SHORT)
{
    LED_GPIO_ON(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
}
/* 短按释放 蓝灯灭*/
else if (current_event == EVENT_SHORT_RELEASE) 
{
    LED_GPIO_OFF(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
}
 
/* 长按红灯亮 */
if (current_event == EVENT_LONG)
{
    LED_GPIO_ON(R_LED1_GPIO_PORT, R_LED1_GPIO_PIN, LED_LOW_TRIGGER);
}
/* 长按释放 红灯灭*/
else if (current_event == EVENT_LONG_RELEASE) 
{
    LED_GPIO_OFF(R_LED1_GPIO_PORT, R_LED1_GPIO_PIN, LED_LOW_TRIGGER);
}

如果按键是短按事件,执行蓝灯亮,按键短按释放事件,执行蓝灯灭。
如果是长按事件,执行红灯亮,长按释放事件,红灯灭。就是通过返回对应的一些事件从而根据不同的事件去执行对应的功能。

3.2.2 事件逻辑部分

3.2.2.1 按键初始化部分

/**
  * @brief  获取当前操作按键电平
  * @param  key_info:   操作按键的信息结构体
  * @note   无
  * @retval 当前操作按键的电平
  */
static uint8_t Get_KeyCurrentLevel(KEY_Info* key_info)
{
    return GPIO_ReadInputDataBit(key_info->GPIOx, key_info->GPIO_Pin);
}

这部分就是获取按键的GPIO的电平状态,高电平,低电平。

KEY_Info key1_info = { KEY1_GPIO_PORT, KEY1_GPIO_PIN, KEY_GENERAL_TRIGGER, KEY_INIT, 0,0,EVENT_ATTONITY,KEY_NONE_CLICK};
/**
  * @brief  获取当前操作按键空闲/抬起后的电平
  * @param  key_info:   操作按键的信息结构体
  * @note   无
  * @retval 无
  */
void KeyLevel_Init(KEY_Info* key_info)
{
    if (key_info->triggerLevel == KEY_GENERAL_TRIGGER) 
    {
        /* 上电后程序启动短时间内默认按键未按,那么根据当前未按下电平,反推出按下的电平*/
        /* 设置按键按下后电平状态是高电平 */
        key_info->triggerLevel = Get_KeyCurrentLevel(key_info)? KEY_LOW_TRIGGER : KEY_HIGH_TRIGGER;
        
        
    }
}

这部分就是很有意思了。
其中key1_info是一个全局变量给结构体里面的成员初始化。
初始化GPIO,Pin脚。

/* 按键按下时的IO电平 */
typedef enum {
    KEY_LOW_TRIGGER = 0,
    KEY_HIGH_TRIGGER = 1,
    KEY_GENERAL_TRIGGER = 2,
}KEY_TriggerLevel;  

电平状态是 KEY_GENERAL_TRIGGER

/* 按键的状态 */
typedef enum {
    KEY_UP = 0,
    KEY_DOWN = 1,
    KEY_INIT = 2,
}KEY_Status;

按键按的状态是 KEY_INIT

按下的时间与释放的时间都是设置0

/* 按键的事件类型 */
typedef enum {
    EVENT_ATTONITY      = 0,  /* 空闲状态 */
    EVENT_PRESS         = 1,  /* 按下状态 */
    EVENT_SHORT         = 2,  /* 短按状态 */
    EVENT_SHORT_RELEASE = 3,  /* 短按释放状态 */
    EVENT_LONG          = 4,  /* 长按状态 */
    EVENT_LONG_RELEASE  = 5,  /* 长按释放状态 */
}KEY_Evnet;

按键事件是 EVENT_ATTONITY 空闲

/* 按键的点击类型 */
typedef enum {
    KEY_NONE_CLICK       = 0,
    KEY_SINGLE_CLICK     = 1,   /*单击*/
    KEY_DOUBLE_CLICK     = 2,   /*双击*/
    KEY_LONG_CLICK       = 3,   /*按键长按*/
}KEY_ClickType;

按键点击类型 KEY_NONE_CLICK

所以初始化按键结构体全局变量

KEY_Info key1_info = { KEY1_GPIO_PORT, KEY1_GPIO_PIN, KEY_GENERAL_TRIGGER, KEY_INIT, 0,0,EVENT_ATTONITY,KEY_NONE_CLICK};

回到代码这里

/**
  * @brief  获取当前操作按键空闲/抬起后的电平
  * @param  key_info:   操作按键的信息结构体
  * @note   无
  * @retval 无
  */
void KeyLevel_Init(KEY_Info* key_info)
{
    if (key_info->triggerLevel == KEY_GENERAL_TRIGGER) 
    {
        /* 上电后程序启动短时间内默认按键未按,那么根据当前未按下电平,反推出按下的电平*/
        /* 设置按键按下后电平状态是高电平 */
        key_info->triggerLevel = Get_KeyCurrentLevel(key_info)? KEY_LOW_TRIGGER : KEY_HIGH_TRIGGER;
        
        
    }
}

由于第三个参数初始化的是KEY_GENERAL_TRIGGER,这个if肯定是成立的。

在这里插入图片描述
按键结构图

按键PA0引脚有一个下拉电阻,上电后默认电平是低电平,当按键按下,此时的PA0引脚是一个高电平。

 Get_KeyCurrentLevel(key_info)

首先通过这个代码,读取按键1的GPIO引脚电平状态,从这个电路图可以看出,上电后默认电平是低电平,所以返回的是低电平。

Get_KeyCurrentLevel(key_info)? KEY_LOW_TRIGGER : KEY_HIGH_TRIGGER;

接着继续下一步

0?KEY_LOW_TRIGGER : KEY_HIGH_TRIGGER;

所以执行:

KEY_HIGH_TRIGGER;

这部分是三目运算符,表达式? a:b
如果表达式成立执行a,否则执行b。

而这里的表达式是0,所以0是假的不成立,执行冒号右边

KEY_HIGH_TRIGGER;

最终

 key_info->triggerLevel = Get_KeyCurrentLevel(key_info)? KEY_LOW_TRIGGER : KEY_HIGH_TRIGGER;
 

key_info->triggerLevel保存的是KEY_HIGH_TRIGGER;
高电平。

这里保存的高电平意味着,按键按下后的电平状态,按键按下后是PA0是高电平。

最后就是初始化部分了

/**
  * @brief  按键初始化
  * @param  无
  * @retval 无
  */
void KEY_Init(KEY_Info* key_info)
{
    /* 对应的 GPIO 配置 */
    KEY_GPIO_Config();
    
    /*按键电平初始化*/
    KeyLevel_Init(key_info);
}

在main.c里面有这句代码
在这里插入图片描述

对按键初始化,按键初始化里面有按键GPIO初始化以及按键电平初始化两部分初始化。

3.2.2.2 按键的事件逻辑部分

按键事件逻辑图
在这里插入图片描述
在这里插入图片描述

KEY_Evnet KEY_SystickScan(KEY_Info* key_info)
{
    uint64_t time_time = 0;
    /* 读取按键GPIO电平状态 与 按下后GPIO电平状态一致*/
    if (Get_KeyCurrentLevel(key_info) == key_info->triggerLevel) 
    {
        key_info->status = KEY_DOWN;
    }
    else
    {
        key_info->status = KEY_UP;
    }
    
    switch(key_info->evnet)
    {
        case EVENT_ATTONITY:  /* 上一个事件处于 空闲/无操作 时 */
            if (key_info->status == KEY_DOWN) 
            {
                key_info->evnet = EVENT_PRESS;
                key_info->press_time = SysTick_GetCount();
            }
            break;
            
        case EVENT_PRESS:  /* 上一个事件处于 按键按下 时 */
            if (key_info->status == KEY_DOWN) 
            {
                time_time = SysTick_GetCount();
                /*按下时间超过自定义长按时间 还继续按下时 属于长按*/
                if (time_time - key_info->press_time > 1000)
                {
                    key_info->evnet = EVENT_LONG; /* 进入长按事件 */
                }
                
            }
            else
            {
                key_info->evnet = EVENT_SHORT;  /* 进入短按事件 */
            }
            break;
            
         case EVENT_SHORT:  
            /*上一轮返回EVENT_SHORT,无进阶事件,重回EVENT_ATTONITY*/
            key_info->evnet = EVENT_SHORT_RELEASE;
         
            /*按键单击*/
            //key_info->clicktype = KEY_SINGLE_CLICK;
            break;
         
         case EVENT_LONG:  
            if (key_info->status == KEY_DOWN) 
            {
                 key_info->evnet = EVENT_LONG; /* 保持长按事件 */
            }
            else 
            {
                /*上一轮返回EVENT_LONG,无进阶事件,重回EVENT_ATTONITY*/
                key_info->evnet = EVENT_LONG_RELEASE;
                
                /*按键长击*/
//                key_info->clicktype = KEY_LONG_CLICK;
            }
            break;
         
         default:
            key_info->evnet = EVENT_ATTONITY;
    }
    return key_info->evnet;
}
3.2.2.2.1 按键按下逻辑部分

进入事件逻辑部分代码开始执行

if (Get_KeyCurrentLevel(key_info) == key_info->triggerLevel) 
{
    key_info->status = KEY_DOWN;
}
else
{
    key_info->status = KEY_UP;
}

key_info->triggerLevel这个地方由于初始化过了是高电平,初始化按键按下后的电平状态
Get_KeyCurrentLevel(key_info),这个是用来获取按键GPIO电平状态。

当如果上电后,按键按下,此时按键的GPIO的状态是高电平,此时if成立的,给按键结构体第四个成员按键状态—— key_info->status初始化KEY_DOWN,按键按下的状态。

3.2.2.2.2 空闲事件逻辑部分
switch(key_info->evnet)
{
    case EVENT_ATTONITY:  /* 上一个事件处于 空闲/无操作 时 */
        if (key_info->status == KEY_DOWN) 
        {
            key_info->evnet = EVENT_PRESS;
            key_info->press_time = SysTick_GetCount();
        }
        break;
        return key_info->evnet;
}

接着进入到switch的事件选择中,由于按键结构体第7个成员按键事件初始化的时候,初始化按键事件的是空闲状态——EVENT_ATTONITY,所以直接会执行
case EVENT_ATTONITY:,先在if里面判断按键是否按键,如果按键按下,会做两件事情,1.按键结构体第7个按键事件成员赋值按下的指令——EVENT_PRESS,2.记录按键按下第一次20ms时间到达的初始时间。然后break跳出,返回事件,此时返回的事件只是按键按下的事件,并不会做什么事情。

3.2.2.2.3 长按事件逻辑部分
 switch(key_info->evnet)
 {       
     case EVENT_PRESS:  /* 上一个事件处于 按键按下 时 */
         if (key_info->status == KEY_DOWN) 
         {
             time_time = SysTick_GetCount();
             /*按下时间超过自定义长按时间 还继续按下时 属于长按*/
             if (time_time - key_info->press_time > 1000)
             {
                 key_info->evnet = EVENT_LONG; /* 进入长按事件 */
             }
         }
         else
         {
             key_info->evnet = EVENT_SHORT;  /* 进入短按事件 */
         }
         break;   
 }
 	return key_info->evnet;
}

接着又过了20ms,又会进入到事件逻辑这里,由于按键结构体第7个成员按键事件此时保存的是按键按下事件,所以会执行case EVENT_PRESS:按键按下的事件。
假如这个按键我一直按着不动,此时按键结构体第四个成员按键状态保存的是KEY_DOWN(按键按下),if判断成立,定义一个变量time_time保存当按键长按时候,第二次或之后很多次查询按键按下的时间,此时就会有一个if判断长按。

/*按下时间超过自定义长按时间 还继续按下时 属于长按*/
if (time_time - key_info->press_time > 1000)
{
    key_info->evnet = EVENT_LONG; /* 进入长按事件 */
}

当按键按下第一次记录初始的时间key_info->press_time保存在按键结构体成员的第5个成员里面,当按键如果一直按下,事件逻辑会每过20ms不断进入,不断记录当前的时间,当前的时间减去初始时间就是按键按下所执行的时间,假如超过1000也就是1秒钟,然后给按键结构体第7个事件成员赋值长按事件。最后return 长按事件。
然后执行的逻辑是

/* 长按红灯亮 */
if (current_event == EVENT_LONG)
{
   LED_GPIO_ON(R_LED1_GPIO_PORT, R_LED1_GPIO_PIN, LED_LOW_TRIGGER);
}

红灯亮。

3.2.2.2.4 短按事件逻辑部分

当我的按键按下马上抬起并不是那种一直长按的情况
此时按键的状态是

/* 读取按键GPIO电平状态 与 按下后GPIO电平状态一致*/
if (Get_KeyCurrentLevel(key_info) == key_info->triggerLevel) 
{
    key_info->status = KEY_DOWN;
}
else
{
    key_info->status = KEY_UP;
} 

当按键松开的时候逻辑,Get_KeyCurrentLevel(key_info)获取按键GPIO电平状态,由于按键是松开的状态,按键有一个下拉电阻,所以此时按键GPIO电平状态是低电平,
而key_info->triggerLevel保存的是高电平,所以if判断条件不成立,
执行key_info->status = KEY_UP,按键结构体的第四个成员按键状态保存的是按键抬起的状态。

 switch(key_info->evnet)
 {
 		/* 读取按键GPIO电平状态 与 按下后GPIO电平状态一致*/
    if (Get_KeyCurrentLevel(key_info) == key_info->triggerLevel) 
    {
        key_info->status = KEY_DOWN;
    }
    else
    {
        key_info->status = KEY_UP;
    }     
      
     case EVENT_PRESS:  /* 上一个事件处于 按键按下 时 */
         if (key_info->status == KEY_DOWN) 
         {
             time_time = SysTick_GetCount();
             /*按下时间超过自定义长按时间 还继续按下时 属于长按*/
             if (time_time - key_info->press_time > 1000)
             {
                 key_info->evnet = EVENT_LONG; /* 进入长按事件 */
             }
         }
         else
         {
             key_info->evnet = EVENT_SHORT;  /* 进入短按事件 */
         }
         break;   
 }
 	return key_info->evnet;
}

由于上一次执行事件,按键结构体第7个事件成员保存的是EVENT_PRESS按下的事件,此时下一次进入事件的时候会进入 case EVENT_PRESS:,按键按下的事件里面去。
由于按键结构体第四个成员按键状态保存的是KEY_UP,所以if判断条件不成立,执行else,按键结构体第7个事件成员保存的是EVENT_SHORT,短按事件。
最后通过return 返回。

执行蓝灯亮的逻辑

/* 短按蓝灯亮*/
if (current_event == EVENT_SHORT)
{
    LED_GPIO_ON(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
}
3.2.2.2.5 短按释放事件逻辑部分
KEY_Evnet KEY_SystickScan(KEY_Info* key_info)
{
    uint64_t time_time = 0;    
    switch(key_info->evnet)
    {
         case EVENT_SHORT:  
            /*上一轮返回EVENT_SHORT,无进阶事件,重回EVENT_ATTONITY*/
            key_info->evnet = EVENT_SHORT_RELEASE;
         
            /*按键单击*/
            //key_info->clicktype = KEY_SINGLE_CLICK;
            break;
    }
    return key_info->evnet;
}

假如上一次事件执行的是短按,此时的事件会进入case EVENT_SHORT:
直接给按键结构体的第7个事件成员赋值EVENT_SHORT_RELEASE——短按释放。
接着return 事件。
执行

/* 短按释放 蓝灯灭*/
else if (current_event == EVENT_SHORT_RELEASE) 
{
    LED_GPIO_OFF(B_LED3_GPIO_PORT, B_LED3_GPIO_PIN, LED_LOW_TRIGGER);
}

蓝灯灭的代码

3.2.2.2.6 长按释放事件逻辑部分
KEY_Evnet KEY_SystickScan(KEY_Info* key_info)
{
/* 读取按键GPIO电平状态 与 按下后GPIO电平状态一致*/
    if (Get_KeyCurrentLevel(key_info) == key_info->triggerLevel) 
    {
        key_info->status = KEY_DOWN;
    }
    else
    {
        key_info->status = KEY_UP;
    }
    
    switch(key_info->evnet)
    {
         case EVENT_LONG:  
            if (key_info->status == KEY_DOWN) 
            {
                 key_info->evnet = EVENT_LONG; /* 保持长按事件 */
            }
            else 
            {
                /*上一轮返回EVENT_LONG,无进阶事件,重回EVENT_ATTONITY*/
                key_info->evnet = EVENT_LONG_RELEASE;
                
                /*按键长击*/
//                key_info->clicktype = KEY_LONG_CLICK;
            }
            break;
    }
    return key_info->evnet;
}

当上一个执行的事件是长按,此时进入到switch中此时执行 case EVENT_LONG:
会判断按键是否松开,长按事件会出现一种情况,假如我按键按下超过1秒钟,此时的事件是长按事件,那么我按键如果没有松开继续长按呢,所以需要对在if里面进行判断按键是否继续按下,如果按键状态还是按下,则事件还是保存的是长按事件,通过return 返回,执行红灯亮的代码。
如果此时按键没有按下,此时保存的事件是长按释放事件EVENT_LONG_RELEASE;
在按键结构体第7个事件成员保存的是长按释放事件,然后通过return返回去。
执行代码

/* 长按释放 红灯灭*/
else if (current_event == EVENT_LONG_RELEASE) 
{
    LED_GPIO_OFF(R_LED1_GPIO_PORT, R_LED1_GPIO_PIN, LED_LOW_TRIGGER);
}

红灯灭。

3.2.2.2.7 回到空闲事件逻辑部分

当如果当前执行的事件是长按释放或者短按释,下一次20ms到达的时候,此时又进入一次switch里面,发现没有对应的case选择,所以选择的是default

default:
		key_info->evnet = EVENT_ATTONITY;

此时的按键结构体第7个事件成员保存的是空闲事件,然后将事件返回去,不做任何操作。
之后不断返回的事件是空闲事件。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐