Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >STM32按键状态机3——增加双击与功能优化

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

作者头像
xxpcb
发布于 2022-10-04 05:57:48
发布于 2022-10-04 05:57:48
1.3K00
代码可运行
举报
运行总次数:0
代码可运行

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

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

  • 短按状态,只要按下去,不需要等按键再释放,就会触发短按事件。对于需要按下再松开作为一次短按的应用来说,此状态图也不满足需求
  • 长按状态,必须先经过短按状态,即长按按键,会先触发一个短按,再触发一个长按。如果实际应用中需要分别使用短按和长按,则此状态图不满足要求

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

1 增加双击检测

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

  • 等待再次按下
  • 确认第2次按下

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

1.1 状态图修改

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

  • 确认按下”不是短按触发的条件,需要等松开后,经消抖进入到“等待再次按下”一段时间后(200ms),没有再次被按下,才触发短按事件,这样就解决了本篇开头提到的第1个问题
  • 确认按下”不是短按触发的条件,另一个用途是,当此状态继续保持按下状态一段时间后(1s),则会单独触发长按事件,同时进入到“确认长按”状态,这样就解决了本篇开头提到的第2个问题
  • 对于双击事件的检测,首先按下按键进入“确认按下”状态,然后在1s内松开进入“等待再次按下”状态,接着在200ms内再次按下进入“确认第2次按下”状态,然后在1s内松开,即可触发双击事件,并同时进入“稳定松开”状态
  • 注意,在“确认第2次按下”状态下,如果在1s内没有松开,也会进入到“确认长按”状态

1.2 程序编写

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 测试

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

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

2 功能优化

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main(void)
{	
	delay_init();
	KEY_Init();
	uart_init(115200);

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//主函数
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 总结

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农爱学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
STM32按键消抖——入门状态机思维
在嵌入式软件开发中,状态机编程是一个十分重要的编程思想,它也是嵌入式开发中一个常用的编程框架。掌握了状态机编程思想,可以更加逻辑清晰的实现复杂的业务逻辑功能。
xxpcb
2022/10/04
1.1K0
STM32按键消抖——入门状态机思维
STM32按键状态机2——状态简化与增加长按功能
上篇文章:STM32按键消抖——入门状态机思维,以按键消抖功能,介绍了状态机的基本原理与使用方法。
xxpcb
2022/10/04
6670
STM32按键状态机2——状态简化与增加长按功能
STM32状态机编程实例——全自动洗衣机(上)
前面几篇文章,以按键功能,介绍了状态机的原理与按键状态机实例,实现按键单击、双击、长按等状态的检测。
xxpcb
2022/10/04
1.3K0
STM32状态机编程实例——全自动洗衣机(上)
基于FreeRTOS车载音响Source切换与诊断功能实现
基于FreeRTOS设计汽车音响的Source切替和Diag功能,需要合理划分任务、充分利用实时操作系统的特性,确保系统的响应速度和稳定性。
不脱发的程序猿
2025/04/21
1220
基于FreeRTOS车载音响Source切换与诊断功能实现
STM32状态机编程实例——全自动洗衣机(下)
本篇,为了更加直观的感受状态机的运行,使用0.96寸OLED来显示各个状态,并搭配对应的动态图标来体现洗衣机工作的各个状态。
xxpcb
2022/10/04
8340
STM32状态机编程实例——全自动洗衣机(下)
一种你值得学习的按键扫描方式
  最近在群里跟小伙伴聊到按键键值的获取方式,提到一种使用状态机的方式,小伙伴有点疑问,小飞哥马上放下饭碗,停止干饭,撸他一个,大神自动飘过...不想飘过的,欢迎批评指导~
用户8913398
2021/08/16
4860
一种你值得学习的按键扫描方式
如何在STM32嵌入式开发中优雅地处理按键(单击、双击、长按)?
按键在物理层面上具有机械抖动特性,即按下或松开时会产生多次的电平波动,导致微控制器读取到多个错误的状态变化。
不脱发的程序猿
2025/02/10
1.1K0
如何在STM32嵌入式开发中优雅地处理按键(单击、双击、长按)?
如何实现按键的短按、长按检测?
在电子产品中经常用到按键,尤其是经常需要MCU判断短按和长按这两种动作,本篇我们来专门聊下这个话题。
用户2366192
2022/09/02
1.9K0
如何实现按键的短按、长按检测?
利用状态机编程学习触摸屏
  PENIRQ引脚在没有触摸时都是高电平,只要有触摸就是低电平,直到没有触摸。用中断检测PENIRQ引脚,当产生下降沿中断时就去读取坐标。但是触摸屏也会象按键一样发生抖动,会产生很多上升沿或下降沿,会引起误判。这里我们使用状态机的方式去处理,使用状态机还有一个好处就是可以很方便的去判断长按,短按,双击等状态。
用户6754675
2019/11/22
7000
第1期 | MultiButton,一个小巧简单易用的事件驱动型按键驱动模块
本专栏由Mculover666创建,主要内容为寻找嵌入式领域内的优质开源项目,一是帮助开发者使用开源项目实现更多的功能,二是通过这些开源项目,学习大佬的代码及背后的实现思想,提升自己的代码水平,和其它专栏相比,本专栏的优势在于:
Mculover666
2020/07/16
1.5K0
第1期 | MultiButton,一个小巧简单易用的事件驱动型按键驱动模块
利用STM32F103精确控制步进电机
STM32F103C8T6 42步进电机(42SHDC4040-17B) TB6600电机驱动器升级版
全栈程序员站长
2022/09/05
2.1K0
利用STM32F103精确控制步进电机
面试加分项|一个优秀的按键驱动框架
按键是嵌入式产品中不可或缺的一部分,但往往受制于结构尺寸等因素,按键数量有限,如何利用有限的按键实现更多的骚操作,本文介绍一种优雅的按键实现方法,纯c语言实现,只需要与底层接口对接便可以轻松移植到嵌入式平台,实现单击、连击、短按、长按功能。
用户8913398
2021/08/13
5090
面试加分项|一个优秀的按键驱动框架
【STM32学习记录2】GPIO输入—按键检测
弹性按键机械触点断开、闭合时,由于触点的弹性作用,按键开关不会马上稳定接通或一 下子断开,使用按键时会产生如图带波纹信号,需要用软件消抖处理滤波,不方便输入检测。当然有专用的去抖电路,也有专用的去抖芯片,但通常我们用软件延时的方法就能解决抖动问题,没有必要添加多余的硬件电路。
AXYZdong
2020/10/28
9780
基于STM32的RFID-RC522门禁系统
这是一个基于STM32的RFID-RC522门禁系统,LCD1602显示当前状态,当检测到IC卡时,如果这个IC卡已经登记过,LCD1602上会显示IC卡的卡号,继电器打开,如果没有登记则报警。部分代码如下,需要源码的朋友可以在文章下方链接下载。
全栈程序员站长
2022/07/29
6500
03-HAL库GPIO输入与多功能按键实现
源码地址:https://gitee.com/MR_Wyf/hal-cubemx-rt-thread.git
用户8913398
2022/11/16
1.2K0
03-HAL库GPIO输入与多功能按键实现
STM32定时器—PWM 输出
PWM(Pulse Width Modulation,脉冲宽度调制)。是一种利用微处理器的数字输出来对模拟电路进行控制的技术,广泛应用在测量、通信、功率控制等诸多领域。
韦东山
2022/05/09
1.7K0
STM32定时器—PWM 输出
状态机编程实例-嵌套switch-case法
嵌入式软件开发中,状态机编程是一个比较实用的代码实现方式,特别适用于事件驱动的系统。
xxpcb
2023/08/22
5370
状态机编程实例-嵌套switch-case法
【STM32H7教程】第19章 STM32H7的GPIO应用之按键FIFO
完整教程下载地址:http://forum.armfly.com/forum.php?mod=viewthread&tid=86980 第19章       STM32H7的GPIO应用之按键FIFO
Simon223
2019/05/19
1.8K0
【STM32H7教程】第19章    STM32H7的GPIO应用之按键FIFO
基于STM32F4单片机对步进电机的控制(有代码)「建议收藏」
步进电机是将电脉冲控制信号转变为角位移或线位移的一种常用的数字控制执行元件,又称为脉冲电机。在驱动电源的作用下,步进电机受到脉冲的控制,其转子的角位移量和速度严格地与输入脉冲的数量和脉冲频率成正比。步进电机每接收一个电脉冲,转子就转过一个相应的角度(步距角)。**改变通电顺序可改变步进电动机的旋转方向;改变通电频率可改变步进电动机的转速。**因此,通过控制输入电脉冲的数目、频率及电动机绕组的通电顺序就可以获得所需要的转角、转速及转向,利用单片机就可以很容易实现步进电机的开环数字控制。 传统的步进电机控制方法是由触发器产生控制脉冲来进行控制的,但此种控制方法工作方式单一而且难于实现人机交互,当步进电机的参数发生变化时,需要重新进行控制器的设计。因此适合于单片机控制,单片机通过向步进电机驱动电路发送控制信号就能实现对步进电机的控制。
全栈程序员站长
2022/08/23
9.5K2
基于STM32F4单片机对步进电机的控制(有代码)「建议收藏」
驱动库分享整理(1)——用于单片机中的小巧多功能按键支持库
按键在嵌入式开发中,是比较重要的,也是常见的外设,因此,很有必要学习,也要掌握编写基础的按键驱动,通常最基本的情况下,都是使用状态机的框架来出来,因为尽管硬件电路上有滤波电路,但还是要软件滤波的。(软件滤波很多采集系统中都是用到的)。 在学习过程中,可以自己尝试编写简单的单机,组合按键单机等,锻炼思维。当然网上有很多的按键驱动库,值得收藏,其中思想是比较好的,可以学习其思路,同事也可以在了解功能后,移植到自己的项目中。
用户4645519
2022/05/09
1.4K0
推荐阅读
相关推荐
STM32按键消抖——入门状态机思维
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验