Arduino基础篇(五)-- 如何快速上手串口通信(Serial)

1 基础篇

1.1 通信基础

1、并行通信

        通过输入/输出端口在 Arduino 和外设之间进行并行连接是短距离(最多几米)的理想解决方案。然而,在其他情况下,当需要在两个设备之间建立较长距离的通信时,不可能使用并行连接。并行接口同时传输多个位。它们通常需要数据总线 ——通过八条,十六条或更多的线路进行传输。数据以1和0的巨大波形传输。

并行通信的优点和缺点

        并行通信虽然可以多位数据同时传输,速度更快,而且控制简单,但其占用的I/O口较多,而 Arduino的I/O资源较少,因此在 Arduino 中更常使用的是串行通信方式。

2、串行通信

        串行通信的最重要的事情之一是协议 应该严格遵守。它是一套规则,必须应用这些规则才能使设备正确地解释它们相互交换的数据。但是,Arduino 会自动处理这个问题,这样程序员/用户的工作就可以简化为简单的写(发送的数据)和读(接收的数据)。

  • 优点:传输线少,长距离传送时成本低
  • 缺点:数据的传送控制比并行通信复杂

串行通信类型

串行通信可以进一步分类为:

  1. 同步 - 同步的设备使用相同的时钟,它们的时序彼此同步。
  2. 异步 - 异步的设备具有各自的时钟,并由前一状态的输出触发。

        如果给所有连接的设备提供相同的时钟,则它们是同步的。如果没有时钟线,它是异步的。

传输方向

  • 单工:是指数据传输仅能沿一个方向,不能实现反向传输。
  • 半双工:是指数据传输可以沿两个方向,但需要分时进行。
  • 全双工:是指数据可以同时进行双向传输。

常见的通信方式、实现协议,请参考:设计总线时,USB、UART、I2C、SPI、CAN总线该如何选择


2 串口通信

        串口通信(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。波特率越大,说明串口通信的速率越快。

2.1 Arduino串口的硬件结构

        单片机是基于微控制器(MCU)搭建的电子系统。单片机的所有功能其实都是由板载的MCU提供的,Arduino开发板当然也不例外。这里以Arduino MEGA 2560为例,它的板载MCU为ATmega2560。
        在ATmega2560内部,实现串口的部件为USART(Universal Synchronous and Asynchronous serial Receiver andTransmitter,通用同步/异步串行收发器)。USART内部结构是十分复杂的,主要由三部分组成:时钟发生器、发射器和接收器。 每个单元的功能全部由硬件实现,同时以寄存器的形式对用户开放了配置接口(控制寄存器),又以寄存器的形式对用户开放了过程监控(状态寄存器)。

图1 USART内部结构

        图中的虚线框分隔了USART的三个主要部分(从顶部列出):时钟发生器、发射器和接收器。控制寄存器由所有单元共享。时钟生成逻辑由同步从操作使用的外部时钟输入的同步逻辑和波特率发生器组成。XCKn(传输时钟)引脚仅用于同步传输模式。发射器由一个写缓冲器、一个串行移位寄存器、奇偶校验发生器和控制逻辑组成,用于处理不同的串行帧格式。写缓冲区允许连续传输数据,帧之间没有任何延迟。由于其时钟和数据恢复单元,接收器是USART模块中最复杂的部分。恢复单元用于异步数据接收。除了恢复单元,接收器还包括奇偶校验器、控制逻辑、移位寄存器和两级接收缓冲器。接收器支持与发送器相同的帧格式,可以检测帧错误、数据溢出和奇偶校验错误。

        时钟发生器为发送器和接收器生成基准时钟。USARTn支持四种时钟操作模式:正常异步、双速异步、主同步和从同步模式。 USART控制状态寄存器UCSRnC中的UMSELn位在异步和同步操作之间进行选择。双速(仅异步模式)由UCSRnA寄存器中的U2Xn控制。当使用同步模式(UMSELn = 1)时,XCKn引脚(DDR_XCKn)的数据方向寄存器控制时钟源是内部(主机模式)还是外部(从机模式)。XCKn引脚仅在使用同步模式时有效。

图2 时钟发生器的内部结构

        USART波特率寄存器(UBRRn)和与之相连的递减计数器作为可编程预分频器或波特率发生器。在系统时钟(fosc)下运行的递减计数器,每次计数器递减到零或写入UBRln寄存器时,都加载UBRRn值。每次计数器归零时都会产生一个时钟。该波特率发生器的时钟输出为 fosc/(UBRn+1)。发射器根据模式将波特率发生器时钟输出除以2、8或16。接收器的时钟和数据恢复单元直接使用波特率发生器输出。

图3 UBRRn寄存器值与波特率的对应关系

        通过Arduino上的USB接口与计算机连接而进行Arduino与计算机之间的串口通信。除此之外,还可以使用串口引脚连接其他的串口设备进行通信。需要注意的是,通常一个串口只能连接一个设备进行通信。

图4 单片机与计算机间的串口通讯方式

        常见的USB转TTL芯片有CH340、PL2303、FT232、CP2102等,但现在的Arduino开发板也内置了USB到TTL的转换芯片,官方版本采用Atmega16U2芯片,而国产版本多采用CH340或FT232芯片,因此,可以直接使用USB线连接计算机,实现与上位机的串口通讯了!

图5 Arduino串口的硬件设计

        图中蓝色线框的TX、RX引脚为TTL电平,可用于两个单片机开发板之间直接的串口通讯;红色线框内的接头为USB接头,可直接用线与电脑的USB口连接;图中绿色线框内的部分实现了TTL到USB的转换。

2.2 串口工作原理

        在 Arduino 与其他器件通信的过程中,数据传输实际上都是以数字信号(即电平高低变化)的形式进行的,串口通信也是如此。当使用 Serial. print() 函数输出数据时,Arduino 的发送端会输出一连串的数字信号,称这些数字信号为数据帧。

图6 串口数据帧格式

(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) 语句配置串口通信的数据位、停止位和校验位参数。

        更多内容,请阅读:深入剖析串口通信数据格式

2.3 硬件串口通信

        硬件串口的操作类为HardwareSerial,定义于 HardwareSerial.h 源文件中,并对用户公开声明了Serial对象,用户在Arduino程序中直接调用Serial, 就可实现串口通讯,更多介绍,请进入Arduino官网串口使用指南。常用的成员函数如下:

(1)begin()

  • 功能:初始化串口,通常置于setup()函数中,该函数可配置串口的各项参数。
  • 语法:
Serial.begin(speed)
Serial.begin(speed, config)
  • 参数:
    speed:波特率,一般取值9600,115200等。
    config:设置数据位、校验位和停止位。例如Serial. begin(9600 , SERIAL _8E2) 语句设置串口波特率为9600,数据位为8,偶校验,停止位为2。
  • 返回值:无。

(2)Serial.end()

  • 功能:结束串口通信。该操作可以释放该串口所在的数字引脚,此时串口Rx和Tx可以作为数字IO引脚使用。
  • 语法:Serial.end()
  • 参数:无。
  • 返回值:无。

(3)available( )

  • 功能:判断串口缓冲区的状态,获取串口接收到的数据个数,即获取串口接收缓冲区中的字节数。接收缓冲区最多可保存64B的数据。
  • 语法: Serial.available( )
  • 参数:无。
  • 返回值:可读取的字节数。

(4)print()

  • 功能:串口输出数据,写入字符数据到串口。将数据输出到串口。数据会以ASCII码形式输出。如果想以字节形式输出数据,则需要使用 write() 函数。
  • 语法:
Serial.print(val)
Serial.print(val, format)
  • 参数:
    val:需要输出的数据,任意数据类型。
    format:输出的数据格式。BIN(二进制)、OCT(八进制)、DEC(十进制)、HEX(十六进制)。对于浮点数,此参数指定要使用的小数位数(默认输出2位)。

        示例:

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()

  • 功能:将数据输出到串口,并回车换行。数据会以ASCII码形式输出。
  • 语法:
Serial.println(val)
Serial.println(val, format)
  • 参数:
    val:需要输出的数据,任意数据类型。
    format:输出的数据格式。和 Serial.print(val) 和相同。
  • 返回值:返回输出的字节数。

(6)read()

  • 功能:读取串口数据,一次读一个字符,读完后删除已读数据。
  • 语法:Serial.read()
  • 参数:无。
  • 返回值:返回串口缓存区中第一个可读字节,当没有可读数据时返回-1。

(7)readBytes()

  • 功能:从接收缓冲区读取指定长度的字符,并将其存人一个数组中。若等待数据时间超过设定的超时时间,则退出该函数。
  • 语法:Serial.readBytes(buffer, length)
  • 参数:
    buffer,用于存储数据的数组(char[]或者byte[])。
    length,需要读取的字符长度。
  • 返回值:读到的字节数;如果没有找到有效的数据,则返回0。

(8) peek( )

  • 功能:返回1字节的数据,但不会从接收缓冲区删除该数据。与read()函数不同,read()函数读取数据后,会从接收缓冲区删除该数据。
  • 语法:Serial. peek()
  • 参数:无。
  • 返回值:进入接收缓冲区的第1字节的数据;如果没有可读数据,则返回一1。

(9) write( )

  • 功能:输出数据到串口。以字节形式输出到串口。
  • 语法:
Serial. write(val)
Serial. write(str)
Serial. write(buf, len)
  • 参数:
    val,发送的数据。
    str,String型的数据。.
    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);
  }
}

实验结果:

图7 实验一输出结果

        左图是有 delay(10) 语句的输出结果,右图注释掉 delay(10) 语句的输出结果。

        补充:串口事件,欢迎了解SerialEvent

2.4 软件模拟串口通信

        软串口的操作类为SoftwareSerial,定义于 SoftwareSerial.h 源文件中,但不像硬串口那样,源文件中并没有事先声明软串口对象,Arduino程序中需要手动创建软串口对象。 在使用前需要先声明包SoftwareSerial.h 头文件。。其中定义的成员函数与硬串口的类似,而 available( )、begin()、read()、write()、print()、println()、peek() 等函数的用法也相同。其他的成员函数:

软串口是由程序模拟生成的,使用起来不如硬串口稳定,并且与硬串口一样,波特率越高越不稳定。
软串口通过AVR芯片的PCINT中断功能来实现,在Arduino UNO 上,所有引脚都支持PCINT中断,因此所有引脚都可设置为软串口的RX接收端。但在其他型号的Arduino上,并不是每个引脚都支持中断功能,所以只有特定的引脚可以设置为RX端。

(1)SoftwareSerial()

  • 功能:这是SoftwareSerial类的构造函数,通过它可以指定软串口的RX和TX引脚。
  • 语法:
SoftwareSerial mySerial= SoftwareSerial(rxPin, txPin)
SoftwareSerial mySerial(rxPin, txPin)
  • 参数:
    mySerial,用户自定义软串口对象。
    rxPin,软串口接收引脚。
    txPin,软串口发送引脚。

(2)listen()

  • 功能:开启软串口监听状态。Arduino在同一时间仅能监听一个软串口,当需要监听某一软串口时,需要该对象调用此函数开启监听功能。
  • 语法:mySerial. listen()
  • 参数:mySerial,用户自定义的软串口对象。
  • 返回值:无。

(3) isListening( )

  • 功能:监测软串口是否正处于监听状态。
  • 语法:mySerial. isListening()
  • 参数:mySerial,用户自定义的软串口对象。
  • 返回值:boolean型值,为true表示正在监听,为false表示没有监听。

(4) overflow( )

  • 功能:检测缓冲区是否已经溢出。软串口缓冲区最多可保存64 B的数据。
  • 语法:mySerial. overflow()
  • 参数:mySerial,用户自定义的软串口对象。
  • 返回值:boolean型值,为true表示溢出,为false表示没有溢出。

实验二:Arduino间的串口通信

        Arduino 可以与众多串口设备连接进行串口通信,但需要注意的是,当使用0(RX)、1(TX)串口连接外部串口设备时,这组串口将被所连接的设备占用,从而可能会造成无法下载程序和通信异常的情况。因此,通常在连接外部设备时尽量避免使用0(RX)、1(TX)这组串口。

图8 Arduino间的串口通信实验的连接示意图

        如上图所示,本实验将两个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 <SoftwareSerial.h>
// 新建一个 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 = "";
    }
  }
}

        下载程序后,分别打开两个设备的串口监视器,选择各自对应的波特率,并将结束符设置为“换行和回车”,在两个串口监视器上随意输人字符,并发送,则会看到如下图所示的效果,这说明串口聊天项目已经成功地运行了。


图9 实验二输出结果

小结:

        在实际使用中可能还会用到其他串口设备,如串口无线传输模块、串口传感器等,只要是标准串口设备,其程序的编写方法都基本相同。

  • 63
    点赞
  • 389
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
实验目的: 使用Arduino平台和US-100超声波测距模块,利用串口模式进行测距实验。 实验器材: - Arduino UNO开发板 - US-100超声波测距模块 - 杜邦线若干 实验原理: 超声波测距原理:通过向目标物体发射一束超声波,当超声波遇到障碍物时,会被反射回来,接收器可以接收到反射回来的超声波,并且计算出目标物体与传感器的距离。 US-100超声波测距模块:US-100模块是一款基于超声波测距原理的传感器模块,它可以实现测距、检测障碍物等功能。US-100模块可以通过串口进行控制,具有测距精度高、测距范围广等特点。 实验步骤: 1.将US-100超声波测距模块与Arduino开发板连接,连接方式如下: - VCC接Arduino的5V电源 - GND接Arduino的GND - Trig接Arduino的数字口13 - Echo接Arduino的数字口12 2.打开Arduino IDE,编写程序,代码如下: ``` void setup() { Serial.begin(9600); pinMode(13,OUTPUT); pinMode(12,INPUT); } void loop() { digitalWrite(13,LOW); delayMicroseconds(2); digitalWrite(13,HIGH); delayMicroseconds(10); digitalWrite(13,LOW); long duration = pulseIn(12,HIGH); long distance = duration / 58.2; Serial.print("Distance:"); Serial.print(distance); Serial.println("cm"); delay(1000); } ``` 3.上传程序到Arduino开发板中,打开串口监视器,即可看到US-100模块测得的距离数据。 实验结果: 通过串口监视器可以看到,US-100模块测得的距离数据可以准确地显示在串口监视器中。 实验总结: 本实验通过Arduino平台和US-100超声波测距模块,利用串口模式进行测距实验。实验结果表明,通过串口可以准确地读取到US-100模块测得的距离数据,实现了测距的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长路漫漫2021

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值