開篇分割線:我們再寫驅(qū)動程序的目的是能夠注冊到系統(tǒng)的框架之中,那么就在創(chuàng)建設(shè)備之初你的設(shè)備結(jié)構(gòu)體(C++中叫類)必須從系統(tǒng)提供的結(jié)構(gòu)中進行派生出新的結(jié)構(gòu)體,根據(jù)自己的設(shè)備類型定義私有數(shù)據(jù)域,
MCU一般會有多個串口,所以串口驅(qū)動也需要支持多個串口的配置,設(shè)備結(jié)構(gòu)體更應(yīng)該以數(shù)組的形式出現(xiàn),config信息就代表了真實的硬件有多少個固定的串口,并通過數(shù)組一次性默認(rèn)配置好,至于是不是要啟用,可以通過預(yù)定義宏的方式進行開關(guān):
有了uart設(shè)備對象以后,我們還有需要能夠操作對象的方法(C++中的類就集成了這一部分),C語言中可以通過函數(shù)指針的方式來實現(xiàn)操作方法的結(jié)構(gòu)體存儲:
/**
* uart operators
*/
struct rt_uart_ops
{
rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
int (*putc)(struct rt_serial_device *serial, char c);
int (*getc)(struct rt_serial_device *serial);
rt_size_t (*dma_transmit)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction);
};
上面定義的是函數(shù)原型的指針:后續(xù)需要我們根據(jù)stm32實現(xiàn)具體的方法來賦值給對應(yīng)的原形,這里先說下每個函數(shù)的作用該實現(xiàn)怎樣的功能,后續(xù)你才好去寫這部分功能。
configure方法:用于配置串口的波特率、數(shù)據(jù)位、校驗位、停止位等參數(shù)。
control方法:用于控制串口。
putc方法:用于串口向外發(fā)送字符數(shù)據(jù)。
getc方法:用于串口獲取接收外部的字符數(shù)據(jù)。
transmit方法:用于數(shù)據(jù)發(fā)送側(cè)重于多個字節(jié)的數(shù)據(jù)發(fā)送。
你是否發(fā)現(xiàn)了一個很奇怪的參數(shù),就是這些操作方法的第一個輸入?yún)?shù)是系統(tǒng)提供的serial的結(jié)構(gòu)體,按理說這里的ops需要進行最底層的硬件操作及數(shù)據(jù)收發(fā),那為什么會是serial,而不是uart呢,其實這源于這些操作函數(shù)的調(diào)用方,假如應(yīng)用和驅(qū)動不是分離的,那么應(yīng)用可以很簡單的知道底層的驅(qū)動是哪個uart,但實際上應(yīng)用和驅(qū)動是隔離開的,應(yīng)用需要通過一個名稱來獲取串口的句柄,而串口的句柄只能來自于系統(tǒng)的定義,也就是serial對象,但是我們實際上需要的是uart,那么這里提前引入一個轉(zhuǎn)換,由成員對象找到派生對象的操作,不得不說C語言的強大,詳細(xì)的分析會放到configure函數(shù)的實現(xiàn)上來講:
/**
* rt_container_of - return the member address of ptr, if the type of ptr is the
* struct type.
*/
#define rt_container_of(ptr, type, member) \
((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))