本文章以蓝桥杯嵌入式开发板为使用案列,使用stm32g431rbt6芯片,使用PB0,PB1,PB2,PA0作为独立按键的输入。
思路:
使用状态机的方式对4个独立按键进行单机,双击以及长按的识别
代码使用前提要求:需要一个10ms调用的定时器(软件定时器,硬件定时器皆可,精度无要求)
要使用本代码,首先你需要配置好如上的引脚为输入模式。
引脚配置:
.h文件:
key.h
/**
————————————————
@copyright Copyright (c) 2023 CSDN原创:believe、悠闲
原文链接:https://blog.csdn.net/qq_64777806/article/details/136200795
未经过允许,禁止商用
————————————————
*/
#ifndef _KEY_H_
#define _KEY_H_
typedef struct Key_Str //按键结构体
{
unsigned char level; //当前按键电平
unsigned char state; //当前按键状态
unsigned char one_flag; //单次按下状态
unsigned int long_time; //长按计数器
unsigned char long_flag; //长按状态
unsigned int twice_star; //单击遗留状态
unsigned int twice_time; //双击计数器
unsigned int twice_flag; //双击状态
} Key_Str;
void Key_Init(void); //按键引脚初始化,如果使用cubemx已经配置了可不调用
void Key_Scan(void); //按键扫描函数,10~15ms调用一次
#endif
.c文件
/**
————————————————
@copyright Copyright (c) 2023 CSDN原创:believe、悠闲
原文链接:https://blog.csdn.net/qq_64777806/article/details/136200795
未经过允许,禁止商用
————————————————
*/
#include "key.h"
#include "gpio.h"
Key_Str key[4]; //按键变量,数组长度4代表4个按键
void Key_Init(void) //按键引脚初始化函数
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
//GPIO A 0
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//GPIO B 0 1 2
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void Key_Scan(void) //按键扫描函数(建议10ms~20ms调用一次),本函数会自动修改key[4]结构体变量中的对应值,需要时在对应代码extern变量即可。
{
unsigned char i;
key[0].level = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
key[1].level = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
key[2].level = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
key[3].level = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
for(i=0;i<4;i++)
{
switch(key[i].state)
{
case 0:
if(0 == key[i].level) //首次按下
{
key[i].state = 1;
}
if(1 == key[i].twice_star) //2次按键判断计数
{
key[i].twice_time++;
if(key[i].twice_time>=100) //当计数超过100次时解除2次判断状态(此处100的值代表10ms持续100次,可自行修改)
{
key[i].twice_star = 0;
key[i].twice_time = 0;
}
}
break;
case 1:
if(0 == key[i].level) //消抖确认
{
key[i].state = 2; //确认
}else
{
key[i].state = 0; //抖动
}
break;
case 2:
if(0 == key[i].level)
{
key[i].long_time++;
if(key[i].long_time >= 50) //这里是长按触发,当长按达到500ms时直接触发长按标志位,无需等待放手,如需放手生效,则注释此if及其代码块(判断值可自行修改)
{
key[i].long_flag = 1;
key[i].state=3;
}
}else if(1 == key[i].level)
{
if(key[i].long_time < 50)
{
key[i].long_time=0;
if(1 == key[i].twice_star)
{
key[i].twice_flag = 1;
key[i].twice_time = 0;
key[i].twice_star = 0;
key[i].state=0;
}else if(0 == key[i].twice_star)
{
key[i].one_flag = 1;
key[i].state=0;
key[i].twice_star =1;
}
}
}
break;
case 3:
if(1 == key[i].level)
{
key[i].long_time=0;
key[i].state=0;
}
break;
}
}
}
以上代码实现了按键的单击,双击,以及长按。
业务部分使用列程:
1、确保按键引脚初始化(如已初始化可省略)
Key_Init(); //初始化按键引脚
2、设置一个10ms调用的中断,这里我使用的1ms定时器4中断配合10ms的软定
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint16_t j;
if (htim == (&htim4)) 硬件计时器
{
if(++j >= 10) //软件计时
{
j=0;
Key_Scan(); //按键扫描
}
}
}
3、在需要按键功能的代码文件中引用按键结构体变量Key_Str key[4],这里我在main.c中引用
//省略cube生成代码
#include "key.h"
//#include "xxx"
extern Key_Str key[4]; //引用外部变量
//省略cube生成代码
void main(void)
{
//省略cube生成代码
while(1)
{
if(1 == key[0].one_flag) //按键1单击
{
key[0].one_flag=0;
//功能代码
}else if(1 == key[0].long_flag) //按键1长按
{
key[0].long_flag=0;
//功能代码
}else if(1 == key[0].twice_flag) //按键1双击
{
key[0].twice_flag = 0;
//功能代码
}
if(1 == key[1].one_flag)
{
key[1].one_flag=0;
//功能代码
}else if(1 == key[1].long_flag)
{
key[1].long_flag=0;
//功能代码
}else if(1 == key[1].twice_flag)
{
key[1].twice_flag = 0;
//功能代码
}
if(1 == key[2].one_flag)
{
key[2].one_flag=0;
//功能代码
}else if(1 == key[2].long_flag)
{
key[2].long_flag=0;
//功能代码
}else if(1 == key[2].twice_flag)
{
key[2].twice_flag = 0;
//功能代码
}
if(1 == key[3].one_flag)
{
key[3].one_flag=0;
//功能代码
}else if(1 == key[3].long_flag)
{
key[3].long_flag=0;
//功能代码
}else if(1 == key[3].twice_flag)
{
key[3].twice_flag = 0;
//功能代码
}
}
}
//省略cube生成代码
演示效果(在功能代码中,我添加了led开关和lcd显示,方便观察是否读取按键成功)