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


    上篇文章,以按键消抖功能,介绍了状态机的基本原理与使用方法。

    上篇的状态图如下:

    由于只检测按下与松开,并具备按键消抖功能,因此用到了如上的4个状态,按下抖动和松开抖动是两个独立的状态,并且这两个抖动的状态,也是可以在多次循环中连续运行的,这个状态机的循环周期设置的为10ms,当在抖动状态连续检测到某一电平5次后,即认为消抖完成,进入下一个稳定状态。

    对于同一个功能,状态图不是一成不变的,对于按键消抖,还可以将两个抖动状态共用一个抖动状态来表示。

    1 消抖状态简化

    1.1 状态图

    将按下抖动与松开抖动共用一个抖动状态来表示,同时需要将状态机的循环周期设置为50ms,这样,抖动状态只需经过一次,通过电平高低即可判定是否真的为按键抖动。简化后的状态图如下:

    为了能在抖动状态时,区分前一状态是松开还是按下,进而判断此次是抖动还是按键真的动作,需要增加一个状态来记录前一状态

    KEY_STATUS g_keyStatus = KS_RELEASE; //当前循环结束的(状态机的)状态
    KEY_STATUS g_nowKeyStatus = KS_RELEASE; //当前状态(每次循环后与g_keyStatus保持一致)
    KEY_STATUS g_lastKeyStatus = KS_RELEASE; //上次状态(用于记录前一状态以区分状态的来源)
    
    • 1
    • 2
    • 3

    注意:此处的g_lastKeyStatus用于记录前一状态,上篇文章中也有这个变量,但作用不同,上篇文章中此变量的作用与此处的g_nowKeyStatus作用相同

    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)
    			{
    				g_keyStatus = KS_RELEASE;
    				if (KS_PRESS == g_lastKeyStatus)
    				{
    					printf("=====> key release\r\n");
    				}
    			}
    			else
    			{
    				g_keyStatus = KS_PRESS;
    				if (KS_RELEASE == g_lastKeyStatus)
    				{
    					printf("=====> key press\r\n");
    				}
    			}
    		}
    		break;
    		
    		//稳定短按
    		case KS_PRESS:
    		{
    			//检测到高电平,先进行消抖
    			if (KEY0 == 1)
    			{
    				g_keyStatus = KS_SHAKE;
    			}
    		}
    		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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    注意g_lastKeyStatus变量的作用。

    1.3 测试

    2 增加长按功能

    在检测按下与松开的基础上,再增加长按功能,在状态图中需要增加一个长按状态。然后,对照着状态图修改代码即可。

    同样,根据是否需要区分两种抖动状态以及状态机循环周期的不同,可以有两种状态图。

    2.1 未简化的状态图

    先来看一下循环周期10ms,区分按下抖动与松开抖动这种情况增加长按功能后的状态图:

    状态图理清逻辑后,根据状态图,修改对应的代码即可,这里不再贴代码,完整代码可去我的代码仓库查看

    2.2 简化的状态图

    下面再来看简化消抖状态的具体长按功能的状态机图:

    对比可以发现,简化的状态图,状态可以少一个,不过抖动的状态,会有更多的输入和输出,因为目前每隔状态都有经过这个状态。

    如果对于抖动检测的要求不高,也可以只保留按下抖动的逻辑,松开抖动的分支去掉,直接跳到松开状态,可以再次简化状态逻辑。

    2.3 代码

    根据状态图图,编写对应的状态机逻辑代码,如下:

    void key_status_check()
    {
    	switch(g_keyStatus)
    	{
    		//按键释放(初始状态)
    		case KS_RELEASE:
    		{
    			//检测到低电平,先进行消抖
    			if (KEY0 == 0)
    			{
    				g_keyStatus = KS_SHAKE;
    			}
    		}
    		break;
    		
    		//抖动
    		case KS_SHAKE:
    		{
    			if (KEY0 == 1)
    			{
    				g_keyStatus = KS_RELEASE;
    				if (KS_SHORT_PRESS == g_lastKeyStatus || KS_LONG_PRESS == g_lastKeyStatus)
    				{
    					printf("=====> key release\r\n");
    				}
    			}
    			else
    			{
    				if (KS_RELEASE == g_lastKeyStatus)
    				{
    					g_PressTimeCnt = 0;
    					g_keyStatus = KS_SHORT_PRESS;
    					printf("=====> key short press\r\n");
    				}
    				else if (KS_SHORT_PRESS == g_lastKeyStatus)
    				{
    					g_keyStatus = KS_SHORT_PRESS;
    				}
    				else
    				{
    				
    				}
    			}
    		}
    		break;
    		
    		//稳定短按
    		case KS_SHORT_PRESS:
    		{
    			//检测到高电平,先进行消抖
    			if (KEY0 == 1)
    			{
    				g_keyStatus = KS_SHAKE;
    			}
    			
    			g_PressTimeCnt++;
    			if (g_PressTimeCnt == 20) //1000ms
    			{
    				g_keyStatus = KS_LONG_PRESS;
    				printf("=====> key long press\r\n");
    			}
    		}
    		break;
    		
    	    //稳定长按
    		case KS_LONG_PRESS:
    		{
    			//检测到高电平,先进行消抖
    			if (KEY0 == 1)
    			{
    				g_keyStatus = KS_SHAKE;
    			}
    			
    			g_PressTimeCnt++;
    			if (g_PressTimeCnt % 20 == 0) //每隔1000ms打印一次
    			{
    				printf("=====> key long press:%d\r\n", g_PressTimeCnt/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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    注意,在抖动状态,当检测为高电平(按键松开),不管前一状态是短按还是长按,下一状态都是松开状态。

    2.4 测试

    3 总结

    本篇继续介绍状态机的使用,在上篇的基础上,通过简化按键去抖逻辑,并增加按键长按功能,进一步介绍状态图的修改与状态机代码的实现,并通过实际测试,演示状态机的运行效果。

  • 相关阅读:
    SpringMVC之JSON数据返回及异常处理机制
    XSS靶场(1-11关)
    vue2升级vue3指南(一)—— 环境准备和构建篇
    win11该文件没有与之关联的应用怎么办
    前端面试题
    Linux---(五)三大工具yum、vim、gcc/g++
    卡牌游戏类型定制开发微信卡牌小程序游戏
    CSS 基本选择器
    PFA晶圆夹在半导体芯片制造中的应用
    CSC青年骨干教师项目教师节当天开始申报
  • 原文地址:https://blog.csdn.net/hbsyaaa/article/details/125610594