串行通信如何在Arduino上工作?


16

关于Arduino Uno,Mega2560,Leonardo和类似的板子:

  • 串行通讯如何工作?
  • 串行速度有多快?
  • 如何在发送方和接收方之间连接?

请注意:这仅供参考。


您可能会发现这个有趣的有关缓冲区在纳米连接到运行一个Python数据记录器一个Raspian系统,只用两者之间的普通的USB编程电缆的两端: arduino.stackexchange.com/questions/11710/...
SDsolar

Answers:


16

异步串行(通常称为串行)通信用于将字节从一台设备发送到另一台设备。设备可以是以下一项或多项:

  • Arduino的
  • 个人电脑
  • 全球定位系统
  • RFID读卡器
  • 液晶显示器
  • 调制解调器
  • 其他

时钟速率和数据采样

与SPI / USB / I2C不同,串行通信没有时钟信号。采样时钟是商定的采样率(称为波特率)。发送方和接收方都必须配置为使用相同的速率,否则接收方将接收到无意义的数据(由于未以与发送时相同的速率对位进行采样)。

传输是异步的,这基本上意味着可以在任何时间发送字节,并且字节之间的间隙可以变化。此图说明了正在发送的单个字节:

串行通讯-发送一个字节

上图显示了正在发送的字母“ F”。在ASCII中,它是0x46(十六进制)或0b01000110(二进制)。所述至少显著(低位)位首先传输,从而在上述图形你看到的顺序到达的位:01100010

字节之间的“空闲”时间以连续的“ 1”位发送(有效地,发送线连续保持高电平)。

为了指示字节的开始,总是通过将线拉低来指示开始位,如图所示。接收器看到起始位后,将等待1.5倍的采样时间,然后对数据位进行采样。它等待1.5次,因此:

  • 跳过起始位
  • 采样到下一位的一半

例如,如果波特率为9600波特,则采样率将为1/9600 = 0.00010416秒(104.16 µs)。

因此,在9600波特下,接收器在接收到起始位后等待156.25 µs,然后每104.16 µs采样一次。

起始位计时

停止位的目的是确保每个字节之间肯定有1位。没有停止位,如果一个字节以零结尾,那么硬件将无法分辨该位与下一个字节的起始位之间的差异。

要在Uno上产生以上输出,您可以编写以下代码:

void setup()
  {
      Serial.begin(9600);
      Serial.print("F");
  }

void loop ()
  {
  }

数据位数

为了节省传输时间(以前是heh),允许您指定不同数量的数据位。AtMega硬件支持从5到9的数据位编号。显然,数据位越少,您可以发送的信息就越少,但速度越快。


奇偶校验位

您可以选择具有奇偶校验位。如果需要,可以通过计算字符中1的数量,然后根据需要将奇偶校验位设置为0或1来确保该数字为奇数或偶数来计算该值。

例如,对于字母“ F”(或0x46或0b01000110),您可以看到那里有3个(在01000110中)。因此,我们已经有了奇偶校验。因此,奇偶校验位将如下所示:

  • 无奇偶校验:省略
  • 偶数奇偶校验:1(3 +1是偶数)
  • 奇校验:0(3 + 0为奇数)

奇偶校验位(如果存在)出现在最后一个数据位之后但在停止位之前。

如果接收器没有获得正确的奇偶校验位,则称为“奇偶校验错误”。这表明存在一些问题。可能将发送方和接收方配置为使用不同的波特率,或者线路上出现了噪声,将零变为零,反之亦然。

一些早期的系统还使用“标记”奇偶校验(其中,奇偶校验位始终与数据无关,始终为1)或“空间”奇偶校验(其中,奇偶校验位与数据无关,始终为0)。


9位传输

某些通信设备使用9位数据,因此在这些情况下,奇偶校验位变为第9位。发送第9位有特殊的技术(寄存器是8位寄存器,因此第9位必须放在其他位置)。


停止位数

早期的设备在电子方面趋向于变慢,因此为了给接收者一些时间来处理传入的字节,有时会指定发送者将发送两个停止位。从根本上来说,这将增加更多的时间,在此位置数据线保持高电平(一个位的时间)之后,下一个起始位才能出现。这个额外的位时间使接收器有时间处理最后一个输入字节。

如果在假定停止位时接收器未获得逻辑1,则称为“成帧错误”。这表明存在一些问题。发送方和接收方很可能配置为使用不同的波特率。


符号

通常,串行通信通过告诉您速度,数据位数,奇偶校验类型和停止位数来表示,如下所示:

9600/8-N-1

这是在告诉我们:

  • 每秒9600位
  • 8个数据位
  • 无奇偶校验(您可能会看到:E =偶数,O =奇数)
  • 1个停止位

发送者和接收者就上述达成一致非常重要,否则通信不太可能成功。


引脚排列

Arduino Uno具有用于硬件串行的数字引脚0和1:

Arduino Uno串行引脚

要连接两个Arduino,您需要像这样交换 Tx和Rx:

将两个Arduino连接在一起


速度

支持多种速度(请参见下图)。“标准”速度通常是300波特的倍数(例如300/600/1200/2400等)。

可以通过设置适当的寄存器来处理其他“非标准”速度。HardwareSerial类为您执行此操作。例如。

Serial.begin (115200);  // set speed to 115200 baud

根据经验法则,假设您使用的是8位数据,则​​可以通过将波特率除以10(由于起始位和停止位)来估计每秒可以传输的字节数。

因此,在9600波特下,您可以9600 / 10 = 960每秒传输960字节()。


波特率错误

通过对系统时钟进行分频,然后向上计数至预设数字,可以生成Atmega的波特率。数据表中的此表显示了16 MHz时钟(例如Arduino Uno上的时钟)的寄存器值和错误百分比。

波特率错误

U2Xn位影响时钟速率除数(0 =除以16,1 =除以8)。UBRRn寄存器包含处理器计数的数字。

因此,从上表可以看出,我们从16 MHz时钟中获得了9600波特,如下所示:

16000000 / 16 / 104 = 9615

我们除以104而不是103,因为计数器是零相关的。因此,这里的误差15 / 9600 = 0.0016接近上表的误差(0.02%)。

您会注意到某些波特率的错误量比其他波特率高。

根据数据手册,8个数据位的最大错误百分比在1.5%至2.0%的范围内(更多信息请参见数据手册)。


Arduino的莱昂纳多

Arduino Leonardo和Micro具有不同的串行通信方法,因为它们直接通过USB连接到主机,而不是通过串行端口。

因此,您必须等待串行变为“就绪”状态(因为软件会建立USB连接),需要额外的几行,如下所示:

void setup()
  {
      Serial.begin(115200);
      while (!Serial)
      {}  // wait for Serial comms to become ready
      Serial.print("Fab");
  }

void loop ()
  {
  }

但是,如果您想通过D0和D1引脚(而不是通过USB电缆)进行实际通信,则需要使用Serial1而不是Serial。您是这样的:

void setup()
  {
      Serial1.begin(115200);
      Serial1.print("Fab");
  }

void loop ()
  {
  }

电压等级

请注意,Arduino使用TTL电平进行串行通信。这意味着它期望:

  • “零”位为0V
  • “ 1”位是+ 5V

设计用于插入PC串行端口的较旧的串行设备可能使用RS232电压电平,即:

  • “零”位为+3至+15伏
  • “一位”位是-3至-15伏

不仅是相对于TTL电平的这种“反转”(“ 1”比“ 0”更负),Arduino无法处理其输入引脚上的负电压(也不能处理大于5V的正电压)。

因此,您需要一个用于与此类设备通信的接口电路。仅对于输入(到Arduino),一个简单的晶体管,二极管和几个电阻就可以做到:

反相缓冲器

对于双向通信,您需要能够产生负电压,因此需要更复杂的电路。例如,MAX232芯片将与四个1 µF电容器一起用作电荷泵电路。


软件序列号

有一个名为SoftwareSerial的库,它使您可以在软件而非硬件中进行串行通信(最大程度)。这样的好处是您可以将不同的引脚配置用于串行通信。缺点是在软件中进行串行处理会占用更多的处理器资源,并且更容易出错。有关更多详细信息,请参见软件序列


Mega2560

Arduino“ Mega”具有3个附加的硬件串行端口。它们在板上标记为Tx1 / Rx1,Tx2 / Rx2,Tx3 / Rx3。如果可能,应优先于SoftwareSerial使用它们。要打开其他端口,请使用名称Serial1,Serial2,Serial3,如下所示:

Serial1.begin (115200);  // start hardware serial port Tx1/Rx1
Serial2.begin (115200);  // start hardware serial port Tx2/Rx2
Serial3.begin (115200);  // start hardware serial port Tx3/Rx3

中断

使用HardwareSerial库发送和接收都使用中断。

正在发送

当您执行时Serial.print,尝试打印的数据将放置在内部“发送”缓冲区中。如果您具有1024字节或更多的RAM(例如Uno),则将获得64字节的缓冲区,否则将获得16字节的缓冲区。如果缓冲区有空间,则Serial.print立即返回,因此不会延迟您的代码。如果没有空间,则它“阻塞”,等待清空缓冲区足以容纳空间。

然后,随着硬件发送每个字节,将调用一个中断(“ USART,数据寄存器为空”中断),并且中断例程将缓冲区中的下一个字节发送到串行端口之外。

接收

当接收到输入数据时,将调用一个中断例程(“ USART Rx完成”中断),并将输入字节放入“接收”缓冲区(与上述发送缓冲区的大小相同)。

调用时,Serial.available您会发现该“接收”缓冲区中有多少字节可用。调用时,Serial.read将从接收缓冲区中删除一个字节并返回到您的代码。

在具有1000字节或更多字节RAM的Arduino上,只要您不让它填满,就不会急于从接收缓冲区中删除数据。如果已填满,则将丢弃任何其他传入数据。

请注意,由于此缓冲区的大小,等待大量字节到达毫无意义,例如:

while (Serial.available () < 200)
  { }  // wait for 200 bytes to arrive

这将永远无法工作,因为缓冲区无法容纳那么多缓冲区。


提示

  • 读取之前,请务必确保数据可用。例如,这是错误的:

    if (Serial.available ())
      {
          char a = Serial.read ();
          char b = Serial.read ();  // may not be available
      }

    Serial.available测试仅确保您有一个字节可用,但是代码尝试读取两个字节。如果缓冲区中有两个字节,它可能会起作用,如果没有,您将得到-1的返回值,如果打印出来,它将看起来像“ÿ”。

  • 请注意发送数据需要多长时间。如上所述,在9600波特下,您每秒仅传输960字节,因此尝试以9600波特从模拟端口发送1000读数将不会非常成功。


参考文献


在第一个图形中:使用箭头,它看起来像先传送了停止位。如果您交换了Rx / Tx和箭头的方向,我会认为它不太混乱。
ott--

它打算从左到右阅读(就像这句话一样),因此左边的事情首先发生。这样说:在示波器上,这就是您看到轨迹的方式。
尼克·加蒙

好的,我用示波器来解释。:-)
ott--

但是,我一直认为您的观点很有意义。别人怎么看?如果箭头反转了,我交换了Rx / Tx,会更清楚吗?
尼克·加蒙

1
@ linhartr22我将其修改为读取可能更接近的“无意义的数据”。
尼克金门
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.