RT-Thread 记录(十一、I/O 设备模型之 UART 设备 — 源码解析)
前言
上文我们认识了解了 RT-Thread I/O 设备模型,本来计划是从最简单的设备 GPIO 口开始讲解 RT-Thread 的设备模型,但是实际上 PIN 设备模型有点特殊,并不是完美符合上一篇博文中 《2.3 访问 I/O 设备相关》小结介绍的函数,所以这个我们放在后面文章说明。
而 UART 设备模型的操作完美贴合上一篇博文的介绍,所以我把 UART 设备先说明了,这样更加加深一下对 RT-Thread I/O 设备模型的认识。
本文从 UART 设备驱动层 和 设备驱动框架层 分析 RT-Thread 中 UART 设备的实现。目的在于通过官方一个成熟的设备驱动的实例,让我们确实的理解体会 RT-Thread I/O 设备模型。
本 RT-Thread 专栏记录的开发环境:
RT-Thread 记录(一、RT-Thread 版本、RT-Thread Studio 开发环境 及 配合 CubeMX 开发快速上手)
https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59
RT-Thread 记录(二、RT-Thread 内核启动流程 — 启动文件和源码分析)
https://xie.infoq.cn/article/44be1057caace7a6a2c4c4b59
RT-Thread 设备篇系列博文链接:
RT-Thread 记录(十、全面认识 RT-Thread I/O 设备模型)
一、初识 UART 操作函数(应用程序)
首先我们来看一下在 RT-Thread 中 UART 操作函数,这是模型框架中最上层的应用层所需要调用的函数,如下面的表格:
可以看到,对 UART 的操作和上一篇文章 《RT-Thread 记录(十、全面认识 RT-Thread I/O 设备模型)》 几乎一模一样,这也是前言中我说的为什么 UART 设备模型 是复习理解 RT-Thread I/O 设备模型的完美设备。
对于这些操作函数,是给最上层的应用程序使用的,我们要使用一个 UART 设备,应用程序最开始肯定是需要使用rt_device_find()
查找设备,在上一篇文章说过,大部分常用的设备 RT-Thread 已经帮我们写好了驱动,我们直接在应用层调用操作接口即可,UART 的驱动也是 RT-Thread 已经写好的。
那么我们该查找什么名字呢?RT-Thread 底层是如何实现的呢? 带着这些问题,我们从最开始来分析说明一下 RT-Thread 的 UART 设备。
先列出 RT-Thread 的 UART 操作函数,让我们对 UART 应用层的函数有个了解,然后带着一些好奇让我们从底层源码来分析一下 RT-Thread 的 UART 设备。
二、UART 的初始化
首先,UART 设备作为一个外设,肯定需要初始化,我们在系列博文第二篇《RT-Thread 记录(二、RT-Thread 内核启动流程 — 启动文件和源码分析)》分析过 RT-Thread 初始化。
2.1 UART 设备初始化位置
在文中章节 “2.2.1 板级硬件初始化 — rt_hw_board_init” 讲到了硬件初始化相关,如下图:
在 rt_hw_board_init()
函数中有一个 hw_board_init
,使用到的 UART 设备的初始化就在这个函数里面,如图:
说明一下,这个hw_board_init
里面初始化的哪些设备是和 RT-Thread 配置一一对应的。
注意到他们都是条件编译,在 env 工具中配置了使用的外设之后,都会在这里进行初始化,对于我们使用 RT-Thread Studio 来说,就是如下图所示:
2.2 UART 设备初始化函数分析
通过上文介绍,我们找到了 UART 设备的初始化函数 rt_hw_usart_init
:
这个初始化函数直接看上去,只有一个函数我们比较熟悉rt_hw_serial_register
,顾名思义,串口设备注册函数,不同于简单的 I/O 设备注册函数 rt_device_register
,说明它 UART 设备还有设备驱动框架层,这个rt_hw_serial_register
就是 UART 设备驱动框架层定义的函数。
这个设备驱动层 和 设备驱动框架层我们待会再来说明,我们先从头简单分析一下这个 UART 设备驱动程序。
第一句,这个语句是为了确认一下有几个串口设备需要进行初始化:
其中 uart_obj
有如下定义:
☆ uart_obj
是 stm32_uart
类型的结构体数组,其数组长度为sizeof(uart_config)/sizeof(uart_config[0])
☆
stm32_uart 结构体
在 RT-Thread 操作系统中,对 UART 设备的初始化,可以理解为就是对 stm32_uart 结构体对象 的初始化 。
我画了一张结构图如下:
stm32_uart
结构体这里我们先不分析里面具体的含义,在后文对应的地方都会有响应的说明,我们先回到初始化的问题上来。
我们接着上面分析,数组变量 uart_obj
的长度是多少呢?看一下 uart_config
是什么,如下图:
uart_config
是 stm32_uart_config
类型的结构体数组,其数组长度是根据 RT-Thread 配置使用哪些串口决定的。
比如我们使用了 串口 1 和 串口 3,那么uart_config
就等于:
UARTX_CONFIG
这里讲到 UART1_CONFIG
就顺带提一下,UART1_CONFIG
是 stm32_uart_config
类型的结构体,在 RT-Thread 中是通过 宏定义来定义的:
引出这么多,我们回到最初的rt_hw_usart_init
函数第一句的代码:
以上面为例,只使用了 UART1 和 UART3 ,uart_obj
数组长度为 2,也就表明有 2 个stm32_uart
结构体的成员需要进行初始化,也就是需要初始化 2 个 UART 设备。 上面句子中 obj_num = 2;
接下来的语句:
串口配置结构体,初始化等于默认配置,这里具体也好理解,看下图便知:
再往下看,获取串口 DMA 配置:
函数如下,如果没有使用 DMA ,那么只会有一条语句,就是 uart_dma_flag = 0;
表示没有使用 DMA。
在上面我们介绍stm32_uart
结构体的时候,uart_dma_flag
就是这个结构体的一个成员变量。
stm32_uart 结构体初始化
再接下来就是uart_obj[i]
的初始化了,有几个串口就初始化几遍:
首先里面第一句:
其中 uart_config[i]
就是我们上文说的 UARTX_CONFIG,通过宏定义定义的 stm32_uart_config
类型的结构体。
第二句:
上文分析过stm32_uart
结构体,但是并没有深入分析其中的成员serial
,它是 RT-Thread 的 UART 设备对象控制块,其中ops
为结构体类型的指针:
stm32_uart_ops
为 RT-Thread 设备驱动层定义好的,其作用是指定 UART 设备的操作函数:
第三句:
上文讲过的,默认都是RT_SERIAL_CONFIG_DEFAULT
,如果我们需要修改,可以通过rt_device_control
修改。
第四句:
这个函数就是我们讲过的 I/O 设备模型中的设备注册函数,如图:
在上面初始化中:uart_obj[i].serial
为 rt_serial_device 类型,就是 UART 设备的控制块,它付给注册函数第一个参数;uart_obj[i].config->name
中的 name 名字,就是设备注册后 使用rt_device_find()
寻找的名字。
其中rt_hw_serial_register
函数属于(设备驱动框架层的函数),他会调用通用的 rt_device_register
(I/O 设备管理层的函数)对 UART 设备进行注册。
2.3 UART 设备初始化结果图
经过上面的一系列分析,最终一个 UART 设备初始化以后的结果如下图所示:
UART 的初始化,最主要的是要了解 stm32_uart
结构体(以 STM32 驱动为例),通过对结构体的认识,初始化步骤的分析,让我们认识到了 RT-Thread 对于 UART 设备驱动层的设计,也让我们接下来对认识 不同层之间如何联系打下了一定的基础。
三、UART 设备驱动框架层
我们回头来看本文开头说的 UART 那些操作函数,再结合上文所提到的初始,再结合上一篇文章《RT-Thread 记录(十、全面认识 RT-Thread I/O 设备模型)》的基础,我们可以确定,上层应用所用到的 UART 操作函数就是在使用rt_hw_serial_register
时候关联到驱动框架层的:
而且再复习一下, 设备驱动框架层是 RT-Thread 系统的东西,官方已经写好的,UART 设备驱动框架层的代码为 serial.c
,其位置如下图:
在其对应的 serial.h
头文件中包含了许多 UART 设备通用的宏定义,大家可以自行查看。
设备驱动框架层如何与设备驱动层关联
☆在这里我们主要需要关注的就是,设备驱动框架层是如何 和 设备驱动层关联起来的。☆
首先我们先看一下其中的几个串口操作函数:
我们随意查看其中一个函数查看,如下图:
可以看到上图有一句关键的代码:
上面我们在将初始化的时候有过代码:
所以上面的表格可进一步的改为如下对应表格:
通过上面的分析,基本上有点拨云见日的感觉!
UART 设备驱动框架层是 RT-Thread 系统通用的,他上连接 I/O 设备管理层,下连接 设备驱动层。 通过分析,我们已经知道他们之间如何关联。
四、UART 设备驱动层
其实在上面的文章分析的时候已经说清楚了 UART 设备驱动是如何与 设备驱动层关联起来的。
在 RT-Thread 中,我们的 UART 设备驱动文件为:drv_usart.c
,其位置位于 drivers
文件夹下面:
这一层就是与我们使用的硬件设备直接关联的一层,我们在上面介绍的 UART 设备初始化函数也在这个驱动文件中。
再次复习一下,设备驱动层是与使用的硬件直接关联的,因为使用的是 STM32 ,其很多地方都调用了 ST 官方 HAL 库的定义,是在 HAL 库的基础之上实现的驱动代码。
我们只选几个部分做示例说明,在驱动中下面几个函数肯定是有的:
配置函数:
我们看一下驱动层的配置函数stm32_configure
,不难发现这个函数其实和裸机中的差不多,其中还调用了 HAL 库中的 HAL_UART_Init
函数(函数还是比较简单的,我们这里说明一下举个例子即可):
发送函数:
关于中断:
中断入口函数还是我们熟悉的USART1_IRQHandler
,其流程如下图所示:
UART 设备驱动层直接与 UART 硬件相关,其中函数都可以直接对硬件进行操作,其实上层应用可以直接调用 驱动层的函数使用,很多函数的实现基于官方的 HAL 库。
结语
本文通过对 UART 设备初始化分析,对 UART 设备模型各层次的源码关联进行对应说明,通过现成的 UART 设备模型,我们更加的理解了 RT-Thread 的 I/O 设备模型,最后总结如图所示:
其实从应用来说,知道不知道底层的这些实现都没有太大的关系,所以即便一下子看不懂也没有关系,多看看源码,静下心来好看还是不难理解的。
如果上一篇博文还没能理解 RT-Thread I/O 设备模型,那么加上这篇文章,你一定行 (* ̄︶ ̄)
为了加深对 RT-Thread 的 I/O 设备模型的说明,本文花了不少时间,在接下来的设备使用测试中,如果不是特除情况,应该就不会再进行这样的分析了,我们就要正式进入 RT-Thread 设备的使用学习过程。
下一篇文章我们就要从 UART 设备使用开始学习 RT-Thread 设备的使用。
版权声明: 本文为 InfoQ 作者【矜辰所致】的原创文章。
原文链接:【http://xie.infoq.cn/article/38bb4bf15cb81f1fdb060b29e】。文章转载请联系作者。
评论