目录
原理简介
用0.5秒进行判断,0.5s内按一下算短按,0.5s内按两下算双击,0.5s以上按一下算长按。是不是一看觉得很简单,先让我们认真分析一下。
首先:你按下0.5s以内,然后松开,这个时候时间还在0.5s以内,那你改对他放入那一类呢?答案是这个时候还不能判断,你无法判断它剩下的时间能否再按一次(总时间未超过0.5s),所以你对一次按键的判断肯定得超过0.5s,所以这个代码的弊端一就出来了。
到这里,有人肯定会说,这还不是照样简单,0.5s按一下我就算短按,0.5s按两下我就算双击,可是还是会有一个问题,我第一次按下抬起在0.5s以内,但我第二次按下了,我还没松开,0.5s已经过了,那这种算啥呢?所以我们这个时候得把第一次算短按中去,剩下的这个第二次按下需要重新判断。
最后聪明的小伙伴会问,那这个时候应没坑了吧?那我也说多余的坑我也不知道。当然这里面的长按是不需要啥顾虑的,你按下的时间超过了0.5s,那肯定是长按了,还有就是双击的判读,0.5s内你就是按了两次,同时最后松开了,那就是双击。这个时候比较牛逼的人就要说了,博主你是不是不行啊,像我手速快,我0.5s按三下,但然我还没回答,更加牛逼的人肯定要说了,0.5s按三下算什么,我手速快的飞起,0.5s四下,0.5s四下算什么,0.5s一万下····
不得不说他们确实牛逼,博主手速没你们这么快。所以我们得引入循环这个概览,10ms定时器加中断,既可以消抖动,还能循环往复,那三次怎么算你,首先前两次肯定算双击,后面一次 保留进行0.5s的重新观察,那0.5s按四次就是两次双击,一万次的博主表示,太快了不好。
主要分析图(按下时低电平,松开是高电平)
代码
我这里用了hal库,板子stm32g431Rbt6,我这里用四个按键,随意弄了一个结构体,但其实不管是啥都一样的,无非就是对一个按键进行不断读取,然后判断状态。
uint16_t count;
struct key
{
bool single_flag; //单次按下
bool double_flag; //双击
bool long_flag; //长按
uint16_t double_time; //按下后松开的时间
bool double_timeEN; //已经有一次按下松开的标志位
uint16_t age; //按下的时间
uint16_t age_old; //需要保留的第一次按下时间
bool press; //松开的标准位
}keys[5]; //懒得减一
uint8_t key_read(void){ //判断按键的返回值
uint8_t key_val=0;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)
key_val=1;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)
key_val=2;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==0)
key_val=3;
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==0)
key_val=4;
return key_val;
}
void key_scan(void)
{
uint8_t key_sta=key_read();
if(key_sta!=0) //有按键按下
{
keys[key_sta].age++; //按下时间记录
if(keys[key_sta].age>1){ //第一次不管,进行消抖
keys[key_sta].press=1; //按下标志位
if(keys[key_sta].double_timeEN==1&&(keys[key_sta].age+keys[key_sta].age_old+keys[key_sta].double_time)>=50){ //0.5s去除前一次,保留后一次
keys[key_sta].double_timeEN=0;
keys[key_sta].double_time=0;
keys[key_sta].single_flag=1;
keys[key_sta].age_old=0;
}
}
}else //松开
{
for(int i=0;i<5;i++)
{
if(keys[i].age>50) //直接长按
{
keys[i].long_flag=1;
}
if(keys[i].double_timeEN==1&&keys[i].press==1&&(keys[i].age+keys[i].age_old+keys[i].double_time)<50) //符合双击
{
keys[i].double_flag=1;
keys[i].press=0;
keys[i].double_timeEN=0;
keys[i].age_old=0;
}
if(keys[i].press==1&&keys[i].long_flag!=1) //第一次按下松开
{
keys[i].double_timeEN=1; //记录松开时间,打开标志位
keys[i].age_old=keys[i].age; //保存第一次按下时间
}
if(keys[i].double_timeEN==1)
{
keys[i].double_time++; //记录松开时间
}
if(keys[i].double_timeEN==1&&(keys[i].double_time+keys[i].age)>50) //符合单击
{
keys[i].single_flag=1;
keys[i].double_timeEN=0;
keys[i].double_time=0;
}
keys[i].age=0; //松开进行数据清理
keys[i].press=0;
}
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){ //10ms定时器中断
if(htim->Instance==TIM6){
key_scan();
}
}
void key_prc1(){ //对具体进行处理,同时记得清零
if(keys[1].single_flag){
count++;
keys[1].single_flag=0; //单击清零
}
if(keys[1].long_flag){
count+=2;
keys[1].long_flag=0;
}
if(keys[1].double_flag){
count+=3;
keys[1].double_flag=0;
}
}
//定时器初始化我就不写了,还有一下其他基础配置之类的也不写了
int main(void)
{
while (1)
{
key_prc1();
}
}
总结
聪明的人是不是发现第二个弊端了,if(keys[key_sta].age>1)竟然第一次进来20ms,博主时间这么长的吗?其实也可以弄成10ms,只不过又得加变量了,需要设置key_old=为之前出入的值,然后key_up,在key_down,有点麻烦了,算了,是我太懒了。
欧克,如有不足,希望指正