深入理解 RT-Thread I/O 設(shè)備模型 — 分析 UART設(shè)備源碼。
目錄
前言
一、初識(shí) UART 操作函數(shù)(應(yīng)用程序)
二、UART 的初始化
2.1 UART 設(shè)備初始化位置
2.2 UART 設(shè)備初始化函數(shù)分析
stm32_uart 結(jié)構(gòu)體
UARTX_CONFIG
stm32_uart 結(jié)構(gòu)體初始化
2.3 UART 設(shè)備初始化結(jié)果圖
三、UART 設(shè)備驅(qū)動(dòng)框架層
設(shè)備驅(qū)動(dòng)框架層如何與設(shè)備驅(qū)動(dòng)層關(guān)聯(lián)
四、UART 設(shè)備驅(qū)動(dòng)層
前言
上文我們認(rèn)識(shí)了解了 RT-Thread I/O 設(shè)備模型,本來計(jì)劃是從最簡單的設(shè)備 GPIO 口開始講解 RT-Thread 的設(shè)備模型,但是實(shí)際上 PIN 設(shè)備模型有點(diǎn)特殊,并不是完美符合上一篇博文中 《2.3 訪問 I/O 設(shè)備相關(guān)》小結(jié)介紹的函數(shù),所以這個(gè)我們放在后面文章說明。
而 UART 設(shè)備模型的操作完美貼合上一篇博文的介紹,所以我把 UART 設(shè)備先說明了,這樣更加加深一下對 RT-Thread I/O 設(shè)備模型的認(rèn)識(shí)。
本文從 UART 設(shè)備驅(qū)動(dòng)層 和 設(shè)備驅(qū)動(dòng)框架層 分析 RT-Thread 中 UART 設(shè)備的實(shí)現(xiàn)。目的在于通過官方一個(gè)成熟的設(shè)備驅(qū)動(dòng)的實(shí)例,讓我們確實(shí)的理解體會(huì) RT-Thread I/O 設(shè)備模型。
本 RT-Thread 專欄記錄的開發(fā)環(huán)境:
RT-Thread記錄(一、RT-Thread 版本、RT-Thread Studio開發(fā)環(huán)境 及 配合CubeMX開發(fā)快速上手)
RT-Thread記錄(二、RT-Thread內(nèi)核啟動(dòng)流程 — 啟動(dòng)文件和源碼分析)
RT-Thread 設(shè)備篇系列博文鏈接:
RT-Thread記錄(十、全面認(rèn)識(shí) RT-Thread I/O 設(shè)備模型)
一、初識(shí) UART 操作函數(shù)(應(yīng)用程序)
首先我們來看一下在 RT-Thread 中 UART 操作函數(shù),這是模型框架中最上層的應(yīng)用層所需要調(diào)用的函數(shù),如下面的表格:
rt_device_find() | 查找設(shè)備 |
rt_device_open() | 打開設(shè)備 |
rt_device_read() | 讀取數(shù)據(jù) |
rt_device_write() | 寫入數(shù)據(jù) |
rt_device_control() | 控制設(shè)備 |
rt_device_set_rx_indicate() | 設(shè)置接收回調(diào)函數(shù) |
rt_device_set_tx_complete() | 設(shè)置發(fā)送完成回調(diào)函數(shù) |
rt_device_close() | 關(guān)閉設(shè)備 |
可以看到,對 UART 的操作和上一篇文章 《RT-Thread記錄(十、全面認(rèn)識(shí) RT-Thread I/O 設(shè)備模型)》 幾乎一模一樣,這也是前言中我說的為什么 UART 設(shè)備模型 是復(fù)習(xí)理解 RT-Thread I/O 設(shè)備模型的完美設(shè)備。
對于這些操作函數(shù),是給最上層的應(yīng)用程序使用的,我們要使用一個(gè) UART 設(shè)備,應(yīng)用程序最開始肯定是需要使用rt_device_find()查找設(shè)備,在上一篇文章說過,大部分常用的設(shè)備 RT-Thread 已經(jīng)幫我們寫好了驅(qū)動(dòng),我們直接在應(yīng)用層調(diào)用操作接口即可,UART的驅(qū)動(dòng)也是 RT-Thread 已經(jīng)寫好的。
那么我們該查找什么名字呢?RT-Thread 底層是如何實(shí)現(xiàn)的呢? 帶著這些問題,我們從最開始來分析說明一下 RT-Thread 的 UART 設(shè)備。
?? 先列出 RT-Thread 的 UART 操作函數(shù),讓我們對 UART 應(yīng)用層的函數(shù)有個(gè)了解,然后帶著一些好奇讓我們從底層源碼來分析一下 RT-Thread 的 UART 設(shè)備。
二、UART 的初始化
首先,UART 設(shè)備作為一個(gè)外設(shè),肯定需要初始化,我們在系列博文第二篇《RT-Thread記錄(二、RT-Thread內(nèi)核啟動(dòng)流程 — 啟動(dòng)文件和源碼分析)》分析過 RT-Thread 初始化。
2.1 UART 設(shè)備初始化位置
在文中章節(jié) “2.2.1 板級硬件初始化 — rt_hw_board_init” 講到了硬件初始化相關(guān),如下圖:
在 rt_hw_board_init()
函數(shù)中有一個(gè) hw_board_init
,使用到的 UART 設(shè)備的初始化就在這個(gè)函數(shù)里面,如圖:
說明一下,這個(gè)hw_board_init
里面初始化的哪些設(shè)備是和 RT-Thread 配置一一對應(yīng)的。
注意到他們都是條件編譯,在 env 工具中配置了使用的外設(shè)之后,都會(huì)在這里進(jìn)行初始化,對于我們使用 RT-Thread Studio 來說,就是如下圖所示:
2.2 UART 設(shè)備初始化函數(shù)分析
通過上文介紹,我們找到了 UART 設(shè)備的初始化函數(shù) rt_hw_usart_init
:
int rt_hw_usart_init(void)
{
rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart);
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
stm32_uart_get_dma_config();
for (int i = 0; i < obj_num; i++)
{
uart_obj[i].config = &uart_config[i];
uart_obj[i].serial.ops = &stm32_uart_ops;
uart_obj[i].serial.config = config;
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR
| RT_DEVICE_FLAG_INT_RX
| RT_DEVICE_FLAG_INT_TX
| uart_obj[i].uart_dma_flag
, NULL);
RT_ASSERT(result == RT_EOK);
}
return result;
}
這個(gè)初始化函數(shù)直接看上去,只有一個(gè)函數(shù)我們比較熟悉rt_hw_serial_register,顧名思義,串口設(shè)備注冊函數(shù),不同于簡單的 I/O 設(shè)備注冊函數(shù) rt_device_register,說明它 UART 設(shè)備還有設(shè)備驅(qū)動(dòng)框架層,這個(gè)rt_hw_serial_register就是 UART 設(shè)備驅(qū)動(dòng)框架層定義的函數(shù)。
這個(gè)設(shè)備驅(qū)動(dòng)層 和 設(shè)備驅(qū)動(dòng)框架層我們待會(huì)再來說明,我們先從頭簡單分析一下這個(gè) UART 設(shè)備驅(qū)動(dòng)程序。
第一句,這個(gè)語句是為了確認(rèn)一下有幾個(gè)串口設(shè)備需要進(jìn)行初始化:
rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart);
其中 uart_obj
有如下定義:
static struct stm32_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {0};
☆ uart_obj
是 stm32_uart
類型的結(jié)構(gòu)體數(shù)組,其數(shù)組長度為sizeof(uart_config)/sizeof(uart_config[0])
☆
stm32_uart 結(jié)構(gòu)體
在 RT-Thread 操作系統(tǒng)中,對 UART設(shè)備的初始化,可以理解為就是對 stm32_uart 結(jié)構(gòu)體對象 的初始化 。
我畫了一張結(jié)構(gòu)圖如下:
stm32_uart
結(jié)構(gòu)體這里我們先不分析里面具體的含義,在后文對應(yīng)的地方都會(huì)有響應(yīng)的說明,我們先回到初始化的問題上來。
我們接著上面分析,數(shù)組變量 uart_obj
的長度是多少呢?看一下 uart_config
是什么,如下圖:
uart_config
是 stm32_uart_config
類型的結(jié)構(gòu)體數(shù)組,其數(shù)組長度是根據(jù) RT-Thread 配置使用哪些串口決定的。
比如我們使用了 串口1 和 串口3,那么uart_config
就等于:
static struct stm32_uart_config uart_config[2] =
{
UART1_CONFIG,
UART3_CONFIG,
};
UARTX_CONFIG
這里講到 UART1_CONFIG
就順帶提一下,UART1_CONFIG
是 stm32_uart_config
類型的結(jié)構(gòu)體,在RT-Thread 中是通過 宏定義來定義的:
引出這么多,我們回到最初的rt_hw_usart_init
函數(shù)第一句的代碼:
rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart);
以上面為例,只使用了 UART1 和 UART3 ,uart_obj
數(shù)組長度為2,也就表明有2個(gè)stm32_uart
結(jié)構(gòu)體的成員需要進(jìn)行初始化,也就是需要初始化 2個(gè) UART 設(shè)備。 上面句子中 obj_num = 2;
接下來的語句:
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
串口配置結(jié)構(gòu)體,初始化等于默認(rèn)配置,這里具體也好理解,看下圖便知:
再往下看,獲取串口 DMA 配置:
stm32_uart_get_dma_config();
函數(shù)如下,如果沒有使用DMA ,那么只會(huì)有一條語句,就是 uart_dma_flag = 0;
表示沒有使用DMA。
在上面我們介紹stm32_uart
結(jié)構(gòu)體的時(shí)候,uart_dma_flag
就是這個(gè)結(jié)構(gòu)體的一個(gè)成員變量。
stm32_uart 結(jié)構(gòu)體初始化
再接下來就是uart_obj[i]
的初始化了,有幾個(gè)串口就初始化幾遍:
for (int i = 0; i < obj_num; i++)
{
uart_obj[i].config = &uart_config[i];
uart_obj[i].serial.ops = &stm32_uart_ops;
uart_obj[i].serial.config = config;
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR
| RT_DEVICE_FLAG_INT_RX
| RT_DEVICE_FLAG_INT_TX
| uart_obj[i].uart_dma_flag
, NULL);
RT_ASSERT(result == RT_EOK);
}
首先里面第一句:
uart_obj[i].config = &uart_config[i];
其中 uart_config[i]
就是我們上文說的 UARTX_CONFIG,通過宏定義定義的 stm32_uart_config
類型的結(jié)構(gòu)體。
第二句:
uart_obj[i].serial.ops = &stm32_uart_ops;
上文分析過stm32_uart
結(jié)構(gòu)體,但是并沒有深入分析其中的成員serial
,它是 RT-Thread 的 UART 設(shè)備對象控制塊,其中ops
為結(jié)構(gòu)體類型的指針:
stm32_uart_ops
為 RT-Thread 設(shè)備驅(qū)動(dòng)層定義好的,其作用是指定 UART 設(shè)備的操作函數(shù):
第三句:
uart_obj[i].serial.config = config;
上文講過的,默認(rèn)都是RT_SERIAL_CONFIG_DEFAULT
,如果我們需要修改,可以通過rt_device_control
修改。
第四句:
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR
| RT_DEVICE_FLAG_INT_RX
| RT_DEVICE_FLAG_INT_TX
| uart_obj[i].uart_dma_flag
, NULL);
這個(gè)函數(shù)就是我們講過的 I/O 設(shè)備模型中的設(shè)備注冊函數(shù),如圖:
在上面初始化中:
uart_obj[i].serial 為 rt_serial_device 類型,就是 UART 設(shè)備的控制塊,它付給注冊函數(shù)第一個(gè)參數(shù);
uart_obj[i].config->name 中的name名字,就是設(shè)備注冊后 使用rt_device_find() 尋找的名字。
其中rt_hw_serial_register函數(shù)屬于(設(shè)備驅(qū)動(dòng)框架層的函數(shù)),他會(huì)調(diào)用通用的 rt_device_register(I/O設(shè)備管理層的函數(shù))對 UART 設(shè)備進(jìn)行注冊。
2.3 UART 設(shè)備初始化結(jié)果圖
經(jīng)過上面的一系列分析,最終一個(gè) UART設(shè)備初始化以后的結(jié)果如下圖所示:
?? UART 的初始化,最主要的是要了解 stm32_uart
結(jié)構(gòu)體(以STM32驅(qū)動(dòng)為例),通過對結(jié)構(gòu)體的認(rèn)識(shí),初始化步驟的分析,讓我們認(rèn)識(shí)到了RT-Thread 對于 UART 設(shè)備驅(qū)動(dòng)層的設(shè)計(jì),也讓我們接下來對認(rèn)識(shí) 不同層之間如何聯(lián)系打下了一定的基礎(chǔ)。
三、UART 設(shè)備驅(qū)動(dòng)框架層
我們回頭來看本文開頭說的 UART 那些操作函數(shù),再結(jié)合上文所提到的初始,再結(jié)合上一篇文章《RT-Thread記錄(十、全面認(rèn)識(shí) RT-Thread I/O 設(shè)備模型)》的基礎(chǔ),我們可以確定,上層應(yīng)用所用到的UART 操作函數(shù)就是在使用rt_hw_serial_register 時(shí)候關(guān)聯(lián)到驅(qū)動(dòng)框架層的:
而且再復(fù)習(xí)一下, 設(shè)備驅(qū)動(dòng)框架層是 RT-Thread 系統(tǒng)的東西,官方已經(jīng)寫好的,UART 設(shè)備驅(qū)動(dòng)框架層的代碼為 serial.c
,其位置如下圖:
在其對應(yīng)的 serial.h
頭文件中包含了許多 UART 設(shè)備通用的宏定義,大家可以自行查看。
設(shè)備驅(qū)動(dòng)框架層如何與設(shè)備驅(qū)動(dòng)層關(guān)聯(lián)
☆在這里我們主要需要關(guān)注的就是,設(shè)備驅(qū)動(dòng)框架層是如何 和 設(shè)備驅(qū)動(dòng)層關(guān)聯(lián)起來的?!?/p>
首先我們先看一下其中的幾個(gè)串口操作函數(shù):
我們隨意查看其中一個(gè)函數(shù)查看,如下圖:
可以看到上圖有一句關(guān)鍵的代碼:
if (serial->ops->configure)
result = serial->ops->configure(serial, &serial->config);
上面我們在將初始化的時(shí)候有過代碼:
/*
static const struct rt_uart_ops stm32_uart_ops =
{
.configure = stm32_configure,
.control = stm32_control,
.putc = stm32_putc,
.getc = stm32_getc,
.dma_transmit = stm32_dma_transmit
};
*/
uart_obj[i].serial.ops = &stm32_uart_ops;
所以上面的表格可進(jìn)一步的改為如下對應(yīng)表格:
通過上面的分析,基本上有點(diǎn)撥云見日的感覺!
?? UART 設(shè)備驅(qū)動(dòng)框架層是 RT-Thread 系統(tǒng)通用的,他上連接 I/O 設(shè)備管理層,下連接 設(shè)備驅(qū)動(dòng)層。 通過分析,我們已經(jīng)知道他們之間如何關(guān)聯(lián)。
四、UART 設(shè)備驅(qū)動(dòng)層
其實(shí)在上面的文章分析的時(shí)候已經(jīng)說清楚了 UART 設(shè)備驅(qū)動(dòng)是如何與 設(shè)備驅(qū)動(dòng)層關(guān)聯(lián)起來的。
在 RT-Thread 中,我們的 UART 設(shè)備驅(qū)動(dòng)文件為:drv_usart.c
,其位置位于 drivers
文件夾下面:
這一層就是與我們使用的硬件設(shè)備直接關(guān)聯(lián)的一層,我們在上面介紹的 UART 設(shè)備初始化函數(shù)也在這個(gè)驅(qū)動(dòng)文件中。
再次復(fù)習(xí)一下,設(shè)備驅(qū)動(dòng)層是與使用的硬件直接關(guān)聯(lián)的,因?yàn)槭褂玫氖荢TM32 ,其很多地方都調(diào)用了 ST官方 HAL 庫的定義,是在 HAL 庫的基礎(chǔ)之上實(shí)現(xiàn)的驅(qū)動(dòng)代碼。
我們只選幾個(gè)部分做示例說明,在驅(qū)動(dòng)中下面幾個(gè)函數(shù)肯定是有的:
配置函數(shù):
我們看一下驅(qū)動(dòng)層的配置函數(shù)stm32_configure
,不難發(fā)現(xiàn)這個(gè)函數(shù)其實(shí)和裸機(jī)中的差不多,其中還調(diào)用了 HAL 庫中的 HAL_UART_Init
函數(shù)(函數(shù)還是比較簡單的,我們這里說明一下舉個(gè)例子即可):
static rt_err_t stm32_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
struct stm32_uart *uart;
RT_ASSERT(serial != RT_NULL);
RT_ASSERT(cfg != RT_NULL);
uart = rt_container_of(serial, struct stm32_uart, serial);
/* uart clock enable */
stm32_uart_clk_enable(uart->config);
/* uart gpio clock enable and gpio pin init */
stm32_gpio_configure(uart->config);
uart->handle.Instance = uart->config->Instance;
uart->handle.Init.BaudRate = cfg->baud_rate;
uart->handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
uart->handle.Init.Mode = UART_MODE_TX_RX;
uart->handle.Init.OverSampling = UART_OVERSAMPLING_16;
switch (cfg->data_bits)
{
case DATA_BITS_8:
uart->handle.Init.WordLength = UART_WORDLENGTH_8B;
break;
case DATA_BITS_9:
uart->handle.Init.WordLength = UART_WORDLENGTH_9B;
break;
default:
uart->handle.Init.WordLength = UART_WORDLENGTH_8B;
break;
}
switch (cfg->stop_bits)
{
case STOP_BITS_1:
uart->handle.Init.StopBits = UART_STOPBITS_1;
break;
case STOP_BITS_2:
uart->handle.Init.StopBits = UART_STOPBITS_2;
break;
default:
uart->handle.Init.StopBits = UART_STOPBITS_1;
break;
}
switch (cfg->parity)
{
case PARITY_NONE:
uart->handle.Init.Parity = UART_PARITY_NONE;
break;
case PARITY_ODD:
uart->handle.Init.Parity = UART_PARITY_ODD;
break;
case PARITY_EVEN:
uart->handle.Init.Parity = UART_PARITY_EVEN;
break;
default:
uart->handle.Init.Parity = UART_PARITY_NONE;
break;
}
if (HAL_UART_Init(&uart->handle) != HAL_OK)
{
return -RT_ERROR;
}
return RT_EOK;
發(fā)送函數(shù):
關(guān)于中斷:
中斷入口函數(shù)還是我們熟悉的USART1_IRQHandler
,其流程如下圖所示:
UART 設(shè)備驅(qū)動(dòng)層直接與 UART 硬件相關(guān),其中函數(shù)都可以直接對硬件進(jìn)行操作,其實(shí)上層應(yīng)用可以直接調(diào)用 驅(qū)動(dòng)層的函數(shù)使用,很多函數(shù)的實(shí)現(xiàn)基于官方的HAL 庫。
結(jié)語
本文通過對 UART設(shè)備初始化分析,對 UART 設(shè)備模型各層次的源碼關(guān)聯(lián)進(jìn)行對應(yīng)說明,通過現(xiàn)成的UART 設(shè)備模型,我們更加的理解了 RT-Thread 的I/O 設(shè)備模型,最后總結(jié)如圖所示:
其實(shí)從應(yīng)用來說,知道不知道底層的這些實(shí)現(xiàn)都沒有太大的關(guān)系,所以即便一下子看不懂也沒有關(guān)系,多看看源碼,靜下心來好看還是不難理解的。
?? 如果上一篇博文還沒能理解 RT-Thread I/O 設(shè)備模型,那么加上這篇文章,你一定行 (* ̄︶ ̄) ??
為了加深對 RT-Thread 的I/O 設(shè)備模型的說明,本文花了不少時(shí)間,在接下來的設(shè)備使用測試中,如果不是特除情況,應(yīng)該就不會(huì)再進(jìn)行這樣的分析了,我們就要正式進(jìn)入 RT-Thread 設(shè)備的使用學(xué)習(xí)過程。
下一篇文章我們就要從 UART 設(shè)備使用開始學(xué)習(xí) RT-Thread 設(shè)備的使用。
-
uart
+關(guān)注
關(guān)注
22文章
1219瀏覽量
101121 -
源碼
+關(guān)注
關(guān)注
8文章
632瀏覽量
29111 -
GPIO
+關(guān)注
關(guān)注
16文章
1189瀏覽量
51839 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1261瀏覽量
39840
發(fā)布評論請先 登錄
相關(guān)推薦
評論