1、并行通信
通过输入/输出端口在 Arduino 和外设之间进行并行连接是短距离(最多几米)的理想解决方案。然而,在其他情况下,当需要在两个设备之间建立较长距离的通信时,不可能使用并行连接。并行接口同时传输多个位。它们通常需要数据总线 ——通过八条,十六条或更多的线路进行传输。数据以1和0的巨大波形传输。
并行通信的优点和缺点
并行通信虽然可以多位数据同时传输,速度更快,而且控制简单,但其占用的I/O口较多,而 Arduino的I/O资源较少,因此在 Arduino 中更常使用的是串行通信方式。
2、串行通信
串行通信的最重要的事情之一是协议, 应该严格遵守。它是一套规则,必须应用这些规则才能使设备正确地解释它们相互交换的数据。但是,Arduino 会自动处理这个问题,这样程序员/用户的工作就可以简化为简单的写(发送的数据)和读(接收的数据)。
串行通信类型
串行通信可以进一步分类为:
如果给所有连接的设备提供相同的时钟,则它们是同步的。如果没有时钟线,它是异步的。
传输方向
常见的通信方式、实现协议,请参考:设计总线时,USB、UART、I2C、SPI、CAN总线该如何选择
串口通信(Serial CommunicaTIons)是指串口按位(bit)发送和接收字节。串口用于ASCII码字符的传输,通信使用3根线完成,分别是地线、发送线、接收线。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据,其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。 对于两个进行通信的端口,这些参数必须要匹配。
通信速率是指单位时间内传输的信息量,可用比特率和波特率来表示。比特率是指每秒传输的二进制位数,用bps (bits)表示。波特率是指每秒传输的符号数,若每个符号所含的信息量为1比特,则波特率等于比特率。在电子学中,一个符号的含义为高电平或低电平,它们分别代表"1"和"0",所以,每个符号所含的信息量刚好为1比特,因此常将比特率称为波特率,即:1波特(B) =1比特(bit) =1位/秒(1bps)
通信双方需要使用一致的的波特率才能正常通信。 Arduino串口通信通常会使用以下波特率:300、600、1200、2400、4800、9600、14400、19200、28800、38400、57600、115200等,最常用的是9600。波特率越大,说明串口通信的速率越快。
单片机是基于微控制器(MCU)搭建的电子系统。单片机的所有功能其实都是由板载的MCU提供的,Arduino开发板当然也不例外。这里以Arduino MEGA 2560为例,它的板载MCU为ATmega2560。
在ATmega2560内部,实现串口的部件为USART(Universal Synchronous and Asynchronous serial Receiver andTransmitter,通用同步/异步串行收发器)。USART内部结构是十分复杂的,主要由三部分组成:时钟发生器、发射器和接收器。 每个单元的功能全部由硬件实现,同时以寄存器的形式对用户开放了配置接口(控制寄存器),又以寄存器的形式对用户开放了过程监控(状态寄存器)。
图中的虚线框分隔了USART的三个主要部分(从顶部列出):时钟发生器、发射器和接收器。控制寄存器由所有单元共享。时钟生成逻辑由同步从操作使用的外部时钟输入的同步逻辑和波特率发生器组成。XCKn(传输时钟)引脚仅用于同步传输模式。发射器由一个写缓冲器、一个串行移位寄存器、奇偶校验发生器和控制逻辑组成,用于处理不同的串行帧格式。写缓冲区允许连续传输数据,帧之间没有任何延迟。由于其时钟和数据恢复单元,接收器是USART模块中最复杂的部分。恢复单元用于异步数据接收。除了恢复单元,接收器还包括奇偶校验器、控制逻辑、移位寄存器和两级接收缓冲器。接收器支持与发送器相同的帧格式,可以检测帧错误、数据溢出和奇偶校验错误。
时钟发生器为发送器和接收器生成基准时钟。USARTn支持四种时钟操作模式:正常异步、双速异步、主同步和从同步模式。 USART控制状态寄存器UCSRnC中的UMSELn位在异步和同步操作之间进行选择。双速(仅异步模式)由UCSRnA寄存器中的U2Xn控制。当使用同步模式(UMSELn = 1)时,XCKn引脚(DDR_XCKn)的数据方向寄存器控制时钟源是内部(主机模式)还是外部(从机模式)。XCKn引脚仅在使用同步模式时有效。
USART波特率寄存器(UBRRn)和与之相连的递减计数器作为可编程预分频器或波特率发生器。在系统时钟(fosc)下运行的递减计数器,每次计数器递减到零或写入UBRln寄存器时,都加载UBRRn值。每次计数器归零时都会产生一个时钟。该波特率发生器的时钟输出为 fosc/(UBRn+1)。发射器根据模式将波特率发生器时钟输出除以2、8或16。接收器的时钟和数据恢复单元直接使用波特率发生器输出。
通过Arduino上的USB接口与计算机连接而进行Arduino与计算机之间的串口通信。除此之外,还可以使用串口引脚连接其他的串口设备进行通信。需要注意的是,通常一个串口只能连接一个设备进行通信。
常见的USB转TTL芯片有CH340、PL2303、FT232、CP2102等,但现在的Arduino开发板也内置了USB到TTL的转换芯片,官方版本采用Atmega16U2芯片,而国产版本多采用CH340或FT232芯片,因此,可以直接使用USB线连接计算机,实现与上位机的串口通讯了!
图中蓝色线框的TX、RX引脚为TTL电平,可用于两个单片机开发板之间直接的串口通讯;红色线框内的接头为USB接头,可直接用线与电脑的USB口连接;图中绿色线框内的部分实现了TTL到USB的转换。
在 Arduino 与其他器件通信的过程中,数据传输实际上都是以数字信号(即电平高低变化)的形式进行的,串口通信也是如此。当使用 Serial. print()
函数输出数据时,Arduino 的发送端会输出一连串的数字信号,称这些数字信号为数据帧。
(1) 起始位
起始位总为低电平,是一组数据帧开始传输的信号。
(2) 数据位
数据位是一个数据包,其中承载了实际发送的数据的数据段。 当Arduino通过串口发送一个数据包时,实际的数据可能不是8位的,比如,标准的ASCII码是0-127(7位)。而扩展的ASCII码则是0-255(8位)。如果数据使用简单的文本(标准ASCII码),那么每个数据包将使用7位数据。Arduino 默认使用8位数据位,即每次可以传输1B数据。
(3) 校验位
校验位是串口通信中一种简单的检错方式。可以设置为偶校验或者奇校验。当然,没有校验位也可以。Arduino 默认无校验位。
(4) 停止位
每段数据帧的最后都有停止位表示该段数据帧传输结束。停止位总为高电平,可以设置停止位为1位或2位。Arduino默认是1位停止位。当串口通信速率较高或外部干扰较大时,可能会出现数据丢失的情况。为了保证数据传输的稳定性,最简单的方式就是降低通信波特率或增加停止位和校验位。在Arduino中,可以通过 Serial.begin(speed,config)
语句配置串口通信的数据位、停止位和校验位参数。
更多内容,请阅读:深入剖析串口通信数据格式。
硬件串口的操作类为HardwareSerial,定义于 HardwareSerial.h
源文件中,并对用户公开声明了Serial对象,用户在Arduino程序中直接调用Serial, 就可实现串口通讯,更多介绍,请进入Arduino官网串口使用指南。常用的成员函数如下:
(1)begin()
Serial.begin(speed)
Serial.begin(speed, config)
Serial. begin(9600 , SERIAL _8E2)
语句设置串口波特率为9600,数据位为8,偶校验,停止位为2。(2)Serial.end()
Serial.end()
(3)available( )
Serial.available( )
(4)print()
write()
函数。Serial.print(val)
Serial.print(val, format)
示例:
Serial.print(55, BIN) // 输出 "110111"
Serial.print(55, OCT) // 输出 "67"
Serial.print(55, DEC) // 输出 "55"
Serial.print(55, HEX) // 输出 "37
Serial.print(3.1415926, 0) // 输出 "3"
Serial.print(3.1415926, 2) // 输出 "3.14"
Serial.print(3.1415926, 4) // 输出 "3.1416"
Serial.print('N') // 输出 "N"
Serial.print("Hello World!") // 输出 "Hello World!"
(5)println()
Serial.println(val)
Serial.println(val, format)
Serial.print(val)
和相同。(6)read()
Serial.read()
(7)readBytes()
Serial.readBytes(buffer, length)
(8) peek( )
Serial. peek()
(9) write( )
Serial. write(val)
Serial. write(str)
Serial. write(buf, len)
具体操作函数,请参考: 串口操作函数与示例代码大全。
实验一:串口读取字符串
当使用 read()
函数时,每次仅能读取1字节的数据,如果要读取一个字符串,则可使用“+=”运算将字符依次添加到字符串中。
示例程序代码如下:
// 读取字符串
void setup(){
Serial.begin(9600);
}
void loop(){
String inString="";
while(Serial.available()>0){
inString += char(Serial.read());
delay(10); // 延时函数用于等待字符完全进入缓冲区,可以尝试没有延时,输出结果会是什么
}
// 检查是否接收到数据,如果接收到数据,则输出该数据
if(inString!=""){
Serial.print("Input String:");
Serial.println(inString);
}
}
实验结果:
左图是有 delay(10)
语句的输出结果,右图注释掉 delay(10)
语句的输出结果。
补充:串口事件,欢迎了解SerialEvent。
软串口的操作类为SoftwareSerial,定义于 SoftwareSerial.h
源文件中,但不像硬串口那样,源文件中并没有事先声明软串口对象,Arduino程序中需要手动创建软串口对象。 在使用前需要先声明包SoftwareSerial.h
头文件。。其中定义的成员函数与硬串口的类似,而 available( )、begin()、read()、write()、print()、println()、peek()
等函数的用法也相同。其他的成员函数:
软串口是由程序模拟生成的,使用起来不如硬串口稳定,并且与硬串口一样,波特率越高越不稳定。
软串口通过AVR芯片的PCINT中断功能来实现,在Arduino UNO 上,所有引脚都支持PCINT中断,因此所有引脚都可设置为软串口的RX接收端。但在其他型号的Arduino上,并不是每个引脚都支持中断功能,所以只有特定的引脚可以设置为RX端。
(1)SoftwareSerial()
SoftwareSerial mySerial= SoftwareSerial(rxPin, txPin)
SoftwareSerial mySerial(rxPin, txPin)
(2)listen()
(3) isListening( )
(4) overflow( )
实验二:Arduino间的串口通信
Arduino 可以与众多串口设备连接进行串口通信,但需要注意的是,当使用0(RX)、1(TX)串口连接外部串口设备时,这组串口将被所连接的设备占用,从而可能会造成无法下载程序和通信异常的情况。因此,通常在连接外部设备时尽量避免使用0(RX)、1(TX)这组串口。
如上图所示,本实验将两个Arduino连接起来进行数据交换,在两台电脑间建立一个简单的串口聊天应用。Arduino MEGA通过Serial1,即19(RX1 )、18(TX1)与Arduino UNO的软串口9(RX)、8(TX)相连。
程序的编写与Arduino连接计算机进行通信一样,其中通信设备A——ArduinoMEGA端的程序如下。
/********************************************
Arduino MEGA 2560端的程序
串口使用情况:
Serial —————— computer
Serial1 —————— UNO SoftwareSerial
*******************************************/
void setup(){
Serial.begin(9600); // 初始化serial,该串口用于与计算机连接通信
Serial1.begin(9600); // 初始化serial1,该串口用于与设备B连接通信
}
// 两个字符串分别用于存储A、B两端传来的数据
String device_A_String = "";
String device_B_String = "";
void loop(){
// 读取从计算机传入的数据,并通过Serial1发送给设备B
if(Serial.available()>0){
if(Serial.peek()!='\n'){
device_A_String += char(Serial.read());
}
else{
Serial.read();
Serial.print("you said:");
Serial.println(device_A_String);
Serial1.println(device_A_String);
device_A_String = "";
}
}
// 读取从设备B传入的数据,并在串口监视器中显示
if(Serial1.available()>0){
if(Serial1.peek()!='\n'){
device_B_String += char(Serial1.read());
}
else{
Serial1.read();
Serial.print("device B said:");
Serial.println(device_B_String);
device_B_String = "";
}
}
}
通信设备B——Arduino UNO端的程序结构基本与Arduino MEGA端的一样,只是将Serial1换成了软串口,其代码如下:
/********************************************
Arduino UNO端的程序
串口使用情况:
Serial —————— computer
softSerial —————— MEGA Serial1
*******************************************/
#include
// 新建一个 SoftSerial 对象,RX:9,TX:8
SoftwareSerial softSerial(9,8);
void setup(){
Serial.begin(9600); // 初始化串口通信
softSerial.begin(9600); // 初始化软串口通信
softSerial.listen(); // 监听软串口通信
}
// 两个字符串分别用于存储A、B两端传来的数据
String device_B_String = "";
String device_A_String = "";
void loop(){
// 读取从计算机传入的数据,并通过Serial1发送给设备A
if(Serial.available()>0){
if(Serial.peek()!='\n'){
device_B_String += char(Serial.read());
}
else{
Serial.read();
Serial.print("you said:");
Serial.println(device_B_String);
softSerial.println(device_B_String);
device_B_String = "";
}
}
// 读取从设备A传入的数据,并在串口监视器中显示
if(softSerial.available()>0){
if(softSerial.peek()!='\n'){
device_A_String += char(softSerial.read());
}
else{
softSerial.read();
Serial.print("device A said:");
Serial.println(device_A_String);
device_A_String = "";
}
}
}
下载程序后,分别打开两个设备的串口监视器,选择各自对应的波特率,并将结束符设置为“换行和回车”,在两个串口监视器上随意输人字符,并发送,则会看到如下图所示的效果,这说明串口聊天项目已经成功地运行了。
小结:
在实际使用中可能还会用到其他串口设备,如串口无线传输模块、串口传感器等,只要是标准串口设备,其程序的编写方法都基本相同。