第八章為深入理解AMetal,本文內容為8.1 LED 通用接口。
第八章導讀
面向通用接口的編程使得應用程序與具體硬件無關,可以很容易地實現跨平臺復用。但究其本質如何,具體是怎樣實現的呢?
8.1 LED 通用接口
本節將以LED 通用接口為例,詳細介紹通用接口的設計方法。
>>> 8.1.1 定義接口
合理的接口應該是易閱讀的、職責明確的,下面將從接口的命名、參數和返回值三個方面闡述在AMetal 中定義接口的一般方法。
1. 接口命名
在AMetal 中,所有通用接口均以“am_”開頭,緊接著是操作對象的名字,對于LED控制接口來說,所有接口應該以“am_led_”為前綴。
當接口的前綴定義好之后,需要考慮定義哪些功能性接口,然后根據功能完善接口名。對于LED 來說,核心的操作是控制LED 的狀態,點亮或熄滅LED,因此需要提供一個設置(set)LED 狀態的函數,比如:
-
am_led_set
顯然,通過該接口可以設置LED 的狀態,為了區分是點亮還是熄滅LED,需要通過一個參數指定具體的操作。
在大多數應用場合中,可能需要頻繁地開燈和關燈操作,每次開關燈都需要通過傳遞參數給am_led_set()接口實現開燈和關燈,這樣做會非常繁瑣。因此可以為常用的開燈和關燈操作定義專用的接口,也就不再需要額外參數區分具體的操作。比如,使用on 和off 分別表示開燈和關燈,則定義開燈和關燈的接口名為:
-
am_led_on
-
am_led_off
在一些特殊的應用場合種,比如,LED 閃爍,其可能并不關心具體的操作是開燈還是關燈,它僅僅需要LED 的狀態發生翻轉。此時,可以定義一個用于翻轉(toggle)LED 狀態的接口,其接口名為:
-
am_led_toggle
2. 接口參數
在AMetal 中,通用接口的第一個參數表示要操作的具體對象。顯然,一個系統可能有多個LED,為了確定操作的LED,最簡單的方法是為每個LED 分配一個唯一編號,即ID號,然后通過ID 號確定需要操作的LED。ID 號是一個從0 開始的整數,其類型為int,基于此,所有接口的第一個參數定義為int 類型的led_id。
對于am_led_set 接口來說,除使用led_id 確定需要控制的LED 外,還需要使用一個參數區分是點亮LED 還是熄滅LED。由于是二選一的操作,因此該參數的類型使用布爾類型:am_bool_t。當值為真(AM_TRUE)時,則點亮LED;當值為假(AM_FALSE)時,則熄滅LED。基于此,包含參數的am_led_set 接口函數原型為(還未定義返回值):
對于am_led_on、am_led_off 和am_led_toggle 接口來說,它們的職責單一,僅僅需要指定控制的LED,即可完成點亮、熄滅或翻轉操作,無需額外的其它參數。因此對于這類接口,參數僅僅只需要led_id。其函數原型如下:
實際上,在AMetal 通用接口的第一個參數中,除使用ID 號表示操作的具體對象外,還可能直接使用指向具體對象的指針,或者表示具體對象的一個句柄來表示,它們的作用在本質上是完全一樣的。
3. 返回值
對于用戶來說,調用通用接口后,應該可以獲取本次執行的結果,成功還是失敗,或一些其它的有用信息。比如,當調用接口時,如果指定的led_id 超過有效范圍時,由于沒有與led_id 對應的LED 設備,操作必定會失敗,此時必須返回錯誤,告知用戶操作失敗,且失敗的原因是led_id 不在有效范圍內,無與之對應的LED 設備。
在AMetal 中,通過返回值返回接口執行的結果,其類型為int,返回值的含義為:若返回值為AM_OK,則表示操作成功;若返回值為負數,則表示操作失敗,失敗原因可根據返回值,查找am_errno.h 文件中定義的宏,根據宏的含義確定失敗的原因;若返回值為正數,其含義與具體接口相關,由具體接口定義,無特殊說明時,表明不會返回正數。
AM_OK 在am_common.h 文件中定義,其定義如下:
錯誤號在am_errno.h 文件中定義,幾個常見錯誤號的定義詳見表8.1。比如,在調用LED 通用接口時,若led_id 不在有效范圍內,則該led_id沒有對應的LED 設備,此時接口應該返回-AM_ENODEV。注意:M_ENODEV 的前面有一個負號,以返回負值。
表8.1 常見錯誤號定義(am_errno.h)
基于此,將所有LED 控制接口的返回值定義為int,LED 控制接口的完整定義詳見表8.2,其對應的類圖詳見圖8.1。
表8.2 LED 通用接口(am_led.h)
圖8.1 LED 對應的類圖
>>> 8.1.2 實現接口
當完成接口定義后,還需要提供相應的驅動實現這些接口,才能使用這些接口操作LED。
1. 實現接口初探
LED 有4 個通用接口函數,其中的am_led_on()和am_led_off()接口是基于am_led_set()接口實現的,詳見程序清單8.1。
程序清單8.1 am_led_on()和am_led_off()接口的實現
實現接口的核心是實現am_led_set()和am_led_toggle()接口,通用接口在于屏蔽底層的差異性,即無論底層硬件如何變化,用戶都可以調用通用接口操作LED。但對于不同的硬件電路,比如,GPIO 和HC595 控制LED 的硬件電路,設置LED 狀態和LED 翻轉的具體實現是不同的。下面以設置LED 狀態的具體實現為例進行詳細說明。
對于GPIO 控制LED 的硬件電路來說,當使用GPIO 控制AM824-Core 的兩個板載LED時,LED0 通過J9 與MCU 的PIO0_20 相連,LED1 通過J10 與MCU 的PIO0_21 相連。使用短路冒將J9 和J10 短路后即可使用PIO0_20 和PIO0_21 控制LED0 和LED1,當引腳輸出低電平時,則點亮LED;當引腳輸出高電平時,則熄滅LED,直接使用GPIO 通用接口實現am_led_set()接口詳見程序清單8.2。
程序清單8.2 am_led_set()的實現(GPIO 控制LED)
對于HC595 控制LED 的硬件電路來說,當將MiniPort-595 和MiniPort-LED 聯合使用時,通過控制HC595 的輸出,可以達到控制LED 點亮和熄滅的效果,當相應引腳輸出低電平時,則點亮LED;當相應輸出高電平時,則熄滅LED,直接使用HC595 通用接口實現am_led_set()接口詳見程序清單8.3。
程序清單8.3 am_led_set()的實現(HC595 控制LED)
在實際的應用中,__g_hc595_handle 需要賦值后才能使用。通過程序清單8.2 和程序清單8.3 比較發現,它們設置LED 狀態的實現是完全不同的。顯然,在同一個應用中,一個接口的實現代碼只能有一份,因此程序清單8.2 和程序清單8.3 所示的實現是不能在一個應用程序中共存的。在這種情況下,要么選擇使用GPIO 控制LED,要么使用HC595 控制LED。
2. 抽象的LED 設備類
在使用不同方式控制LED 時,雖然它們對應am_led_set()和am_led_toggle()的實現方法不同,但它們要實現的功能卻是一樣的,這是它們的共性:均要實現設置LED 狀態和翻轉LED 狀態的功能。由于一個接口的實現代碼只能有一份,因此它們的實現不能直接作為通用接口的實現代碼。為此,可以對它們的共性進行抽象,即抽象為如下兩個方法:
相對通用接口來說,抽象方法多了一個p_cookie 參數。在面向對象的編程中,對象中的方法都能通過隱形指針p_this 訪問對象自身,引用自身的一些私有數據。而在C 語言中則需要顯式的聲明,這里的p_cookie 就有相同的作用。
為了節省內存空間,將所有抽象方法放在一個結構體中,形成一個虛函數表,比如:
這里定義了一個虛函數表,包含了兩個方法,分別用于設置LED 的狀態和翻轉LED。針對不同的硬件設備,都可以根據自身特性實現這兩個方法。GPIO 控制LED 的偽代碼詳見程序清單8.4,HC595 控制LED 的偽代碼詳見程序清單8.5。
程序清單8.4 抽象方法的實現(GPIO 控制LED)
程序清單8.5 抽象方法的實現(HC595 控制LED)
顯然,__g_led_gpio_drv_funcs 和__g_led_hc595_drv_funcs 分別是使用GPIO 和HC595控制LED 的一種具體實現,它們在形式上是兩個不同的結構體常量,在同一系統中是可以共存的。當有了針對不同硬件的驅動后,在am_led_set()接口的實現中,就需要根據實際情況找到對應的驅動,然后調用其中實現的pfn_led_set 方法。在調用pfn_led_set()方法時,由于該方法的第一個參數為p_cookie,p_cookie 代表了具體的對象,實際上驅動函數和p_cookie一起唯一地確定了一個具體的LED 設備。基于此可以將驅動函數和p_cookie 定義在一起,形成一個新的LED 設備類型。即:
其中,p_funcs 為指向驅動虛函數表的指針,比如,指向__g_led_gpio_drv_funcs 或__g_led_hc595_drv_funcs,p_cookie 為指向設備的指針,即傳遞給驅動函數的第一個參數。
此時,在am_led_set()接口的實現中,無需完成真實的設置LED 狀態的操作,僅需調用設備中的pfn_led_set 方法即可,其范例程序詳見程序清單8.6。
程序清單8.6 am_led_set()實現(1)
假定LED 設備為全局變量__gp_led_dev 指向的設備,展示了pfn_led_set 方法的調用形式。而實際上LED 設備往往不止一個,比如,用GPIO 控制LED 的設備和使用HC595 控制LED 的設備,就需要在系統中管理多個LED 設備。由于它們的具體數目無法確定,因此需要使用單向鏈表進行動態管理。在am_led_dev_t 中增加一個p_next 成員,用于指向下一個設備。即:
此時,系統中的多個LED 設備使用鏈表的形式管理。那么在通用接口的實現中,如何確定該使用哪個LED 設備呢?在定義通用接口時,使用了led_id 區分不同LED,若將一個LED 設備和該設備對應的led_id 綁定在一起,則在通用接口的實現中,就可以根據led_id找到對應的LED 設備,然后使用驅動中提供的相應方法完成LED 的操作。
顯然,一個LED 設備可能包含多個LED,在AM824-Core 中,GPIO 控制了2 個LED,HC595 控制了8 個LED。如果兩個設備同時使用,則整個系統中共有10 個LED,編號為0~9。一般來說,一個設備中的所有LED 編號是連續的,比如,兩個LED 設備的編號分別為 0~1,2~9。如需獲得一個LED 設備中所有LED 的編號,僅需知道LED 的起始編號和結束編號即可,為此定義LED 設備對應的led_id 信息為:
在設備中新增指向LED 信息的p_info 指針,便于在通用接口實現中根據led_id 查找到對應的LED 設備,即:
基于此,am_led_set()函數的實現詳見程序清單8.7。
程序清單8.7 am_led_set()實現(2)
其中,__led_dev_find_with_id()的作用就是遍歷設備鏈表,與各個設備中的ID 信息一一比對,以找到led_id 對應的LED 設備,其實現詳見程序清單8.8。
程序清單8.8 查找指定led_id 的LED 設備
其中,__gp_head 是一個全局變量,初始為NULL,表示初始時系統中無任何LED 設備。同理,可得到am_led_toggle()接口的實現,詳見程序清單8.9。
程序清單8.9 am_led_toggle()實現
至此,實現了所有通用接口。由于當前沒有任何LED 設備,因此__led_dev_find_with_id()為NULL,通用接口的返回值也始終為-AM_ENODEV。
為了使通用接口能夠操作到具體有效的LED,就必須向系統中添加一個有效的LED 設備。根據LED 設備類型的定義,添加一個設備時,需要完成p_funcs、p_cookie 和p_info 的正確賦值,這些成員的賦值需要具體的LED 設備對象來完成,如GPIO 控制LED 的設備。為此,可以為驅動提供一個添加LED 設備的接口。比如:
其中,為了方便驅動直接添加一個設備,避免直接操作LED 設備的各個成員,將需要賦值的成員通過參數傳遞給接口函數,其實現詳見程序清單8.10。
程序清單8.10 向系統中添加LED 設備
該程序首先通過判斷新設備的起始LED 編號和結束LED 編號是否已經存在于系統之中來判斷ID 是否是有效范圍,確保添加的各個LED 設備的ID 不沖突,即保證了LED 編號的唯一性,然后將設備中的各個成員賦值,最后通過程序清單8.10的21~22 行共計2 行代碼將新設備添加到鏈表首部。
顯然,接下來需要在具體的LED 設備驅動實現中,使用am_led_dev_add()接口向系統中添加設備,使得用戶可以使用LED 通用接口操作到具體有效的LED。
在上述分析的過程中,定義了LED 設備類,在其中完成了LED 通用接口的實現,可以用類圖來表示這個關系,詳見圖8.2。
圖8.2 抽象的LED 設備類
LED 設備中存在抽象方法pfn_led_set 和pfn_led_toggle,這兩個抽象方法是以虛函數表的形式存在LED 設備類中的。由于存在抽象方法,因此LED 設備類是一個抽象類,它本身不能夠直接實例化,必須由其派生的具體類實現這兩個抽象方法。為了便于查閱,如程序清單8.11 所示展示了LED 設備的接口文件am_led_dev.h 的內容。
程序清單8.11 am_led_dev.h 文件內容
3. 具體的LED 設備類
前面定義的抽象LED 設備類中包含了兩個抽象方法:pfn_led_set 和pfn_led_toggle。為了使用戶可以通過LED 通用接口操作LED,就必須根據實際硬件連接,實現兩個抽象方法,然后將具體設備添加到系統設備鏈表中。
下面分別以GPIO 控制LED 的驅動實現和HC595 控制LED 的驅動實現為例,闡述LED設備驅動開發的一般方法,如果后續有其它類型的LED 控制電路,可以按照此方法添加自定義的LED 驅動。
(1) GPIO 控制LED 的驅動實現
具體LED 設備的核心功能是實現抽象設備類中定義的方法,首先應該基于抽象設備類派生一個具體的設備類,其類圖詳見圖8.3。
圖8.3 具體設備類(GPIO)
可直接定義具體的LED 設備類。比如:
am_led_gpio_dev_t 即為具體的LED 設備類。具有該類型后,即可使用該類型定義一個具體的LED 設備實例:
在使用GPIO 控制LED 時,需要知道對應的引腳信息和LED 點亮的電平信息,為了便于修改配置,這些信息往往由用戶傳遞給驅動。此外,還需要提供LED 設備的ID 信息,包含起始ID 和結束ID,以確定的為設備中的每個LED 分配一個唯一ID。基于此,可以將需要由用戶提供的設備相關信息存放到一個新的結構體類型中,將其作為需要由用戶提供的設備信息。即:
對于AM824_Core 的兩個板載LED 來說,若編號為0~1,則可以使用該類型定義其對應的設備實例信息如下
為了便于通過設備直接找到對應的設備信息,在設備類中往往直接維持一個指向設備信息的指針。即:
顯然,在使用GPIO 控制LED 前,引腳需要初始化為輸出模式,此外,在完成初始化后,還需要將具體的LED 設備添加到系統中,便于使用通用接口操作LED。這些工作通常在驅動的初始化函數中完成,初始化函數的原型為:
其中,p_dev 指向am_led_gpio_dev_t 類型的設備,p_info 為指向am_led_gpio_info_t 類型實例信息的指針,其調用形式如下:
初始化函數的的實現詳見程序清單8.12。
程序清單8.12 初始化函數實現(GPIO 控制LED)
程序中,首先通過LED 的起始編號和結束編號,得到了LED 的數目,由于GPIO 的引腳數目與LED 數目相等,因此,也就得到了GPIO 引腳的數目。然后將所有引腳配置為了輸出模式,并根據是否為低電平點亮,初始時使所有LED 處于熄滅狀態。最后,通過am_led_dev_add()函數,將具體的LED 設備添加到了系統之中。
在添加LED 設備時,LED 的 ID 信息直接使用了設備信息中的ID 信息,抽象方法的實現使用了__g_led_gpio_drv_funcs 中實現的方法(其定義詳見程序清單8.4),p_cookie 直接設置為了指向設備自身的指針,正因為如此,在抽象方法的實現中,p_cookie 參數即為指向設備自身的指針,可以通過p_cookie 得到具體設備相關的信息,如GPIO 信息等,進而實現LED 的相關操作,完善程序清單8.4 中實現的抽象方法,詳見程序清單8.13。
程序清單8.13 抽象方法的實現(GPIO 控制LED)
在抽象方法的實現中,首先通過類型強制轉換將p_cookie 轉換為指向具體設備的指針。然后通過它找到相應的引腳信息,進而實現LED 的相關操作。在設置LED 狀態的實現中,巧妙的使用了“異或(^)”預算。因為active_low 的值與實際的點亮電平是恰好相反的,即若active_low 為AM_TRUE,表明輸出低電平點亮,反之,輸出高電平點亮。state 及active_low 的值都將影響本次GPIO 的輸出電平,GPIO 的輸出電平與state 和active_low 的真值表詳見表8.3。由此可見,當state 與active_low 相同時,則GPIO 輸出0;當state 與active_low 不同時,則GPIO 輸出1,恰好是異或關系。
表8.3 GPIO 輸出增值表
為了便于查閱,如程序清單8.14 所示展示了LED 設備接口文件am_led_gpio.h 的內容。
程序清單8.14 am_led_gpio.h 文件內容
(2) HC595 控制LED 的驅動實現
同樣,首先基于抽象設備類派生一個具體的設備類,其類圖詳見圖8.4,可直接定義具體的LED 設備類:
am_led_hc595_dev_t 為具體的LED 設備類,當具有該類型后,即可使用該類型定義一個具體的LED 設備實例:
圖8.4 具體設備類(HC595)
在使用HC595 控制LED 時,需要知道LED 和HC595相關的信息,如LED 點亮的電平信息和HC595 的數目。雖然MiniPort-595 只有一個HC595,但作為一個通用的驅動,應考慮到這些基礎的擴展,以便驅動可以盡可能的支持更多的硬件電路。
特別地,HC595 的每次輸出都是完整的輸出,如對于單個HC595,其每次輸出都只能輸出完整的8 位數據,不能單獨輸出1 位數據,而LED 的控制又是對單個LED 進行的,因此,為了在控制一個LED 時,不影響到其它LED,必須使其他位的輸出保持不變,這就需要實時保存當前的輸出,為了保存當前所有HC595 的輸出信息,需要用戶提供一個緩沖區,緩沖區的大小與HC595 的個數相等。
此外,還需要提供包含起始ID 和結束ID 的ID 信息。基于此,可以將需要由用戶提供的設備相關信息存放到一個新的結構體類型中,將其作為需要由用戶提供的設備信息:
對于使用MiniPort-595 和MiniPort-LED 聯合使用的情況,共計8 個LED,若分配的編號為2~9,則可以使用該類型定義其對應的設備實例信息如下:
同理,在設備類中需要維持一個指向設備信息的指針。此外,由于使用HC595 驅動LED時,需要使用HC595 的句柄handle 來傳輸數據,因此,用戶還需要提供一個595 的句柄。handle 需要保存到設備中:
注意,由于句柄往往需要通過動態的調用實例初始化函數獲得,比如,HC595 的句柄可通過如下語句獲得:
而設備信息往往在系統啟動后不會改變,可以定義為常量,因此,handle 往往由用戶單獨提供,不存放在設備信息中。
顯然,在使用GPIO 控制LED 前,需要完成設備中各成員的賦值,并熄滅所有LED,此外,在初始化完成后,還需要將具體的LED 設備添加到系統中。這些工作通常在驅動的初始化函數中完成,初始化函數的原型為:
其中, p_dev 為指向am_led_hc595_dev_t 類型實例的指針, p_info 為指向am_led_hc595_info_t 類型實例信息的指針,其調用形式如下:
初始化函數的的實現詳見程序清單8.15。
程序清單8.15 初始化函數實現(HC595 控制LED)
首先將緩存中的值設置為使所有LED 熄滅的值,然后使用am_hc595_send()將緩存中的值輸出,使所有LED 處于熄滅狀態。最后,通過am_led_dev_add()函數,將具體的LED 設備添加到了系統之中。
在添加LED 設備時,LED 的 ID 信息直接使用了設備信息中的ID 信息,抽象方法的實現使用了__g_led_hc595_drv_funcs 中實現的方法(其定義詳見程序清單8.5),p_cookie 直接設置為了指向設備自身的指針,正因為如此,在抽象方法的實現中,p_cookie 參數即為指向設備自身的指針,可以通過p_cookie 得到具體設備相關的信息,如HC595 句柄,HC595 緩存等,進而實現LED 的相關操作,完善程序清單8.5 中實現的抽象方法詳見程序清單8.16。
程序清單8.16 抽象方法的實現(HC595 控制LED)
在抽象方法的實現中,首先通過類型強制轉換將p_cookie 轉換為指向具體設備的指針。然后通過它找到相關的信息,進而實現LED 的相關操作。為了便于查閱,如程序清單8.17所示展示了LED 設備接口文件am_led_hc595.h 的內容。
程序清單8.17 am_led_hc595.h 文件內容
-
led
+關注
關注
242文章
23252瀏覽量
660574 -
單片機設計
+關注
關注
5文章
612瀏覽量
21160 -
ametal_led
+關注
關注
0文章
1瀏覽量
1695
原文標題:周立功:深入理解AMetal——LED 通用接口
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠電子】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論