按键的状态机方法实现
按键的状态机方法实现 为什么要使用状态机?使用状态机有啥好处?是不是装13? 要我说,很简单的一个按键,可以不必使用,要是你有很多按键,或者需要判断长按,短按,双击666,那么,状态机方法可以让你设计简洁,功能稳定,不会出现你自己测试好了,别人测试就会出问题;或者今天测试没有问题,第二天不稳定了,是不是很糟心?功能复杂势必会声明很多个变量一大丢,然后把功能也 写进按键处理里面,是不是很乱,不好读懂?按键识别就是按键部分,不要有任何功能上面的处理,要通过接口去传递。这么说,本文主要就是两个思想:状态机和接口。懂得人自然懂。下面详细说说,实例上菜。 开局一张图,内容全靠编。
正确,合理的状态划分是做好任何功能的前提,根据按键全生命周期的发生顺序,我划分了上面4个状态。去抖大家都清楚,按键值持续2-30Ms才进入正式的识别按键值状态,否则退回idle状态。我说的是持续,不是间隔2--30Ms再次去确认一次。好了,我把程序里面需要用到的一些数据类型都贴出来吧:
1 enum KEYDOWNSTATE 2 {//按键状态机 3 MKEY_IDLE, ///<待机状态 4 MKEY_DELAY, ///<去抖状态 5 MKEY_GETVALUE,///<取值状态 6 MKEY_UNFIX ///<取值成功状态 7 }; 8 typedef struct 9 { 10 unsigned char modeKey : 1; 11 unsigned char levelKey : 1; 12 unsigned char powerKey : 1; 13 unsigned char datb3 : 1; 14 unsigned char datb4 : 1; 15 unsigned char datb5 : 1; 16 unsigned char datb6 : 1; 17 unsigned char datb7 : 1; 18 }datbit; //使用位段 19 typedef union { 20 unsigned char bytedat; 21 datbit data_bits; 22 }ByteChar; //非常方便采集数据和取值的一个联合体 23 enum EKEYVALUE 24 {//按键值 25 MMODE = 0XFE, 26 MLEVEL = 0XFD, 27 MREUSE = 0XFC, 28 MPOWERON = 0XFB, 29 MTEST = 0X07 30 }; //无论是独立式按键还是adc方式,都会有个值或者范围 31 enum EKEYTYPE 32 { 33 MNONE, 34 MSHORTKEY = 0X13, //短按,随意定的一个值 35 M2SKEY, //长按 36 M10KEY, //复位按 37 }; //代表着按键长按,短按,复位 38 typedef struct { 39 bit getkeyFlag; //按键值获取成功 40 ByteChar gkeyValueIn; //按键值 41 UINT8 gDelayType; //根据按键delay长短确定的类型 42 }keyInterface_t; 43 keyInterface_t keyInterface; //按键驱动模块与外部的接口 44 /************************************************************************************************************ 45 * void keyDriver(void) 46 * 注意:间隔10ms左右调用一下本函数 47 * 本函数采用状态机方式编程,适合于独立按键,可以识别出长按,短按,超长10S按。 48 ************************************************************************************************************/ 49 static void keyDriver(void) 50 { 51 ByteChar keyValueIn; 52 keyValueIn.bytedat = 0xFF; 53 keyValueIn.data_bits.modeKey = MODEKEY; 54 keyValueIn.data_bits.levelKey = LevelKEY; 55 keyValueIn.data_bits.powerKey = PowerKEY; 56 switch (keyState) 57 { 58 case MKEY_IDLE: /* constant-expression */ 59 keyDownConts = 0; 60 keyInterface.gDelayType = MNONE; 61 if (keyValueIn.bytedat != 0xFF) 62 { 63 keyInterface.gkeyValueIn.bytedat = keyValueIn.bytedat; 64 keyState = MKEY_DELAY; 65 keyDownConts = 0; 66 } 67 break; 68 case MKEY_DELAY: /* 去抖动 */ 69 if (keyInterface.gkeyValueIn.bytedat ==keyValueIn.bytedat) 70 { 71 keyDownConts++; 72 } 73 else 74 { 75 keyDownConts = 0; 76 keyState = MKEY_IDLE; 77 } 78 if (keyDownConts > 3) 79 { 80 keyState = MKEY_GETVALUE; 81 } 82 break; 83 case MKEY_GETVALUE: /* 进入判断按键 */ 84 keyDownConts++; 85 if (keyDownConts > KEY10s) //如果是复原按,则无需等待松开按键 86 { 87 keyInterface.gDelayType = M10KEY; //复位按 88 keyState = MKEY_UNFIX; 89 } 90 if (keyDownConts > KEY2s) //如果是长按,则无需等待松开按键 91 { 92 keyInterface.gDelayType = M2SKEY; //长按 93 keyState = MKEY_UNFIX; 94 } 95 if (keyValueIn.bytedat == 0xFF) 96 { 97 keyUpConts++; 98 } 99 else 100 { 101 keyUpConts = 0; 102 } 103 if (keyUpConts > 2) 104 { 105 if (keyDownConts > KEYSHORTMIN &&keyDownConts < KEYSHORTMAX) //如果是短按,则需等待松开按键 106 { 107 keyInterface.gDelayType = MSHORTKEY; //短按 108 keyState = MKEY_UNFIX; 109 } 110 else 111 { 112 keyState = MKEY_IDLE; 113 } 114 } 115 break; 116 case MKEY_UNFIX: /* 此状态代表按键可以正常取值 */ 117 _nop_(); 118 break; 119 default: 120 _nop_(); 121 break; 122 } 123 }
再次总结一下,keyState变量就是交际花,在不同的状态切换;按键驱动模块与外部的接口keyInterface_t keyInterface ,使用一个结构体变量,实现了模块化。
针对本文中所有的错误和不足,欢迎交流,交流技术和项目合作均可。等你来撩我哦!加我请说明来意,谢谢。