RT-Thread 记录(十二、I/O 设备模型之 UART 设备 — 使用测试)
前言
通过前面的两篇文章,我们基本上完全明白了 RT-Thread I/O 设备模型的基本原理,当然我们的最终目的还是应用,所以本文开始我们就开始进行常用设备的使用学习和测试,就从 UART 设备开始。
从本文开始,就开始进行常用 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 设备模型)
https://xie.infoq.cn/article/40536d29988d683c78b4ba5ff
RT-Thread 记录(十一、I/O 设备模型之 UART 设备 — 源码解析)
一、UART 设备操作
虽然在上一篇文章中,我们已经认识过 RT-Thread UART 的操作函数,但是我们并没有对其参数进行说明。学习使用一个设备,在 RT-Thread 系统中就是一个对象, 还是得按照我们之前的流程进行简单介绍。
1.1 UART 设备控制块
在我们前面许多文章介绍其他内核对象的时候,我们首先都会介绍其对象控制块,对于 UART 设备而言,它也有自己的控制块。
但是与其他对象机制不同的是,UART 属于 I/O 设备,对于上层应用程序而言,所有的 I/O 设备都是属于 struct rt_device
类。
在我们前面文章《RT-Thread 记录(十、全面认识 RT-Thread I/O 设备模型》初次介绍 I/O 设备模型的时候就已经说明了这个统一的控制块:
上面的控制块是对于应用程序而言,在我们的 UART 设备的设备驱动框架层,是有定义了 UART 设备自己的控制块,其继承了rt_device
的内容,同时还增加了 UART 设备特有的一些配置,操作,回调函数之类的内容,如下图:
上面的 UART 设备控制块在我们的上一篇文章也有过分析说明。
UART 设备属于 I/O 设备大类中的一个小类,对于上层应用程序而言,UART 设备控制块rt_serial_device
并不透明,我们用户操作的还是 I/O 设备模型的控制块rt_device_t
类型。
1.2 UART 操作函数
因为 UART 的操作函数 与 I/O 设备的操作函数基本一致,所以本小结有点类似《RT-Thread 记录(十、全面认识 RT-Thread I/O 设备模型》中的 2.3 访问 I/O 设备相关 API 操作,但是针对 UART 设备,也有一些独有的参数说明。
老规矩,函数介绍部分说明看注释。
1.2.1 查找 UART 设备
需要先定义一个 I/O 设备结构体(rt_device_t
类型)的指针变量,接收创建好的句柄。
1.2.2 打开/关闭 UART 设备
先说打开 UART 设备:
打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。
如果 oflags 没有指定使用中断模式或者 DMA 模式,则默认使用轮询模式。
这里有个问题,流模式是什么情况下使用的?485 通讯? 暂时不知道,希望知道的朋友能够给个说明。
在官方的文档中,关于流模式有如下说明:
实际应用中的串口读写说明
串口 RX:
在我们正常的项目使用中,一般都是 中断接收 或者 DMA 接收,基本上不会使用 轮询接收的方式(极大的浪费资源,反正我是没用过)。
所以我们打开串口设备的时候,基本上都是如下两种:
在 RT-Thread 系统中,我们常用信号量或者消息队列 来标志是否接收到串口数据,这样的好处是当没有数据的时候,会将数据处理线程挂机,让出 CPU 资源。
串口 TX:
对于串口 TX 来说,大部分项目中我自己一直都用的是 轮询 方式发送。
对于串口的中断发送方式,在上一篇文章我们分析 UART 源码,虽然没有详细说明,但是实际上在设备驱动层 drv_usart.c
驱动文件里,中断发送方式最终还是调用了该驱动文件里面的stm32_putc
函数:
我感觉还是和轮询一样,将数据写入 数据寄存器 DR,使用 while 死等发送完成(虽然时间很短)。
上面虽然只是 RT-Thread 中的 UART 设备驱动文件,也多少能说明一些问题,中断发送最终无非就是发送完了多一个中断通知。
对于另外一种 DMA 发送,我记得以前听老人提到过,DMA 发送使用不得当,可能导致发送数据异常,简单来说就是 DMA 发送函数返回后,数据都不一定发送完成了,如果此时修改了 DMA 发送指定的 buffer 区的内容,那么后面的数据就错误了。
所以,如果没有特殊需求,我们项目中的串口发送使用 轮询发送 即可(有些特殊情况的根据自己的实际需求而定)。
所以结合上面所说,我们实际应用中,使用以下两种方式打开串口设备能满足大部分场合需求:
有打开设备,当然也有关闭设备:
关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。
当然在一般的应用场合,用到串口通讯的地方设备都是需要一直开启的,所以很多情况下都不需要使用 UART 设备关闭函数。
1.2.3 控制 UART 设备
rt_device_control
一般用在 rt_device_open
(打开串口设备)之前,对需要使用的串口进行必要的配置。
我们已经知道,在串口初始化的时候会有一个默认配置:
所以在我们使用串口的时候,如果对应的配置与默认的配置不一样,就需要使用此函数修改配置。
接收缓冲区:
当串口使用中断接收模式打开时,串口驱动框架会根据 RT_SERIAL_RB_BUFSZ
大小开辟一块缓冲区用于保存接收到的数据,底层驱动接收到一个数据,都会在中断服务程序里面将数据放入缓冲区。
在修改缓冲区大小时请注意,缓冲区大小无法动态改变,只有在 open 设备之前可以配置。open 设备之后,缓冲区大小不可再进行更改。但除缓冲区之外的其他参数,在 open 设备前 / 后,均可进行更改。
串口控制修改使用官方修改示例说明一下:
1.2.4 发送数据
写其实很好理解,除了多一个设备句柄参数,和我们裸机中使用的发送函数一样,看一下一个普通的裸机串口发送函数:
这里说明一下,因为我们上面分析过实际应用中的串口读写,一般都使用轮询发送,所以我这里并不打断介绍 设置发送完成回调函数 。
1.2.5 设置接收回调函数
若串口以中断接收模式打开:当串口接收到一个数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在 size 参数里,把串口设备句柄放在 dev 参数里供调用者获取。
若串口以 DMA 接收模式打开:当 DMA 完成一批数据的接收后会调用此回调函数。
在使用 RT-Thread 时候,一般会用一个信号量通知串口数据处理线程有数据到达。
在使用 RT-Thread Nano 的时候,其实我也是使用信号量来处理数据的接收:
具体详情可查看博文:
RT-Thread 应用篇 — 在 STM32L051 上使用 RT-Thread (四、无线温湿度传感器 之 串口通讯)https://xie.infoq.cn/article/550465aa9a61bf7f4bf3258b2
回调函数处理的示例我们使用官方示例说明,与下面的接收数据函数一起展示。
1.2.6 接收数据
数据接收处理函数,在接收回调函数运行之后运行。
我们与上面的设置接收回调函数一起使用官方示例作为说明:
示例中使用的是信号量,收到一个数据,便会唤醒接收数据的线程,所以其实是一个字节一个字节(一个字符等于一个字节)的读取,<font color=#0033FF> 示例处理方式只能使用 RT_DEVICE_FLAG_INT_RX
方式接收。
接收数据rt_device_read
函数的返回值需要注意一下,返回值为读到的数据实际大小,就是接收到的数据长度。
二、UART 设备使用步骤
简单介绍一下在 RT-Thread Studio 开发环境下 UART 的使用步骤。
2.1 RT-Thread setting
如果需要使用某个设备,是需要在 ENV 工具中配置的,现在有了 RT-Thread Studio ,所以可以直接通过工程目录下的 RT-Thread setting 进行图形化界面的配置,如下图:
因为 Shell 工具需要使用串口,所以默认串口这里已经是勾选中的,这里说明只是为了让大家知道,在以后的 I/O 设备使用的时候,第一步就是在 RT-Thread setting 中使能设备。
2.2 board.h 设置
完成设备使能,我们还需要使用宏定义进行串口的基本设置,该设置在board.h
文件中进行,如下图:
board.h
中包括了很多外设的使用说明,除了 UART,还有 I2C、SPI、ADC 等设备,我们在后面学习这些设备使用的时候,需要经常用到这个头文件,一些基本的使能配置都是在这个文件中用宏定义使能。
2.3 应用程序流程
完成上面 2 步的基本配置以后,我们就可以在应用程序通过上文介绍的 UART 设备操作函数进行串口的使用,具体的步骤概括如下:
UART 设备使用步骤 :
/
#include "rtdevice.h"
/
1、使用
rt_device_find
查找串口设备;/
2、根据需求使用
rt_device_control
设置串口;/
3、初始化回调函数中使用的信号量(在接收回调函数中 发送信号量 唤醒数据处理线程),如果使用消息队列接收初始化消息队列;
/
4、使用
rt_device_open
打开串口设备(根据自己的情况判断使用什么方式接收,发送前面分析过了,一本应用使用轮询发送即可);/
5、使用
rt_device_set_rx_indicate
设置串口设备的接收回调函数/
6、创建数据读取的线程。
按照上面的步骤,我进行了如下的示例测试,不要忘记 #include "rtdevice.h"
:
上图其实是根据官方示例代码,使用的 ESP8266 WIFI 模块做了一个简单的测试:
三、UART 示例测试
在上面介绍应用程序流程的时候,其实已经做了一个简单的示例测试。
同时在官方已经也提供了 3 种典型的示例程序:
中断接收及轮询发送、DMA 接收及轮询发送、串口接收不定长数据
作为以应用为目的系列博文,我自己还是根据自己的工作需求进行串口通讯的测试,使用的是 Enocean 无线通讯模块,当时在 RT-Thread 的应用篇,RT-Thread Nano 使用记录的时候就使用的这个无线模块。
要说明的是,用什么模块做通讯并不是重点,重点在于使用过程中对串口数据的处理方式。
3.1 与无线模块串口通讯
虽然换了一个通讯设备,但是官方给的例程:中断接收及轮询发送 还是适用的,我们先来看一看直接使用官方的例程做的测试:
其测试结果如下:
为了更好的做数据解析,我们需要对原始的程序进行修改,使得能够针对一帧数据一帧数据进行接收处理:
测试结果如下,实现了我们所需要的的针对每一帧数据的接收(既然都已经可以区别每一帧数据了,所那么后续的处理也就简单了):
对于数据的接收处理信号量只是其中一种。
即便是信号量,也有多种可实现行的方式,而上面我测试使用的方式也只不过其中的一种:收到第一个数据的时候等待一定的时间,然后认为是一帧数据接收完成。
这也只是判断一帧数据接收完成的方法中的一种 = =!
3.2 示例说明
在上面我们用了信号量作为通知的方式接收串口数据,官方的示: DMA 接收及轮询发送 采用了消息队列的方式进行处理,表面上看起来与我们上面那种方式不一样。
其实本质都是一样的,都不过是给线程一个通知,并没有“真正意义上的传递了消息”(比如串口接收到的数据):
如果想要使用消息队列作为缓存正常的传输串口接收的数据,不使用 I/O 设备模型的情况下更加适合,究其原因,如下图分析:
如上面表格所说,使用了 I/O 设备模型之后,我们底层串口初始化的时候已经有了一段数据接收的 buffer 了,所以我们直接使用 rt_device_read
函数从驱动层的 buffer 读取数据,用临时 buffer 来处理就可以了(不过如果需要对处理程序,单独设计函数,也可以用一个全局 buffer 来处理),也不过是 2 个 buffer 的内存占用。
所以在官方的示例中,虽然给的是信号量,和消息队列的不同的处理方式,但是究其根本还是一样的。只是给了一个通知,这个其他的 IPC 机制 比如 事件集一样可以做到,即便不用 IPC 机制,普通简单的应用,全局变量也未尝不可。(对于消息队列传递串口接收数据的应用,以后我还是会单独的说明的,本文在于说明 UART 基于 I/O 设备模型的使用,所以就不做测试了 = =!)
使用了 UART 设备模型,最终还是需要使用rt_device_read
函数,从内部缓存读取串口数据,IPC 只不过是给线程一个通知。
结语
一个 UART 设备画了两篇文章,还算是比较值得的,通过上一篇文章加深对 RT-Thread I/O 设备模型的理解,通过本文实际体验了一把 UART 设备。
体验上来说,还是感觉特别方便简单的。但是这个前提条件时,能够真正的理解 RT-Thread I/O 设备模型,理解到位才能用起来游刃有余,也能够在以后出问题的时候更容易的发现问题,解决问题。
学会了使用一个东西当然是一件庆幸的事情,但是能够理解它才是更加重要的事情!
推荐阅读:
RT-Thread 应用篇 — 在 STM32L051 上使用 RT-Thread (四、无线温湿度传感器 之 串口通讯)
https://xie.infoq.cn/article/550465aa9a61bf7f4bf3258b2
RT-Thread 记录(六、IPC 机制之信号量、互斥量和事件集)
版权声明: 本文为 InfoQ 作者【矜辰所致】的原创文章。
原文链接:【http://xie.infoq.cn/article/1df47f3ae5174dfcb418b07b6】。文章转载请联系作者。
评论