上篇文章,介绍了将按键检测增加长按功能,并将按下抖动与松开抖动共用一个抖动状态来表示,其状态图如下:
仔细研究这个状态图,其它还存在一些问题:
- 短按状态,只要按下去,不需要等按键再释放,就会触发短按事件。对于需要按下再松开作为一次短按的应用来说,此状态图也不满足需求
- 长按状态,必须先经过短按状态,即长按按键,会先触发一个短按,再触发一个长按。如果实际应用中需要分别使用短按和长按,则此状态图不满足要求
本篇,就来解决上述两个问题,并再增加一个按键双击检测,实现一个功能更全面的按键检测。
1 增加双击检测
增加一个双击检测,需要增加两个状态:
同时,之前的“短按状态”和“长按状态”分别改为“确认按下”和“确认长按”。
1.1 状态图修改
修改后的状态图如下,有以下几点需要注意:
- “确认按下”不是短按触发的条件,需要等松开后,经消抖进入到“等待再次按下”一段时间后(200ms),没有再次被按下,才触发短按事件,这样就解决了本篇开头提到的第1个问题
- “确认按下”不是短按触发的条件,另一个用途是,当此状态继续保持按下状态一段时间后(1s),则会单独触发长按事件,同时进入到“确认长按”状态,这样就解决了本篇开头提到的第2个问题
- 对于双击事件的检测,首先按下按键进入“确认按下”状态,然后在1s内松开进入“等待再次按下”状态,接着在200ms内再次按下进入“确认第2次按下”状态,然后在1s内松开,即可触发双击事件,并同时进入“稳定松开”状态
- 注意,在“确认第2次按下”状态下,如果在1s内没有松开,也会进入到“确认长按”状态
1.2 程序编写
根据状态图,修改对应的状态机逻辑,修改后的代码如下:
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 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
| 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; } 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; } 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) { 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) { printf("=====> key single press\r\n"); g_keyStatus = KS_RELEASE; } } break; case KS_AFFIRM_PRESS_AGAIN: { if (KEY0 == 1) { g_keyStatus = KS_SHAKE; } g_Press2TimeCnt++; if (g_Press2TimeCnt == 20) { 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) { 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; } }
|
最后注释掉的一句是调试打印,调试时可打开,方便观察状态变化
1.3 测试
短按、长按、双击的测试结果如下:
还有从确认第2次按下状态到达的长按状态:
2 功能优化
上面的代码实现,是在主函数中,每50ms延时执行一次状态机循环(主函数代码如下),仅用做演示按键状态机的运行机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int main(void) { delay_init(); KEY_Init(); uart_init(115200);
printf("hello\r\n"); while(1) { key_status_check(); delay_ms(50); } }
|
实际开发中,按键检测程序,应该作为一个独立的模块运行,当检测到某一按键状态触发时,通知应用程序来使用。
对于stm32裸机开发来说,可以将按键状态机放到一个定时器中断服务函数中运行,当检测到某一按键状态触发后,通知应用程序:
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
| int main(void) { delay_init(); KEY_Init(); uart_init(115200); TIM3_Int_Init(500-1,7200-1);
printf("hello\r\n"); while(1) { } }
void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); 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 总结
本篇在前两篇按键状态机的基础上,继续介绍增加按键的双击功能,并解决之前状态存在的两个问题,通过实测验证,演示短按、长按、双击的使用效果。最后对代码结构进行优化,使其更符合实际开发应用。