單片機上的按鍵通常一端鏈接高電位,一端鏈接低電位,二者其一鏈接GPIO管脚。
按鍵按下,高低電位導通,引起GPIO管脚電位改變。GPIO管脚処電位變化説明按鍵狀態改變,可以透過讀取GPIO引脚電平來獲取當前按鍵狀態。而考慮到 按鍵機械結構設計,按下后按鍵兩端電平會發生抖動,可能引發判斷錯誤,所以需要一定的操作來“消抖”實現準確檢測。
如下述代碼所示:透過直接讀取GPIO電平得到當前按鍵狀態,實測會發生一次按下引發多次響應的現象,判斷會出錯,需要一定的處理。
1 | // 不合理的寫法 |
按鍵單擊
按鍵單擊判斷的思路是:
如若檢測按下,延時一段時間,再次檢測,如果依舊按下,那麽認爲按鍵按下,直到按鍵鬆開,再重新開始檢測。
我們可以使用延時判定的方法,跳過抖動部分,以獲得更准確的結果。如若抖動部分持續過久,需要適當增加延時,或給按鍵并聯電容來減輕抖動帶來的影響。本案例使用judge_sta來記錄判斷到哪一階段,key_sta記錄GPIO引脚電位,single_flag記錄判斷結果,使用結構體封裝這三個參數。
按鍵狀態結構體定義如下:
1 | struct Keys { |
在實例化結構體時,强烈建議使用volatile修飾struct,以確保主函數每次能從中斷讀取到最新的數據,因爲key[]共享於中斷與主循環,未用volatile會因編譯器優化導致讀取過期值。如若使用舊版本編譯器或不使用中斷函數進行按鍵判斷,則無需考慮這種情況。對volatile關鍵字的介紹詳見此處
1 | volatile struct keys key[4] = {0, 0, 0}; |
本例使用中斷,使判斷代碼在外部自動循環執行,減少對主函數的影響。在使用中斷前,需要進行時鐘配置,使用STM32 CubeMX進行配置大致流程如下:
Clock Configuration->HCLK= 80MHz
Clock Configuration->PLL Source= HSE
Clock Configuration->Input Source= 晶振頻率
RCC->HSE= Crystal/Ceramic Resonator
TIM2->Clock Source= Internal clock
Prescaler= 80 - 1
Counter Period= 10000 - 1
這樣可以實現每10ms進入一次中斷進行判斷。
(10ms <–> 100Hz = 80,000,000 / 80 / 10000)
上述操作并不普適,具體操作因電路不同而有差異。配置完成後透過初始化HAL_TIM_Base_Start_IT(&htim2)啓動中斷函數,否則中斷内的代碼不會執行。中斷内具體實現代碼如下:
1 | //代碼寫在中斷的囘調函數HAL_TIM_PeriodElapsedCallback裏 |
使用中斷可以避免按鍵檢測阻塞主函數進程,但如若認爲10ms對程序影響可以忽略,那麽可以不使用中斷函數,直接在main.c中添加如下所示Key_Scan按鍵掃描函數進行判斷,這種情況下無需volatile關鍵字來保證數據時效性。
1 | uint8_t Key_Scan(void) { |
按鍵長按
類似地,我們有按鍵長按的判斷邏輯。
1 | case 0: |
