RTT(RT-Thread)串口设备(RTT保姆级教程)

news2024/7/6 18:01:55

目录

UART串口设备

串口概述

访问串口设备接口

数据发送方法

数据接收方法

串口设备使用流程

串口中断接受实例

串口配置及串口发送

串口中断接收

DMA接收


UART串口设备

串口概述

本章主要介绍串口设备在RT-Thread操作系统中应用层如何使用。关于串口设备的使用,我们只需要在应用层找到串口设备,然后去初始化,初始化完成以后就可以读写串口设备了。

操作的方法在应用层,驱动是不需要我们去完成的,驱动已经在操作系统中提供了。

关于串口的详细介绍和STM32的串口使用可查看我的如下博客:

通用同步异步收发器(USART)(STM32HAL库)

访问串口设备接口

  • 应用程序通过RT-Thread提供的IO设备管理接口来访问串口硬件:

  • 查找串口设备(例:"uart2"),使用rt_device_find函数
  • 打开串口设备(串口收发数据模式:中断、轮询、DMA),使用rt_device_open函数

以上具体查看IO设备章节

  • 控制串口设备
rt_err_trt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
cmd 命令控制字,可取值:RT_DEVICE_CTRL_CONFIG 
arg   控制的参数,可取类型:struct serial_conigure
struct serial_configure
{
    rt_uint32_t baud_rate;    
    rt_uint32_t data_bits :4; 
    rt_uint32_t stop_bits :2;
   rt_uint32_t parity :2;    
   rt_uint32_t bit_order :1; 
   rt_uint32_t invert :1;    
   rt_uint32_t bufsz :16;    
   rt_uint32_t reserved :4; 
};

/* 波特率可取值*/
#define BAUD_RATE_2400 2400
#define BAUD_RATE_4800 4800
#define BAUD_RATE_9600 9600
#define BAUD_RATE_19200 19200
#define BAUD_RATE_38400 38400
#define BAUD_RATE_57600 57600
#define BAUD_RATE_115200 115200
#define BAUD_RATE_230400 230400
#define BAUD_RATE_460800 460800
#define BAUD_RATE_921600 921600
#define BAUD_RATE_2000000 2000000
#define BAUD_RATE_3000000 3000000
/* 数据位可取值*/
#define DATA_BITS_5 5
#define DATA_BITS_6 6
#define DATA_BITS_7 7
#define DATA_BITS_8 8
#define DATA_BITS_9 9
/*停止位可取值 */
#define STOP_BITS_1 0
#define STOP_BITS_2 1
#define STOP_BITS_3 2
#define STOP_BITS_4 3
/*极性位可取值*/
#define PARITY_NONE 0
#define PARITY_ODD 1
#define PARITY_EVEN 2
/* 高低位顺序可取值*/
#define BIT_ORDER_LSB 0
#define BIT_ORDER_MSB 1
/*模式可取值*/
#define NRZ_NORMAL 0 /* normal mode*/
#define NRZ_INVERTED 1 /* invertedmode */
/* 接收数据缓冲区默认大小*/
#define RT_SERIAL_RB_BUFSZ 64

RT-Thread 提供的默认串口配置
#define RT_SERIAL_CONFIG_DEFAULT \
{ \
   BAUD_RATE_115200, /* 115200 bits/s */ \
   DATA_BITS_8, /* 8 databits */ \
   STOP_BITS_1, /* 1 stopbit */ \
   PARITY_NONE, /* No parity */ \
   BIT_ORDER_LSB, /* LSB first sent */ \
   NRZ_NORMAL, /* Normal mode */ \
   RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
0 \
}

注:缓冲区通过 control 接口修改,缓冲区大小无法动态改变,只有在 open 设备之前可以配置。open 设备之后,缓冲区大小不可再进行更改。但除过缓冲区之外的其他参数,在 open 设备前 / 后,均可进行更改。

  • 发送数据

  •  设置发送完成回调函数

  在应用程序调用rt_device_write()写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用

  • 设置接收回调函数

若串口以中断接收模式打开,当串口接收到一个数据产生中断时,就会调用回调函数,并且会把此时缓冲区的数据大小放在size参数里,把串口设备句柄放在dev参数里供调用者获取。

若串口以 DMA 接收模式打开,当 DMA 完成一批数据的接收后会调用此回调函数。

 使用:一般情况下接收回调函数可以发送一个信号量或者事件通知串口数据处理线程有数据到达。(也就是说接收数据需要我们另外开启一个线程来接收,通过回调函数来通知线程是否有数据到达。当没有数据的时候,接收线程应该处于挂起状态,可以采用信号量或事件通知的方式)

比如在线程接收数据的时候,先获取一下信号量,如果有资源,就执行,如果没有就让接收线程挂起。我们需要在设置接收回调函数里面发送信号量。当接收回调函数被调用的时候,说明接收到了数据,一旦接收到了数据就设置信号量来唤醒挂起状态中的接收线程。

  • 接收数据

  • 关闭串口设备
rt_err_t rt_device_close(rt_device_t dev);

数据发送方法

一般来说,对于串口发送没有太多的要求,我们在想要发送的时候只需要调用write函数写入数据即可,只要发送的频率不是那么快(如在while循环里一直发送,可能会导致发送的数据出问题),一般都会间隔一段时间再发,如用延时函数或者定时器一定时间后发送采集到的传感器数据。

但如果发送的频率过快,有可能硬件的上一个数据还没发送出去,下一条发送指令就已经执行到了。这种情况就需要用到发送中断或者DMA的发送方式。

数据接收方法

对于接收来讲,有一个问题。接收并不是由主控设备自己来决定的,因为接收数据的前提条件是对方由数据发过来之后,接收方才能接收。因此接收这块我们必须想方法,看采用什么样的方式才能够更好地接收到数据。

一种方式就是写一个while死循环,然后在循环里面一直读,什么时候对方发送数据,什么时候就接收到数据。但这样在RTT中需要我们单独开启一个线程一直去读,线程一直在while循环里面去读,而且不加延时,这样就与RTT线程的创建和线程的要求相矛盾。所以我们在RT-Thread不采用这种方式。

因此我们可以采用中断或者DMA的方式来接收数据。

中断读取:当硬件设备接收到数据以后,触发接收中断,在中断处理函数里面我们将数据接收到。

DMA读取:开启DMA以后,通过DMA的方式直接从串口数据寄存器中接收数据,接收到数据以后先放到缓冲区。接收一段时间以后,通过触发一个中断,然后我们将数据从缓冲区读走。

串口设备使用流程

其中我们只需要编写应用程序代码,ISR和串口外设相关的硬件驱动代码在操作系统中是有驱动支持的,不需要人为关心。

在硬件产生一个中断以后是如何通知应用层的呢?我们是通过设置回调函数的方式来接收中断信息的。

先来看我们的应用程序端:

应用程序端,我们在编写的时候,首先要初始化信号量,要通过信号量来进行数据的接收;然后设置接收回调函数;接着创建数据处理线程,用来专门处理接收到的数据;最后我们在创建好线程以后,先开启线程,然后让线程阻塞等待信号量,信号量的作用就是实现数据接收的同步。

再解释一下,我们在线程处理函数中写一个while循环,一直去接收数据。但在接收数据之前要先获取信号量,如果信号量能够获取成功,就往下读取数据。如果信号量获取失败,则线程进入休眠态。

接收线程的接收数据还是休眠(挂起),由信号量来决定。也就是说信号量标志着是否有数据发送过来了,而数据是否发过来是由接受回调函数决定的,一旦回调函数被执行。我们就在回调函数中发送信号量,从而唤醒数据处理线程去接收数据。

再来看一下硬件驱动:

当用户从电脑通过串口输入一个字符发送给STM32时,单片机接收到数据之后就会触发一个接收中断,从而调用我们再应用层设置的接收回调函数。在中断服务程序(ISR)中,会将接收的数据放入缓冲区,并在接收回调函数中发送信号量激活线程,也就是说我们设置的回调函数中一定要记得发送信号量。

串口中断接受实例

串口配置及串口发送

1.首先我们先将串口配置好,比如我们在创建工程的时候,选择了串口2。那么创建好工程后,系统会自动配置串口2,并在board.h中可以查看

2.如果我们想再添加一个UART1,我们可以参考自带的步骤说明

(1)首先我们要定义要使用串口的宏

(2)说明所要使用串口的发送和接收引脚

(3)和(4)是使用DMA来配置的,在后面介绍

/** After configuring corresponding UART or UART DMA, you can use it.
 *
 * STEP 1, define macro define related to the serial port opening based on the serial port number
 *                 such as     #define BSP_USING_UART1
 *
 * STEP 2, according to the corresponding pin of serial port, define the related serial port information macro
 *                 such as     #define BSP_UART1_TX_PIN       "PA9"
 *                             #define BSP_UART1_RX_PIN       "PA10"
 *
 * STEP 3, if you want using SERIAL DMA, you must open it in the RT-Thread Settings.
 *                 RT-Thread Setting -> Components -> Device Drivers -> Serial Device Drivers -> Enable Serial DMA Mode
 *
 * STEP 4, according to serial port number to define serial port tx/rx DMA function in the board.h file
 *                 such as     #define BSP_UART1_RX_USING_DMA
 *
 */

3.我们将UART1添加进去

4.添加完之后,我们在main.c中完成对UART1的应用层配置

(1)首先查找串口1设备句柄

(2)成功找到之后,打开串口设备(注意要以读写和接收中断的方式打开)

(3)打开设备之后,我们还需要对串口设备进行协议相关的配置,我们使用rt_device_control函数

首先我们要创建一个串口信息结构体,这里我们使用默认的串口配置

默认配置具体内容如下:

然后我们将结构体取地址传入设备控制函数中,并将cmd设置为设备配置模式

5.配置完成以后,如果我们想要发送数据,需要调用rt_device_write函数,其中第一个参数传入发送的设备,第二个参数为相对于buffer起始地址的偏移量(起始位置),第三个参数为buff(要发送的内容),第四个参数为buff的大小

编译一下,发现有错误

这是因为没有包含串口相关的头文件,我们将serial.h包含进来

编译发送,仍有错:找不到头文件

我们通过光标定位找到头文件位置

我们将头文件路径手动添加到工程中:

(1)打开构建配置

(2)打开C/C++构建中的设置

(3)右边出现汇编工具和C编译工具,我们选择C Compiler的Includes

(4)我们添加目录路径,通过文件系统的方式来添加

(5)重新编译,发现新的错误,serial.h中有些类型无法找到

我们通过定位将相关头文件包含进serial.h中

(6)再次编译发现还是有错误,我们需要将上一级ipc的路径也手动添加到工程中

最终编译发现没有任何错误

6.我们验证下程序是否正确

首先下载程序,然后打开串口

然后选择相应串口,并点击确定

复位STM32开发板,成功发送信息

串口中断接收

在之前已经成功验证了串口1可以正常使用,下面就来写一下中断接收的方式

1.首先设置接收回调函数

第二个参数为我们需要调用的回调函数的函数指针

(1)我们拿到函数原型

(2)然后到main.c中定义

我们起名叫做rx_callback,并返回值设置为RT_EOK,即返回0

(3)最后将设置回调函数相关参数填入

2.回调函数设置好以后,也就是说当硬件接收到数据以后,触发中断。触发中断就会回调我们设置的rx_callback函数。下面我们还需要加上信号量和线程。

3.创建信号量

我们之前使用的是动态创建信号量,所以这里再以静态创建信号量为例

(1)我们首先要定义一个信号量结构体变量,待会儿取地址传入参数

(2)初始化信号量

第一个参数传入刚刚创建的结构体地址

第二个参数为信号量名字,我们表示为接收信号量

第三个参数:信号量的资源值设置为0,因为我们一开始是没有数据的。只有当对方发送数据之后,调用接收回调函数,在回调函数中发送信号量,这样信号量的value值就会加1,从而唤醒接收线程。

第四个参数模式设置为先进先出

这里返回值就不做判断了。

4.创建线程

我们使用动态创建的方式

(1)首先创建线程结构体指针和线程处理函数

(2)然后动态创建线程(由于时间问题,返回值就不做判断了)

5.启动线程

6.完善线程处理函数

(1)我们使用while循环在线程处理函数中一直读取,这里单次读取一个字符,如果读取成功就进入一次中断

(2)如果成功接收到数据,就将数据通过调试串口打印出去(调试串口为创建工程时选择的串口)。

7.完善接收回调函数,一旦回调函数被执行就释放信号量

8.实验现象:

我们将程序下载到开发板,打开串口2的终端,可以发现暂时没有任何数据输出

由于我们的接收数据是通过串口1接收的,因此我们需要打开一个串口调试助手,通过上位机向串口1发送数据,然后串口2将串口1接收到的数据打印出来。

在本例中我们是一个字符一个字符来接收的,每一次接收新的字符都会将buffer中的数据给覆盖掉。在实际的应用开发中,我们应该定义某些数据类型将每次接收的数据保存起来,如可以定义数组或者使用复杂点的数据结构(如队列),将数据循环的保存到一块缓存区。需要处理的时候,再将数据从数组或队列中读走再处理。

DMA接收

使用DMA接收,首先我们要再设置里面使能串口DMA模式,最后要再board.h中加上宏声明

1.使能DMA模式

添加宏定义

2.打开设备我们修改为以DMA的方式接收

3.然后对于之前的中断接收工程,我们其它的代码都不需要动,我们只需要动接收回调函数和线程处理函数。

再之前使用中断接收的时候是每次接收到一个字符触发一次中断,我们现在使用DMA接收的时候每次接收的是一块缓冲区,也就是说接收的是一段数据。接收到的数据长度会通过回调函数的第二个参数返回。

因此返回多少个长度,我们在线程接收函数中就应该接收到多少

4.因此我们定义一个全局变量rx_len,在回调函数和线程处理函数中使用

5.然后我们再定义一个局部变量len来接收实际读取的数据长度,再将接收到的数据通过串口2打印出来。

6.此外还要加上信号量的释放和获取

7.编译并下载程序到开发板,复位发现出现一个错误:线程在挂起的时候报错了

这是因为创建线程的时候,设置线程栈的大小是1024,而在线程处理函数中的buffer里面我们创建的也是1024,这样就会导致线程栈溢出。

因此我们将线程处理函数中的buffer大小改小一点,如512

再重新下载程序,发现运行正常,使用上位机向STM32发送数据也可以正常被接收到

至此,RT-Thread的串口设备介绍和应用到此结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/879210.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解析TCP/IP协议的分层模型

了解ISO模型:构建通信的蓝图 为了促进网络应用的普及,国际标准化组织(ISO)引入了开放式系统互联(Open System Interconnect,OSI)模型。这个模型包括了七个层次,从底层的物理连接到顶…

Spring AOP实践:如何通过aop记录日志?

目录 一、依赖 二、自定义注解 三、切面 一、依赖 以SpringBoot工程为例&#xff0c;导入aop的依赖。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency> 二…

GCviewer分析java垃圾回收的情况

一&#xff0c;下载并打包 1.在github上下载gcviewer,并解压。 2. 运行maven命令打包。mvn clean package -DskipTests 二&#xff0c;启动GCViewer 进入target目录&#xff0c;运行 java -jar gcviewer-1.37-SNAPSHOT.jar 运行后&#xff0c;会出来界面 三&#xff0c;加参…

轻松学会WiFi模块(ESP8266)—基于STM32,学到就是赚到!

目录 前言 一、ESP8266介绍 二、如何实现WiFi传输&#xff1f;代码详解附上 三、结果实现流程与展示 四、总结 题外话&#xff1a; 前言 哎哎哎&#xff0c;发觉好久没有更新博客了&#xff0c;最近一直事情比较多&#xff0c;也没什么时间注意博客&#xff0c;不过接下…

每天一道leetcode:1129. 颜色交替的最短路径(图论中等广度优先遍历)

今日份题目&#xff1a; 给定一个整数 n&#xff0c;即有向图中的节点数&#xff0c;其中节点标记为 0 到 n - 1。图中的每条边为红色或者蓝色&#xff0c;并且可能存在自环或平行边。 给定两个数组 redEdges 和 blueEdges&#xff0c;其中&#xff1a; redEdges[i] [ai, bi…

opencv实战项目 手势识别-实现尺寸缩放效果

手势识别系列文章目录 手势识别是一种人机交互技术&#xff0c;通过识别人的手势动作&#xff0c;从而实现对计算机、智能手机、智能电视等设备的操作和控制。 1. opencv实现手部追踪&#xff08;定位手部关键点&#xff09; 2.opencv实战项目 实现手势跟踪并返回位置信息&…

【Git】安装以及基本操作

目录 一、初识Git二、 在Linux底下安装Git一&#xff09;centOS二&#xff09;Ubuntu 三、 Git基本操作一&#xff09; 创建本地仓库二&#xff09;配置本地仓库三&#xff09;认识工作区、暂存区、版本库四&#xff09;添加文件五&#xff09;查看.git文件六&#xff09;修改文…

问道管理:旅游酒店板块逆市拉升,桂林旅游、华天酒店涨停

游览酒店板块14日盘中逆市拉升&#xff0c;到发稿&#xff0c;桂林游览、华天酒店涨停&#xff0c;张家界涨超8%&#xff0c;君亭酒店涨超5%&#xff0c;众信游览、云南游览涨逾4%。 音讯面上&#xff0c;8月10日&#xff0c;文旅部办公厅发布康复出境团队游览第三批名单&#…

Flink源码之StreamTask启动流程

每个ExecutionVertex分配Slot后&#xff0c;JobMaster就会向Slot所在的TaskExecutor提交RPC请求执行Task&#xff0c;接口为TaskExecutorGateway::submitTask CompletableFuture<Acknowledge> submitTask(TaskDeploymentDescriptor tdd, JobMasterId jobMasterId, RpcTi…

无涯教程-Perl - select函数

描述 此函数将输出的默认文件句柄设置为FILEHANDLE,如果未指定文件句柄,则设置由print和write等功能使用的文件句柄。如果未指定FILEHANDLE,则它将返回当前默认文件句柄的名称。 select(RBITS,WBITS,EBITS,TIMEOUT)使用指定的位调用系统功能select()。 select函数设置用于处理…

Postgresql 基础使用语法

1.数据类型 1.数字类型 类型 长度 说明 范围 与其他db比较 Smallint 2字节 小范围整数类型 32768到32767 integer 4字节 整数类型 2147483648到2147483647 bigint 8字节 大范围整数类型 -9233203685477808到9223203685477807 decimal 可变 用户指定 精度小…

用友Java后端笔试2023-8-5

计算被直线划分区域 在笛卡尔坐标系&#xff0c;存在区域[A,B],被不同线划分成多块小的区域&#xff0c;简单起见&#xff0c;假设这些不同线都直线并且不存在三条直线相交于一点的情况。 img 那么&#xff0c;如何快速计算某个时刻&#xff0c;在 X 坐标轴上[ A&#xff0c;…

读书笔记 |【项目思维与管理】➾ 项目成为一种生存方式

读书笔记 |【项目思维与管理】➾ 项目成为一种生存方式 一、理解项目固有的挑战二、项目对企业的价值三、知识型企业的经营逻辑四、做项目管理的推进者 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 项目无处不在&#xff0c;项目已经成为…

【佳佳怪文献分享】安全人机交互的学习责任分配与自动驾驶应用

标题&#xff1a;Learning Responsibility Allocations for Safe Human-Robot Interaction with Applications to Autonomous Driving 作者&#xff1a;Ryan K. Cosner, Yuxiao Chen, Karen Leung, and Marco Pavone 来源&#xff1a;2023 IEEE International Conference on …

鸿蒙开发学习笔记2——实现页面之间跳转

鸿蒙开发学习笔记2——实现页面之间跳转 问题背景 上篇文章中&#xff0c;介绍了鸿蒙开发如何新建一个项目跑通hello world&#xff0c;本文将介绍在新建的项目中实现页面跳转的功能。 问题分析 ArkTS工程目录结构&#xff08;FA模型&#xff09; 各目录和路径的介绍如下…

iOS手机无法安装Charles 的ssl证书

问题描述 iOS客户端安装证书时一直卡在下载这一步&#xff0c;无法抓包 1、打开Charles&#xff0c;选择help→SSL Proxying→Install Charles Root Certificate on a Mobile Device or Remote Browser 2、按照步骤1中的提示进行操作&#xff0c;手机连接电脑代理&#xff0c;…

C语言学习笔记---结构体初阶

C语言程序设计笔记---014 C语言结构体初阶1、结构的基础与概念1.1、结构体基础例程1 --- 描述一个学生属性 2、结构体成员的类型2.1、结构体类型例程1 --- 描述一个学生属性 3、结构体的变量定义与初识化3.1、结构体的变量定义与初识化例程13.2、结构体的变量定义与初识化例程2…

C++多线程场景中的变量提前释放导致栈内存异常

多线程场景中的栈内存异常 在子线程中尝试使用当前函数的资源&#xff0c;是非常危险的&#xff0c;但是C支持这么做。因此C这么做可能会造成栈内存异常。 正常代码 #include <iostream> #include <thread> #include <windows.h>// 线程函数&#xff0c;用…

截止到目前全量主体总数有多少?

企业主体类型 企业主体类型有很多种&#xff0c;一般我们会分为公司&#xff08;有限责任&#xff09;、合伙企业、个人独资企业、个体经营户这些类别。 今天我们按照企业&#xff0c;个体&#xff0c;组织的分类方式来看各个主体的总数。 企业&#xff1a;统一社会信用代码…

三、性能测试场景设计

性能测试场景设计 一、引言&#xff1a;如果公司要求你去做性能测试&#xff0c;遇到这些场景&#xff0c;我们要如何设计&#xff1f;二、6种常见设计方法1、普通性能场景设计2、负载测试性能场景 一、引言&#xff1a;如果公司要求你去做性能测试&#xff0c;遇到这些场景&am…