目錄
IO設備模型
模型框架原理
IO設備類型
創建和注冊IO設備
RTT設備管理程序實現原理
訪問IO設備
查找設備
初始化設備
打開設備
關閉設備
?控制設備
?讀寫設備
數據收發回調
數據接收回調
數據發送回調
設備模型實例
IO設備模型
????????RT-Thread 提供了一套簡單的 I/O 設備模型框架,如下圖所示,它位于硬件和應用程序之間,共分成三層,從上到下分別是 I/O 設備管理層、設備驅動框架層、設備驅動層。
- 應用程序通過 I/O 設備管理接口獲得正確的設備驅動,然后通過這個設備驅動與底層 I/O 硬件設備進行交互。
- I/O 設備管理層實現了對設備驅動程序的封裝
- 設備驅動框架層是對同類硬件設備驅動的抽象,將不同廠家的同類硬件設備驅動中相同的部分抽取出來,將不同部分留出接口,由驅動程序實現。
- 設備驅動層是一組驅使硬件設備工作的程序,實現訪問硬件設備的功能。
簡單設備的注冊不經過設備驅動框架層,直接將設備注冊到I/O設備管理器中
- 設備驅動根據設備模型定義,創建出具備硬件訪問能力的設備實例,將該設備通過rt_device_register()接口注冊到 I/O 設備管理器中
- 應用程序通過 rt_device_find()接口查找到設備,然后使用 I/O 設備管理接口來訪問硬件
模型框架原理
圖中:在左邊是應用層代碼,在右邊是設備驅動代碼,設備驅動層是與硬件最接近的(用于直接訪問硬件)。而設備驅動和應用程序通過同一的IO設備管理器來統一管理起來。
如何去管理呢?設備驅動在要操作硬件的時候,要向IO設備管理器去注冊,一旦注冊完之后,設備管理器就知道了哪個設備的驅動注冊到系統里面了。當注冊成功以后,當應用層想要訪問硬件的時候,通過調用rt_device_find函數來找到相應的設備驅動,一旦找到以后,就可以打開設備,接著進行讀寫操作,最后要記得關閉設備。
應用層想要去訪問硬件的時候,只需要查找到相應的設備驅動,然后調用統一的接口就能對硬件設備進行操作,不需要關注硬件的實現原理。而對應硬件驅動來講,我們只需要提供對硬件的訪問方法。具體如何去訪問,以及訪問到的數據如何去處理也跟設備驅動沒有關系。也就是說將不同的事情交給不同的層去完成,實現解耦(高內聚、低耦合)
對于一些復雜設備,需要使用到對應的設備驅動框架層,進行注冊,它們擁有自己專屬的注冊函數 如:看門狗定時器
- 看門狗設備驅動程序根據看門狗設備模型定義,創建出具備硬件訪問能力的看門狗設備實例,并將該看門狗設備通過特定的函數 rt_hw_watchdog_register()接口注冊到看門狗設備驅動框架中
- 看門狗設備驅動框架通過 rt_device_register()接口將看門狗設備注冊到 I/O 設備管理器中
- 應用程序通過 I/O 設備管理接口來訪問看門狗設備硬件
IO設備類型
- RT-Thread 支持多種 I/O 設備類型,主要設備類型如下所示
RT_Device_Class_Char = 0, /**< character device */
RT_Device_Class_Block, /**< block device */
RT_Device_Class_NetIf, /**< net interface */
RT_Device_Class_MTD, /**< memory device */
RT_Device_Class_CAN, /**< CAN device */
RT_Device_Class_RTC, /**< RTC device */
RT_Device_Class_Sound, /**< Sound device */
RT_Device_Class_Graphic, /**< Graphic device */
RT_Device_Class_I2CBUS, /**< I2C bus device */
RT_Device_Class_USBDevice, /**< USB slave device */
RT_Device_Class_USBHost, /**< USB host bus */
RT_Device_Class_SPIBUS, /**< SPI bus device */
RT_Device_Class_SPIDevice, /**< SPI device */
RT_Device_Class_SDIO, /**< SDIO bus device */
RT_Device_Class_Timer, /**< Timer device */
RT_Device_Class_Miscellaneous, /**< misc device */
RT_Device_Class_Sensor, /**< Sensor device */
RT_Device_Class_Touch, /**< Touch device */
RT_Device_Class_Unknown /**< unknown device */
創建和注冊IO設備
- 驅動層負責創建設備實例,并注冊到 I/O 設備管理器中
/*** This function creates a device object with user data size.** @param type, the kind type of this device object.* @param attach_size, the size of user data.** @return the allocated device object, or RT_NULL when failed.*/
rt_device_t rt_device_create(int type, int attach_size)
- 當一個動態創建的設備不再需要使用時可以通過如下函數來銷毀
/*** This function destroy the specific device object.** @param dev, the specific device object.*/
void rt_device_destroy(rt_device_t dev)
- 設備被創建后,需要實現它訪問硬件的操作方法
struct rt_device_ops
{/* common device interface */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, int cmd, void *args);
};
- 設備被創建后,需要注冊到 I/O 設備管理器中,應用程序才能夠訪問
/*** This function registers a device driver withspecified name.** @param dev the pointer of device driverstructure* @param name the device driver's name* @param flags the capabilities flag ofdevice 設備模式標志** @return the error code, RT_EOK oninitialization successfully.*/
rt_err_t rt_device_register(rt_device_t dev,const char *name,rt_uint16_t flags)#define RT_DEVICE_FLAG_RDONLY 0x001/*只讀*/
#define RT_DEVICE_FLAG_WRONLY 0x002/*只寫*/
#define RT_DEVICE_FLAG_RDWR 0x003 /*讀寫*/
#define RT_DEVICE_FLAG_REMOVABLE0x004 /*可移除*/
#define RT_DEVICE_FLAG_STANDALONE0x008 /*獨立*/
#define RT_DEVICE_FLAG_SUSPENDED0x020 /*掛起*/
#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發送*/
- 設備注銷后的,設備將從設備管理器中移除,也就不能再通過設備查找搜索到該設備。注銷設備不會釋放設備控制塊占用的內存
/*** This function removes a previouslyregistered device driver** @param dev the pointer of device driverstructure** @return the error code, RT_EOK onsuccessfully.*/
rt_err_t rt_device_unregister(rt_device_t dev)
RTT設備管理程序實現原理
當我們創建一個設備的時候,系統會使用一個結構體描述這個設備的所有信息。當我們創建并注冊多個設備的時候,系統就會通過列表的方式將這些結構體統一管理起來。當我們應用層想要找到某個設備的時候,就會調用find函數來找到列表頭來根據name遍歷查找,找到以后就會調用相應的方法來操作設備。當設備不用的時候,調用unregister函數來移除(將結構體變量從列表中移除),但結構體的空間依然存在,如果我們想要將結構體的空間釋放掉,就需要調用destroy函數來進行刪除釋放。
訪問IO設備
應用程序通過 I/O 設備管理接口來訪問硬件設備,當設備驅動實現后,應用程序就可以訪問該硬件,I/O 設備管理接口與 I/O 設備的操作方法的映射關系下圖所示
相應接口函數在設備句柄結構體中
使用應用層接口前,首先要查找到設備
查找設備
返回值為設備句柄指針
/*** This function finds a device driver byspecified name.** @param name the device driver's name** @return the registered device driver onsuccessful, or RT_NULL on failure.*/
rt_device_t rt_device_find(const char*name)
操作接口如下:
初始化設備
/*** This function will initialize the specified device** @param dev the pointer of device driver structure** @return the result*/
rt_err_t rt_device_init(rt_device_t dev)
打開設備
/*** This function will open a device** @param dev the pointer of device driver structure* @param oflag the flags for device open** @return the result*/
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)
注:RT_DEVICE_FLAG_STREAM:流模式用于向串口終端輸出字符串:當輸出的字符是 "\n"(對應 16 進制值為 0x0A)時,自動在前面輸出一個 "\r"(對應 16 進制值為 0x0D)做分行。
流模式 RT_DEVICE_FLAG_STREAM 可以和接收發送模式參數使用或 “|” 運算符一起使用
關閉設備
/*** This function will close a device** @param dev the pointer of device driver structure** @return the result*/
rt_err_t rt_device_close(rt_device_t dev)
?控制設備
/*** This function will perform a variety of control functions on devices.** @param dev the pointer of device driver structure* @param cmd the command sent to device* @param arg the argument of command** @return the result*/
rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)
?讀寫設備
/*** This function will read some data from adevice.** @param dev the pointer of device driverstructure* @param pos the position of reading* @param buffer the data buffer to save read data* @param size the size of buffer** @return the actually read size onsuccessful, otherwise negative returned.** @note since 0.4.0, the unit of size/pos is ablock for block device.*/
rt_size_t rt_device_read(rt_device_tdev,rt_off_t pos,void *buffer,rt_size_t size)
/*** This function will write some data to adevice.** @param dev the pointer of device driverstructure* @param pos the position of written* @param buffer the data buffer to be writtento device* @param size the size of buffer** @return the actually written size onsuccessful, otherwise negative returned.** @note since 0.4.0, the unit of size/pos is ablock for block device.*/
rt_size_trt_device_write(rt_device_t dev,rt_off_t pos,const void *buffer,rt_size_t size)
數據收發回調
當硬件設備收到數據時,可以通過如下函數回調另一個函數來設置數據接收指示,通知上層應用線程有數據到達
原理:通過設置數據的收發回調來通知我們的應用層某個線程來接收數據。這樣就不需要再接收數據的時候寫一個while循環一直去讀。讓它在沒有數據的時候去休眠阻塞,一旦有數據的時候,這個函數就會被回調去通知相應線程喚醒去讀寫,這樣可以減少系統的調用,提高系統調用效率。
數據接收回調
/*** This function will set the receptionindication callback function. * This callback function* is invoked when this device receives data.** @param dev the pointer of device driverstructure* @param rx_ind the indication callbackfunction** @return RT_EOK*/
rt_err_t
rt_device_set_rx_indicate(rt_device_tdev,rt_err_t (*rx_ind)(rt_device_t dev, rt_size_t size))
數據發送回調
/*** This function will set the indicationcallback function when device has* written data to physical hardware.** @param dev the pointer of device driverstructure* @param tx_done the indication callbackfunction** @return RT_EOK*/
rt_err_t
rt_device_set_tx_complete(rt_device_tdev,rt_err_t (*tx_done)(rt_device_t dev,void *buffer))
設備模型實例
(1)首先在drivers下創建一個drv_demo.c的驅動文件
(2)然后刷新工程目錄,打開drv_demo.c
(3)我們可以參考drv_wdt.c的設備驅動模型來寫
在文件最后我們可以發現有一個板級初始化的導出函數:我們使用INIT_BOARD_EXPORT宏將rt_wdt_init函數導出,那么在板級初始化的時候就會調用rt_wdt_init函數
我們再drv_demo.c中導出自己的設備初始化函數,并編寫設備初始化函數
(4)接著我們創建設備,使用rt_device_create函數
第一個參數是設備類型,這里用字符設備
第二個參數是用戶的數據大小,如果要傳入用戶數據的話,我們就根據用戶數據的大小來傳參。因為我們不需要傳入用戶數據,因此大小可以隨便寫一個,如32。
返回值為指針類型rt_device_t
(5)設備創建成功以后,我們需要對設備編寫相應的接口函數,我們以init、open、close為例
我們可以按F3跳轉到rt_device_t中,并將相應接口函數指針復制到我們自己的設備驅動文件下
(6)將函數進行簡單完善
并對接口進行賦值
(7)然后我們以讀寫的方式注冊我們的設備模型
(8)最后我們在main函數中使用,首先查找設備,如果查找成功,返回一個設備對象指針,如果查找失敗,我們返回錯誤:變量無效
然后調用相應的應用層接口函數
(10)編譯之后發現有一個警告LOG_E未定義
我們需要將添加調試頭文件#include
(11)運行結果
通過列舉設備,可以查看到demo是我們自己創建的設備;uart2是用來監控當前STM32單片機的,用作用戶的調試接口;pin是GPIO引腳。(其中要注意串口也是屬于字符設備類型的)