0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

STM32按键状态机3——增加双击与功能优化

码农爱学习 来源:码农爱学习 作者:码农爱学习 2022-09-04 17:05 次阅读

上篇文章,介绍了将按键检测增加长按功能,并将按下抖动与松开抖动共用一个抖动状态来表示,其状态图如下:

pYYBAGMTVcKAAzPKAABa-BfEo28672.png

仔细研究这个状态图,其它还存在一些问题:

短按状态,只要按下去,不需要等按键再释放,就会触发短按事件。对于需要按下再松开作为一次短按的应用来说,此状态图也不满足需求

长按状态,必须先经过短按状态,即长按按键,会先触发一个短按,再触发一个长按。如果实际应用中需要分别使用短按和长按,则此状态图不满足要求

本篇,就来解决上述两个问题,并再增加一个按键双击检测,实现一个功能更全面的按键检测。

1 增加双击检测

增加一个双击检测,需要增加两个状态:

等待再次按下

确认第2次按下

同时,之前的“短按状态”和“长按状态”分别改为“确认按下”和“确认长按”。

1.1 状态图修改

修改后的状态图如下,有以下几点需要注意:

确认按下”不是短按触发的条件,需要等松开后,经消抖进入到“等待再次按下”一段时间后(200ms),没有再次被按下,才触发短按事件,这样就解决了本篇开头提到的第1个问题

确认按下”不是短按触发的条件,另一个用途是,当此状态继续保持按下状态一段时间后(1s),则会单独触发长按事件,同时进入到“确认长按”状态,这样就解决了本篇开头提到的第2个问题

对于双击事件的检测,首先按下按键进入“确认按下”状态,然后在1s内松开进入“等待再次按下”状态,接着在200ms内再次按下进入“确认第2次按下”状态,然后在1s内松开,即可触发双击事件,并同时进入“稳定松开”状态

注意,在“确认第2次按下”状态下,如果在1s内没有松开,也会进入到“确认长按”状态

poYBAGMUaY-AEN6DAACzMOOUn5E684.png

1.2 程序编写

根据状态图,修改对应的状态机逻辑,修改后的代码如下:

void key_status_check()
{
	switch(g_keyStatus)
	{
		//按键释放(初始状态)
		case KS_RELEASE:
		{
			//检测到低电平,先进行消抖
			if (KEY0 == 0)
			{
				g_keyStatus = KS_SHAKE;
			}
		}
		break;
		
		//抖动
		case KS_SHAKE:
		{
			if (KEY0 == 1) 
			{
				//从松开状态来的抖动
				if (KS_RELEASE == g_lastKeyStatus)
				{
					g_keyStatus = KS_RELEASE;
				}
				//从等待再次按下状态来的抖动
				else if (KS_WAIT_PRESS_AGAIN == g_lastKeyStatus)
				{
					g_keyStatus = KS_WAIT_PRESS_AGAIN;
				}
				//从确认按下状态来
				else if (KS_AFFIRM_SHORT_PRESS == g_lastKeyStatus)
				{
					g_WaitPressAgainCnt = 0;
					g_keyStatus = KS_WAIT_PRESS_AGAIN;
				}
				//从确认再次按下状态来
				else if (KS_AFFIRM_PRESS_AGAIN == g_lastKeyStatus)
				{
					printf("=====> key double press\r\n");
					g_keyStatus = KS_RELEASE;
				}
				//从确认长按状态来
				else if (KS_AFFIRM_LONG_PRESS == g_lastKeyStatus)
				{
					g_keyStatus = KS_RELEASE;
				}
				else
				{
					printf("err!\r\n");
				}
			}
			else
			{
				//从确认按下状态来的抖动
				if (KS_AFFIRM_SHORT_PRESS == g_lastKeyStatus)
				{
					g_keyStatus = KS_AFFIRM_SHORT_PRESS;
				}
		        //从第2次按下状态来的抖动
				else if (KS_AFFIRM_PRESS_AGAIN == g_lastKeyStatus)
				{
					g_keyStatus = KS_AFFIRM_PRESS_AGAIN;
				}
				//从确认长按状态来的抖动
				else if (KS_AFFIRM_LONG_PRESS == g_lastKeyStatus)
				{
					g_keyStatus = KS_AFFIRM_LONG_PRESS;
				}
				//从松开状态而来
				else if (KS_RELEASE == g_lastKeyStatus)
				{
					g_PressTimeCnt = 0;
					g_keyStatus = KS_AFFIRM_SHORT_PRESS;
					//printf("=====> key short press\r\n");
				}
				//从等待再次看下(的松开)状态而来
				else if (KS_WAIT_PRESS_AGAIN == g_lastKeyStatus)
				{
					g_Press2TimeCnt = 0;
					g_keyStatus = KS_AFFIRM_PRESS_AGAIN;
				}
				else
				{
					printf("err!\r\n");
				}
			}
		}
		break;
		
		//确认按下
		case KS_AFFIRM_SHORT_PRESS:
		{
			//检测到高电平,先进行消抖
			if (KEY0 == 1)
			{
				g_keyStatus = KS_SHAKE;
			}
			else
			{
				if (g_LongPressTimeCnt % 20 == 0) //每隔1000ms打印一次
				{
					printf("=====> key long press:%d\r\n", g_LongPressTimeCnt/20);
					
					keyEvent = KE_LONG_PRESS;
				}
				g_LongPressTimeCnt++;
			}
		}
		break;
		
		//等待再次按下
		case KS_WAIT_PRESS_AGAIN:
		{
			//检测到低电平,先进行消抖
			if (KEY0 == 0)
			{
				g_keyStatus = KS_SHAKE;
			}
			
			g_WaitPressAgainCnt++;
			if (g_WaitPressAgainCnt == 4) //200ms没有再次按下
			{
				printf("=====> key single press\r\n");
				g_keyStatus = KS_RELEASE;
			}
		}
		break;
		
		//确认第2次按下
		case KS_AFFIRM_PRESS_AGAIN:
		{
			//检测到高电平,先进行消抖
			if (KEY0 == 1)
			{
				g_keyStatus = KS_SHAKE;
			}
			
			g_Press2TimeCnt++;
			if (g_Press2TimeCnt == 20) //1000ms
			{
				g_LongPressTimeCnt = 0;
				g_keyStatus = KS_AFFIRM_LONG_PRESS;
			}
		}
		break;
		
	    //确认长按
		case KS_AFFIRM_LONG_PRESS:
		{
			//检测到高电平,先进行消抖
			if (KEY0 == 1)
			{
				g_keyStatus = KS_SHAKE;
			}
			
			g_LongPressTimeCnt++;
			if (g_LongPressTimeCnt % 20 == 0) //每隔1000ms打印一次
			{
				printf("=====> key long press:%d\r\n", g_LongPressTimeCnt/20);
			}
		}
		break;
		
		default:break;
	}
	
	if (g_keyStatus != g_nowKeyStatus)
	{
		g_lastKeyStatus = g_nowKeyStatus;
		g_nowKeyStatus = g_keyStatus;
		//printf("new key status:%d(%s)\r\n", g_keyStatus, key_status_name[g_keyStatus]);
	}
}

最后注释掉的一句是调试打印,调试时可打开,方便观察状态变化

1.3 测试

短按、长按、双击的测试结果如下:

poYBAGMUaeGAAoP2AAEtu7uP7lU210.png

还有从确认第2次按下状态到达的长按状态:

pYYBAGMUaeeANCNHAAByVtpyQgE552.png

2 功能优化

上面的代码实现,是在主函数中,每50ms延时执行一次状态机循环(主函数代码如下),仅用做演示按键状态机的运行机制。

int main(void)
{	
	delay_init();
	KEY_Init();
	uart_init(115200);

	printf("hello\r\n");
	
	while(1)
	{
		key_status_check();
		delay_ms(50);
	}
}

实际开发中,按键检测程序,应该作为一个独立的模块运行,当检测到某一按键状态触发时,通知应用程序来使用。

对于stm32裸机开发来说,可以将按键状态机放到一个定时器中断服务函数中运行,当检测到某一按键状态触发后,通知应用程序:

//主函数
int main(void)
{	
	delay_init();
	KEY_Init();
	uart_init(115200);
	TIM3_Int_Init(500-1,7200-1); //调用定时器使得50ms产生一个中断

	printf("hello\r\n");
	
	while(1)
	{
	}
}

//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
		
		KEY_EVENT keyEvent = key_status_check();
		switch (keyEvent)
		{
			case KE_SHORT_PRESS:  printf("检测到单击\r\n"); break;
			case KE_DOUBLE_PRESS: printf("检测到双击\r\n"); break;
			case KE_LONG_PRESS:   printf("检测到长按\r\n"); break;
			default:break;
		}
	}
}

3 总结

本篇在前两篇按键状态机的基础上,继续介绍增加按键的双击功能,并解决之前状态存在的两个问题,通过实测验证,演示短按、长按、双击的使用效果。最后对代码结构进行优化,使其更符合实际开发应用。

审核编辑 黄昊宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6001

    文章

    43970

    浏览量

    620785
  • 嵌入式
    +关注

    关注

    4981

    文章

    18274

    浏览量

    288362
  • STM32
    +关注

    关注

    2239

    文章

    10671

    浏览量

    348720
  • 状态机
    +关注

    关注

    2

    文章

    486

    浏览量

    27161
收藏 人收藏

    评论

    相关推荐

    STM32按键消抖——入门状态机思维

    本篇介绍了嵌入式软件开发中常用的状态机编程实现,并通过按键消抖实例,以常用的switch-case形式,实现了对应的状态机编程代码实现,并通过测试,串口打印对应状态,分析
    的头像 发表于 09-02 21:54 4278次阅读
    <b class='flag-5'>STM32</b><b class='flag-5'>按键</b>消抖——入门<b class='flag-5'>状态机</b>思维

    STM32按键状态机2——状态简化与增加长按功能

    本篇继续介绍状态机的使用,在上篇的基础上,通过简化按键去抖逻辑,并增加按键长按功能,进一步介绍状态图的修改与
    的头像 发表于 09-03 21:26 3380次阅读
    <b class='flag-5'>STM32</b><b class='flag-5'>按键</b><b class='flag-5'>状态机</b>2——<b class='flag-5'>状态</b>简化与<b class='flag-5'>增加</b>长按<b class='flag-5'>功能</b>

    状态机编程

    状态机编程基于状态机按键输入软件接口设计一般的教课书中给出的按键输入软件接口程序通常非常简单,在程序中一旦检测到按键输入口为低电平时(图9
    发表于 07-10 18:00

    单片状态机按键长按和短按实现

    本文只介绍主要代码段,完整代码可参考我的“蓝桥杯单片状态机按键按下和松开实现不同功能”蓝桥杯单片状态
    发表于 01-06 08:26

    如何利用状态机进行编程呢

    很多上升沿或下降沿,会引起误判。这里我们使用状态机的方式去处理,使用状态机还有一个好处就是可以很方便的去判断长按,短按,双击状态。当触摸屏有触点按下时,PENIRQ 引脚会输出低电平
    发表于 01-13 07:18

    STM32独立按键扫描功能的相关资料推荐

    STM32 独立按键扫描功能大全-支持连击、组合连击、任意连击本人刚学习STM32开发,最近看了硬汉的按键检测程序,进行了架构的深度
    发表于 02-08 07:29

    如何利用STM32去实现一种按键有限状态机

    STM32实现按键有限状态机(超详细,易移植)一、状态机简而言之,状态机是使不同状态之间的改变以
    发表于 02-16 06:58

    独立按键状态机读取函数的过程分享

    蓝桥杯单片状态机按键按下和松开实现不同功能独立按键状态机读取函数key_flag 键值读取标志
    发表于 02-23 06:20

    利用状态机按键消抖程序

    利用状态机按键消抖程序讲解,很好的资料下载吧。
    发表于 01-11 09:32 30次下载

    基于状态机的单片机按键短按长按功能的实现

    本文主要介绍了基于状态机的单片机按键短按长按功能的实现,按键的击键过程也是一种状态的切换,也可以看着是一个
    发表于 12-28 08:43 1.8w次阅读
    基于<b class='flag-5'>状态机</b>的单片机<b class='flag-5'>按键</b>短按长按<b class='flag-5'>功能</b>的实现

    基于STM32按键的防抖和松开处理:状态机

    用延时和while();去处理按键很浪费资源,这里我们用定时器来做一个按键的处理-状态机;typedef enum {KEY_RELEASED,KEY_PRESSED,KEY_PROCESSED
    发表于 12-09 09:21 7次下载
    基于<b class='flag-5'>STM32</b><b class='flag-5'>按键</b>的防抖和松开处理:<b class='flag-5'>状态机</b>

    STM32实现按键有限状态机(超详细,易移植)

    STM32实现按键有限状态机(超详细,易移植)一、状态机简而言之,状态机是使不同状态之间的改变以
    发表于 12-17 18:37 25次下载
    <b class='flag-5'>STM32</b>实现<b class='flag-5'>按键</b>有限<b class='flag-5'>状态机</b>(超详细,易移植)

    蓝桥杯单片机状态机按键按下和松开实现不同功能

    蓝桥杯单片机状态机按键按下和松开实现不同功能独立按键状态机读取函数key_flag 键值读取标志位key 主函数中得到键值key_press
    发表于 12-29 19:25 20次下载
    蓝桥杯单片机<b class='flag-5'>状态机</b><b class='flag-5'>按键</b>按下和松开实现不同<b class='flag-5'>功能</b>

    STM32状态机编程实例——全自动洗衣机(上)

    本篇实现了一款全自动洗衣机的基础洗衣控制流程,可实现不同水位与清洗次数的设置,以及任务的暂停与继续。此外,通过对之前按键状态机的进一步优化修改,实现了按键
    的头像 发表于 09-06 08:47 1768次阅读
    <b class='flag-5'>STM32</b><b class='flag-5'>状态机</b>编程实例——全自动洗衣机(上)

    按键状态机代码

    自己写的按键状态机,需要的时候根据情况修改一下
    发表于 03-27 10:42 6次下载