摘要:本文介绍UART通信协议的基本知识以及在Arduino中如何实现UART通信
下面来看一下,使用UART进行数据通信的时候,除了底层电器特性必须保持一致之外,在通信规则方面,应该确定哪些东西。必须要保持一致的东西包括:
- 统一的同步位(一致的开始位和停止位)
- 数据位数,是5位、6位、7位还是8位。
- 奇偶校验一致,这包括时奇校验、偶校验还是无校验。所谓的奇校验,就是当数据为的1为偶数个时,校验位是“1”,当数据位中“1”的个数为奇数个时,校验位是“0”。简单的说,就是把数据位和校验位中的1凑成奇数个。偶校验就是凑成偶数个“1”。
- 波特率(bps)。就是每秒传送数据的位数。由于硬件设备的原因,这个波特率不是任意选择的,通常是在一系列波特率中选择。通常有1200 bps、4800 bps、9600 bps、19200 bps、38400bps、115200 bps等等。最常使用的是9600bps,这是比较低的一个波特率数值,这是由于早期机器主频不高的原因。我们现在开发中串口监视器一般使用的是115200bps,这个是9600的100多倍了。
这个UART通信规则通常会简写为一个字符串:波特率,奇偶校验,数据位,停止位。例如最常见通信规则是:9600,n,8,1,就表示通信波特率是9600,无校验,8位数据位,1位停止位。其中n表示无校验位,o表示奇校验,e表示偶校验。
最后再来看一下UART的优缺点:
优点有:
- 全双工只需要2根数据线(电源线除外)。
- 不需要时钟信号线。
- 在数据帧中定义了奇偶校验,可以排除一些基本的通信错误。
不足之处在于:
- 每一个数据帧的数据大小有限,一个数据帧不能包含多个字节的数据。
- 数据收发双方必须实现约定传输规则,两者必须使用适当的波特率,并且两者的波特率误差要在一定的限度内。
好了,UART通信协议的的基本知识就介绍到这里了。下面来看一下ESP32中如何进行UART通信。
其实,在前面的开发中已经多次用到UART通信了,我们输出的调试信息就是通过ESP32的UART控制器发送出去,而被串口监控程序捕获的。在ESP32模块中,共有3个UART控制器,分别是uart 0,uart 1和uart 2。其中的uart 0就是烧写程序、输出调试信息用的串口。默认的GPIO引脚为1和3,uart 1控制器使用的默认引脚为9和10,uart 2控制器使用的默认引脚为16和17。这三个UART控制器在Arduino中对应的串口对象实例是Serial,Serial1和Serial2。在使用Serial1的时候要特别注意,ESP32的引脚9和10通常被用作连接外部Flash使用,因此在使用Serial1的时候,一定要将引脚修改为其他的空闲引脚,这个串口的默认引脚是无法使用的。
下面就来相信的了解一下Serial对象的使用方法:
1. 串口初始化方法begin()
在标准的Arduino中,有两个begin()方法,形式如下:
begin(speed)
begin(speed, config)
其中的speed是指定通信的波特率。config是指定UART的通信规则,主要是指数据位数量、奇偶校验、停止位数量。默认值为:SERIAL_8N1,这个大家应该一看就明白是什么意思了。标准的Arduino库中,Serial对象所使用的引脚都是根据相应的开发板,固定了串行通信的引脚。
在ESP32中,因为大部分引脚的功能都是可以灵活定义的,并且Serial1的默认引脚是被占用的了,因此,ESP32的驱动程序又提供了新的初始化方法,使得开发者可以自定义串行通信的引脚,形式如下:
void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1, bool invert=false, unsigned long timeout_ms = 20000UL);
在这里,开发者可以指定begin()方法中的txPin和rxPin参数来指定通信时使用的GPIO引脚,以此来解决串行通信引脚被占用的问题。
2. 判断串口是否可用if (Serial)
这个方法通常用在初始化begin()方法之后,用于判断串口是否已经可用。
3. 串行通信结束方法end()
这个方法用来结束串行通信并释放占用的引脚资源。被释放的引脚可以用作普通的GPIO引脚。
4. 查询可以读取数据的数量available()
这个方法用来判断接收缓冲区的状态,其返回值为已收到并保存在缓冲区中未读取数据的数量。
5. 通过串口输出数据print()
print()方法在前面已经多次使用了,其作用就是输出字符串到串口,或者将数据转换成ASCII码发送到串口。
这个方法有两种形式:
print(val)
print(val, format)
其中的val为输出的数值,可以是任意类型。而format用于指定输出数据的格式。可以指定的格式包括:BIN(二进制)、OCT(八进制)、DEC(十进制)、HEX(十六进制)。对于浮点数,此参数指定要输出的小数位数(默认输出2位)。
6. 通过串口输出数据,并回车换行println()
这个函数根上面的类似,也是将数据以ASCII的形式输出到串口,唯一的区别就是在输出字符串之后,输出回车(ASCII 13,’\r’)和换行(ASCII 10, ‘\n’)字符。
7. 读入一个字节的数据read()
这个方法用于从缓冲区读入一个字节的数据,当数据被读入后,将从缓冲区中被删除掉。如果数据缓冲区中没有数据可以读取,那么该方法返回-1。
8. 读入多个数据到指定的数组中readBytes()
这个函数的形式为:readBytes(buffer, length)
其中的buffer用于存储读入的数据,length为最大读取数据的字节数。这个方法的返回值为已经读入的字节数。
9. 返回缓冲区中一个字节的数据peek()
这个方法与read()方法都是返回缓冲区中一个字节的数据,区别就在于peek()方法不会将返回的数据从缓冲区中删除。也就是再次调用读取缓冲区中数据的方法时,还可以得到刚才peek()方法返回过的数据。
10. 发送数据write()
这个方法用发送数据到串口。其形式有一下三个:
write(val)
write(str)
write(buf, len)
通过上面的方法可以看到,write()方法不仅可以发送字符串,还可以发送数据以及存储在数组中的任意的二进制数据。
好了这就是常用的串口通信方法。对于数据发送,前面已经用过多次了,在这里就不举例了,下面看一个数据接收的例子。
Serial.begin(9600);
String inString="";
while(Serial.available()>0){
inString += char(Serial.read());
delay(10);
}
这就是一个典型的数据读取的程序,首先判断有没有数据可以读取,然后再用read()方法读取一个字节的数据。