1. 程式人生 > >RT-Thread的I/O裝置模組及其驅動實現步驟

RT-Thread的I/O裝置模組及其驅動實現步驟


一、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裝置框架中。