第八章為深入理解AMetal,本文內容為8.6 通用數碼管接口。
8.6 通用數碼管接口
>>> 8.6.1 定義接口
1. 接口命名
由于操作的對象是數碼管(digitron),因此接口命名以“am_digitron_”作為前綴。數碼管最常見的操作是設置數碼管的顯示內容,提供一個顯示字符和字符串的接口,其對應的接口名為:
-
am_digitron_disp_char_at
-
am_digitron_disp_str
當顯示字符或字符串時,需要將各個字符解碼為對應的段碼后,數碼管才能正常顯示。為此需要提供一個設置解碼函數的接口,便于用戶根據實際數碼管自定義解碼函數,然后通過該接口設置到系統中。當需要顯示一個字符時,系統首先會使用該解碼函數將字符解碼為段碼。其對應的接口名為:
-
am_digitron_disp_decode_set
在一些應用場合,可能需要顯示特殊的圖形,此時僅僅有顯示字符或字符串的接口是不夠的,還需要提供一個直接顯示段碼的接口,其對應的接口名為:
-
am_digitron_disp_at
此外,作為一個顯示器,還需要清除當前數碼管顯示的所有內容,便于重新設置顯示內容,其對應的接口名為:
-
am_digitron_disp_clr
特別地,除設置顯示內容相關的操作外,還需要數碼管閃爍顯示,其對應的接口名為:
-
am_digitron_disp_blink_set
2. 接口參數
通常系統存在多個數碼管,比如,同時使用MiniPort-View 和MiniPort-ZLG72128。在一個數碼管設備中,又可能包含多個數碼管,比如,MiniPort-View 和MiniPort-ZLG72128均包含兩個數碼管。
為了區分不同的數碼管設備,需要為每個數碼管設備分配一個唯一ID,基于此,將所有接口的第一個參數設定為數碼管ID,用于指定需要操作的數碼管設備。
am_digitron_disp_char_at()接口用于顯示一個字符,雖然有數碼管設備ID 用于確定顯示該字符的數碼管設備,但僅僅通過數碼管設備ID 還不能確定在數碼管設備中顯示的具體位置,為此需要新增一個索引參數,用于指定字符顯示的位置,索引的有效范圍為0 ~(數碼管個數–1),如MiniPort-View 有兩個數碼管,則索引的有效范圍為 0 ~ 1。此外,該接口還需要一個參數用以指定要顯示的字符。定義該接口的函數原型為(暫未定義返回值類型):
對于am_digitron_disp_str()接口,其用于顯示一個字符串,除數碼管設備ID 外,同樣需要一個索引參數以指定字符串顯示的起始位置,此外,還需要使用參數指定要顯示的字符串以及顯示字符串的長度。定義該接口的函數原型為(暫未定義返回值類型):
其中,len 指定顯示的長度,p_str 指定要顯示的字符串,實際顯示的長度為字符串長度和len 中的較小值。
對于am_digitron_disp_decode_set()接口,其用于設定字符的解碼函數,顯然,一個數碼管設備中的多個數碼管往往是相同的,可以使用同樣的解碼規則,共用一個解碼函數。因而接口僅需使用ID 指定數碼管設備,無需使用index 指定具體的數碼管索引。解碼函數的作用是對字符進行解碼,輸入一個字符,輸出該字符對應的編碼。基于此,定義該接口的函數原型為(暫未定義返回值類型):
其中,pfn_decode 是指向解碼函數的指針,表明了解碼函數的類型:具有一個16 位無符號類型的ch 參數,返回值為16 位無符號類型的編碼。這里使用16 位的數據表示字符和編碼,是為了更好的擴展性。如除8 段數碼管外,還存在14 段的米字型數碼管、16 段數碼管等,這些情況下,8 位數據就無法表示完整的段碼了。
對于am_digitron_disp_at()接口,其用于直接設置顯示的段碼,和顯示一個字符類似,除數碼管設備ID 外,同樣需要使用參數指定顯示的位置以及要顯示的內容(段碼),可定義該接口的原型為(暫未定義返回值類型):
對于am_digitron_disp_clr()接口,其用于清除一個數碼管設備顯示的所有內容,僅需使用ID 指定需要清除的數碼管設備,無需其它額外參數。定義該接口的原型為(暫未定義返回值類型):
對于am_digitron_disp_blink_set()接口,其用于設置數碼管的閃爍屬性,除數碼管設備ID 外,還需要使用參數指定設置閃爍屬性的數碼管位置以及使用本次設置的閃爍屬性(打開閃爍還是關閉閃爍),定義該接口的原型為(暫未定義返回值類型):
其中,index 指定本次設置閃爍屬性的數碼管位置,blink 指定閃爍屬性,當值為AM_TRUE 時,則打開閃爍;當值為AM_FALSE 時,則關閉閃爍。
3. 返回值
接口無特殊說明,直接將所有接口的返回值定義為int 類型的標準錯誤號。數碼管接口的完整定義詳見表8.8。其對應的類圖詳見圖8.16。
圖8.16 數碼管接口
表8.8 數碼管通用接口(am_digitron_disp.h)
>>> 8.6.2 實現接口
1. 抽象的數碼管設備類
類似地,應該根據通用數碼管接口,定義相應的抽象方法,以顯示字符函數為例,按照前面的通用做法,其抽象方法定義為:
相比于通用接口,其新增了一個用于指向設備自身的p_cookie 參數。在定義數碼管通用接口時,使用ID 唯一的代表了一個數碼管設備,可見,在這里,p_cookie 和id 均代指了某一確定的數碼管設備,由于抽象方法是由具體數碼管設備實現的,p_cookie 也用于指向設備自身,通過p_cookie 已經能夠唯一的確定某一具體設備,因此,ID 參數在抽象方法中再無實際用處,將ID 參數從抽象方法中移除,即:
實際上,可以看作是參數由抽象意義的ID(僅是一個數字)變為了具有實際意義的p_cookie(指向設備自身的指針)。
基于此,根據其它通用數碼管通用接口,為它們一一定義相應的抽象方法,并將其存放在一個虛函數表中,即:
讀者可能會發現,在實現LED 接口時,定義的抽象方法同時包含了p_cookie 和led_id參數。即:
這是由于在通用LED 接口的設計中,ID 并非是對LED 設備進行的編號,而是對系統中所有LED 進行的編號,如AM824-Core 板載了2 個LED,MiniPort-LED 上有8 個LED,如果它們同時使用,則系統中有兩個LED 設備,但總共有10 個LED,LED 編號為0~9。因此,雖然p_cookie 能夠確定要操作的LED 設備,但還是不能確定要操作的具體LED,因此,必須將LED 的編號作為參數傳遞給具體方法,以便準確的操作到某一具體的LED。
在通用數碼管接口的設計中,ID 是對數碼管設備的編號,如同時使用MiniPort-View 和MiniPort-ZLG72128 時,系統中有兩個數碼管設備,雖總共有4 個數碼管,但數碼管設備的編號只會是0~1。因為如此,數碼管設備ID 中并不包含具體數碼管的位置信息,為了將顯示內容顯示到某一確定的數碼管上,需要使用額外的index 參數指定。
類似地,將抽象方法和p_cookie 定義在一起,即為抽象的數碼管設備。比如:
和LED 抽象設備類似,實際上可能存在多個數碼管設備,由于它們的具體數目是無法預先確定的,因此這里使用單向鏈表進行動態管理,在am_digitron_dev_t 中增加一個p_next成員,用以指向下一個設備。即:
此時,系統中的多個數碼管設備使用鏈表的形式管理。由于在通用接口中,使用id 區分不同的數碼管設備。因此,在通用接口的實現中,需要能夠通過ID 號找到對應的數碼管設備,以便使用其中的抽象方法。和LED 設備類似,可以將一個數碼管設備和該設備對應的ID 信息綁定在一起,就可以通過ID 找到對應的數碼管設備。
一個數碼管設備對應了一個唯一的ID,可以定義數碼管設備ID 信息的類型為:
在設備中新增指向ID 信息的p_info 指針,便于在通用接口實現中根據ID 查找到對應的數碼管設備,即:
基于此,am_digitron_disp_char_at()函數的實現詳見程序清單8.44。
程序清單8.44 am_digitron_disp_char_at()接口實現范例
其中,__digitron_dev_find_with_id()的作用就是遍歷設備鏈表,與各個設備中的ID 信息一一比對,以找到數碼管ID 對應的數碼管設備,其實現詳見程序清單8.45。
程序清單8.45 查找指定id 的數碼管設備
其中,__gp_head 是一個全局變量,指向數碼管設備的鏈表頭,初始為NULL,表示初始時系統中無任何數碼管設備。同理可得到其它接口的實現,詳見程序清單8.46,它們的實現都非常類似,均為首先通過__digitron_dev_find_with_id()函數找到ID 對應的數碼管設備,然后直接調用設備中的抽象方法。
程序清單8.46 其它數碼管接口的實現范例
由于當前沒有任何數碼管設備,因此__digitron_dev_find_with_id()的返回值始終為NULL,使得通用接口的返回值始終為-AM_ENODEV(錯誤:無此設備)。
為了使通用接口能夠操作到具體有效的數碼管設備,就必須在使用通用數碼管接口前,向系統中添加有效的數碼管設備。根據數碼管設備類型的定義,添加一個設備時,需要完成p_ops、p_cookie 和p_info 的正確賦值,這些成員的值是由具體數碼管設備實現或定義的,為此,可以為具體數碼管的設備驅動提供一個添加數碼管設備的接口,定義其函數原型為:
其中,為了方便直接添加一個設備,避免直接操作數碼管設備的各個成員,將需要賦值的成員通過參數傳遞給接口函數。其實現詳見程序清單8.47。
程序清單8.47 向系統中添加數碼管設備
首先檢查了各個參數的有效性,然后使用__digitron_dev_find_with_id()函數判斷新設備的ID 號是否已經在系統中,若系統中已經存在該ID,則添加失敗,直接返回操作不允許錯誤(-AM_EPERM)。若系統中不存在該ID,則繼續執行,以確保添加的各個數碼管設備的ID 不沖突,保證了數碼管設備編號的唯一性,接著將設備中的各個成員賦值,最后通過程序清單8.47 的17~18 這2 行代碼將新設備添加到鏈表首部。
圖8.17 抽象的數碼管設備類
顯然,接下來需要在具體的數碼管設備中,實現相應的抽象方法,然后使用am_digitron_dev_add()接口將設備添加到系統中,使得用戶可以使用數碼管通用接口操作到具體有效的數碼管。為了便于查閱,如程序清單8.48 所示展示了數碼管設備接口文件(am_digitron_dev.h)的內容。
程序清單8.48 am_digitron_dev.h 文件內容
2. 具體的數碼管設備類
以使用GPIO 驅動的MiniPort-View 為例,簡述具體數碼管設備的實現方法。首先應該基于抽象設備類派生一個具體的設備類,其類圖詳見圖8.18,定義具體的數碼管設備類如下:
圖8.18 具體的數碼管設備類
am_digitron_miniport_view_t 即為具體的數碼管設備類。具有該類型后,即可使用該類型定義一個具體的數碼管設備實例,即:
對于動態掃描類的數碼管,需要將數碼管顯示的段碼緩存到一段內存中,然后定時掃描,依次掃描各個數碼管,從緩存中取出當前掃描數碼管的段碼,然后將段碼輸送到相應的引腳上顯示。
為了實現數碼管定時自動掃描,需要使用到軟件定時器,可以新增一個軟件定時器timer成員;在掃描過程中,需要實時記錄當前的掃描位置,以便從相應的數碼管緩存中取出對應的段碼,一個數碼管掃描結束后,掃描位置要更新為下一個數碼管的位置,可以新增scan_idx成員來實時存儲當前數碼管的掃描位置。即設備類型可定義為:
此外,為了保存閃爍屬性,可以新增一個blink_flags 的成員表示各個需要數碼管的閃爍屬性,某一位的值為1 時,表明對應的數碼管需要閃爍。在一個閃爍周期中,一段時間需要點亮,一段時間需要熄滅,為了判定當前應該處于何種狀態,可以新增一個閃爍計時器成員blink_cnt,用于在一個閃爍周期內計時。特別地,在通用接口中,有一個設置數碼管解碼函數的接口,為了在用戶顯示字符時,能夠使用其設置的解碼函數對字符進行解碼,則需要一個函數指針保存用戶設置的解碼函數,基于此,設備類型可定義為:
此外,為了正常使用數碼管,還需要知道一些硬件相關的基本信息,如:位選引腳信息、段碼引腳信息、數碼管個數,段碼數目等,據此,可以定義數碼管設備的信息類型為:
同時,對于動態掃描類數碼管,需要一個緩存用以存儲顯示的段碼,緩存的大小應該與數碼管個數相同,可以新增一個p_disp_buf 指針以指向相應的緩存。此外,數碼管動態掃描時,掃描的頻率必須大于25Hz,才能使肉眼看不到動態掃描的過程,使整個數碼管的顯示完整、流暢。顯然,頻率越高,掃描越快,顯示就越流暢,但掃描數碼管時占用的CPU 資源也就越多;頻率越低,系統CPU 資源占用也就越少,但也不能過低。為此,可以在設備信息中新增一個scan_freq 成員,用以指定掃描的頻率,使得掃描頻率可以由用戶根據實際情況配置。掃描頻率直接決定了定時器定時掃描的周期,若掃描頻率為50Hz,則掃描一次數碼管的時間為20ms,由于MiniPort-View 存在兩個數碼管,因此定時器定時掃描的周期為10ms。可見,定時器定時掃描的時間間隔為:1000 / scan_freq / digitron_num。
此外,當數碼管需要閃爍時,為了更加個性化的定制閃爍的效果,可以使用blink_on_time和blink_off_time 分別指定一個閃爍周期內點亮的時間和熄滅的時間,它們的時間之和即為閃爍周期,決定了閃爍的頻率。
同時,在數碼管通用接口中,各個數碼管設備使用ID 號進行區分,顯然,這就要求為具體的數碼管設備分配一個唯一ID,可以在設備信息中新增表示ID 信息的成員dev_info。完整的數碼管設備信息的類型定義為:
當將AM824-Core 與數碼管配板MiniPort-View 聯合使用時,若分配給數碼管設備的ID號為0,掃描頻率為50Hz,在一個閃爍周期內,數碼管點亮和熄滅的時間均為500ms,基于等效電路圖以及數碼管信息,定義與MiniPort-View 對應的設備實例信息為:
類似地,在數碼管的設備類型中需要維持一個指向數碼管設備信息的指針,以便在任何時候都可以從數碼管設備中取出相關的信息使用,完整的數碼管設備類型定義為:
實際開發過程中,通常并不能一次性完整的定義出設備或設備信息的結構體類型,往往是在定義好基本結構后,在后續實現各個抽象方法的過程中,根據需要增加成員,不斷完善結構體類型的定義。
為了正常掃描數碼管,需要完成設備中各成員的賦值,在完成初始賦值后,則可以啟動軟件定時器,進而以設備信息中指定的掃描頻率自動掃描數碼管。這些工作通常在驅動的初始化函數中完成,定義初始化函數的原型為:
其中,p_dev 為指向am_digitron_miniport_view_t 類型實例的指針,p_info 為指向am_digitron_miniport_view_info_t 類型實例信息的指針,其調用形式如下:
初始化完成后,可使用通用數碼管接口操作編號為0 的數碼管設備,初始化函數的實現范例詳見程序清單8.49。
程序清單8.49 初始化函數實現范例
該程序首先檢查了參數的有效性,然后完成了設備中各個成員的初始賦值,接著根據位選引腳和段碼引腳的激活電平,將位選引腳和段碼引腳配置成輸出模式,并將初始電平設置為未激活電平,以使數碼管初始處于完全熄滅狀態。
緊接著初始化并啟動了軟件定時器,根據掃描頻率設定了軟件定時器的定時周期,必將軟件定時器的周期性回調函數設置為了__digitron_dynamic_scan_timer_cb(),即在該函數中完成數碼管的掃描。最后,使用am_digitron_dev_add()接口將設備添加到了系統中,并將數碼管ID 號信息作為該接口p_info 的實參,&__g_digitron_dev_ops 作為該接口p_ops 的實參,指向自身的指針p_dev 作為了接口p_cookie 的實參,__g_digitron_dev_ops 中即包含了各個抽象方法的實現。
由此可見,實現整個具體數碼管設備的關鍵,是在__digitron_dynamic_scan_timer_cb()中完成數碼管的掃描,以及實現各個抽象方法并存于__g_digitron_dev_ops 中。
定時器回調函數__digitron_dynamic_scan_timer_cb()的實現詳見程序清單8.50。
程序清單8.50 定時器回調函數的實現(數碼管掃描)
該程序首先處理閃爍計時器,若存在閃爍的數碼管,則增加閃爍計時器p_dev->blink_cnt,增加的值即為掃描時間間隔。特別地,若值增加后超過了閃爍周期,則重新回到0。然后使用__scan_seg_send()發送消影段碼,使用__scan_com_sel()處理位選。若當前數碼管需要正常顯示,即當前數碼管不需要閃爍,或者雖然需要閃爍,但根據閃爍計時器判定當前時間處在點亮數碼管的時間周期,則從顯示緩存中取出當前數碼管的段碼,并使用__scan_seg_send()發送出去。最后更新了掃描位置索引scan_idx 的值,以便下一次掃描時繼續掃描下一個數碼管,段碼發送函數和位選函數的實現詳見程序清單8.51。
程序清單8.51 段碼發送函數和位選函數的實現
接下來,需要一一實現抽象數碼管設備中共計定義了6 個抽象方法,以完成__g_digitron_dev_ops 的定義。
-
pfn_decode_set
該方法用于設定解碼函數,便于顯示時,對各個字符進行解碼。顯然,需要將其保存到設備中,以便后續使用。范例程序詳見程序清單8.52。
程序清單8.52 設置解碼函數的實現范例程序
-
pfn_blink_set
該方法用于設定某一個數碼管的閃爍屬性,設置閃爍屬性時,只需要將設備中的閃爍標記blibk_flags 相應位置1(閃爍)或清零(不閃爍)即可,范例程序詳見程序清單8.53。
程序清單8.53 設置閃爍屬性函數的實現范例程序
-
pfn_disp_at
該方法用于在指定數碼管上顯示指定的段碼圖形,只需要將段碼存放在數碼管緩存中即可,范例程序詳見程序清單8.54。
程序清單8.54 顯示段碼函數的實現范例程序
該程序調用了__digitron_disp_buf_set()函數將段碼設置到緩存中,詳見程序清單8.55。
程序清單8.55 __digitron_disp_buf_set()函數實現
該程序根據數碼管段的激活電平決定是否需要將用戶設置的段碼取反后存入緩沖區中。
-
pfn_disp_char_at
該方法用于在指定位置顯示字符,這就需要先使用解碼函得到字符對應的段碼,然后將段碼設置到緩沖區中,范例程序詳見程序清單8.56。
程序清單8.56 顯示字符函數的實現范例程序
該程序首先通過解碼函數得到字符的段碼存于seg 中。然后將段碼保存到相應的緩沖區中,且在保存段碼時對小數點作了特殊的處理。
當字符為小數點時,則使用__digitron_disp_buf_xor()函數將段碼設置到函數區;否則直接使用__digitron_disp_buf_set()函數將段碼設置到緩存區中。
由于由于小數點比較特殊,因此顯示小數點時,往往不希望影響該位數碼管的正常顯示內容,比如,如果當前數碼管顯示數字3,又需要在該數碼管添加小數點,則期望的結果是顯示“3.”,而不僅僅顯示一個小數點,將之前的3 覆蓋掉。由此可見,在顯示小數點時,可視為顯示內容的一種疊加,而不是直接改變顯示內容,__digitron_disp_buf_xor()函數的實現詳見程序清單8.57。
程序清單8.57 __digitron_disp_buf_xor()函數實現
若數碼管段為低電平激活,則需要在原緩沖區段碼的基礎上,將顯示小數點需要點亮的段清零,以顯示小數點;反之則將顯示小數點需要點亮的段置1,以顯示小數點。
-
pfn_disp_str
該方法用于從指定位置開始顯示一個字符串,范例程序詳見程序清單8.58。
程序清單8.58 字符串顯示函數實現范例程序
該程序首先確定了字符串的長度,字符串長度取字符串實際長度和參數len 中的較小值,然后再while 循環中,調用了__digitron_disp_char_at()函數依次顯示單個字符。
其中的idx 用于指定顯示位置,初始值為index – 1,即字符串顯示起始位置的上一個數碼管位置,若起始位置為0,則idx 的初始值表示了一個無效的位置。每次顯示新內容前,需要更新idx 的值(將idx 加1)。但在某種特殊情況下,小數點不需要更新顯示位置,例如,顯示字符串“3.5”,期望顯示的效果是只占用2 個數碼管,分別顯示“3.”和“5”,而不是占用3 個數碼管,這種情況下,當顯示小數點時,直接顯示在“3”所在的數碼管中即可,無需將其單獨顯示到一個數碼管上。程序需要更新顯示位置的條件為:
由此可見,不需要更新位置的條件即為上述條件的反面:
不需要更新位置的條件為:當前顯示的字符為小數點,且上一個字符不為小數點,同時idx 指定的顯示位置有效。
-
pfn_clr
該方法用于清空數碼管顯示,需要將緩沖區中的內容全部設置為熄滅段碼,范例程序詳見程序清單8.59。
程序清單8.59 清空顯示內容函數的實現范例程序
至此,實現了各個抽象方法,基于各個抽象方法的實現函數,__g_digitron_dev_ops 的定義詳見程序清單8.60。
程序清單8.60 __g_digitron_dev_ops 的定義
當用戶使用初始化函數完成一個具體數碼管設備的初始化后,即可使用通用數碼管接口操作數碼管,顯示具體內容。但是,在顯示字符或字符前,必須使用通用接口設置一個解碼函數。對于8 段數碼管,可以將各個ASCII 字符的段碼定義在一個數組中,然后實現一個解碼函數,詳見程序清單8.61。
程序清單8.61 解碼函數的實現
基于此,在用戶使用數碼管接口顯示字符或字符串前,可以使用設置解碼函數的接口將該解碼函數設置到系統中,以便正確解碼。比如:
如果用戶每次使用數碼管前,都需要自定義一個解碼函數,則顯得非常麻煩。對于8段數碼管,常見圖形的顯示方法是固定的,對應的段碼是可以確定的,如數字0 ~ 9。用戶如果沒有特殊需求,使用程序清單8.61 所示的解碼函數是能滿足絕大部分應用的?;诖?,可以將程序清單8.61 所示的解碼函數定義在系統中,直接供用戶使用。為方便用戶使用,可以將該解碼函數聲明到數碼管接口文件中。
為了便于查閱,如程序清單8.62 所示展示了具體數碼管設備(MiniPort_View)接口文件(am_digitron_miniport_view.h)的內容。
程序清單8.62 am_digitron_miniport_view.h 文件內容
原文標題:周立功:深入理解AMetal——通用數碼管接口
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠電子】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論