本文轉(zhuǎn)自公眾號(hào),歡迎關(guān)注
基于DWC2的USB驅(qū)動(dòng)開發(fā)-0x0C 驅(qū)動(dòng)框架設(shè)計(jì) (qq.com)
一. 驅(qū)動(dòng)架構(gòu)
1.1 前言
前面我們介紹了DWC2的控制器相關(guān)的寄存器,驅(qū)動(dòng)的編寫本質(zhì)上就是進(jìn)行寄存器的配置。為了代碼的健壯性,可移植性,可調(diào)試性等我們必須設(shè)計(jì)一個(gè)比較好的架構(gòu)。對(duì)于驅(qū)動(dòng)編程來(lái)說(shuō)輸入就是各個(gè)寄存器,如果使用的都是DWC2的話甚至來(lái)說(shuō)寄存器都是一樣的輸入就只需要寄存器基地址,即如果都是使用該IP的驅(qū)動(dòng)甚至只要修改寄存器基地址,然后移植中斷等一些和SOC相關(guān)的內(nèi)容即可復(fù)用。
1.2 架構(gòu)框圖
整體的設(shè)計(jì)思想如下:
借鑒面向?qū)ο蟮脑O(shè)計(jì)的依賴倒置原則:
面向接口編程,不依賴具體實(shí)現(xiàn)編程,應(yīng)用層不直接依賴具體實(shí)現(xiàn)而是依賴接口,上層調(diào)用接口底層實(shí)現(xiàn)接口,接口是硬件隔離的橋梁.
接口即對(duì)應(yīng)上圖的HAL層。
硬件: 對(duì)于使用DWC2控制器的則還可以復(fù)用HW層本身的內(nèi)容,只需要修改寄存器的基地址即可,因?yàn)镈WC2控制器寄存器都是一樣的。另外需要修改一些和SOC相關(guān)的內(nèi)容,比如中斷的設(shè)置。對(duì)于不同的控制器和SOC平臺(tái)的則需要根據(jù)具體的平臺(tái)進(jìn)行封裝。理論上也可以直接依賴硬件實(shí)現(xiàn)HAL層,而不需要HW層的封裝,但是這樣的話直接在HAL層操作寄存器代碼的閱讀性上不佳。
HW層:即對(duì)頭文件中寄存器的操作進(jìn)行封裝,主要采用宏的方式封裝,目的是為了可閱讀性。同時(shí)對(duì)于一些聯(lián)系緊密的操作放在一起進(jìn)行封裝,比如初始化等,提供比如結(jié)構(gòu)體參數(shù)等,進(jìn)行相應(yīng)的參數(shù)配置。這一層對(duì)于使用同樣的DWC2控制器的也完全可復(fù)用。
HAL層:這是硬件和上層隔離的一層,定義了一系列接口,協(xié)議棧調(diào)用該接口進(jìn)行硬件操作,而HAL調(diào)用HW層實(shí)現(xiàn)上述的接口。對(duì)于不使用DWC2控制器的,則需要重新實(shí)現(xiàn)HAL層,而同使用DWC2控制器的則本層也通用。本層的設(shè)計(jì)原則是接口個(gè)數(shù)盡可能少和簡(jiǎn)單但是要足夠滿足協(xié)議棧層的需要。符合面向?qū)ο笤O(shè)計(jì)的最少知道原則。
協(xié)議棧層:依賴HAL接口,實(shí)現(xiàn)USB規(guī)格書規(guī)定的協(xié)議。
協(xié)議棧和HAL層以及以下的層次,盡可能不進(jìn)行資源分配(比如內(nèi)存)等預(yù)留相應(yīng)的接口,資源分配由應(yīng)用層實(shí)現(xiàn),這樣設(shè)計(jì)的目的是在嵌入式開發(fā)環(huán)境資源往往受限,資源的分配往往需要應(yīng)用層來(lái)把控,應(yīng)用層來(lái)負(fù)責(zé)。底層盡可能少的依賴資源,盡可能保持確定性,避免過(guò)多動(dòng)態(tài)行為。
應(yīng)用層:調(diào)用協(xié)議棧實(shí)現(xiàn)具體的應(yīng)用功能。
考慮到通用性和可移植性,我們要支持OS和無(wú)OS的實(shí)現(xiàn),所以非必要不依賴OS相關(guān)的API,如果不可避免則盡可能少依賴,且實(shí)現(xiàn)需要依賴的API的OS和NONE-OS版本,將依賴抽離出來(lái)作為需要移植實(shí)現(xiàn)的部分,使得在不同OS和裸機(jī)下都能運(yùn)行,這里會(huì)涉及到協(xié)議?;谥袛囹?qū)動(dòng)的處理方式和基于事件驅(qū)動(dòng)的處理方式兩種實(shí)現(xiàn),后面到協(xié)議棧設(shè)計(jì)部分再說(shuō)。
1.3 調(diào)試方案
調(diào)試手段是驅(qū)動(dòng)開發(fā)中需要重點(diǎn)考慮的一環(huán),沒(méi)有合適的調(diào)試手段,遇到問(wèn)題,會(huì)兩眼一抹黑完全無(wú)從下手。進(jìn)行USB開發(fā),硬件上示波器和協(xié)議分析儀是必須的。另外軟件上也需要一些手段配合調(diào)試。以下是幾種常見的方式。
1.3.1 仿真器
這是嵌入式開發(fā)必須的,尤其是驅(qū)動(dòng)開發(fā)階段,需要跟蹤代碼流,寄存器的配置,變量的值等等。仿真器調(diào)試有個(gè)問(wèn)題就是需要中斷正常的程序流.USB是一個(gè)有著嚴(yán)格時(shí)序要求,且高速的協(xié)議,程序中斷會(huì)導(dǎo)致USB的處理過(guò)程由于超時(shí)等導(dǎo)致異常,所以很多時(shí)候不能通過(guò)仿真器打斷點(diǎn)等方式進(jìn)行調(diào)試。
1.3.2 IO
USB由于其嚴(yán)格的時(shí)序要求,且高速的處理,很多時(shí)候我們需要測(cè)試相應(yīng)的處理時(shí)間,使用定時(shí)器進(jìn)行計(jì)時(shí)并打印時(shí)間是一種方法,但是對(duì)于高精度計(jì)時(shí)定時(shí)器的處理代碼本身需要時(shí)間會(huì)帶來(lái)誤差。這時(shí)使用IO翻轉(zhuǎn),來(lái)指示某種狀態(tài)的變化非常有用,比如測(cè)試SOF之間的時(shí)間,可以在SOF中斷中翻轉(zhuǎn)IO,使用示波器或者邏輯分析儀測(cè)量波形即可。翻轉(zhuǎn)IO往往一條指令即可,這使得代碼帶來(lái)的誤差減小。
更重要的是可以通過(guò)多個(gè)IO關(guān)聯(lián)多個(gè)事件,看到多個(gè)事件之間的關(guān)聯(lián)關(guān)系,尤其是使用示波器和邏輯分析儀查看時(shí)。曲線會(huì)非常直觀。這是使用定時(shí)器打印所不具備的。
IO翻轉(zhuǎn)的調(diào)試方法是嵌入式實(shí)時(shí)分析中重要的手段。
1.3.3 串口打印
USB的處理流程對(duì)應(yīng)著狀態(tài)的流轉(zhuǎn),實(shí)際對(duì)于軟件來(lái)說(shuō)過(guò)是各種中斷的進(jìn)入與退出。使用仿真器跟蹤會(huì)破壞USB的處理流程導(dǎo)致超時(shí)等,所以需要一個(gè)非中斷方式的跟蹤事件的方式。常見的方式是使用串口打印,這里要注意串口打印不能使用阻塞方式,否則同樣的會(huì)導(dǎo)致USB處理流的異常。一般采用緩沖區(qū)的方式,打印接口將數(shù)據(jù)寫入緩沖區(qū),主循環(huán)或者其他線程中將緩沖區(qū)的數(shù)據(jù)通過(guò)串口發(fā)送。
當(dāng)然除了串口打印還有很多其他類似的方式比如 Jlink提供的rtt等性能更高,也可以使用網(wǎng)口等接口,根據(jù)具體平臺(tái)而定。
串口打印可是設(shè)置為宏的方式,debug版本使能,release版本可不使能,打印輸出也可以設(shè)置等級(jí)和類別,這樣通過(guò)等級(jí)和類別控制輸出,避免一次打印過(guò)多干擾分析,也可以使用CLI動(dòng)態(tài)配置等級(jí)可列別的控制。
如下是一個(gè)簡(jiǎn)單的示例,依賴printf,假設(shè)printf已經(jīng)實(shí)現(xiàn)是一個(gè)非阻塞的版本(即寫入緩沖區(qū)),其他地方再真正的發(fā)送數(shù)據(jù)。可以通過(guò)宏配置不同等級(jí)的LOG,且打印信息對(duì)應(yīng)不同的顏色。
頭文件中
#define USB_HAL_DEBUG 1
#define USB_HAL_LOG_LEVEL_ERROR 1
#define USB_HAL_LOG_LEVEL_WARN 1
#define USB_HAL_LOG_LEVEL_INFO 1
/** Debug levels */
typedef enum
{
USB_HAL_DEBUG_ERROR = 0,
USB_HAL_DEBUG_WARN = 1,
USB_HAL_DEBUG_INFO = 2,
}USB_HAL_DEBUG_e;
#define USB_HAL_DEBUG_COLOR_MASK_COLOR 0x0F
#define USB_HAL_DEBUG_COLOR_MASK_MODIFIER 0xF0
typedef enum {
/* Colors */
USB_HAL_DEBUG_COLOR_RESET = 0xF0,
USB_HAL_DEBUG_COLOR_BLACK = 0x01,
USB_HAL_DEBUG_COLOR_RED = 0x02,
USB_HAL_DEBUG_COLOR_GREEN = 0x03,
USB_HAL_DEBUG_COLOR_YELLOW = 0x04,
USB_HAL_DEBUG_COLOR_BLUE = 0x05,
USB_HAL_DEBUG_COLOR_MAGENTA = 0x06,
USB_HAL_DEBUG_COLOR_CYAN = 0x07,
USB_HAL_DEBUG_COLOR_WHITE = 0x08,
/* Modifiers */
USB_HAL_DEBUG_COLOR_NORMAL = 0x0F,
USB_HAL_DEBUG_COLOR_BOLD = 0x10,
USB_HAL_DEBUG_COLOR_UNDERLINE = 0x20,
USB_HAL_DEBUG_COLOR_BLINK = 0x30,
USB_HAL_DEBUG_COLOR_HIDE = 0x40,
} USB_HAL_DEBUG_COLOR_e;
void usb_hal_do_debug(USB_HAL_DEBUG_e level, const char *format, ...);
#ifdef USB_HAL_DEBUG
#define usb_hal_debug(level, format, ...) do { usb_hal_do_debug(level, format, ##__VA_ARGS__); } while(0)
#else
#define usb_hal_debug(...) do {} while (0)
#endif
#ifdef USB_HAL_LOG_LEVEL_ERROR
#define usb_hal_error(format, ...) usb_hal_debug(USB_HAL_DEBUG_ERROR, format, ##__VA_ARGS__)
#else
#define usb_hal_error(...) do {} while (0)
#endif
#ifdef USB_HAL_LOG_LEVEL_WARN
#define usb_hal_warn(format, ...) usb_hal_debug(USB_HAL_DEBUG_WARN, format, ##__VA_ARGS__)
#else
#define usb_hal_warn(...) do {} while (0)
#endif
#ifdef USB_HAL_LOG_LEVEL_INFO
#define usb_hal_info(format, ...) usb_hal_debug(USB_HAL_DEBUG_INFO, format, ##__VA_ARGS__)
#else
#define usb_hal_info(...) do {} while (0)
#endif