RT-Thread记录(十一、UART设备—源码解析)

深入理解 rt-thread i/o 设备模型 — 分析 uart设备源码。 目录 前言
一、初识 uart 操作函数(应用程序)
二、uart 的初始化
   2.1 uart 设备初始化位置
   2.2 uart 设备初始化函数分析
       stm32_uart 结构体
       uartx_config
       stm32_uart 结构体初始化
   2.3 uart 设备初始化结果图
三、uart 设备驱动框架层
   设备驱动框架层如何与设备驱动层关联
四、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开发快速上手)
rt-thread记录(二、rt-thread内核启动流程 — 启动文件和源码分析)
rt-thread 设备篇系列博文链接:
rt-thread记录(十、全面认识 rt-thread i/o 设备模型)
一、初识 uart 操作函数(应用程序) 首先我们来看一下在 rt-thread 中 uart 操作函数,这是模型框架中最上层的应用层所需要调用的函数,如下面的表格:
rt_device_find() 查找设备
rt_device_open() 打开设备
rt_device_read() 读取数据
rt_device_write() 写入数据
rt_device_control() 控制设备
rt_device_set_rx_indicate() 设置接收回调函数
rt_device_set_tx_complete() 设置发送完成回调函数
rt_device_close() 关闭设备
可以看到,对 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:
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 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;} 这个初始化函数直接看上去,只有一个函数我们比较熟悉rt_hw_serial_register,顾名思义,串口设备注册函数,不同于简单的 i/o 设备注册函数 rt_device_register,说明它 uart 设备还有设备驱动框架层,这个rt_hw_serial_register就是 uart 设备驱动框架层定义的函数。
这个设备驱动层 和 设备驱动框架层我们待会再来说明,我们先从头简单分析一下这个 uart 设备驱动程序。
第一句,这个语句是为了确认一下有几个串口设备需要进行初始化:
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 类型的结构体数组,其数组长度为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 就等于:
static struct stm32_uart_config uart_config[2] ={ uart1_config, uart3_config,}; uartx_config 这里讲到 uart1_config 就顺带提一下,uart1_config 是 stm32_uart_config 类型的结构体,在rt-thread 中是通过 宏定义来定义的:
引出这么多,我们回到最初的rt_hw_usart_init函数第一句的代码:
rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart); 以上面为例,只使用了 uart1 和 uart3 ,uart_obj数组长度为2,也就表明有2个stm32_uart 结构体的成员需要进行初始化,也就是需要初始化 2个 uart 设备。 上面句子中 obj_num = 2;
接下来的语句:
struct serial_configure config = rt_serial_config_default; 串口配置结构体,初始化等于默认配置,这里具体也好理解,看下图便知:
再往下看,获取串口 dma 配置:
stm32_uart_get_dma_config(); 函数如下,如果没有使用dma ,那么只会有一条语句,就是 uart_dma_flag = 0; 表示没有使用dma。
在上面我们介绍stm32_uart 结构体的时候,uart_dma_flag 就是这个结构体的一个成员变量。
stm32_uart 结构体初始化 再接下来就是uart_obj[i]的初始化了,有几个串口就初始化几遍:
for (int i = 0; i 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 类型的结构体。
第二句:
uart_obj[i].serial.ops = &stm32_uart_ops; 上文分析过stm32_uart 结构体,但是并没有深入分析其中的成员serial,它是 rt-thread 的 uart 设备对象控制块,其中ops为结构体类型的指针:
stm32_uart_ops为 rt-thread 设备驱动层定义好的,其作用是指定 uart 设备的操作函数:
第三句:
uart_obj[i].serial.config = config; 上文讲过的,默认都是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); 这个函数就是我们讲过的 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 设备通用的宏定义,大家可以自行查看。
设备驱动框架层如何与设备驱动层关联 ☆在这里我们主要需要关注的就是,设备驱动框架层是如何 和 设备驱动层关联起来的。☆
首先我们先看一下其中的几个串口操作函数:
我们随意查看其中一个函数查看,如下图:
可以看到上图有一句关键的代码:
if (serial->ops->configure) result = serial->ops->configure(serial, &serial->config); 上面我们在将初始化的时候有过代码:
/*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; 所以上面的表格可进一步的改为如下对应表格:
通过上面的分析,基本上有点拨云见日的感觉!
❤️ 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函数(函数还是比较简单的,我们这里说明一下举个例子即可):
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;  
发送函数:
关于中断:
中断入口函数还是我们熟悉的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 设备的使用。

STM32电源低功耗管理有哪几种呢?
低噪声高保真RIA前置放大器(OPA606)
中兴Axon30旗舰机将搭载强悍且自创的影像系统
用于储存电网电力的液流电池
500mA双灯指示防反接锂电充电管理IC-DP4057
RT-Thread记录(十一、UART设备—源码解析)
科学家发明“智能窗户”,可吸收储存和释放热能
高频电流探头操作原理
几种FTTH室内光缆及应用
三分钟看懂压电蜂鸣器工作原理及应用
无人数据中心实现还需时间 仍然无法摆脱人类的控制
广和通斩获2023 AIoT新维奖·行业先锋榜
有刷电机工作原理
华友钴业三大项目落地!
RC振荡器的二种常见电路
华为P10闪存门高调回应,又来WIFI门,看来三星爆炸门要被遗忘了!
Freescale荣膺华为2012年“杰出核心合作伙伴奖”
梅赛德斯-奔驰新款S级轿跑版车型的测试谍照
上海电信打造多云交换智能IP专网,轻松实现一线入多云
贝塔扬尘检测仪是什么,贝塔射线法扬尘在线监测系统介绍