RT-Thread的I/O设备模块及其驱动实现步骤
生活随笔
收集整理的這篇文章主要介紹了
RT-Thread的I/O设备模块及其驱动实现步骤
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
一、I/O設(shè)備控制塊
1、I/O設(shè)備控制塊
struct rt_device
{struct rt_object parent;/* 設(shè)備類型 */enum rt_device_class_type type;/* 設(shè)備參數(shù)及打開參數(shù) */rt_uint16_t flag, open_flag;/* 提供給上層應(yīng)用的回調(diào)函數(shù) */rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);rt_err_t (*tx_complete)(rt_device_t dev, void* buffer);/* 公共的設(shè)備接口(由驅(qū)動程序提供) */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);/* 用于支持電源管理的函數(shù)接口 */
#ifdef RT_USING_DEVICE_SUSPENDrt_err_t (*suspend) (rt_device_t dev);rt_err_t (*resumed) (rt_device_t dev);
#endif
/* 設(shè)備的私有數(shù)據(jù) */void* user_data;
};typedef struct rt_device* rt_device_t;當(dāng)前RT-Thread支持的設(shè)備類型包括:
enum rt_device_class_type
{RT_Device_Class_Char = 0, /* 字符設(shè)備 */RT_Device_Class_Block, /* 塊設(shè)備 */RT_Device_Class_NetIf, /* 網(wǎng)絡(luò)接口 */RT_Device_Class_MTD, /* 內(nèi)存設(shè)備 */RT_Device_Class_CAN, /* CAN設(shè)備 */RT_Device_Class_RTC, /* RTC設(shè)備 */RT_Device_Class_Sound, /* 聲音設(shè)備 */RT_Device_Class_Display, /* 顯示設(shè)備 */RT_Device_Class_Unknown /* 未知設(shè)備 */
};注:uspend、resume回調(diào)函數(shù)只會在RT_USING_DEVICE_SUSPEND宏使能的情況下才
會有效。
從設(shè)備控制塊,我們可以看到,每個設(shè)備對象都會在內(nèi)核中維護(hù)一個設(shè)備控制塊結(jié)構(gòu),
這種結(jié)構(gòu)是使設(shè)備對象繼承rt_object基類,然后形成rt_device設(shè)備類型。2、注冊設(shè)備
一個設(shè)備能夠被上層應(yīng)用訪問前,需要先把這個設(shè)備注冊到系統(tǒng)中,并添加一些相應(yīng)的
一些屬性。這些注冊的設(shè)備均可以通過設(shè)備名,采用“查找設(shè)備接口”的方式從系統(tǒng)中查找
到,從而獲得該設(shè)備控制塊(或設(shè)備句柄)。注冊設(shè)備的函數(shù)接口如下:
rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);函數(shù)參數(shù):
dev 設(shè)備句柄;
name 設(shè)備名稱;
flag 設(shè)備模式標(biāo)志:
flags參數(shù)支持下列參數(shù)(可以采用或的方式支持多種參數(shù)):#define RT_DEVICE_FLAG_DEACTIVATE 0x000 /* 未初始化設(shè)備 */
#define RT_DEVICE_FLAG_RDONLY 0x001 /* 只讀設(shè)備 */
#define RT_DEVICE_FLAG_WRONLY 0x002 /* 只寫設(shè)備 */
#define RT_DEVICE_FLAG_RDWR 0x003 /* 讀寫設(shè)備 */
#define RT_DEVICE_FLAG_REMOVABLE 0x004 /* 可移除設(shè)備 */
#define RT_DEVICE_FLAG_STANDALONE 0x008 /* 獨(dú)立設(shè)備 */
#define RT_DEVICE_FLAG_ACTIVATED 0x010 /* 已激活設(shè)備 */
#define RT_DEVICE_FLAG_SUSPENDED 0x020 /* 掛起設(shè)備 */
#define RT_DEVICE_FLAG_STREAM 0x040 /* 設(shè)備處于流模式 */
#define RT_DEVICE_FLAG_INT_RX 0x100 /* 設(shè)備處于中斷接收模式*/
#define RT_DEVICE_FLAG_DMA_RX 0x200 /* 設(shè)備處于DMA接收模式 */
#define RT_DEVICE_FLAG_INT_TX 0x400 /* 設(shè)備處于中斷發(fā)送模式*/
#define RT_DEVICE_FLAG_DMA_TX 0x800 /* 設(shè)備處于DMA發(fā)送模式 */設(shè)備流模式RT_DEVICE_FLAG_STREAM參數(shù)用于向串口終端輸出字符串:當(dāng)輸出的字符
是“\n”時,自動在前面補(bǔ)一個“\r”做分行。
函數(shù)返回
返回RT_EOK
警告:應(yīng)當(dāng)避免重復(fù)注冊已經(jīng)注冊的設(shè)備,以及注冊相同名字的設(shè)備。3、移除設(shè)備
將設(shè)備從設(shè)備系統(tǒng)中移除,被卸載的設(shè)備將不能再通過“查找設(shè)備接口”被查找到。卸
載設(shè)備的函數(shù)接口如下所示:
rt_err_t rt_device_unregister(rt_device_t dev)函數(shù)參數(shù)
參數(shù) 描述
dev 設(shè)備句柄;
函數(shù)返回
返回RT_EOK
注:卸載設(shè)備并不會釋放設(shè)備控制塊所占用的內(nèi)存4、初始化所有設(shè)備
初始化所有注冊到設(shè)備對象管理器中的未初始化的設(shè)備,可以通過如下函數(shù)接口完成:rt_err_t rt_device_init_all(void)函數(shù)參數(shù)
無
函數(shù)返回
返回RT_EO
? 注:此函數(shù)將逐漸廢棄,不推薦在應(yīng)用程序中調(diào)用。當(dāng)一個設(shè)備初始化完成后它
的flags域中的RT_DEVICE_FLAG_ACTIVATED應(yīng)該被置位。如果設(shè)備的flags域已經(jīng)是
RT_DEVICE_FLAG_ACTIVATED,調(diào)用這個接口將不再重復(fù)做初始化。5、查找設(shè)備
根據(jù)指定的設(shè)備名稱查找設(shè)備,可以通過如下接口完成:rt_device_t rt_device_find(const char* name)使用這個函數(shù)接口時,系統(tǒng)會在設(shè)備對象類型所對應(yīng)的對象容器中遍歷尋找設(shè)備對象,
然后返回該設(shè)備的句柄,如果沒有找到相應(yīng)的設(shè)備對象,則返回RT_NULL。
函數(shù)參數(shù)
參數(shù) 描述
name 設(shè)備名稱。
函數(shù)返回
查找到對應(yīng)設(shè)備將返回相應(yīng)的設(shè)備句柄;否則返回RT_NULL。6、打開設(shè)備
根據(jù)設(shè)備控制塊來打開設(shè)備,可以通過如下函數(shù)接口完成:rt_err_t rt_device_open (rt_device_t dev, rt_uint16_t oflags);函數(shù)參數(shù)
參數(shù) 描述
dev 設(shè)備句柄;
oflags 訪問模式。其中oflags支持以下列表中的參數(shù):#define RT_DEVICE_OFLAG_CLOSE 0x000 /* 設(shè)備已經(jīng)關(guān)閉(內(nèi)部使用) */
#define RT_DEVICE_OFLAG_RDONLY 0x001 /* 以只讀方式打開設(shè)備 */
#define RT_DEVICE_OFLAG_WRONLY 0x002 /* 以只寫方式打開設(shè)備 */
#define RT_DEVICE_OFLAG_RDWR 0x003 /* 以讀寫方式打開設(shè)備 */
#define RT_DEVICE_OFLAG_OPEN 0x008 /* 設(shè)備已經(jīng)打開(內(nèi)部使用) */函數(shù)返回
返回驅(qū)動的open函數(shù)返回值
注:如果設(shè)備注冊時指定的參數(shù)中包括RT_DEVICE_FLAG_STANDALONE參數(shù),此設(shè)備將
不允許重復(fù)打開,返回-RT_EBUSY。7、關(guān)閉設(shè)備
根據(jù)設(shè)備控制塊來關(guān)閉設(shè)備,可以通過如下函數(shù)接口完成:rt_err_t rt_device_close(rt_device_t dev)函數(shù)參數(shù)
參數(shù) 描述
dev 設(shè)備句柄;函數(shù)返回
返回驅(qū)動的close函數(shù)返回值8、讀設(shè)備
從設(shè)備中讀取,或獲得數(shù)據(jù),可以通過如下函數(shù)接口完成:rt_size_t rt_device_read (rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)調(diào)用這個函數(shù),會從設(shè)備dev中獲得數(shù)據(jù),并存放在buffer緩沖區(qū)中。這個緩沖區(qū)的最
大長度是size。pos根據(jù)不同的設(shè)備類別存在不同的意義。
函數(shù)參數(shù)
參數(shù) 描述
dev 設(shè)備句柄;
pos 讀取數(shù)據(jù)偏移量;
buffer 內(nèi)存緩沖區(qū)指針,讀取的數(shù)據(jù)將會被保存在緩沖區(qū)中;
size 讀取數(shù)據(jù)的大小。
函數(shù)返回返回讀到數(shù)據(jù)的實(shí)際大小(如果是字符設(shè)備,返回大小以字節(jié)為單位;如果是塊設(shè)備,
返回的大小以塊為單位);如果返回0,則需要讀取當(dāng)前線程的errno來判斷錯誤狀態(tài)。9、寫設(shè)備
向設(shè)備中寫入數(shù)據(jù),可以通過如下函數(shù)接口完成: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è)備類別存在不同的意義。
函數(shù)參數(shù)參數(shù) 描述
dev 設(shè)備句柄;
pos 讀取數(shù)據(jù)偏移量;
buffer 內(nèi)存緩沖區(qū)指針,放置要寫入的數(shù)據(jù);
size 寫入數(shù)據(jù)的大小。函數(shù)返回
返回寫入數(shù)據(jù)的實(shí)際大小(如果是字符設(shè)備,返回大小以字節(jié)為單位;如果是塊設(shè)備,
返回的大小以塊為單位);如果返回0,則需要讀取當(dāng)前線程的errno來判斷錯誤狀態(tài)
? 注:在RT-Thread的塊設(shè)備中,從1.0.0版本開始, rt_device_read()/rt_device_write()接
口的pos、size參數(shù)按照以塊為單位。0.3.x以前的版本則按字節(jié)為單位。10、控制設(shè)備
根據(jù)設(shè)備控制塊來控制設(shè)備,可以通過下面的函數(shù)接口完成:rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);函數(shù)參數(shù)
參數(shù) 描述
dev 設(shè)備句柄;
cmd 命令控制字,這個參數(shù)通常與設(shè)備驅(qū)動程序相關(guān);
arg 控制的參數(shù)函數(shù)返回
返回驅(qū)動控制接口的返回值。11、設(shè)置數(shù)據(jù)接收指示
設(shè)置一個回調(diào)函數(shù),當(dāng)硬件設(shè)備收到數(shù)據(jù)時回調(diào)以通知用程序有數(shù)據(jù)到達(dá)??梢酝ㄟ^如
下函數(shù)接口完成設(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));在調(diào)用這個函數(shù)時,回調(diào)函數(shù)rx_ind由調(diào)用者提供。當(dāng)硬件設(shè)備接收到數(shù)據(jù)時,會回調(diào)
這個函數(shù)并把收到的數(shù)據(jù)長度放在size參數(shù)中傳遞給上層應(yīng)用。上層應(yīng)用線程應(yīng)在收到指示
后,立刻從設(shè)備中讀取數(shù)據(jù)。
函數(shù)參數(shù)
參數(shù) 描述
dev 設(shè)備句柄;
rx_ind 接收回調(diào)函數(shù)。函數(shù)返回
返回RT_EOK12、設(shè)置發(fā)送完成指示
在上層應(yīng)用調(diào)用rt_device_write寫入數(shù)據(jù)時,如果底層硬件能夠支持自動發(fā)送,那么上層應(yīng)用可以設(shè)置一個回調(diào)函數(shù)。這個回調(diào)函數(shù)會在底層硬件給出的發(fā)送完成后(例如DMA傳送完成或FIFO已經(jīng)寫入完畢產(chǎn)生完成中斷時)被調(diào)用??梢酝ㄟ^如下函數(shù)接口設(shè)置設(shè)備發(fā)送完成指示:
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ù)tx_done參數(shù)由調(diào)用者提供,當(dāng)硬件設(shè)備發(fā)送完數(shù)據(jù)時,由驅(qū)動程序回調(diào)這個函數(shù)并把發(fā)送完成的數(shù)據(jù)塊地址buffer做為參數(shù)傳遞給上層應(yīng)用。上層應(yīng)用(線程)在收到指示時應(yīng)根據(jù)發(fā)送buffer的情況,釋放buffer內(nèi)存塊或?qū)⑵渥鰹橄乱粋€寫數(shù)據(jù)的緩存。
函數(shù)參數(shù)
參數(shù) 描述
dev 設(shè)備句柄;
tx_done 發(fā)送回調(diào)函數(shù)。函數(shù)返回
返回RT_EOK二、設(shè)備驅(qū)動設(shè)備驅(qū)動必須實(shí)現(xiàn)的接口
在10.1節(jié)中提及了RT-Thread設(shè)備接口類,我們著重看看其中包含的一套公共設(shè)備接口
(類似上節(jié)說的設(shè)備訪問接口,但面向的層次已經(jīng)不一樣,這里是面向底層驅(qū)動):/* 公共的設(shè)備接口(由驅(qū)動程序提供) */
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);/* 用于支持電源管理的函數(shù)接口 */#ifdef RT_USING_DEVICE_SUSPENDrt_err_t (*suspend) (rt_device_t dev);rt_err_t (*resumed) (rt_device_t dev);
#endif這些接口也是上層應(yīng)用通過RT-Thread設(shè)備接口進(jìn)行訪問的實(shí)際底層接口(如設(shè)備操作接口與設(shè)備驅(qū)動程序接口的映射 ):即這些驅(qū)動實(shí)現(xiàn)的底層接口是上層應(yīng)用最終訪問的落腳點(diǎn),例如上層應(yīng)用調(diào)用rt_device_read接口進(jìn)行設(shè)備讀取數(shù)據(jù)操作,上層應(yīng)先調(diào)用rt_device_find獲得相對應(yīng)的設(shè)備句柄,而在調(diào)用rt_device_read時,就是使用這個設(shè)備句柄所對應(yīng)驅(qū)動的driver_read。上述的接口是一一對應(yīng)關(guān)系。
I/O設(shè)備模塊提供的這六個接口(rt_device_init/open/read/write/control),對應(yīng)到設(shè)備驅(qū)動程序的六個接口(driver_init/open/read/write/control等),可以認(rèn)為是底層設(shè)備驅(qū)動必須提供的接口:1、init
設(shè)備的初始化。設(shè)備初始化完成后,設(shè)備控制塊的flag會被置 成已激活狀態(tài)(RT_DEVICE_FLAG_ACTIVATED)。如果設(shè)備控制塊 的flag
不是已激活狀態(tài),那么在設(shè)備框架調(diào)用 rt_device_init_all接口時將調(diào)用此設(shè)備驅(qū)動的init接口進(jìn)行 設(shè)備初始化;如果設(shè)備控制塊中的flag標(biāo)志已經(jīng)設(shè)置成激活狀 態(tài),那么再運(yùn)行初始化接口時,會立刻返回,而不會重新進(jìn)行 初始化。
2、open
打開設(shè)備。有些設(shè)備并不是系統(tǒng)一啟動就已經(jīng)打開開始運(yùn)行; 或者設(shè)備需要進(jìn)行數(shù)據(jù)接收,但如果上層應(yīng)用還未準(zhǔn)備好,設(shè) 備也
不應(yīng)默認(rèn)已經(jīng)使能并開始接收數(shù)據(jù)。所以建議在寫底層驅(qū) 動程序時,應(yīng)在調(diào)用open接口時才使能設(shè)備。3、close
關(guān)閉設(shè)備。建議在打開設(shè)備時,設(shè)備驅(qū)動自行維護(hù)一個打開計數(shù),在打開設(shè)備時進(jìn)行+1操作,在關(guān)閉設(shè)備時進(jìn)行-1操作, 當(dāng)計數(shù)
器變?yōu)?時,進(jìn)行真正的關(guān)閉操作。4、read
從設(shè)備中讀取數(shù)據(jù)。參數(shù)pos指出讀取數(shù)據(jù)的偏移量,但是有些 設(shè)備并不一定需要指定偏移量,例如串口設(shè)備,設(shè)備驅(qū)動應(yīng)忽 略這
個參數(shù)。而對于塊設(shè)備來說,pos以及size都是以塊設(shè)備的 數(shù)據(jù)塊大小做為單位的。例如塊設(shè)備的數(shù)據(jù)塊大小是512,而參 數(shù)中pos= 10, size = 2,那么驅(qū)動應(yīng)該返回設(shè)備中第10個塊 (從第0個塊做為起始),共計2個塊的數(shù)據(jù)。這個接口返回的 類型rt_size_t,即讀到的字節(jié)數(shù)或塊數(shù)目。正常情況下應(yīng) 該會返回參數(shù)中size的數(shù)值,如果返回零請設(shè)置對應(yīng)的errno值。
5、write
向設(shè)備中寫入數(shù)據(jù)。參數(shù)pos指出寫入數(shù)據(jù)的偏移量。與讀操作 類似,對于塊設(shè)備來說,pos以及size都是以塊設(shè)備的數(shù)據(jù)塊 大小做
為單位的。這個接口返回的類型是rt_size_t,即真實(shí)寫 入數(shù)據(jù)的字節(jié)數(shù)或塊數(shù)目。正常情況下應(yīng)該會返回參數(shù)中size 的數(shù)值,如果返回零請設(shè)置對應(yīng)的errno值。6、control
根據(jù)不同的cmd命令控制設(shè)備。命令往往是由底層各類設(shè)備驅(qū) 動自定義實(shí)現(xiàn)。例如參數(shù)RT_DEVICE_CTRL_BLK_GETGEOME,意思 是獲取塊設(shè)備的大小信息。三、設(shè)備驅(qū)動實(shí)現(xiàn)的步驟在實(shí)現(xiàn)一個RT-Thread設(shè)備時,可以按照如下的步驟進(jìn)行(對于一些復(fù)雜的設(shè)備驅(qū)動,例如以太網(wǎng)接口驅(qū)動、圖形設(shè)備驅(qū)動,請參看網(wǎng)絡(luò)組件、GUI部分章節(jié)):
? 按照RT-Thread的對象模型,擴(kuò)展一個對象有兩種方式:
? 定義自己的私有數(shù)據(jù)結(jié)構(gòu),然后賦值到RT-Thread設(shè)備控制塊的user_data指針上;
? 從struct rt_device結(jié)構(gòu)中進(jìn)行派生。
? 實(shí)現(xiàn)RT-Thread I/O設(shè)備模塊中定義的6個公共設(shè)備接口,開始可以是空函數(shù)(返回類型是rt_err_t的可默認(rèn)返回RT_EOK);
? 根據(jù)自己的設(shè)備類型定義自己的私有數(shù)據(jù)域。特別是在可能有多個相類似設(shè)備的情況下(例如串口1、2),設(shè)備接口可以共用同一套接口,不同的只是各自 的數(shù)據(jù)域(例如寄存器基地址);
? 根據(jù)設(shè)備的類型,注冊到RT-Thread設(shè)備框架中。
總結(jié)
以上是生活随笔為你收集整理的RT-Thread的I/O设备模块及其驱动实现步骤的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。