周立功教授新書《面向AMetal框架與接口的編程(上)》,對AMetal框架進行了詳細介紹,通過閱讀這本書,你可以學到高度復用的軟件設計原則和面向接口編程的開發(fā)思想,聚焦自己的“核心域”,改變自己的編程思維,實現(xiàn)企業(yè)和個人的共同進步。經(jīng)周立功教授授權,即日起,致遠電子公眾號將對該書內(nèi)容進行連載,愿共勉之。
第八章為深入理解AMetal,本文內(nèi)容為8.5 通用按鍵接口。
8.5 通用按鍵接口
>>> 8.5.1 定義接口
1. 接口命名
由于操作的對象是按鍵(key),按鍵是一種輸入設備,為了使含義更加清晰,接口命名增加input 關鍵字,因此,接口命名以“am_input_key_”作為前綴。
按鍵的操作主要分為兩大部分:按鍵檢測和按鍵處理。按鍵檢測與具體硬件相關,按鍵處理與應用相關,由用戶完成。
對于用戶,其只需關心如何對按鍵進行處理,進而定義相應的按鍵處理方法(函數(shù)),不需要關心按鍵檢測的具體細節(jié)。
對于按鍵檢測,其只需要檢測是否有按鍵事件發(fā)生(按鍵按下或按鍵釋放),當檢測到按鍵事件時,應該通知到用戶,以便進行相關的按鍵處理。
顯然,按鍵處理方法是由用戶定義的,只有用戶知道,按鍵檢測模塊無從得知。當檢測到按鍵事件時,為了能夠執(zhí)行到相應的按鍵處理方法,必須使按鍵檢測模塊可以通過某種方法調(diào)用到相應的按鍵處理方法。
由此可見,按鍵處理方法是由用戶定義的,但卻需要由按鍵檢測模塊調(diào)用,可以使用典型的回調(diào)機制來處理這種情況,即:將按鍵處理方法視為需要由按鍵檢測模塊回調(diào)的函數(shù),用戶將按鍵處理函數(shù)注冊到按鍵檢測模塊中(將函數(shù)地址傳遞給按鍵處理模塊,按鍵處理模塊使用函數(shù)指針將其保存),當檢測到按鍵事件時,查找模塊中已經(jīng)注冊的按鍵處理函數(shù),然后一一調(diào)用(通過函數(shù)指針調(diào)用)。
雖然使用單一的回調(diào)機制可以實現(xiàn)按鍵管理,但是,卻使得按鍵檢測模塊的職責變得不單一,其不僅要處理與硬件相關的按鍵檢測,還要管理用戶注冊的回調(diào)函數(shù),有悖于單一職責原則。
基于按鍵檢測模塊的本質(zhì):檢測按鍵事件。可以將管理用戶注冊回調(diào)函數(shù)的部分分離出來,形成一個單獨的按鍵管理模塊。
基于此,用戶不再直接與按鍵檢測模塊產(chǎn)生交互,用戶將按鍵處理方法注冊到按鍵管理模塊中。當按鍵檢測模塊檢測到按鍵事件時,通知按鍵管理模塊,告知有按鍵事件發(fā)生,按鍵管理模塊接收到通知后,查找模塊中已經(jīng)注冊的按鍵處理函數(shù),然后一一調(diào)用。
由此可見,新增按鍵管理模塊后,用戶和按鍵檢測都僅僅與按鍵管理模塊交互,實現(xiàn)了用戶和按鍵檢測的完全分離。這就是典型的分層設計思想,用戶屬于應用層、按鍵管理模塊屬于中間層、按鍵檢測屬于硬件層,示意圖詳見圖8.14,中間層將應用層與硬件層完全隔離。
圖8.14 按鍵系統(tǒng)分層結構圖
中間層需要為應用層提供一個注冊按鍵處理函數(shù)的接口,其接口名定義為:
-
am_input_key_handler_register
同時,中間層還需要為硬件層提供一個上報按鍵事件的接口,用于當檢測到按鍵事件時,使用該接口通知中間層有按鍵事件發(fā)生,進而使中間層調(diào)用用戶注冊的按鍵處理函數(shù)。其接口名定義為:
-
am_input_key_report
2. 接口參數(shù)
要使用am_input_key_handler_register()接口注冊按鍵處理函數(shù),首先必須定義好按鍵處理函數(shù)的類型。
在AMetal 中,一般將回調(diào)函數(shù)的第一個形參設置為void *類型的p_arg 參數(shù),在用戶注冊回調(diào)函數(shù)時,指定一個void*類型的變量作為調(diào)用回調(diào)函數(shù)時傳遞給第一個參數(shù)的實參,以便在回調(diào)函數(shù)中處理用戶自定義的一些上下文數(shù)據(jù)。
此外,系統(tǒng)中可能存在多個按鍵,為了使用戶可以區(qū)分各個按鍵,以便針對不同的按鍵作不同的處理,可以為每個按鍵分配一個唯一編碼key_code。編碼是一個整數(shù),如0、1、2……為了可讀性,可以使用宏的形式定義一些常見的具有實際意義的按鍵編碼,如對應PC 鍵盤,可以定義KEY_A ~ KEY_Z(字母鍵)、KEY_0 ~ KEY_9(數(shù)字鍵)、KEY_KP0 ~ KEY_KP9(小鍵盤數(shù)字鍵)等等,將按鍵編碼定義在am_input_code.h 文件中,如KEY_0 ~ KEY_9的定義詳見程序清單8.37。
程序清單8.37 按鍵編碼定義范例(am_input_code.h)
如此一來,應用程序可以直接使用具有實際意義的按鍵編碼宏,而無需關心其對應的具體編碼值,這樣不僅增加了應用程序的可讀性,也使應用程序不依賴于具體的編碼值,更有利于跨平臺復用。例如,在一個應用中,其使用了數(shù)字鍵0,在當前平臺中,數(shù)字鍵0 對應的按鍵編碼值為11,假如在另外的某一平臺中,數(shù)字鍵0 的編碼值為12。若當前應用程序是使用數(shù)字鍵0 對應的宏KEY_0 實現(xiàn)的,則更換平臺后,應用程序無需作任何修改;但若應用程序直接使用了編碼值11,則更換平臺后需要修改程序,將編碼值修改為12。
雖然在絕大部分情況下都只需要處理按鍵按下事件,但是,作為通用接口,還需要考慮到,在一些特殊的應用場合,可能需要處理按鍵釋放事件,為此,可以使用一個表示按鍵狀態(tài)的key_state 參數(shù)。由于key_state 僅用于表示按下或釋放,可以使用宏的形式將可能的取值定義出來,其定義如下(am_input.h):
回調(diào)函數(shù)作為按鍵處理函數(shù),不需要反饋任何信息給實際調(diào)用者(中間層),因此無需返回值。基于此,按鍵處理函數(shù)的類型即為:無返回值,具有3 個參數(shù):p_arg,key_code,key_state 的函數(shù)。注冊的回調(diào)函數(shù)需要使用函數(shù)指針來存儲函數(shù)的地址,以便當按鍵事件發(fā)生時,使用函數(shù)指針調(diào)用實際的按鍵處理函數(shù),函數(shù)指針的類型定義為:
注冊按鍵處理函數(shù)時,需要指定注冊的按鍵回調(diào)函數(shù)以及一個void*類型的p_arg 參數(shù)作為按鍵處理函數(shù)的第一個參數(shù)。am_input_key_handler_register()的函數(shù)原型為:
實際中,回調(diào)函數(shù)和p_arg 需要存儲在內(nèi)存中,才能在合適的時候使用它們??梢远x一個專門的結構體類型存儲它們,假定類型為:am_input_key_handler_t(按鍵處理器),其具體的定義在后文根據(jù)實現(xiàn)來定義。顯然,每注冊一個按鍵回調(diào)函數(shù),都需要提供這樣一個類型的內(nèi)存空間。因此,注冊按鍵處理函數(shù)時,需要使用該類型的指針指定一個按鍵處理器空間,完善am_input_key_handler_register()的函數(shù)原型為:
根據(jù)前面分析的按鍵處理函數(shù)類型,在按鍵管理模塊調(diào)用回調(diào)函數(shù)時,需要知道按鍵的編碼和按鍵的狀態(tài),以便應用程序根據(jù)實際情況進行處理。
顯然,按鍵編碼和按鍵狀態(tài)需要由按鍵檢測模塊進行檢測,當檢測到某一編碼的按鍵發(fā)生按鍵事件時,就將按鍵編碼和按鍵狀態(tài)上報給按鍵管理模塊,按鍵管理模塊進而根據(jù)這些信息調(diào)用按鍵處理函數(shù),基于此,使用am_input_key_report 接口上報按鍵事件時,需要指定按鍵編碼key_code 和按鍵狀態(tài)key_state,其函數(shù)原型為:
3. 返回值
接口無特殊說明,直接將所有接口的返回值定義為int 類型的標準錯誤號。按鍵管理模塊接口的完整定義詳見表8.6。其對應的類圖詳見圖8.15。
圖8.15 按鍵管理接口
表8.6 按鍵管理通用接口(am_input.h)
>>> 8.5.2 實現(xiàn)接口
1. 實現(xiàn)am_input_key_handler_register()接口
由于按鍵管理器是一個中間層模塊,其本身與具體硬件無關,因此可以直接實現(xiàn)相應的接口,而無需為適應不同的硬件而定義抽象方法。
在定義接口參數(shù)時,提到了使用am_input_key_handler_t 類型的按鍵處理器來存儲指向回調(diào)函數(shù)的指針和回調(diào)函數(shù)的p_arg 參數(shù),基于該用途,其類型定義為:
顯然,一個系統(tǒng)中,可能遠不止一個按鍵處理器,比如,A 應用需要處理編碼為KEY_1的按鍵,B 應用需要處理編碼為KEY_2 的按鍵,它們可以分別定義按鍵處理函數(shù)以處理各自的KEY_1 或KEY_2 按鍵,此時,就需要兩個按鍵處理器來分別存儲A 應用和B 應用的按鍵處理函數(shù)。
當系統(tǒng)中有多個按鍵處理器時,就存在一個如何管理的問題,由于按鍵處理器的個數(shù)與應用相關,具體個數(shù)不定,因此,采用單向鏈表的方式進行管理,為此,在按鍵處理器類型中新增一個p_next 成員,使其指向下一個按鍵處理器:
基于此,可以實現(xiàn)注冊按鍵處理函數(shù)接口,其范例程序詳見程序清單8.38。
程序清單8.38 am_input_key_handler_register()接口實現(xiàn)范例程序
該程序首先判定了參數(shù)的有效性,然后完成了按鍵處理器中pfn_cb 和p_usr_data 的賦值,將用戶的按鍵處理函數(shù)和用戶參數(shù)保存到了按鍵處理器中,接著通過程序清單8.38 的14 ~ 15 行共計2 行代碼將新的按鍵處理器添加到鏈表首部。全局變量__gp_handler_head 指向了鏈表頭,初始時,由于沒有注冊任何按鍵處理器,因此其值為NULL。
2. 實現(xiàn)am_input_key_report()接口
該接口用于當硬件層檢測到按鍵事件時,通過該接口上報按鍵事件。當接收到上報事件時,需要遍歷當前系統(tǒng)中所有的按鍵處理器,并一一調(diào)用它們的按鍵處理函數(shù),基于此實現(xiàn)按鍵事件上報接口的范例程序詳見程序清單8.39。
程序清單8.39 am_input_key_report()接口實現(xiàn)范例程序
該程序從鏈表的頭結點開始,依次遍歷各個按鍵處理器,然后通過函數(shù)指針調(diào)用其中的按鍵處理函數(shù),在調(diào)用按鍵處理函數(shù)時,將按鍵處理器中存儲的用戶參數(shù)p_usr_data 作為按鍵處理函數(shù)的用戶參數(shù)傳遞,key_code 和key_state 則直接使用上報的按鍵編碼和按鍵狀態(tài)。
上述程序作為一種范例,實現(xiàn)非常簡潔,和其它通用接口的實現(xiàn)不同的是,這里沒有定義任何抽象方法,僅僅通過簡短的代碼直接實現(xiàn)了相應的兩個接口,這是由于按鍵管理器本身是基于分層設計的思想定義出來的,其不依賴于具體硬件,它為具體硬件檢測模塊提供了一個am_input_key_report()接口用于上報按鍵事件。
為了便于查閱,如程序清單8.40 所示展示了按鍵管理接口文件(am_input.h)的內(nèi)容。
程序清單8.40 am_input.h 文件內(nèi)容
>>> 8.5.3 檢測按鍵的實現(xiàn)
上面實現(xiàn)了按鍵管理器的接口,按鍵管理器作為一個中間層,其為上層應用提供了注冊按鍵處理函數(shù)的接口,為下層硬件驅(qū)動提供了按鍵事件的上報接口。顯然,對于不同的硬件,其按鍵掃描的方法是不同的,但當掃描的按鍵事件時,均只需要調(diào)用am_input_key_report()接口上報按鍵事件即可。
本節(jié)以獨立鍵盤為例,講述硬件層檢測按鍵的具體實現(xiàn)方法。根據(jù)面向?qū)ο蟮脑O計思想,將獨立鍵盤看做一個對象,定義其類型為:am_key_gpio_t。即:
具體需要包含哪些成員呢?為了實現(xiàn)按鍵定時自動掃描,需要使用軟件定時器,可以新增一個軟件定時器timer 成員;在掃描過程中,為了實現(xiàn)消抖需要將當前掃描的鍵值和上一次掃描得到的鍵值比較,可以新增一個key_prev 成員,用于保存上一次掃描到的鍵值;當檢測到有效的掃描鍵值時(本次掃描得到的鍵值和上一次掃描得到的鍵值相同),需要和上一次有效的掃描鍵值進行比較,以確定哪些按鍵的狀態(tài)發(fā)生了變化,可以新增一個key_press成員,用于保存上一次有效的掃描鍵值。獨立鍵盤的類型可以定義為:
am_key_gpio_t 即為獨立鍵盤設備類。具有該類型后,即可使用該類型定義一個獨立鍵盤設備實例,即:
此外,為了正常使用獨立鍵盤,還需要知道一些硬件相關的基本信息,比如,引腳信息、按鍵按下的電平信息(按下為高電平還是低電平)和按鍵數(shù)目,同時還可以指定一個鍵盤掃描的時間間隔,即軟件定時器的定時周期,決定了鍵盤掃描的快慢??梢远x獨立鍵盤的信息類型為:
特別地,當檢測到某一按鍵事件時,需要使用am_input_key_report()上報按鍵事件,按鍵事件包括按鍵的編碼和按鍵的狀態(tài),按鍵的狀態(tài)(按下或釋放)可以通過按鍵掃描的鍵值判定出來。為了便于用戶區(qū)分不同按鍵,為每個按鍵分配的唯一編碼值,相當于唯一ID 號,因此按鍵的編碼值只能由用戶決定,按鍵掃描程序是無法決定的,為了在上報按鍵事件時使用正確的編碼,需要由用戶提供各個按鍵的編碼信息,為此,在獨立鍵盤的信息中,新增p_codes 成員,使其指向按鍵的編碼信息,完整的獨立鍵盤信息類型定義為:
AM824-Core 上板載了一個獨立按鍵,當J14 的1 和2 短接時,KEY 與PIO_KEY(PIO0_1)連接。假定為其分配的按鍵唯一編碼為KEY_KP0,則獨立鍵盤的信息可以定義為:
類似地,在獨立鍵盤的設備類型中需要維持一個指向獨立鍵盤信息的指針,以便在任何時候都可以從獨立鍵盤設備中取出相關的信息使用,完整的獨立鍵盤設備類型定義為:
顯然,要使按鍵能夠正常掃描,需要完成設備中各成員的賦值,在完成初始賦值后,則可以啟動軟件定時器,進而以設備信息中指定的掃描時間間隔自動掃描按鍵。這些工作通常在驅(qū)動的初始化函數(shù)中完成,定義初始化函數(shù)的原型為:
其中,p_dev 為指向am_key_gpio_t 類型實例的指針,p_info 為指向am_key_gpio_info_t類型實例信息的指針?;谇懊娑x的設備實例和實例信息,其調(diào)用形式如下:
初始化函數(shù)的實現(xiàn)范例詳見程序清單8.41。
程序清單8.41 獨立鍵盤初始化函數(shù)實現(xiàn)范例
該程序首先判定了參數(shù)的有效性,需要特別注意的是,由于當前設備中使用了uint32_t類型的數(shù)據(jù)存儲掃描鍵值(如key_prev,key_press),最多只能支持32 個按鍵,因此當按鍵數(shù)目超過32 時,返回“不支持”的錯誤號。若為了支持更多的獨立按鍵,可以使用位寬更寬的數(shù)據(jù)類型,但實際上獨立鍵盤每個按鍵需要占用一個引腳,往往獨立按鍵的數(shù)目都不會過多,具有大量按鍵時,往往采用矩陣鍵盤。
接著根據(jù)按鍵按下時的電平,將引腳配置為了輸入模式,并將key_prev 和key_press 初始賦值為所有按鍵均未按下時對應的鍵值。最后,初始化并啟動了軟件定時器,將軟件定時器的定時周期設定為了獨立鍵盤信息中的掃描時間間隔,軟件定時器的周期性回調(diào)函數(shù)設置為了__key_gpio_timer_cb,即在該函數(shù)中完成獨立鍵盤的掃描,其實現(xiàn)詳見程序清單8.42。
程序清單8.42 獨立鍵盤掃描函數(shù)實現(xiàn)
首先使用了__key_val_read()函數(shù)讀取當前的掃描鍵值,在__key_val_read()函數(shù)的實現(xiàn)中,依次讀取各個按鍵對應的引腳電平,將各個引腳的電平信息保存在對應的位中。當前掃描到的鍵值存儲在key_value 中。
然后將key_value 與上一次的掃描鍵值p_dev->key_prev 比較,若兩者相等,表明本次掃描鍵值key_value 是有效的鍵值。此時將有效鍵值key_value 與上一次的有效掃描鍵值p_dev->key_press 比較,若二者不等,則表明有按鍵事件發(fā)生,通過異或運算找出兩者之間發(fā)生變化了的位,其值存儲在key_change 中。
接著遍歷key_change 的各個位,若key_change 的相應位為1,則表明對應按鍵的狀態(tài)發(fā)生的變化,需要上報。按鍵位值和active_low 共同決定了當前按鍵的狀態(tài)(按下或者未按下),其對應的真值表詳見表8.7??梢姡敯存I掃描的值與active_low 相等時,表明當前按鍵未處于按下狀態(tài),此次狀態(tài)變化是由于按鍵釋放產(chǎn)生的,應該上報按鍵釋放事件。反之,表明當前按鍵處于按下狀態(tài),此次狀態(tài)變化是由于按鍵按下產(chǎn)生的,應該上報按鍵按下事件。上報事件時,按鍵編碼是從獨立鍵盤信息中的編碼信息中得到的。注意,在進行比較之前,將它們連續(xù)進行了兩次“取非”操作,即 “!!”,確保待比較的值只能為0 或1。
表8.7 按鍵狀態(tài)真值表
在所有按鍵事件上報結束后,表明完成了對一次有效掃描鍵值的處理,需要更新上一次的有效掃描鍵值p_dev->key_press 為key_value。無論有效按鍵的鍵值是否發(fā)生變化,在程序的末尾都會更新上一次的掃描鍵值p_dev->key_prev 為本次的掃描鍵值key_value。
為了便于查閱,如程序清單8.43 所示展示了獨立鍵盤接口文件(am_key_gpio.h)。
程序清單8.43 am_key_gpio.h 文件內(nèi)容
-
嵌入式
+關注
關注
5068文章
19019瀏覽量
303287 -
周立功
+關注
關注
38文章
130瀏覽量
37584 -
回調(diào)函數(shù)
+關注
關注
0文章
87瀏覽量
11543
原文標題:周立功:深入理解AMetal——通用按鍵接口
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠電子】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論