學(xué)完 RT-Thread 內(nèi)核,從本文開始熟悉了解 RT-Thread I/O 設(shè)備管理相關(guān)知識。
目錄
前言
一、RT-Thread 的 I/O設(shè)備管理
1.1 什么是 I/O 設(shè)備模型
1.2 I/O 設(shè)備模型框架解析
1.2.1 應(yīng)用程序
1.2.2 I/O 設(shè)備管理層
1.2.3 設(shè)備驅(qū)動框架層
1.2.4 設(shè)備驅(qū)動層
1.2.5 硬件層
1.3 I/O 設(shè)備操作邏輯說明
1.4 I/O 設(shè)備模型框架有什么用?
二、I/O 設(shè)備模型操作 API
2.1 I/O 設(shè)備控制塊
2.1.1 設(shè)備類型 type
2.1.2 設(shè)備注冊 flag
2.1.3 設(shè)備訪問 open_flag
2.2 創(chuàng)建 I/O 設(shè)備相關(guān)
2.2.1 創(chuàng)建設(shè)備
2.2.2 銷毀設(shè)備
2.2.3 設(shè)備注冊
2.2.4 設(shè)備注銷
2.3 訪問 I/O 設(shè)備相關(guān)
2.3.1 查找設(shè)備
2.3.2 初始化設(shè)備
2.3.3 打開和關(guān)閉設(shè)備
2.3.4 讀寫設(shè)備
2.3.5 控制設(shè)備
2.3.6 數(shù)據(jù)收發(fā)回調(diào)
三、新建 I/O 設(shè)備模型實例
結(jié)語
前言
我們已經(jīng)把 RT-Thread 內(nèi)核學(xué)習(xí)完成,也已經(jīng)使用 RT-Thread 做了一個實例應(yīng)用。
但是作為一個操作系統(tǒng), RT-Thread 除了內(nèi)核部分還有自己的設(shè)備框架,類似于 Linux 操作系統(tǒng)設(shè)備的管理方式的 I/O 設(shè)備模型。
從本文開始,我們要開始學(xué)習(xí)了解 RT-Thread 的 I/O 設(shè)備模型。
說明,概念性質(zhì)的說明主要還是使用引用方式,畢竟有權(quán)威的官方在,對于一些細(xì)節(jié)的理解,我會闡述自己的看法,同時會設(shè)計一些實例加深我們對基本概念的理解。
?? 我想做到的是,僅此一片文章,讓所有人都能明白RT-Thread 的I/O 設(shè)備模型 ??
一、RT-Thread 的 I/O設(shè)備管理
有些小伙伴在剛接觸到這個概念的時候還不太明白, I/O 設(shè)備模型,IO口? IO口的模型?
注意這里的 I/O 指的是 Input/Output。I/O 設(shè)備,就是指的輸入 / 輸出設(shè)備。
所謂 I/O 設(shè)備模型,指的是 RT-Thread 把所有的 輸入 / 輸出設(shè)備當(dāng)做一類對象,然后通過自己的一套體系對這類對象進(jìn)行管理,這類 I/O 設(shè)備對象就可以認(rèn)為是 I/O 設(shè)備模型。
RT-Thread 提供了一套模型框架用來對所有的輸入/輸出設(shè)備進(jìn)行管理的,名叫 I/O 設(shè)備模型框架 ,其 位于硬件層和應(yīng)用程序之間,包括IO設(shè)備管理層、設(shè)備驅(qū)動框架層和設(shè)備驅(qū)動層,向上層層抽象,目標(biāo)是針對各種不同的I/O設(shè)備提供給應(yīng)用程序相同的接口,如下圖:
1.2 I/O 設(shè)備模型框架解析
根據(jù)上圖,結(jié)合工程代碼,我們對每個層分別說:
1.2.1 應(yīng)用程序
我們寫的應(yīng)用程序,調(diào)用 I/O 設(shè)備管理層給的接口進(jìn)行底層硬件操作;
對應(yīng) main.c :
1.2.2 I/O 設(shè)備管理層
I/O 設(shè)備管理層實現(xiàn)了對設(shè)備驅(qū)動程序的封裝。
應(yīng)用程序通過 I/O 設(shè)備管理接口獲得正確的設(shè)備驅(qū)動,然后通過這個設(shè)備驅(qū)動與底層 I/O 硬件設(shè)備進(jìn)行數(shù)據(jù)交互。
設(shè)備驅(qū)動程序的升級、更替不會對上層應(yīng)用產(chǎn)生影響。這種方式使得設(shè)備的硬件操作相關(guān)的代碼能夠獨立于應(yīng)用程序而存在,雙方只需關(guān)注各自的功能實現(xiàn),從而降低了代碼的耦合性、復(fù)雜性,提高了系統(tǒng)的可靠性。
對應(yīng) device.c
1.2.3 設(shè)備驅(qū)動框架層
設(shè)備驅(qū)動框架層對同類硬件設(shè)備驅(qū)動的抽象,將不同廠家的同類硬件設(shè)備驅(qū)動中相同的部分抽取出來,將不同部分留出接口,由驅(qū)動程序?qū)崿F(xiàn)。
對應(yīng)的比如 serial.c 等
?? 上面的 I/O 設(shè)備管理層 和 設(shè)備驅(qū)動框架層 是屬于RT-Thread 系統(tǒng)的范疇,官方已寫好,所以在項目中的位置存放于 rt-thread 文件夾下面。
1.2.4 設(shè)備驅(qū)動層
設(shè)備驅(qū)動層是一組驅(qū)使硬件設(shè)備工作的程序,實現(xiàn)訪問硬件設(shè)備的功能。它負(fù)責(zé)創(chuàng)建和注冊 I/O 設(shè)備,就類似于使用裸機編寫程序時候的底層驅(qū)動。
裸機的驅(qū)動是直接被應(yīng)用層調(diào)用,這里的驅(qū)動是提供給 設(shè)備驅(qū)動框架層調(diào)用 或者 直接給 I/O 設(shè)備管理層調(diào)用的。
對于一些常用的芯片或者與官方有合作的芯片,官方也會提供了寫好的驅(qū)動,比如STM32,下圖就是官方已經(jīng)寫好的基于STM32 的設(shè)備驅(qū)動層的代碼。
對應(yīng)比如 drv_gpio.c 、drv_usart.c 這些 :
?? 設(shè)備驅(qū)動層的編寫是需要基于芯片或外設(shè)的手冊、SDK,是需要額外實現(xiàn)的。
但是有些常用芯片和外設(shè)官方已經(jīng)幫我們寫好了,比如基于 STM32 的 HAL 庫,RT-Thread官方已經(jīng)實現(xiàn)了基于 STM32 的設(shè)備驅(qū)動層 。
1.2.5 硬件層
比如Flash芯片,SD卡,stm32芯片等外設(shè)和MCU設(shè)備。
1.3 I/O 設(shè)備操作邏輯說明
簡單介紹一下 I/O 設(shè)備操作邏輯,這部分應(yīng)用官方的說明。
對于操作邏輯簡單的設(shè)備,可以不經(jīng)過設(shè)備驅(qū)動框架層,直接將設(shè)備注冊到 I/O 設(shè)備管理器中,過程如下:
在我們本文后面的新建設(shè)備模型實例中就舉了個簡單設(shè)備的例子。
對于復(fù)雜點的設(shè)備,需要經(jīng)過設(shè)備驅(qū)動框架層:
為了更好的理解上面的流程,可以結(jié)合工程源碼理解,多看源碼,比如下圖所示:
1.4 I/O 設(shè)備模型框架有什么用?
有很多初學(xué)者會問, I/O 設(shè)備模型意義在哪里? 在裸機使用中,比如操作一個IO口,直接調(diào)用驅(qū)動函數(shù),不是更簡單,更直接,使用了設(shè)個模型,反而變得復(fù)雜了?
在某些時候,如果你只使用一種芯片一種方案,或許某種意義上說, I/O 設(shè)備模型確實可能會比直接調(diào)用驅(qū)動函數(shù)復(fù)雜。 也可以說,你使用的方案單一,項目簡單,沒有必要使用 I/O 設(shè)備模型,甚至可能連 RTOS 都不一定需要。
總之就是,只用一種芯片方案簡單項目或者內(nèi)存空間實在有限,完全是可以不用這個框架,直接用 RT-Thread Nano 完成功能,和我們前面講的項目實例一樣完成,是沒有問題的!
但是作為一個工程師,不能局限在一種方案上面,尤其當(dāng)今芯片市場變幻莫測,指不定哪天需要換芯片,換方案呢? 而且 RT-Thread 作為一個面向?qū)ο笏枷朐O(shè)計的操作系統(tǒng),必須得全面考慮,需要降低了代碼的耦合性、復(fù)雜性,提高了系統(tǒng)的可靠性,能夠使得系統(tǒng)運行與不同的芯片設(shè)備上。
如果沒有 I/O 設(shè)備模型,那么每次換方案,從底層到應(yīng)用層所有的代碼基本上都得重寫,對于簡單的項目無所謂,對于復(fù)雜一點的項目,那可是需要花費大量功夫。
使用了 RT-Thread 的 I/O 設(shè)備模型,不管你使用哪種MCU,應(yīng)用層對設(shè)備操作的函數(shù)一模一樣,設(shè)備驅(qū)動程序的升級、更替不會對上層應(yīng)用產(chǎn)生影響。這種方式使得設(shè)備的硬件操作相關(guān)的代碼能夠獨立于應(yīng)用程序而存在,雙方只需關(guān)注各自的功能實現(xiàn)。
如果學(xué)過 Linux 的朋友肯定知道,RT-Thread 的 I/O 設(shè)備模型 思想 是和 Linux 類似的,作為嵌入式工程師,如果你懂 Linux,那么就應(yīng)該知道 I/O 設(shè)備模型框架 的優(yōu)點。如果你不懂 Linux,那么學(xué)會了 RT-Thread 操作系統(tǒng)的 I/O 設(shè)備模型,對于以后深入學(xué)習(xí) Linux 操作系統(tǒng),也是有幫助的 。 這話沒毛??! 感覺怎么說怎么有道理,人往高處走嘛!= =!
所有最終結(jié)果就是,反正就是好,人往高處走,學(xué)會了沒壞處 = =!
二、I/O 設(shè)備模型操作 API
上文我們說明了RT-Thread I/O 設(shè)備模型的基本概念先關(guān)內(nèi)容,也了解了 I/O 設(shè)備模型框架以及操作邏輯,那么我們用戶該如何來實現(xiàn)這一流程呢?
所以現(xiàn)在我們就來學(xué)習(xí)一下I/O設(shè)備模型操作的相關(guān)API函數(shù),包括創(chuàng)建、注冊、訪問等 。。。
2.1 I/O 設(shè)備控制塊
我們不止一次的說明了 RT-Thread 的面向?qū)ο蟮乃枷?,在RT-Thread中,設(shè)備也是一種內(nèi)核對象,那么他和以前說的線程,IPC機制,定時器等對象一樣,有自己的對象控制塊。
在以前博文:RT-Thread記錄(六、IPC機制之信號量、互斥量和事件集)這里再次額外說明一下,因為只要理解了這種思想,對于學(xué)會他們的使用就更加簡單了,在IPC機制的時候我們使用圖片說明過:
其實我們可以看一下設(shè)備對象的控制塊:
上源碼方便以后復(fù)制:
/**
* Device structure 設(shè)備控制塊
*/
struct rt_device
{
struct rt_object parent; /**< inherit from rt_object */
enum rt_device_class_type type; /**< device type */
rt_uint16_t flag; /**< device flag */
rt_uint16_t open_flag; /**< device open flag */
rt_uint8_t ref_count; /**< reference count */
rt_uint8_t device_id; /**< 0 - 255 */
/* device call back */
rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
#ifdef RT_USING_DEVICE_OPS
const struct rt_device_ops *ops;
#else
/* 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);
#endif
#if defined(RT_USING_POSIX)
const struct dfs_file_ops *fops;
struct rt_wqueue wait_queue;
#endif
void *user_data; /**< device private data */
};
2.1.1 設(shè)備類型 type
設(shè)備對象的控制塊中對于設(shè)備類型使用了一個rt_device_class_type
枚舉的方式,其可能的設(shè)備類型如下(簡單的沒有注釋,還有部分嗎不太清楚的,以后更新):
設(shè)備類型 需要在創(chuàng)建的時候選擇好,寫驅(qū)動的時候應(yīng)該知道自己寫的是什么類型的設(shè)備。
2.1.2 設(shè)備注冊 flag
設(shè)備對象的控制塊中有一個 flag 參數(shù),設(shè)備模式標(biāo)志 ,表示設(shè)備是屬于什么狀態(tài)的設(shè)備,可讀、可寫、收發(fā)等,其可以有的參數(shù)如下:
注意,該標(biāo)志可以采用或的方式支持多種參數(shù)。
設(shè)備注冊 flag 需要在創(chuàng)建的時候選擇好,寫驅(qū)動的時候應(yīng)該知道自己寫的設(shè)備是什么狀態(tài),比如只讀。只寫,中斷接收之類的。
2.1.3 設(shè)備訪問 open_flag
設(shè)備對象的控制塊中有一個 open_flag 參數(shù),設(shè)備打開模式標(biāo)志 ,表示對設(shè)備進(jìn)行什么操作,其可以有的參數(shù)如下:
設(shè)備訪問的時候,需要使用這個 open_flag 來判斷需要對設(shè)備進(jìn)行什么操作,是讀?還是寫? 還是發(fā)送等。。。
?? 介紹了 I/O 設(shè)備控制塊,讓我們清楚我們要操作的對象是什么,我們在 設(shè)備驅(qū)動層 要寫的設(shè)備驅(qū)動以及上層應(yīng)用對 I/O 設(shè)備的訪問, 就是基于這個控制塊來進(jìn)行的。
2.2 創(chuàng)建 I/O 設(shè)備相關(guān)
我們按照 先創(chuàng)建設(shè)備,再訪問設(shè)備 的順序來介紹對于的 API 函數(shù)。
2.2.1 創(chuàng)建設(shè)備
老規(guī)矩用源碼,解釋看注釋(使用起來也方便復(fù)制 ~ ~?。?/p>
/*
參數(shù)的含義:
1、type 設(shè)備類型,上面 2.1.1 小結(jié)說明的設(shè)備類型
2、attach_size 用戶數(shù)據(jù)大小
返回值:
創(chuàng)建成功,返回設(shè)備的控制塊指針
創(chuàng)建失敗,返回RT_BULL
*/
rt_device_t rt_device_create(int type, int attach_size)
/**
* device (I/O) class type 設(shè)備類型
*/
enum rt_device_class_type
{
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_PM, /**< PM pseudo device */
RT_Device_Class_Pipe, /**< Pipe device */
RT_Device_Class_Portal, /**< Portal device */
RT_Device_Class_Timer, /**< Timer device */
RT_Device_Class_Miscellaneous, /**< Miscellaneous device */
RT_Device_Class_Sensor, /**< Sensor device */
RT_Device_Class_Touch, /**< Touch device */
RT_Device_Class_PHY, /**< PHY device */
RT_Device_Class_Unknown /**< unknown device */
};
設(shè)備被創(chuàng)建后,需要實現(xiàn)它訪問硬件的操作方法,要按照下面的函數(shù)指針實現(xiàn)這些對于設(shè)備的操作函數(shù):
/* 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);
這里引用官方的說明:
2.2.2 銷毀設(shè)備
此函數(shù)不一定需要使用,但是有創(chuàng)建就應(yīng)該有銷毀:
/*
參數(shù)的含義:
dev 設(shè)備句柄
*/
void rt_device_destroy(rt_device_t dev)
2.2.3 設(shè)備注冊
設(shè)備被創(chuàng)建后,需要注冊到 I/O 設(shè)備管理器中,應(yīng)用程序才能夠訪問:
/*
參數(shù)的含義:
dev 設(shè)備句柄
name 設(shè)備名稱,
設(shè)備名稱的最大長度由 rtconfig.h 中定義的宏 RT_NAME_MAX 指定,多余部分會被自動截掉
flags 設(shè)備模式標(biāo)志,就是上面介紹的 2.1.2 設(shè)備注冊 flag
返回值:
RT_EOK 注冊成功
-RT_ERROR 注冊失敗,dev 為空或者 name 已經(jīng)存在
*/
rt_err_t rt_device_register(rt_device_t dev,
const char *name,
rt_uint16_t flags)
/*設(shè)備注冊 flag*/
#define RT_DEVICE_FLAG_DEACTIVATE 0x000 /**< device is not not initialized */
#define RT_DEVICE_FLAG_RDONLY 0x001 /**< read only */
#define RT_DEVICE_FLAG_WRONLY 0x002 /**< write only */
#define RT_DEVICE_FLAG_RDWR 0x003 /**< read and write */
#define RT_DEVICE_FLAG_REMOVABLE 0x004 /**< removable device */
#define RT_DEVICE_FLAG_STANDALONE 0x008 /**< standalone device */
#define RT_DEVICE_FLAG_ACTIVATED 0x010 /**< device is activated */
#define RT_DEVICE_FLAG_SUSPENDED 0x020 /**< device is suspended */
#define RT_DEVICE_FLAG_STREAM 0x040 /**< stream mode */
#define RT_DEVICE_FLAG_INT_RX 0x100 /**< INT mode on Rx */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /**< DMA mode on Rx */
#define RT_DEVICE_FLAG_INT_TX 0x400 /**< INT mode on Tx */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /**< DMA mode on Tx */
上面需要額外說明的一點,設(shè)備流模式 `RT_DEVICE_FLAG_STREAM` 參數(shù)用于向串口終端輸出字符串:
當(dāng)輸出的字符是 “\n” 時,自動在前面補一個 “\r” 做分行。
2.2.4 設(shè)備注銷
創(chuàng)建對銷毀,注冊對注銷:
/**
參數(shù) 描述
dev 設(shè)備句柄
返回 ——
RT_EOK 成功
*/
rt_err_t rt_device_unregister(rt_device_t dev)
當(dāng)設(shè)備注銷后的,設(shè)備將從設(shè)備管理器中移除,也就不能再通過設(shè)備查找搜索到該設(shè)備。注銷設(shè)備不會釋放設(shè)備控制塊占用的內(nèi)存。
注銷只是把這個 設(shè)備對象結(jié)構(gòu)體 從管理鏈表中去掉,并不會釋放這個對象結(jié)構(gòu)體的空間,要釋放空間需要調(diào)用銷毀設(shè)備函數(shù)。
?? 創(chuàng)建 I/O 設(shè)備相關(guān)的函數(shù),和 I/O 設(shè)備模型框架中 設(shè)備驅(qū)動層 有關(guān),在我們底層寫驅(qū)動的時候需要使用。
2.3 訪問 I/O 設(shè)備相關(guān)
上面我們說的創(chuàng)建 I/O 設(shè)備相關(guān)是和 設(shè)備驅(qū)動層 有關(guān)的代碼,本小結(jié)說的訪問 I/O 設(shè)備就是和 應(yīng)用程序有關(guān)的代碼,就是告訴我們應(yīng)用程序怎么去操作我們上面創(chuàng)建的設(shè)備。
I/O 設(shè)備管理接口與 I/O 設(shè)備的操作方法的映射關(guān)系下圖:
2.3.1 查找設(shè)備
注冊過的設(shè)備才能被查找到,名字對應(yīng)注冊時候使用的名字:
/**
參數(shù) 描述
name 設(shè)備名稱
返回 ——
設(shè)備句柄 查找到對應(yīng)設(shè)備將返回相應(yīng)的設(shè)備句柄
RT_NULL 沒有找到相應(yīng)的設(shè)備對象
*/
rt_device_t rt_device_find(const char *name)
2.3.2 初始化設(shè)備
對應(yīng)底層rt_err_t (*init) (rt_device_t dev);
的實現(xiàn)函數(shù):
/**
參數(shù) 描述
dev 設(shè)備句柄
返回 ——
RT_EOK 設(shè)備初始化成功
錯誤碼 設(shè)備初始化失敗
*/
rt_err_t rt_device_init(rt_device_t dev)
當(dāng)一個設(shè)備已經(jīng)初始化成功后,調(diào)用這個接口將不再重復(fù)做初始化 0。
2.3.3 打開和關(guān)閉設(shè)備
打開設(shè)備:
對應(yīng)底層rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
的實現(xiàn)函數(shù):
/*
參數(shù) 描述
dev 設(shè)備句柄
oflags 設(shè)備打開模式標(biāo)志,上面 2.1.3 小結(jié)說明的設(shè)備訪問 open_flag
返回 ——
RT_EOK 設(shè)備打開成功
-RT_EBUSY 如果設(shè)備注冊時指定的參數(shù)中包括 RT_DEVICE_FLAG_STANDALONE 參數(shù),此設(shè)備將不允許重復(fù)打開
其他錯誤碼 設(shè)備打開失敗
*/
rt_err_t rt_device_open (rt_device_t dev, rt_uint16_t oflag);
#define RT_DEVICE_FLAG_INT_RX 0x100 /**< INT mode on Rx */
#define RT_DEVICE_FLAG_DMA_RX 0x200 /**< DMA mode on Rx */
#define RT_DEVICE_FLAG_INT_TX 0x400 /**< INT mode on Tx */
#define RT_DEVICE_FLAG_DMA_TX 0x800 /**< DMA mode on Tx */
#define RT_DEVICE_OFLAG_CLOSE 0x000 /**< 設(shè)備已經(jīng)關(guān)閉(內(nèi)部使用) */
#define RT_DEVICE_OFLAG_RDONLY 0x001 /**< read only access */
#define RT_DEVICE_OFLAG_WRONLY 0x002 /**< write only access */
#define RT_DEVICE_OFLAG_RDWR 0x003 /**< read and write */
#define RT_DEVICE_OFLAG_OPEN 0x008 /**< device is opened */
#define RT_DEVICE_OFLAG_MASK 0xf0f /**< mask of open flag */
注:如果上層應(yīng)用程序需要設(shè)置設(shè)備的接收回調(diào)函數(shù),則必須以 RT_DEVICE_FLAG_INT_RX
或者 RT_DEVICE_FLAG_DMA_RX
的方式打開設(shè)備,否則不會回調(diào)函數(shù)。
關(guān)閉設(shè)備:
對應(yīng)底層rt_err_t (*close) (rt_device_t dev);
的實現(xiàn)函數(shù):
/*
dev 設(shè)備句柄
返回 ——
RT_EOK 關(guān)閉設(shè)備成功
-RT_ERROR 設(shè)備已經(jīng)完全關(guān)閉,不能重復(fù)關(guān)閉設(shè)備
其他錯誤碼 關(guān)閉設(shè)備失敗
*/
rt_err_t rt_device_open (rt_device_t dev, rt_uint16_t oflag);
關(guān)閉設(shè)備接口和打開設(shè)備接口需配對使用,打開一次設(shè)備對應(yīng)要關(guān)閉一次設(shè)備,這樣設(shè)備才會被完全關(guān)閉,否則設(shè)備仍處于未關(guān)閉狀態(tài)。
2.3.4 讀寫設(shè)備
讀設(shè)備:
對應(yīng)底層rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
的實現(xiàn)函數(shù):
/**
參數(shù) 描述
dev 設(shè)備句柄
pos 讀取數(shù)據(jù)偏移量
buffer 內(nèi)存緩沖區(qū)指針,讀取的數(shù)據(jù)將會被保存在緩沖區(qū)中
size 讀取數(shù)據(jù)的大小
返回 ——
讀到數(shù)據(jù)的實際大小 如果是字符設(shè)備,返回大小以字節(jié)為單位,如果是塊設(shè)備,返回的大小以塊為單位
0 需要讀取當(dāng)前線程的 errno 來判斷錯誤狀態(tài)
*/
rt_size_t rt_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
調(diào)用這個函數(shù),會從 dev 設(shè)備中讀取數(shù)據(jù),并存放在 buffer 緩沖區(qū)中,這個緩沖區(qū)的最大長度是 size,pos 根據(jù)不同的設(shè)備類別有不同的意義。
寫設(shè)備:
對應(yīng)底層rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
的實現(xiàn)函數(shù):
/**
參數(shù) 描述
dev 設(shè)備句柄
pos 寫入數(shù)據(jù)偏移量
buffer 內(nèi)存緩沖區(qū)指針,放置要寫入的數(shù)據(jù)
size 寫入數(shù)據(jù)的大小
返回 ——
寫入數(shù)據(jù)的實際大小 如果是字符設(shè)備,返回大小以字節(jié)為單位;如果是塊設(shè)備,返回的大小以塊為單位
0 需要讀取當(dāng)前線程的 errno 來判斷錯誤狀態(tài)
*/
rt_size_t rt_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
調(diào)用這個函數(shù),會把緩沖區(qū) buffer 中的數(shù)據(jù)寫入到設(shè)備 dev 中,寫入數(shù)據(jù)的最大長度是 size,pos 根據(jù)不同的設(shè)備類別存在不同的意義。
2.3.5 控制設(shè)備
對應(yīng)底層rt_err_t (*control)(rt_device_t dev, int cmd, void *args);
的實現(xiàn)函數(shù):
/**
參數(shù) 描述
dev 設(shè)備句柄
cmd 命令控制字,這個參數(shù)通常與設(shè)備驅(qū)動程序相關(guān),見下面列出的參數(shù)
arg 控制的參數(shù)
返回 ——
RT_EOK 函數(shù)執(zhí)行成功
-RT_ENOSYS 執(zhí)行失敗,dev 為空
其他錯誤碼 執(zhí)行失敗
*/
rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)
/**
* general device commands 上面 cmd 的參數(shù)
*/
#define RT_DEVICE_CTRL_RESUME 0x01 /**< resume device */
#define RT_DEVICE_CTRL_SUSPEND 0x02 /**< suspend device */
#define RT_DEVICE_CTRL_CONFIG 0x03 /**< configure device */
#define RT_DEVICE_CTRL_CLOSE 0x04 /**< close device */
#define RT_DEVICE_CTRL_SET_INT 0x10 /**< set interrupt */
#define RT_DEVICE_CTRL_CLR_INT 0x11 /**< clear interrupt */
#define RT_DEVICE_CTRL_GET_INT 0x12 /**< get interrupt status */
2.3.6 數(shù)據(jù)收發(fā)回調(diào)
這個對于 設(shè)備控制塊中參數(shù)中的兩個設(shè)備回調(diào)函數(shù):
硬件設(shè)備收到數(shù)據(jù):
/**
參數(shù) 描述
dev 設(shè)備句柄
rx_ind 回調(diào)函數(shù)指針
返回 ——
RT_EOK 設(shè)置成功
*/
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))
該函數(shù)的回調(diào)函數(shù)由調(diào)用者提供。當(dāng)硬件設(shè)備接收到數(shù)據(jù)時,會回調(diào)這個函數(shù)并把收到的數(shù)據(jù)長度放在 size 參數(shù)中傳遞給上層應(yīng)用。上層應(yīng)用線程應(yīng)在收到指示后,立刻從設(shè)備中讀取數(shù)據(jù)。
硬件設(shè)備發(fā)送數(shù)據(jù):
在應(yīng)用程序調(diào)用 rt_device_write() 寫入數(shù)據(jù)時,如果底層硬件能夠支持自動發(fā)送,那么上層應(yīng)用可以設(shè)置一個回調(diào)函數(shù)。
這個回調(diào)函數(shù)會在底層硬件數(shù)據(jù)發(fā)送完成后 (例如 DMA 傳送完成或 FIFO 已經(jīng)寫入完畢產(chǎn)生完成中斷時) 調(diào)用。
通過如下函數(shù)設(shè)置設(shè)備發(fā)送完成指示:
/**
參數(shù) 描述
dev 設(shè)備句柄
tx_done 回調(diào)函數(shù)指針
返回 ——
RT_EOK 設(shè)置成功
*/
rt_err_t
rt_device_set_tx_complete(rt_device_t dev,
rt_err_t (*tx_done)(rt_device_t dev, void *buffer))
調(diào)用這個函數(shù)時,回調(diào)函數(shù)由調(diào)用者提供,當(dāng)硬件設(shè)備發(fā)送完數(shù)據(jù)時,由驅(qū)動程序回調(diào)這個函數(shù)并把發(fā)送完成的數(shù)據(jù)塊地址 buffer 作為參數(shù)傳遞給上層應(yīng)用。上層應(yīng)用(線程)在收到指示時會根據(jù)發(fā)送 buffer 的情況,釋放 buffer 內(nèi)存塊或?qū)⑵渥鳛橄乱粋€寫數(shù)據(jù)的緩存。
?? 訪問 I/O 設(shè)備相關(guān)的函數(shù),和 I/O 設(shè)備模型框架中 應(yīng)用程序 有關(guān),是我們上層寫應(yīng)用程序直接調(diào)用的函數(shù)。
三、新建 I/O 設(shè)備模型實例
RT-Thread 驅(qū)動都是在 drivers 目錄下面:
我們在目錄下新建一個文件,作為驅(qū)動示例:
我們寫一個簡單的基本框架:
1、創(chuàng)建一個設(shè)備;
使用 rt_device_create 創(chuàng)建一個設(shè)備,需要定義一個 rt_device_t 接口體接收設(shè)備設(shè)備句柄。
2、實現(xiàn)設(shè)備操作的函數(shù):
實現(xiàn)設(shè)備對象中對于 設(shè)備操作的init,open,close等 函數(shù)。
3、注冊設(shè)備到 I/O 設(shè)備管理器;
使用 rt_device_register 將設(shè)備注冊到設(shè)備管理器。
在drv_demo.c
中,我們實現(xiàn)如下代碼:
其次,我們需要實現(xiàn)一下設(shè)備操作的函數(shù):
最后,別忘了使用 INIT_BOARD_EXPORT
把設(shè)備初始化的代碼加入板級硬件初始化:
上一下設(shè)備模型實例代碼:
#include
#include
rt_err_t demo_init(rt_device_t dev)
{
rt_kprintf("demo_init ok!\n");
return 0;
}
rt_err_t demo_open(rt_device_t dev, rt_uint16_t oflag)
{
rt_kprintf("demo_open ok!\n");
return 0;
}
rt_err_t demo_cloes(rt_device_t dev)
{
rt_kprintf("demo_cloes ok!\n");
return 0;
}
int rt_drvdemo_init(void){
rt_device_t demo_dev = RT_NULL;
demo_dev = rt_device_create(RT_Device_Class_Char, 0);
if(demo_dev == RT_NULL){
LOG_E("demo device create failed...\n");
return -1;
}
demo_dev->init=demo_init;
demo_dev->open=demo_open;
demo_dev->close=demo_cloes;
rt_device_register(demo_dev,"drvdemo",RT_DEVICE_FLAG_RDWR);
return 0;
}
INIT_BOARD_EXPORT(rt_drvdemo_init);
上面我們完成的是 設(shè)備驅(qū)動層的 代碼,接下來我們還需要簡單演示一下,如果在應(yīng)用層 使用這個 demo 設(shè)備。
我們根據(jù)上文所介紹的 訪問 I/O 設(shè)備 進(jìn)行對應(yīng)操作,這里直接上圖說明一下使用流程:
看一下測試結(jié)果,我們實現(xiàn)的 3 個驅(qū)動函數(shù)都只有打印輸出,所以我們可以通過打印信息查看是否正確執(zhí)行的驅(qū)動函數(shù)的內(nèi)容:
通過上面的測試,我們實現(xiàn)了一個簡單的設(shè)備驅(qū)動的設(shè)計,雖然demo比較簡單,但是經(jīng)過這么一個過程可以讓我們更加的理解 RT-Thread I/O 設(shè)備模型的工作方式和流程。
結(jié)語
本文全面了解了 RT-Thread I/O 設(shè)備模型,說明了設(shè)備模型存在的意義,描述了一下設(shè)備模型相關(guān)的操作函數(shù),最后使用了一個新建 I/O設(shè)備模型的例子,說明了 I/O 設(shè)備模型 的工作方式。
在我們使用 RT-Thread 的時候,其實大部分常用的設(shè)備 RT-Thread 已經(jīng)幫我們寫好了驅(qū)動,我們直接在應(yīng)用層調(diào)用操作接口即可,接下來的系列文章我們將要學(xué)習(xí) RT-Thread 常用的 I/O 設(shè)備模型。
?? 希望開頭的愿景能夠?qū)崿F(xiàn),通過本文讓所有人了解 RT-Thread I/O 設(shè)備模型 (* ̄︶ ̄) ??
本文就到這里,謝謝大家!
審核編輯:湯梓紅
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1336瀏覽量
40084 -
Linux
+關(guān)注
關(guān)注
87文章
11123瀏覽量
207918 -
模型
+關(guān)注
關(guān)注
1文章
3032瀏覽量
48363 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1239瀏覽量
39437
發(fā)布評論請先 登錄
相關(guān)推薦
評論