狀態機實現雙擊、短按、長按等按鍵識別檢測算法

1、按鍵識別算法的作用

????????按鍵識別算法在不同的技術和應用背景下有不同的作用,但其核心目標都是準確、可靠地檢測和區分用戶通過物理或虛擬按鍵所執行的操作。按鍵識別算法在各類電子設備及系統中起到至關重要的作用,它確保了人機交互的有效性和準確性,提升了用戶體驗,并保證了系統的正常運行。按鍵識別算法也有較多的種類,在這一部分主要介紹獨立按鍵的雙擊、短按、長按識別算法。? ? ? ?

????????雙擊、長按、短按一般是用來擴展按鍵功能,讓人機交互界面用起來更方便。比如手機觸摸屏的長按APP圖標時,會出現卸載軟件等功能選擇,而單擊時則是進入應用程序。此外,還有滑動等其他功能操作。雖然上述舉例目前是在手機觸摸屏中非常常見的,但其最早是應用于實體按鍵中的,后面擴展延伸到了觸摸屏的使用中,它們的實現本質邏輯框架是同一套,相信以前玩過MP5、PSP游戲機的讀者對這個是最有體會的。除了上述的場景中,還有我們的自拍桿、鍵盤、鼠標等等無一不是按鍵算法的身影。本文的下一部分開始將詳細剖析雙擊、短按、長按等按鍵算法實現的邏輯原理。

????????短按:一般定義為按鍵按下并快速釋放的操作,持續時間較短,按下時不會立即觸動動作,而是過一個較短的時間才觸發動作。

????????長按:通常是指按鍵保持按壓狀態超過短按時間閾值的操作,持續時間較長,松開時觸發動作。

????????雙擊:較短的時間內,連續按下兩次才會觸發動作。

2、按鍵電平時序圖

????????在這一部分主要是使用了24MHz 8通道的邏輯分析儀通過PulseView軟件對按鍵的三種狀態進行采樣觀察。如果有想進一步了解邏輯分析儀的使用和波形解析,可以參考本人在之前寫的一篇博客文章,已經對邏輯分析儀進行了較為詳細的講解。

邏輯分析儀使用配置,PulseView通信波形解析-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_49337111/article/details/135602908?spm=1001.2014.3001.5501

????????提醒:這一部分所提到的檢測時間范圍都是可以靈活變動的,并不是說一成不變,最終的效果都是為了讓實現按鍵狀態識別更加的快速、準確。

(1)短按

????????通過邏輯分析儀觀測一連串的按鍵短按的電平時序圖

????????通過邏輯分析儀的測量工具可知,本次短按測量的持續時間為159ms

????????通過邏輯分析儀的測量工具可知,本次短按測量的持續時間為74ms

????????后續又經過多次測量,發現大部分短按時間是在160ms左右,考慮到實際日常使用過程中,有些用戶按下按鍵的速度可能比較緩慢,因此本文中將按鍵短按設定為90ms~250ms的閾值。

????????如果檢測出按鍵按下的時間>=90ms且<=250ms時,則會被認定為按鍵短按。程序則是進入短按的邏輯處理狀態。

(2)長按

????????通過邏輯分析儀觀測一連串的按鍵長按的電平時序圖。從邏輯時序圖中可以非常明顯的看出,長按相對于短按來說,就是按下時間延長,其余操作并沒有什么區別。因此在檢測算法編寫時,只需要判斷一個臨界值,超過該值,便可以判定為按鍵長按操作。

????????通過邏輯分析儀的測量工具可知,本次長按測量的持續時間為781ms

????????通過邏輯分析儀的測量工具可知,本次長按測量的持續時間為697ms

? ? 經過網上大量的資料查閱和實際按下的情況對比,長按的時間一般大于500ms,因此在本文的按鍵識別算法中,將臨界時間設定為500ms。? ?

(3)雙擊

????????通過邏輯分析儀觀測按鍵雙擊的時序圖。

????????

????????通過邏輯分析儀的測量工具可知,第一次按下結束后到第二次按下開始的時間間隔為74ms。

????????通過邏輯分析儀的測量工具可知,第一次按下結束后到第二次按下開始的時間間隔為199ms。

????????又經過多次測量發現,雙擊的第一次按下松開后到第二次按下開始的時間間隔較多分布在200ms以內,而考慮到一些用戶操作動作較為緩慢,及誤觸發的風險、硬件和軟件的響應能力。因此將雙擊檢測時間設置為了250ms,如果250ms內沒有檢測到按下第二次短按,則是會判斷為單擊,否則就是觸發雙擊。

? ? ? ? 通過上面三種模式的電平時序圖可知,按鍵檢測必須要實現毫秒級定時才實現較為精準的按鍵識別算法。

3、按鍵識別算法代碼

????????因按鍵識別算法有一定的難度,所以本文聚焦于按鍵算法的核心實現。如果需要實現按鍵識別算法,需要具備定時器的基礎,掌握GPIO引腳的使用,知道如何配置毫秒級定時器中斷等。

? ? ? ? 在按鍵檢測算法代碼編寫時,考慮到了移植性的問題,因在對不同的平臺進行適配時,修改電平狀態讀取和電平狀態判斷后,其余的檢測算法部分變動不需要很大既可以實現按鍵檢測算法功能。

????????本文中使用的是STM32G431RB6開發板進行按鍵識別算法開發編寫,如果對STM32的定時器配置不熟悉,可以參考本人在此前寫的博客文章。
STM32標準庫+HAL庫 | 高精度動態調節PWM輸出頻率+占空比_hal庫中設置pwm輸出頻率和占空比的函數-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_49337111/article/details/135671353?spm=1001.2014.3001.5501? ? ? ? 本文所呈現的檢測效果是將定時器配置為了10ms檢測一次,且配置了4個按鍵,如果有更多的按鍵或更少的按鍵,將其中的循環次數修改和數組大小修改即可。

(1)狀態機

????????狀態機(Finite State Machine,FSM)是一種理論模型,用于描述一個系統在不同時間點可能存在的不同狀態以及引起狀態轉移的條件。它在計算機科學、電子工程、自動化控制、編譯原理等多個領域有著廣泛應用。

????????狀態機中有幾個術語:state(狀態)?、transition(轉移)?、action(動作)?、transition condition(轉移條件)?。

? ? ? ? 狀態機的內容大致了解一下,在本文使用了狀態機的思想對按鍵檢測算法進行編寫。其中的狀態有三種,分別是消抖裁決狀態、單擊狀態、雙擊狀態。如果是單擊狀態,則會判斷是長按還是短按,如果是短按,又會進行狀態轉移到雙擊,進行是否即將雙擊檢測。

(2)長按、短按識別算法

? ? ? ? JUDGE狀態主要是用于消除按鍵的抖動狀態,而SINGLE狀態中,則是對按鍵的長按、短按邏輯進行檢測判斷。


//按鍵狀態結構體
typedef struct key_state{int8_t short_flag;        //短按flagint8_t long_flag;         //長按flagint8_t level_state;       //電平狀態    int8_t judge_flag;        //裁決抖動int32_t press_time_cnt;   //按下時間}key_state_t;enum {JUDGE = 0, SINGLE = 1};//狀態機的狀態類型key_state_t g_key[4] = {0};  //按鍵狀態全局變量//定時器回調函數,已經配置為10ms觸發一次定時器中斷回調
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM4)    //已設置10ms觸發一次定時器中斷{static int32_t press_time_cnt[4] = {0};    //按下持續計數值g_key[0].level_state =     HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);   //讀取引腳電平值g_key[1].level_state =     HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);        g_key[2].level_state =     HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);        g_key[3].level_state =     HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);    for(int i=0; i<4; i++){switch(g_key[i].judge_flag){case JUDGE:if(g_key[i].level_state == GPIO_PIN_RESET)    //主要用于消抖{g_key[i].judge_flag = 1;}break;case SINGLE://第一次按下,長按、短按if(g_key[i].level_state == GPIO_PIN_RESET)        //按鍵按下{press_time_cnt[i]++;}else if(g_key[i].level_state == GPIO_PIN_SET)    //按鍵松開{if(press_time_cnt[i] >= 50)        //長按(按下持續時間>=500ms){g_key[i].long_flag  = 1;    //長按標志置1g_key[i].short_flag = 0;g_key[i].two_flag   = 0;}else if(press_time_cnt[i] >= 7)        //短按{g_key[i].short_flag = 1;    //短按標志置1g_key[i].long_flag  = 0;g_key[i].two_flag   = 0;}g_key[i].judge_flag = 0;press_time_cnt[i] = 0;        //清空按下和松開間的計數}break;}}}}

//頭文件int main(void)
{//其它代碼,如初始化,函數調用,變量定義//主函數中調用長短按鍵算法部分代碼while (1){for(int i=0; i<4; i++){if(g_key[i].short_flag == 1){g_key[i].short_flag = 0;printf("\r\nKEY %d 短按\r\n", i+1);}else if(g_key[i].long_flag == 1){g_key[i].long_flag = 0;printf("\r\nKEY %d 長按\r\n", i+1);}    }}
}

????????短按、長按、檢測效果圖

(3)雙擊、長按、短按算法


//按鍵狀態結構體
typedef struct key_state{int8_t short_flag;    //短按flagint8_t long_flag;     //長按flagint8_t two_flag;      //雙擊flagint8_t level_state;   //電平狀態int32_t start_time;   //按下時間int32_t end_time;     //松開時間int8_t press_cnt;     //按下次數int32_t time_gap;     //時間間隔(兩次按下)int8_t judge_flag;    //按鍵裁決
}key_state_t;enum {JUDGE = 0, SINGLE = 1, DBCLICK = 2};    //狀態機的狀態類型
extern key_state_t g_key[4];    //按鍵狀態全局變量//定時器回調函數,已經配置為10ms觸發一次定時器中斷回調
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{if(htim->Instance == TIM4)    //已設置10ms觸發一次定時器中斷{static int32_t press_time_cnt[4] = {0};        //按下持續計數值static int32_t wait_press_cnt[4] = {0};        //等待按鍵按下計數值g_key[0].level_state =    HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);//讀取引腳電平值g_key[1].level_state =    HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);        g_key[2].level_state =    HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);        g_key[3].level_state =    HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);    for(int i=0; i<4; i++){switch(g_key[i].judge_flag){case JUDGE:if(g_key[i].level_state == GPIO_PIN_RESET)         //按下{if(g_key[i].press_cnt == 0)g_key[i].judge_flag = 1;elseg_key[i].judge_flag = 2;}else if(g_key[i].level_state == GPIO_PIN_SET)     //松開{        if(g_key[i].press_cnt == 1)                     //雙擊的第一次按下已經松開了{g_key[i].judge_flag = 2;}}break;case SINGLE:    //第一次按下,長按、短按if(g_key[i].level_state == GPIO_PIN_RESET)        //按鍵按下{press_time_cnt[i]++;}else if(g_key[i].level_state == GPIO_PIN_SET)    //按鍵松開{if(press_time_cnt[i] > 50)       //長按(按下持續時間>=500ms){g_key[i].long_flag  = 1;     //長按標志置1g_key[i].short_flag = 0;g_key[i].two_flag   = 0;g_key[i].press_cnt  = 0;     //長按則清除雙擊的按下按鍵計數 }else if(press_time_cnt[i] > 9)  //短按(持續時間>=90ms){g_key[i].short_flag = 0;     //短按標志需要進一步判斷,在一定時間內,如果沒有第二次按下,則是短按g_key[i].long_flag  = 0;g_key[i].two_flag   = 0;if(g_key[i].press_cnt == 0){g_key[i].press_cnt = 1;  //記錄雙擊的第一次按下}}g_key[i].judge_flag = 0;        press_time_cnt[i] = 0;           //清空按下和松開間的計數}break;case DBCLICK:if(g_key[i].level_state == GPIO_PIN_SET) //按鍵松開{if(wait_press_cnt[i]++ >= 25)    //250ms內未按下第二次,說明為短按{g_key[i].short_flag = 1;    //短按標志位置1g_key[i].long_flag  = 0;g_key[i].two_flag   = 0;g_key[i].press_cnt  = 0;g_key[i].judge_flag = 0;wait_press_cnt[i]   = 0;    }}else if(g_key[i].level_state == GPIO_PIN_RESET)    //第二次按鍵按下{if((press_time_cnt[i]++ >=7) && (wait_press_cnt[i] >= 5))        //50~250ms內第二次按下且按下的時間大于70ms,則是雙擊{g_key[i].two_flag   = 1;    //雙擊標志置1g_key[i].short_flag = 0;g_key[i].long_flag  = 0;g_key[i].press_cnt  = 0;g_key[i].judge_flag = 0;wait_press_cnt[i]   = 0;press_time_cnt[i]   = 0;}}break;}}}}
//頭文件int main(void)
{//其它代碼,如初始化,函數調用,變量定義//主函數中,調用按鍵識別算法部分邏輯代碼while (1){for(int i=0; i<4; i++){if(g_key[i].short_flag == 1){g_key[i].short_flag = 0;printf("\r\nKEY %d 短按\r\n", i+1);}else if(g_key[i].long_flag == 1){g_key[i].long_flag = 0;printf("\r\nKEY %d 長按\r\n", i+1);}else if(g_key[i].two_flag == 1){g_key[i].two_flag = 0;printf("\r\nKEY %d 雙擊\r\n", i+1);        }}}return 0;
}

????????短按、長按、雙擊檢測效果圖

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/717803.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/717803.shtml
英文地址,請注明出處:http://en.pswp.cn/news/717803.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue前端+快速入門【詳解】

目錄 1.Vue概述 2. 快速入門 3. Vue指令 4.表格信息案例 5. 生命周期 1.Vue概述 1.MVVM思想 原始HTMLCSSJavaScript開發存在的問題&#xff1a;操作麻煩&#xff0c;耦合性強 為了實現html標簽與數據的解耦&#xff0c;前端開發中提供了MVVM思想&#xff1a;即Model-Vi…

Mysql-主從架構篇(一主多從,半同步案例搭建)

主從架構 主從架構有什么用&#xff1f; 通過搭建MySQL主從集群&#xff0c;可以緩解MySQL的數據存儲以及訪問的壓力。 數據安全&#xff08;主備&#xff09;&#xff1a;給主服務增加一個數據備份。基于這個目的&#xff0c;可以搭建主從架構&#xff0c;或者也可以基于主…

GO語言學習筆記(與Java的比較學習)(九)

讀寫數據 讀取用戶的輸入 最簡單的辦法是使用 fmt 包提供的 Scan 和 Sscan 開頭的函數。 Scanln 掃描來自標準輸入的文本&#xff0c;將空格分隔的值依次存放到后續的參數內&#xff0c;直到碰到換行。Scanf 與其類似&#xff0c;除了 Scanf 的第一個參數用作格式字符串&…

大數據開發(Java面試真題-卷三)

大數據開發&#xff08;Java面試真題&#xff09; 1、簡要介紹以下JVM有幾種垃圾收集器&#xff1f;2、Java中Synchronized的底層原理是什么&#xff1f;3、Java String為什么是不可變的&#xff1f;為什么要設計成不可變&#xff1f;4、泛型&#xff1f;5、常用的反射方法&…

深入Java日志框架及其最佳實踐

概述 在Java應用開發中&#xff0c;日志框架是確保應用穩定性和可觀察性的關鍵組件。它幫助開發者記錄應用的行為、診斷問題&#xff0c;并監控系統的健康狀況。隨著Java生態系統的不斷發展&#xff0c;各種日志框架也應運而生&#xff0c;各有特點和優勢。本文將詳細探討幾個…

redis進階(一)

文章目錄 前言一、Redis中的對象的結構體如下&#xff1a;二、壓縮鏈表三、跳躍表 前言 Redis是一種key/value型數據庫&#xff0c;其中&#xff0c;每個key和value都是使用對象表示的。 一、Redis中的對象的結構體如下&#xff1a; /** Redis 對象*/ typedef struct redisO…

c# .net8 香橙派orangepi + hc-04藍牙 實例

這些使用c# .net8開發&#xff0c;硬件 香橙派 orangepi 3lts和 hc-04藍牙 使用場景&#xff1a;可以通過這個功能&#xff0c;手機連接orangepi進行wifi等參數配置 硬件&#xff1a; 1、帶USB口的linux開發板orangepi 2、USB 轉TTL 中轉接藍牙&#xff08;HC-04) 某寶上買…

Vue的響應式原理是如何實現的

Vue的響應式原理主要**基于JavaScript的Object.defineProperty方法實現**。具體如下&#xff1a; 1. 數據劫持&#xff08;Data Hijacking&#xff09; Vue在初始化時&#xff0c;會遍歷data對象中的所有屬性&#xff0c;并使用Object.defineProperty將這些屬性轉換為getter/s…

Flink:Temporal Table Function(時態表函數)和 Temporal Join

博主歷時三年精心創作的《大數據平臺架構與原型實現&#xff1a;數據中臺建設實戰》一書現已由知名IT圖書品牌電子工業出版社博文視點出版發行&#xff0c;點擊《重磅推薦&#xff1a;建大數據平臺太難了&#xff01;給我發個工程原型吧&#xff01;》了解圖書詳情&#xff0c;…

AR時間序列模型

AR時間序列模型&#xff08;AutoRegressive Time Series Model&#xff09;是一種用于分析和預測時間序列數據的統計模型。該模型假設未來的觀測值與過去的觀測值相關&#xff0c;且該相關性可以通過線性回歸來描述。 AR模型的基本思想是將當前時刻的觀測值表示為過去幾個時刻…

設計模式(十五)狀態模式

請直接看原文:設計模式系列 ------------------------------------------------------------------------------------------------------------------------------- 前言 建議在閱讀本文前先閱讀設計模式&#xff08;十一&#xff09;策略模式這篇文章&#xff0c;雖說狀態…

java.sqlrecoverableexception: io error: the network adapter could not establ

數據庫相關學習資料下載地址&#xff1a; 數據庫相關資料合集 Java SQLRecoverableException: IO Error: The Network Adapter Could Not Establish Connection 在進行Java開發中&#xff0c;經常會遇到與數據庫進行交互的情況。然而&#xff0c;有時候我們可能會遇到java.sq…

Redis過期刪除策略

1、定時刪除&#xff1a; 一旦鍵過期就立即從內存中刪除&#xff0c;節省內存空間&#xff0c;但刪除過程會占用大量CPU時間&#xff0c;可能影響服務器的響應時間和吞吐量。 2、惰性刪除&#xff1a; 并不會立即從內存中刪除過期鍵&#xff0c;而是在需要訪問時才會檢查是否…

【MATLAB】MVMD_ MFE_SVM_LSTM 神經網絡時序預測算法

有意向獲取代碼&#xff0c;請轉文末觀看代碼獲取方式~也可轉原文鏈接獲取~ 1 基本定義 MVMD_MFE_SVM_LSTM神經網絡時序預測算法結合了多變量多尺度分解&#xff08;MVMD&#xff09;、多尺度特征提取&#xff08;MFE&#xff09;、支持向量機&#xff08;SVM&#xff09;和長…

Python爬蟲之爬取并下載嗶哩嗶哩視頻

親自使用過&#xff0c;太好用了 # 導入requests模塊&#xff0c;模擬發送請求 import requests # 導入json import json # 導入re import re# 定義請求頭 headers {Accept: */*,Accept-Language: en-US,en;q0.5,User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6…

小米消息隊列的選型與實踐

之前寫了一篇關于消息隊列的文章&#xff1a;《消息隊列介紹與對比》&#xff0c;本文主要介紹消息隊列在實際工作中的使用情況&#xff08;截止到2023年&#xff0c;因為我2023年離職了&#xff0c;后續的情況不了解了&#xff0c;哈哈&#xff09;。 市面上的多種消息隊列都有…

node問題: command not found: nodemon

如何安裝并使用 nodemon npm i -g nodemon 問題與解決方案&#xff1a; 問題&#xff1a;zsh: command not found: nodemon 解決方案&#xff1a; 在你的 package.json 中加入&#xff1a; "scripts": {"auto": "npx nodemon server.js" }…

單例模式及應用場景

如果希望自己的代碼更優雅、可維護性更高以及更簡潔&#xff0c;往往離不開設計模式這一解決方案。 在JS設計模式中&#xff0c;最核心的思想&#xff1a;封裝變化&#xff08;將變與不變分離&#xff0c;確保變化的部分靈活&#xff0c;不變的部分穩定&#xff09;。 那么來…

[嵌入式系統-36]:龍芯1B 開發學習套件 -5- PMON常見命令

目錄 0. 開機時按c鍵進入pmon模式 &#xff08;自啟動模式時&#xff09; 1、幫助命令 h 2、顯示設備信息 devls 3.重啟&#xff1a;reboot 4、設置環境變量 set/unset 5.查詢環境變量&#xff1a;env 6.網絡設置相關命令&#xff1a; ①設置IP&#xff1a;ifconfig&am…

Linux 基礎IO(1)內存文件

文章目錄 鋪墊文件的系統調用接口文件描述符緩沖區 鋪墊 文件文件內容 文件屬性訪問文件之前&#xff0c;都要先打開文件&#xff0c;而要訪問&#xff0c;修改&#xff0c;編輯文件&#xff0c;文件就必須加載到內存中程序運行起來變成進程&#xff0c;被CPU調度&#xff0c;…