物理按鍵,在很多嵌入式產(chǎn)品里面應(yīng)用得非常廣泛,很多嵌入式軟件工程師在剛剛開(kāi)始入門的時(shí)候,點(diǎn)完燈之后就開(kāi)始學(xué)習(xí)按鍵輸入檢測(cè)。按鍵輸入可以說(shuō)是繼點(diǎn)燈之后,又一經(jīng)典的嵌入式入門必學(xué)內(nèi)容之一。
在很多嵌入式入門學(xué)習(xí)的教程里面,按鍵原理普遍被認(rèn)為是“很簡(jiǎn)單”的知識(shí)點(diǎn)之一,按鍵輸入檢測(cè)的原理,無(wú)非就是通過(guò)CPU不斷掃描按鍵引腳的電平狀態(tài),或者采用單片機(jī)引腳外部中斷方式,然后在死循環(huán)或者中斷服務(wù)程序里面處理按鍵被按下后的邏輯。
然而,在這個(gè)“很簡(jiǎn)單的高低電平檢測(cè)”的原理背后,通過(guò)產(chǎn)品經(jīng)理給物理按鍵各個(gè)動(dòng)作賦予的(難以理解的)意義,一個(gè)小小的物理按鍵開(kāi)始變得復(fù)雜起來(lái),這些動(dòng)作包括:按下、抬起、單擊、雙擊、點(diǎn)動(dòng)、長(zhǎng)按、組合按鍵。。。等等。
以上這些復(fù)雜的按鍵動(dòng)作,已經(jīng)不是一個(gè)“簡(jiǎn)單的高低電平檢測(cè)”所能描述清楚的了,成熟的單片機(jī)按鍵檢測(cè)模塊,必須能很好地處理以上按鍵動(dòng)作,并且具有很高的內(nèi)聚度,與單片機(jī)的底層引腳盡量低耦合,且能提供靈活的應(yīng)用層調(diào)用接口。
采用嵌入式 C 語(yǔ)言面向?qū)ο蟮乃枷?,通過(guò)狀態(tài)機(jī)和回調(diào)函數(shù)的方式,我們來(lái)編寫一個(gè)通用的按鍵檢測(cè)模塊,以更好地覆蓋單片機(jī)的物理按鍵應(yīng)用場(chǎng)合。
以下是物理按鍵模塊的設(shè)計(jì)過(guò)程。
1、這個(gè)通用的物理按鍵模塊,主要是由4個(gè)源代碼文件組成,key_driver.c和key_driver.h主要是驅(qū)動(dòng)層接口,主要面向不同的單片機(jī)引腳適配。key_module.c和key_module.h主要是面向應(yīng)用層接口,與芯片硬件引腳無(wú)關(guān)。
2、key_driver.c 和 key_driver.h主要是用來(lái)適配不同的單片機(jī)GPIO外設(shè)的,在key_driver.h里面,聲明了一個(gè)key_driver_t類型的結(jié)構(gòu)體,主要提供GPIO引腳初始化接口以及引腳電平讀取接口,如下圖所示。
3、在key_driver.c里面,主要是對(duì)初始化接口和引腳電平讀取接口的具體實(shí)現(xiàn),比如,引腳初始化接口_init()函數(shù)和電平讀取接口_read_pin_state(),其具體實(shí)現(xiàn)如下圖所示。
4、在key_driver.c里面,定義了一個(gè)key_driver結(jié)構(gòu)體變量,記住這個(gè)變量,很重要,后面會(huì)被key_module進(jìn)行調(diào)用,key_driver的具體內(nèi)容如下圖所示。
5、在key_module.h里面,主要是聲明了兩個(gè)重要的結(jié)構(gòu)體,key_t結(jié)構(gòu)體是面向單個(gè)按鍵對(duì)象的,主要是包括按鍵ID以及按鍵狀態(tài)枚舉,還有一些變量是用來(lái)進(jìn)行按鍵檢測(cè)過(guò)程的,key_manager_t結(jié)構(gòu)體主要是用來(lái)管理多個(gè)按鍵對(duì)象的,包括各個(gè)按鍵動(dòng)作的函數(shù)接口,還有按鍵引腳的驅(qū)動(dòng)程序,如下圖所示。
6、按鍵模塊還對(duì)外提供了多個(gè)外部調(diào)用接口,包括模塊初始化,按鍵模塊時(shí)間更新,按鍵模塊的時(shí)基更新,按鍵模塊的按鍵動(dòng)作回調(diào)函數(shù)處理,如下圖所示。
7、在key_module.c里面,主要是對(duì)以上外部接口的具體實(shí)現(xiàn),比如,key_module_init()主要是對(duì)按鍵模塊的各個(gè)參數(shù)初始化,以及注冊(cè)按鍵模塊的引腳驅(qū)動(dòng)程序,代碼如下圖所示。
8、在key_module_update()函數(shù)里面,主要是以狀態(tài)機(jī)和回調(diào)函數(shù)的方式,處理各個(gè)按鍵狀態(tài)和動(dòng)作,按鍵狀態(tài)有KEY_IDLE、KEY_PRESSED、KEY_RELEASED、KEY_SINGLE_CLICK、KEY_DOUBLE_CLICK、KEY_LONG_PRESS。代碼如下圖所示。
9、在各個(gè)不同的狀態(tài)里面,通過(guò)回調(diào)函數(shù)的方式,分別對(duì)按下、抬起、單擊、雙擊、長(zhǎng)按、等按鍵動(dòng)作進(jìn)行處理,限于篇幅,這里只列出部分代碼,具體實(shí)現(xiàn)請(qǐng)參考具體源碼和注釋。
10、按鍵模塊需要對(duì)其提供系統(tǒng)時(shí)基,通常以1毫秒或者10毫秒作為時(shí)間基準(zhǔn),key_module_ticks_update()主要是在外部定時(shí)器或者外部1毫秒線程中被調(diào)用,key_module_set_event_handler()主要是用來(lái)設(shè)置各個(gè)按鍵狀態(tài)的回調(diào)函數(shù),如下圖所示。
11、如何使用key_module?假如項(xiàng)目采用RT-Thread進(jìn)行調(diào)度,在main()函數(shù)里面,先創(chuàng)建一個(gè)key_module_thread()線程,然后在該線程里面先對(duì)按鍵管理器進(jìn)行初始化,然后注冊(cè)各種按鍵狀態(tài)的回調(diào)函數(shù),最后在while循環(huán)里面,更新按鍵管理器的時(shí)基以及狀態(tài)更新函數(shù),線程主體以1毫秒的間隔進(jìn)行調(diào)度,如下圖所示。
12、以上,就是一個(gè)通用的單片機(jī)按鍵模塊具體設(shè)計(jì),通過(guò)這個(gè)按鍵檢測(cè)模塊,可以很好地處理各種按鍵狀態(tài)事件,并且該按鍵模塊在設(shè)計(jì)上遵循設(shè)備與驅(qū)動(dòng)分離的原則,盡量做到了高內(nèi)聚低耦合,具體很好的移植性和單片機(jī)平臺(tái)適配性。
13、美中不足的是,這個(gè)模塊還沒(méi)有加入組合按鍵處理,感興趣的讀者,可以下載該模塊的源碼,對(duì)其進(jìn)行修改和擴(kuò)展。