RT-Thread的I/O裝置模組及其驅動實現步驟
阿新 • • 發佈:2019-01-09
一、I/O裝置控制塊 1、I/O裝置控制塊 struct rt_device { struct rt_object parent; /* 裝置型別 */ enum rt_device_class_type type; /* 裝置引數及開啟引數 */ rt_uint16_t flag, open_flag; /* 提供給上層應用的回撥函式 */ rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); rt_err_t (*tx_complete)(rt_device_t dev, void* buffer); /* 公共的裝置介面(由驅動程式提供) */ rt_err_t (*init) (rt_device_t dev); rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); rt_err_t (*close)(rt_device_t dev); rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); rt_err_t (*control)(rt_device_t dev, rt_uint8_t cmd, void *args); /* 用於支援電源管理的函式介面 */ #ifdef RT_USING_DEVICE_SUSPEND rt_err_t (*suspend) (rt_device_t dev); rt_err_t (*resumed) (rt_device_t dev); #endif /* 裝置的私有資料 */ void* user_data; }; typedef struct rt_device* rt_device_t; 當前RT-Thread支援的裝置型別包括: enum rt_device_class_type { RT_Device_Class_Char = 0, /* 字元裝置 */ RT_Device_Class_Block, /* 塊裝置 */ RT_Device_Class_NetIf, /* 網路介面 */ RT_Device_Class_MTD, /* 記憶體裝置 */ RT_Device_Class_CAN, /* CAN裝置 */ RT_Device_Class_RTC, /* RTC裝置 */ RT_Device_Class_Sound, /* 聲音裝置 */ RT_Device_Class_Display, /* 顯示裝置 */ RT_Device_Class_Unknown /* 未知裝置 */ }; 注:uspend、resume回撥函式只會在RT_USING_DEVICE_SUSPEND巨集使能的情況下才 會有效。 從裝置控制塊,我們可以看到,每個裝置物件都會在核心中維護一個裝置控制塊結構, 這種結構是使裝置物件繼承rt_object基類,然後形成rt_device裝置型別。 2、註冊裝置 一個裝置能夠被上層應用訪問前,需要先把這個設備註冊到系統中,並新增一些相應的 一些屬性。這些註冊的裝置均可以通過裝置名,採用“查詢裝置介面”的方式從系統中查詢 到,從而獲得該裝置控制塊(或裝置控制代碼)。註冊裝置的函式介面如下: rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags); 函式引數: dev 裝置控制代碼; name 裝置名稱; flag 裝置模式標誌: flags引數支援下列引數(可以採用或的方式支援多種引數): #define RT_DEVICE_FLAG_DEACTIVATE 0x000 /* 未初始化裝置 */ #define RT_DEVICE_FLAG_RDONLY 0x001 /* 只讀裝置 */ #define RT_DEVICE_FLAG_WRONLY 0x002 /* 只寫裝置 */ #define RT_DEVICE_FLAG_RDWR 0x003 /* 讀寫裝置 */ #define RT_DEVICE_FLAG_REMOVABLE 0x004 /* 可移除裝置 */ #define RT_DEVICE_FLAG_STANDALONE 0x008 /* 獨立裝置 */ #define RT_DEVICE_FLAG_ACTIVATED 0x010 /* 已啟用裝置 */ #define RT_DEVICE_FLAG_SUSPENDED 0x020 /* 掛起裝置 */ #define RT_DEVICE_FLAG_STREAM 0x040 /* 裝置處於流模式 */ #define RT_DEVICE_FLAG_INT_RX 0x100 /* 裝置處於中斷接收模式*/ #define RT_DEVICE_FLAG_DMA_RX 0x200 /* 裝置處於DMA接收模式 */ #define RT_DEVICE_FLAG_INT_TX 0x400 /* 裝置處於中斷髮送模式*/ #define RT_DEVICE_FLAG_DMA_TX 0x800 /* 裝置處於DMA傳送模式 */ 裝置流模式RT_DEVICE_FLAG_STREAM引數用於向串列埠終端輸出字串:當輸出的字元 是“\n”時,自動在前面補一個“\r”做分行。 函式返回 返回RT_EOK 警告:應當避免重複註冊已經註冊的裝置,以及註冊相同名字的裝置。 3、移除裝置 將裝置從裝置系統中移除,被解除安裝的裝置將不能再通過“查詢裝置介面”被查詢到。卸 載裝置的函式介面如下所示: rt_err_t rt_device_unregister(rt_device_t dev) 函式引數 引數 描述 dev 裝置控制代碼; 函式返回 返回RT_EOK 注:解除安裝裝置並不會釋放裝置控制塊所佔用的記憶體 4、初始化所有裝置 初始化所有註冊到裝置物件管理器中的未初始化的裝置,可以通過如下函式介面完成: rt_err_t rt_device_init_all(void) 函式引數 無 函式返回 返回RT_EO • 注:此函式將逐漸廢棄,不推薦在應用程式中呼叫。當一個裝置初始化完成後它 的flags域中的RT_DEVICE_FLAG_ACTIVATED應該被置位。如果裝置的flags域已經是 RT_DEVICE_FLAG_ACTIVATED,呼叫這個介面將不再重複做初始化。 5、查詢裝置 根據指定的裝置名稱查詢裝置,可以通過如下介面完成: rt_device_t rt_device_find(const char* name) 使用這個函式介面時,系統會在裝置物件型別所對應的物件容器中遍歷尋找裝置物件, 然後返回該裝置的控制代碼,如果沒有找到相應的裝置物件,則返回RT_NULL。 函式引數 引數 描述 name 裝置名稱。 函式返回 查詢到對應裝置將返回相應的裝置控制代碼;否則返回RT_NULL。 6、開啟裝置 根據裝置控制塊來開啟裝置,可以通過如下函式介面完成: rt_err_t rt_device_open (rt_device_t dev, rt_uint16_t oflags); 函式引數 引數 描述 dev 裝置控制代碼; oflags 訪問模式。 其中oflags支援以下列表中的引數: #define RT_DEVICE_OFLAG_CLOSE 0x000 /* 裝置已經關閉(內部使用) */ #define RT_DEVICE_OFLAG_RDONLY 0x001 /* 以只讀方式開啟裝置 */ #define RT_DEVICE_OFLAG_WRONLY 0x002 /* 以只寫方式開啟裝置 */ #define RT_DEVICE_OFLAG_RDWR 0x003 /* 以讀寫方式開啟裝置 */ #define RT_DEVICE_OFLAG_OPEN 0x008 /* 裝置已經開啟(內部使用) */ 函式返回 返回驅動的open函式返回值 注:如果設備註冊時指定的引數中包括RT_DEVICE_FLAG_STANDALONE引數,此裝置將 不允許重複開啟,返回-RT_EBUSY。 7、關閉裝置 根據裝置控制塊來關閉裝置,可以通過如下函式介面完成: rt_err_t rt_device_close(rt_device_t dev) 函式引數 引數 描述 dev 裝置控制代碼; 函式返回 返回驅動的close函式返回值 8、讀裝置 從裝置中讀取,或獲得資料,可以通過如下函式介面完成: rt_size_t rt_device_read (rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size) 呼叫這個函式,會從裝置dev中獲得資料,並存放在buffer緩衝區中。這個緩衝區的最 大長度是size。pos根據不同的裝置類別存在不同的意義。 函式引數 引數 描述 dev 裝置控制代碼; pos 讀取資料偏移量; buffer 記憶體緩衝區指標,讀取的資料將會被儲存在緩衝區中; size 讀取資料的大小。 函式返回 返回讀到資料的實際大小(如果是字元裝置,返回大小以位元組為單位;如果是塊裝置, 返回的大小以塊為單位);如果返回0,則需要讀取當前執行緒的errno來判斷錯誤狀態。 9、寫裝置 向裝置中寫入資料,可以通過如下函式介面完成: rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size) 呼叫這個函式,會把緩衝區buffer中的資料寫入到裝置dev中。寫入資料的最大長度是 size。pos根據不同的裝置類別存在不同的意義。 函式引數 引數 描述 dev 裝置控制代碼; pos 讀取資料偏移量; buffer 記憶體緩衝區指標,放置要寫入的資料; size 寫入資料的大小。 函式返回 返回寫入資料的實際大小(如果是字元裝置,返回大小以位元組為單位;如果是塊裝置, 返回的大小以塊為單位);如果返回0,則需要讀取當前執行緒的errno來判斷錯誤狀態 • 注:在RT-Thread的塊裝置中,從1.0.0版本開始, rt_device_read()/rt_device_write()接 口的pos、size引數按照以塊為單位。0.3.x以前的版本則按位元組為單位。 10、控制裝置 根據裝置控制塊來控制裝置,可以通過下面的函式介面完成: rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); 函式引數 引數 描述 dev 裝置控制代碼; cmd 命令控制字,這個引數通常與裝置驅動程式相關; arg 控制的引數 函式返回 返回驅動控制介面的返回值。 11、設定資料接收指示 設定一個回撥函式,當硬體裝置收到資料時回撥以通知用程式有資料到達。可以通過如 下函式介面完成設定接收指示: rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind ); (rt_device_t dev,rt_size_t size)); 在呼叫這個函式時,回撥函式rx_ind由呼叫者提供。當硬體裝置接收到資料時,會回撥 這個函式並把收到的資料長度放在size引數中傳遞給上層應用。上層應用執行緒應在收到指示 後,立刻從裝置中讀取資料。 函式引數 引數 描述 dev 裝置控制代碼; rx_ind 接收回調函式。 函式返回 返回RT_EOK 12、設定傳送完成指示 在上層應用呼叫rt_device_write寫入資料時,如果底層硬體能夠支援自動傳送,那麼上層應用可以設定一個回撥函式。這個回撥函式會在底層硬體給出的傳送完成後(例如DMA傳送完成或FIFO已經寫入完畢產生完成中斷時)被呼叫。可以通過如下函式介面設定裝置傳送完成指示: rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer)) 呼叫這個函式時,回撥函式tx_done引數由呼叫者提供,當硬體裝置傳送完資料時,由驅動程式回撥這個函式並把傳送完成的資料塊地址buffer做為引數傳遞給上層應用。上層應用(執行緒)在收到指示時應根據傳送buffer的情況,釋放buffer記憶體塊或將其做為下一個寫資料的快取。 函式引數 引數 描述 dev 裝置控制代碼; tx_done 傳送回撥函式。 函式返回 返回RT_EOK 二、裝置驅動 裝置驅動必須實現的介面 在10.1節中提及了RT-Thread裝置介面類,我們著重看看其中包含的一套公共裝置介面 (類似上節說的裝置訪問介面,但面向的層次已經不一樣,這裡是面向底層驅動): /* 公共的裝置介面(由驅動程式提供) */ rt_err_t (*init) (rt_device_t dev); rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); rt_err_t (*close)(rt_device_t dev); rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); rt_size_t (*write)(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); rt_err_t (*control)(rt_device_t dev, rt_uint8_t cmd, void *args); /* 用於支援電源管理的函式介面 */ #ifdef RT_USING_DEVICE_SUSPEND rt_err_t (*suspend) (rt_device_t dev); rt_err_t (*resumed) (rt_device_t dev); #endif 這些介面也是上層應用通過RT-Thread裝置介面進行訪問的實際底層介面(如裝置操作介面與裝置驅動程式介面的對映 ): 即這些驅動實現的底層介面是上層應用最終訪問的落腳點,例如上層應用呼叫rt_device_read介面進行裝置讀取資料操作,上層應先呼叫rt_device_find獲得相對應的裝置控制代碼,而在呼叫rt_device_read時,就是使用這個裝置控制代碼所對應驅動的driver_read。上述的介面是一一對應關係。 I/O裝置模組提供的這六個介面(rt_device_init/open/read/write/control),對應到裝置驅動程式的六個介面(driver_init/open/read/write/control等),可以認為是底層裝置驅動必須提供的介面: 1、init 裝置的初始化。裝置初始化完成後,裝置控制塊的flag會被置 成已啟用狀態(RT_DEVICE_FLAG_ACTIVATED)。如果裝置控制塊 的flag 不是已啟用狀態,那麼在裝置框架呼叫 rt_device_init_all介面時將呼叫此裝置驅動的init介面進行 裝置初始化;如果裝置控制塊中的flag標誌已經設定成啟用狀 態,那麼再執行初始化介面時,會立刻返回,而不會重新進行 初始化。 2、open 開啟裝置。有些裝置並不是系統一啟動就已經開啟開始執行; 或者裝置需要進行資料接收,但如果上層應用還未準備好,設 備也 不應預設已經使能並開始接收資料。所以建議在寫底層驅 動程式時,應在呼叫open介面時才使能裝置。 3、close 關閉裝置。建議在開啟裝置時,裝置驅動自行維護一個開啟計數,在開啟裝置時進行+1操作,在關閉裝置時進行-1操作, 當計數 器變為0時,進行真正的關閉操作。 4、read 從裝置中讀取資料。引數pos指出讀取資料的偏移量,但是有些 裝置並不一定需要指定偏移量,例如串列埠裝置,裝置驅動應忽 略這 個引數。而對於塊裝置來說,pos以及size都是以塊裝置的 資料塊大小做為單位的。例如塊裝置的資料塊大小是512,而參 數中pos= 10, size = 2,那麼驅動應該返回裝置中第10個塊 (從第0個塊做為起始),共計2個塊的資料。這個介面返回的 型別rt_size_t,即讀到的位元組數或塊數目。正常情況下應 該會返回引數中size的數值,如果返回零請設定對應的errno值。 5、write 向裝置中寫入資料。引數pos指出寫入資料的偏移量。與讀操作 類似,對於塊裝置來說,pos以及size都是以塊裝置的資料塊 大小做 為單位的。這個介面返回的型別是rt_size_t,即真實寫 入資料的位元組數或塊數目。正常情況下應該會返回引數中size 的數值,如果返回零請設定對應的errno值。 6、control 根據不同的cmd命令控制裝置。命令往往是由底層各類裝置驅 動自定義實現。例如引數RT_DEVICE_CTRL_BLK_GETGEOME,意思 是獲取塊裝置的大小資訊。 三、裝置驅動實現的步驟 在實現一個RT-Thread裝置時,可以按照如下的步驟進行(對於一些複雜的裝置驅動,例如乙太網介面驅動、圖形裝置驅動,請參看網路元件、GUI部分章節): • 按照RT-Thread的物件模型,擴充套件一個物件有兩種方式: • 定義自己的私有資料結構,然後賦值到RT-Thread裝置控制塊的user_data指標上; • 從struct rt_device結構中進行派生。 • 實現RT-Thread I/O裝置模組中定義的6個公共裝置介面,開始可以是空函式(返回型別是rt_err_t的可預設返回RT_EOK); • 根據自己的裝置型別定義自己的私有資料域。特別是在可能有多個相類似裝置的情況下(例如串列埠1、2),裝置介面可以共用同一套介面,不同的只是各自 的資料域(例如暫存器基地址); • 根據裝置的型別,註冊到RT-Thread裝置框架中。