Arduino 入门学习笔记(七):UART实验

Arduino 入门学习笔记(七):UART实验

开发板:正点原子ESP32S3
例程源码在文章顶部可免费下载

1 串口介绍

由于大部分 Arduino 开发板都没有调试功能,所以在开发中,最常用的就是串口打印信息到串口助手去调试程序。而串口打印,正规一点来讲就是串口通信,开发板通过串口外设把数据通过串口线传输到电脑的串口助手上位机。
在开发板上存在各种各样的通信,所以在这里花一点篇幅来讲解一下数据通信方面知识,方便大家对通信有点概念,知其然知其所以然。

1.1 数据通信的基本概念

这里将会简单讲解一下“串行/并行通信”、“单工/半双工/全双工通信”、“同步/异步通信”、“波特率”等知识。
串行/并行通信
平常经常会听到串行通信和并行通信,这两者其实很好去区分。而串口属于串行通信, 特点就是数据是逐位按顺序依次传输,如下图所示:
在这里插入图片描述
并行通信,特点是数据各位通过多条线同时传输,如下图所示:
在这里插入图片描述
简单理解,这两者就是单车道和多车道的概念,同一个时刻,单车道只能通行一辆车,好比串行通信只能传递 1 位数据;多车道可以通行多辆车好比并行通信传递多位数据。
这两者的对比,我们这里也做了一点归纳,如下图所示:
在这里插入图片描述
单工/半双工/全双工通信
按数据传输方向分类:单工通信、半双工通信和全双工通信,他们的通信图如下图所示:
在这里插入图片描述
单工通信:数据只能沿一个方向传输
半双工通信:数据可以沿两个方向传输,但需要分时进行
全双工通信:数据可以同时进行双向传输
串行通信属于以上的全双工通信。
同步/异步通信
按数据同步方式分类:同步通信、异步通信,他们的区别如下图所示。
在这里插入图片描述
同步通信:共用同一时钟信号
异步通信:没有时钟信号,通过在数据信号中加入起始位和停止位等一些同步信号
串行通信属于以上的异步通信。
波特率
双方进行通信,假如不存在时钟信号去同步数据的传输的过程,就需要双方设置一样的数据传输速度,也就是波特率。
在二进制系统中,波特率的含义就是每秒传输多少位,当我们设置波特率为 115200,这里的含义就是 1 秒钟传送 9600 位(bit)数据。串口通信默认的传输方式除了数据本身 8 位外,还需要加上起始位和停止位,所以传输 1 字节数据就需要 10 位。那 1 秒钟传输的数据量就是 960 字节,即一秒钟传输 9600 位 / 一字节需要传输 10 位 = 960 字节。
电脑的串口助手或者 Arduino IDE 自带的串口监视器波特率需要跟程序设置的一样,才可以正确接收数据。

1.2 UART 介绍

UART, Universal asynchronous receiver transmitter,是通用异步接收器/发送器,通常集成在主控器中。 UART 控制器设有一定容量的数据缓冲区,用于存储通信时的数据。
通常, UART 使用两条信号线传输数据,分别为数据发送端 TX 和数据接收端 RX。通信时,一端的数据发送端(TX)连接到另一端的数据接收端(RX),连接形式如下图所示:
在这里插入图片描述
在 ESP32-S3 中,是有 3 个 UART 控制器,即 UART0、 UART1 和 UART2。 3 个 UART 端口对应的引脚如下表所示:
在这里插入图片描述
上表带有具体 IO 口是默认使用 IO,但是 ESP32-S3 有 IO MUX,所以是可以选择任意 GPIO管脚作为 UART 的引脚。使用 Arduino,调用串口初始化函数时,可以指定发送引脚和接收引脚。
ESP32-S3 开发板上有一个 TYPEC 接口是通过一个 USB 转 UART 接口芯片连接 UART0,使用该串口上传程序或与计算机交互。注意:要识别串口,就得安装串口驱动。

1.3 串口相关函数介绍

在 HardwareSerial.cpp 中已经定义好了三个 UART 对象 Serial、 Serial1 和 Serial2,对应的就是 UART0、 UART1 和 UART2,直接使用它们即可。
串口初始化函数介绍
在 Arduino 中,是要使用 begin 函数初始化串口功能,即

void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin,
int8_t txPin, bool invert, unsigned long timeout_ms,uint8_t rxfifo_full_thrhd);

由于参数比较多,所以这里以表格形式说明参数,如下表所示:
在这里插入图片描述
通常情况下, 通过如下语句便可以初始化串口 0,默认使用 IO43 作为串口 0 的发送引脚,使用 IO44 作为串口 0 的接收引脚, 8 位数据位,无奇偶检验位, 1 位停止位。

Serial.begin(115200);

当然,我们也可以通过 Serial1.begin 或 Serial2.begin 接口去设置串口 2 和串口 3,带上参数既可自由设置发送和接收引脚。
串口发送函数介绍
串口初始化完成后,便可以 Serial.print、 Serial.println 和 Serial.printf 函数向串口助手发送数据。
Serial.print 函数用法:

Serial.print(val);

其中参数 val 是要输出的数据,各种类型数据都可以。
Serial.println 函数用法:

Serial.println(val);

Serial.println(val)函数也是使用串口输出数据,不同于 Serial.print 函数,该函数输出完指定数据后,再输出回车换行符。
下面测试一下这两个函数,在 UART 例程的 loop 函数中加入这四句代码。

Serial.print(1);
Serial.println("hello world ");

Serial.print(2);
Serial.println("hello world ");

通过点击 Arduino IDE 右上角的串口监视器图标打开串口监视器,可以看到如下效果:
在这里插入图片描述
通过上图可以清楚看到 print 是直接输出,而 println 是输出数据后,还要进行换行。
需要注意,串口监视器窗口有一个选择波特率的设置,要选择与程序一样的设置,才能正常发送/接收数据。
Serial.printf 函数用法:

Serial.printf(char * format, ...);

该函数功能是输出一个字符串,或者按指定格式和数据类型输出若干变量的值,函数返回值为输出字符的个数。
在 Serial.printf()函数使用中,会涉及到比较多的格式字符“%d、 %c、 %f”,“\n、 \r”等为转义字符,这里整理了一个表格来说明这些常用的格式字符和转义字符,如下表所示:
在这里插入图片描述
串口接收函数介绍
除了输出,串口同样可以接收由串口助手发出的数据。接收串口数据需要使用 Serial.read()函数,函数用法:

Serial.read();

调用该函数,每次都会返回 1 字节数据,该返回值便是当前串口读取到的数据。
下面测试一下这个函数,在 UART 例程的 loop 函数中加入这两句代码。

char c = Serial.read();
Serial.print(c);

程序执行情况如下图所示:
在这里插入图片描述
通过串口发送数据框写入“hello_world”回车进行发送,然后在串口监视器界面是可以见到“hello_world”字样,除此之外,还有一些乱码。这些乱码数据是因为没有可读数据造成的。当我们用 Serial.print(Serial.read())程序,若没有可读数据,返回的是 int 型数据-1,对应到 char 型数据就是乱码。
在使用串口时, Arduino 会在 SRAM 中开辟一段大小为 64 字节的空间,窗口接收到的数据都会被暂时存放在该空间中,称这个存储空间为缓冲区。当调用 Serial.read()函数时, Arduino 便会从缓冲区中取出 1 字节的数据。
通常在使用串口读取数据时,需要搭配使用 Serial.available()函数,用法是:

Serial.available();

Serial.available 函数的返回值为当前缓冲区中接收到的数据字节数。通常该函数会搭配 if 或者 while 语句来使用,先检测缓冲区中是否有可读数据,如果有数据,再读取;如果没有数据,跳过读取或等待读取,如下所示:

if (Serial.available() > 0)

while (Serial.available() > 0)

下面改进一下上面的代码:

while (Serial.available() > 0)
{
char c = Serial.read();
Serial.print(c);
}

程序执行情况如下图所示:
在这里插入图片描述
可以看到 Serial.available()函数和 Serial.read()函数搭配使用下,不会出现乱码现象。

2. 硬件设计

2.1 例程功能

1.回显串口接收到的数据
2.每间隔一定时间,串口发送一段提示信息

2.2 硬件资源

1) LED 灯: LED-IO1
2) USART0:U0TXD-IO43 U0RXD-IO44

2.3 原理图

USB 转串口硬件部分的原理图,如下图所示:
在这里插入图片描述
这里需要注意的是:上图中的红色框中的 TXD 和 U0_RXD 需要用跳线帽连接,以及 RXD和 U0_TXD 也需要用跳线帽连接。 跳线帽连接方法如下图所示:
在这里插入图片描述

3. 软件设计

3.1 程序流程图

下面看看本实验的程序流程图:
在这里插入图片描述

3.2 程序解析

  1. uart 驱动代码
    UART 驱动源码包括两个文件: uart.cpp 和 uart.h。
    下面我们先解析 uart.h 的程序。 在 uart 头文件中, 我们做了下面的引脚定义:
/* 引脚定义 */
/* 串口 0 默认已经使用了固定 IO(GPIO43 为 U0TXD,GPIO44 为 U0RXD)
* 以下两个宏为串口 1 或串口 2 使用到的 IO 口(例程未使用)
*/
#define TXD_PIN 19
#define RXD_PIN 20

下面我们再解析 uart.cpp 的程序,这里只有一个函数 uart_init,其定义如下:

/**
* @brief 初始化 UART
* @param uartx:串口 x
* @param baud:波特率
* @retval 无
*/
void uart_init(uint8_t uartx, uint32_t baud)
{
if (uartx == 0)
{
Serial.begin(baud); /* 串口 0 初始化 */
}
else if (uartx == 1)
{
Serial1.begin(baud, SERIAL_8N1, RXD_PIN, TXD_PIN); /* 串口 1 初始化 */
}
else if (uartx == 2)
{
Serial2.begin(baud, SERIAL_8N1, RXD_PIN, TXD_PIN); /* 串口 2 初始化 */
	}
}

为了方便大家使用不同的串口,所以特定封装了一个 uart_init 函数。该函数是根据传参去初始化对应串口,而在 uart.h 中的 TXD_PIN 和 RXD_PIN 是专门给串口 1 或者串口 2 指定引脚的。初始化串口就是用到 Serial 库的 begin 函数。 由于 Serial 库属于系统的核心库,所以使用时不需要导入库的头文件。

  1. 04_uart.ino 代码
    在 04_uart.ino 里面编写如下代码:
#include "uart.h"
uint32_t chip_id = 0; /* 芯片 ID */
/**
* @brief 当程序开始执行时,将调用 setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
uart_init(0, 115200); /* 串口 0 初始化 */
for(int i = 0; i < 17; i = i + 8)
{
chip_id |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
/* 获取 ESP32 芯片 MAC 地址(6Byte),该地址也可作为芯片 ID */
}
Serial.printf("ESP32 Chip model = %s Rev %d \n", ESP.getChipModel(),
ESP.getChipRevision()); /* 打印芯片类型和芯片版本号 */
Serial.printf("This chip has %d cores \n", ESP.getChipCores());
/* 打印芯片内核数 */
Serial.print("Chip ID: "); Serial.println(chip_id); /* 打印芯片 ID */
Serial.printf("CpuFreqMHz: %d MHz\n", ESP.getCpuFreqMHz());
/* 打印芯片主频 */
Serial.printf("SdkVersion: %s \n", ESP.getSdkVersion()); /* 打印 SDK 版本 */
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
Serial.println("Waitting for Serial Data \n"); /* 等待串口助手发过来的串口数据 */
while (Serial.available() > 0) /* 当串口 0 接收到数据 */
{
Serial.println("Serial Data Available..."); /* 通过串口监视器通知用户 */
String serial_data; /* 存放接收到的串口数据 */
int c = Serial.read(); /* 读取一字节串口数据 */
while (c >= 0)
{
serial_data += (char)c; /* 存放到 serial_data 变量中 */
c = Serial.read(); /* 继续读取一字节串口数据 */
}

/* 将接收到的信息使用 readString()存储于 serial_data 变量(跟前面 4 行代码具有同样效果) */
// serial_data = Serial.readString();
		Serial.print("Received Serial Data: "); /* 串口监视器输出 serial_data 内容 */
		Serial.println(serial_data); /* 查看 serial_data 变量的信息 */
	}
	
delay(1000);
}

在 setup 函数中, 调用 uart_init 函数对串口 0 进行初始化。
接下来,在 loop 函数中, 就通过 Serial.available 函数去查询是否接收到串口数据,假如有接收到,那么就通过 Serial.read 函数去读取串口数据,把这些串口数据保存到 serial_data 变量中,最终打印 serial_data 变量就为串口接收到的数据。程序中提供了两种方式去读取完整串口数据,大家可以自行去选择使用。

4. 下载验证

下载完之后, 打开串口监视器,可以看到不断打印出“Waitting for Serial Data”信息,当我们在消息输入框写入“hello esp32”后,回车发送,“hello esp32”将会回显出来,可以看到如下效果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值