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

面向ametal框架與接口的編程ametal uart總線

深入了解邏輯分析儀入門手冊
深入淺出AMetal之接口與實(shí)現(xiàn)

評論