分享

<font style="vertical-align: inherit;"><font style="vertical-align: inherit;">实验:</font></font>

 青樓滿座wu0pn9 2018-08-08
     最初由Tom Igoe
2015年10月5日撰写最后修改于2015年10月6日,作者:Benedetta Piantella Simeonidis

介绍

内容[ 显示 ]

从第一个数字I / O和模拟实验室开始,您一直在使用异步串行通信将数据发送回Arduino串行监视器。在本实验中,您将更深入地了解从微控制器到个人计算机的串行通信,以便您可以开始在计算机上编写其他语言的程序以与我们的微控制器进行交互。您将从Arduino的多个传感器向您的计算机发送数据,并了解如何格式化数据,以及管理两者之间的交换。

你需要知道什么

为了充分利用本实验,您应该熟悉如何编程Arduino,以及串行通信的基础知识。如果您不是,请查看以下链接:

你需要的东西

对于本实验,您需要:
线路板 Arduino的 hookup_wire
无焊面包板 Arduino模块 22-AWG连接线
加速度计 开关
加速度计(或其他两个模拟传感器) 开关或按钮

连接传感器

将两个模拟传感器连接到模拟引脚0和1,就像在  模拟实验室中一样数字实验室一样,将开关连接到数字引脚2 

本实验中的照片和示意图显示了加速度计和按钮。但是,您不必使用这些。使用适合您最终应用的传感器。当您想要使用哪些传感器时,请使用您手中最方便的传感器; 也许两个电位器用于模拟传感器和一个按钮?

电路:
LabSerialDuplexReference_schem LabSerialDuplexReference
示意图 Arduino带加速度计和按钮

(用Fritzing 制作的图表 

注意:并非所有加速度计都相同。然而,Analog Electronics制造了一系列非常受欢迎的加速度计 ADXL3xx系列,它具有三个用于X,Y和Z加速度的模拟输出。这个原理图应该可以互换使用它们中的大多数。

了解CoolTerm

Arduino串行监视器是查看串行通信的一种很好的基本方法,但它没有很多功能。CoolTerm是一个功能更全面的串行终端程序。它是免费的,可用于OSX,Windows和Linux,并且包括一些您无法从串行监视器获得的功能,例如能够在多个窗口中打开多个端口,能够以ASCII或十六进制值查看数据,以及更多。

相关视频:使用CoolTerm

CoolTerm-截图1 coolterm-截图-2-

注意:一次只能有一个程序控制串口。如果您没有使用给定的程序,请记得关闭串口。 如果不这样做,您将无法重新编程Arduino模块,因为串行终端程序将控制串行端口。

初始化通信

什么是异步串行通信?串行   意味着两台计算机通过来回发送数据脉冲一次一位地进行通信。 异步意味着正在通信的两台计算机各自独立地跟踪时间。两台计算机需要达成一致意见: 

  • 电气:脉冲的电压是多少?
  • 数据速率:发送一些数据的频率是多少?
  • 逻辑:低电压意味着该位是0还是1?

当您使用Arduino时,将为您设置电气和逻辑协议。您可以在代码中设置数据速率。到目前为止,在您的Arduino草图中,您已使用此行将数据速率设置为每秒9600位:

Serial.begin(9600);

无论您正在与之通信的程序(无论是串行监视器,CoolTerm还是其他编程环境)都必须设置相同的数据速率。只要将它们更改为相同的速率,您就可以更改它们。

两台计算机之间有三个连接:

  • 从发送方到接收方的传输线
  • 从接收方到发送方的接收线路
  • 共同的地面线

发送(有时称为TX)和接收(有时称为RX)是相对的:我的发送连接到接收,反之亦然。在Arduino Uno上,数字引脚0和1用于接收和发送。它们连接到电路板上的USB转串口芯片。当您将Arduino插入计算机时,它会显示为USB COM设备,这意味着串行通信设备。当您要求提供串行端口列表时,无论是在Arduino Serial Monitor或CoolTerm中,还是在任何程序中,Arduino都将显示为新端口。

ASCII与二进制:你发送什么?

为了在两个设备之间进行清晰的通信,您需要了解ASCII和原始二进制数据之间的区别。

首先,只需从一个传感器发送值,即第一个模拟传感器(照片中加速度计的第一个轴),然后将输出除以最大值1023:

void setup() {
   // start serial port at 9600 bps:
   Serial.begin(9600);
}
void loop() {
   int analogValue = analogRead(A0);
   Serial.println(analogValue);
}

打开串行监视器时,您应该看到向下滚动窗口的0到1023之间的数字。这是因为Serial.println()将它打印的值格式化为ASCII编码的十进制数,并在末尾的回车符处添加换行符。此外,串行监视器假定它应显示与其接收的每个字节对应ASCII字符

analogRead()的输出不能适合单个字节,因为微控制器的模数转换器(ADC)以10位或2 10的分辨率读取输入要将输出转换为单个字节,请将输出映射到0-255的范围,如下所示:

void setup() {
   // start serial port at 9600 bps:
   Serial.begin(9600);
}
void loop() {
   int analogValue = analogRead(A0);
   int mappedValue = map(analogValue, 0, 1023, 0, 255);
   Serial.println(mappedValue);
}

立即尝试,输出范围应为0到255。

尝试将Serial.println()更改为Serial.write()现在你得到了一系列垃圾字符。这是怎么回事?所述Serial.write()命令不格式化字节为ASCII字符。它发出传感器读数的二进制值。每个传感器读数范围为0到1023; 换句话说,它具有10位范围,因为2 10  = 1024个可能的值。由于这超过了可以容纳在一个字节中的8位,您可以在上面的代码中将值除以4,或使用map()函数获得0到255或2 8  位的范围。有关此问题的更多背景信息,请参阅有关变量的说明

arduino串口监视器

因此,例如,如果传感器读数的值为234,则Serial.write()命令发送二进制值11101010.如果读数为255,则Serial.write()发送11111111.如果它是157,则命令发送10011101。要获得更多的十进制到二进制转换,请打开计算机的计算器并选择Programmer视图(在Mac上按apple-3,在Windows上按Alt-3)。垃圾字符是与Monitor正在接收的ASCII值对应的字符。当串行监视器收到一个字节时,它会假定它应该显示 与该字节值对应的  ASCII字符

例如,假设analogValue = 32:

  • Serial.println(mappedValue)导致带有换行和回车的“32”
  • Serial.write(mappedValue)产生“”,即空格字符,其ASCII值为32。

当analogValue = 32时,Serial.println(mappedValue)发送了多少字节?

Serial.println(mappedValue)实际上发送了四个字节!它发送一个字节来表示3,一个字节表示2,一个字节告诉Monitor将光标向下移动一行(换行符),一个字节将光标一直移动到左边(回车) 。这四个字节的原始二进制值是51(ASCII表示“3”),50(ASCII表示“2”),10(ASCII表示“换行符”)和13(ASCII表示“回车”)。检查ASCII表,你会亲眼看到。

以多种格式发送数据

尝试此程序并在串行监视器中查看结果

void setup() {
   // start serial port at 9600 bps:
   Serial.begin(9600);
}
void loop() {
   // read analog input, map it to make the range 0-255:
   int analogValue = analogRead(A0);
   int mappedValue = map(analogValue, 0, 1023, 0, 255);
   Serial.println(mappedValue);
   // print different formats:
   Serial.write(mappedValue);  // Print the raw binary value
   Serial.print('\t');             // print a tab
   // print ASCII-encoded values:
   Serial.print(mappedValue, BIN); // print ASCII-encoded binary value
   Serial.print('\t');             // print a tab
   Serial.print(mappedValue);      // print decimal value
   Serial.print('\t');             // print a tab
   Serial.print(mappedValue, HEX); // print hexadecimal value
   Serial.print('\t');             // print a tab
   Serial.print(mappedValue, OCT); // print octal value
   Serial.println();               // print linefeed and carriage return
}

你应该得到这样的输出:

â11100010226 E2 342
á11100001225 E1 341
á11100001225 E1 341
á11100001225 E1 341
à11100000224 E0 340
à11100000224 E0 340
ß11011111223 DF 337
ß11011111223 DF 337
ß11011111223 DF 337

此草图打印原始二进制值,然后是ASCII编码的二进制值,然后是ASCII编码的十进制,十六进制和八进制值。您可能永远不需要所有这些不同的格式,但在某些时候您可能至少需要十进制和原始二进制版本。

什么是\ r \ n和\ n Stuff,无论如何?

ASCII表  包含几个字符是“隐形”,如制表符,换行符,回车,等等。在大多数编程语言中,这些字符用以反斜杠开头转义字符串表示:\。例如,换行符是\ n。回车是\ r \ n。标签是\ t。当您在代码示例中看到转义字符串时,请将其替换为您认为适当的ASCII值。

发送所有三个传感器的值

在上面的第一个例子中,使用Serial.write(),您一次又一次地发送了一个代表一个传感器值的字节。当您发送多个传感器值时,它会变得更复杂一些。您需要一种方法来了解哪个值代表哪个传感器。例如,假设您使用以下循环发送传感器值:

void loop() {
    for (int thisSensor = 0; thisSensor < 3; thisSensor++) {
       int sensorValue = analogRead(thisSensor);
       Serial.print(sensorValue);
       Serial.print(",");
    }
 }

你会得到一个像这样的字符串:

452,345,416,234,534,417,325,452,231

如何判断哪个值对应哪个传感器?你不知道哪个传感器是哪个。您可以假设,如果您在微控制器开始发送时开始收听,则第一个读数对应于第一个传感器,但您无法确切知道。有两种方法可以按顺序获取传感器值。您可以使用标点符号,也可以使用呼叫响应握手方法。使用对你来说最有意义的。他们在下面解释。

格式化多个串行数据:标点符号

发送数据以便可以清楚地解释的一种方法是唯一地标点每组数据。正如句子以句点结尾一样,您可以使用回车符和换行符结束数据。更改上面的for循环,以便在每个三个值的字符串末尾打印回车符和换行符。

void loop() {
   for (int thisSensor = 0; thisSensor < 3; thisSensor++) {
      int sensorValue = analogRead(thisSensor);
      Serial.print(sensorValue);
      // if you're on the last sensor value, end with a println()
      // otherwise, print a comma
      if (thisSensor == 2) {
         Serial.println();
      } else {
         Serial.print(",");
      }
   }
}

从这个循环中,你得到这样的输出:

452345416
234534417
325452231

这要好得多。无论何时获得换行符,您都知道下一个值是第一个传感器。

现在编写一个程序,读取电路板上的两个模拟传感器和一个数字开关,并以这种格式打印出来:

analog1,analog2,switch
analog1,analog2,switch
analog1,analog2,switch

首先为开关引脚的编号设置一个常量。然后在设置中,以9600bps初始化串行通信,并将开关引脚声明为输入。

const int switchPin = 2;      // digital input
 void setup() {
   // configure the serial connection:
   Serial.begin(9600);
   // configure the digital input:
   pinMode(switchPin, INPUT);
 }

在主循环中,使用名为sensorValue的局部变量来读取每个输入。首先读取两个模拟输入,然后在每个输入后用逗号打印。然后读取数字输入,并使用回车打印并在末尾添加换行符。

void loop() {
   // read the sensor:
   int sensorValue = analogRead(A0);
   // print the results:
   Serial.print(sensorValue);
   Serial.print(",");
   // read the sensor:
   sensorValue = analogRead(A1);
   // print the results:
   Serial.print(sensorValue);
   Serial.print(",");
   // read the sensor:
   sensorValue = digitalRead(switchPin);
   // print the results:
   Serial.println(sensorValue);
}

一旦获得了数据格式,您所要做的就是编写一个读取该格式的程序。你会看到在这些实验室中如何做到这一点。选择适合您所知的编程语言的那个:

流量控制:呼叫和响应(握手)

标点符号有助于保持数据的有序,但由于异步串行通信是异步的,因此当发送方发送的速度超过接收方可读取的速度时,您可能会遇到问题。发生这种情况时,接收器程序会在串行缓冲区填满时减慢速度。您可以通过实现某种形式的流控制来管理它最简单的方法是使用一个调用和响应方法,其中发送程序仅在被告知这样做时发送,并且接收程序必须在每次完成读取它所获得的内容时请求新数据。

您可以非常简单地为上面的代码添加握手。修改Arduino代码如下。首先,在setup()中添加一个新的代码块。该块发送一条消息,直到它从远程计算机获取一个数据字节:

void setup() {
  Serial.begin(9600);
  while (Serial.available() <= 0) {
    Serial.println("hello"); // send a starting message
    delay(300);              // wait 1/3 second
  }
}

现在,通过添加if()语句来修改loop()以查找传入的串行数据并读取它。

void loop() {
  if (Serial.available() > 0) {
    // read the incoming byte:
    int inByte = Serial.read();
    // read the sensor:
    sensorValue = analogRead(A0);
    // print the results:
    Serial.print(sensorValue);
    Serial.print(",");
    // read the sensor:
    sensorValue = analogRead(A1);
    // print the results:
    Serial.print(sensorValue);
    Serial.print(",");
    // read the sensor:
    sensorValue = digitalRead(switchPin);
    // print the results:
    Serial.println(sensorValue);
  }
}

草图的其余部分保持不变。当您运行它并打开串行监视器时,您将看到:

你好
你好
你好
你好

在输出框中键入任何字符,然后单击“发送”。您将在hellos的末尾获得一串传感器值:

510,497,0

键入其他字符并单击“发送”。您发送的字符无关紧要,但在发送一组新的传感器值之前,循环将始终等待传入的字节。当您编写一个程序来接收这种格式时,它的行为必须与您的行为相同:

打开串口
等一下你好
发送一个字节来请求数据
开始循环:
  等待一组数据
  发送一个字节以请求新数据
结束循环

原始二进制与ASCII的优点

此处显示的所有示例都将传感器值发送为ASCII编码的字符串。如上所述,这意味着您发送了三个字节来发送三位数值。如果相同的值小于255,则可以在一个原始二进制字节中发送它。所以ASCII效率肯定不高。但是,它对于调试来说更具可读性,如果接收程序非常适合将字符串转换为数字,那么ASCII是一个很好的方法。例如,JavaScript几乎默认将数据作为ASCII字符串发送,因此在JavaScript中编写时使用ASCII是有意义的。如果接收器不太擅长将字符串转换为数字(例如,在Arduino中读取多字节字符串比处理中更具挑战性)那么您可能希望将数据作为二进制值发送。

标点符号和呼叫响应的优点

用于发送多个串行值的标点方法可能看起来更简单,但它有其局限性。您不能轻易使用它来发送二进制值,因为您需要为标点符号设置一个具有唯一值的字节。在上面的示例中,您使用值10(ASCII换行符)作为标点符号,因此如果您将传感器值作为原始字节发送,则当传感器值为10时您将遇到麻烦。接收方将解释10作为标点符号,而不是传感器值。相反,无论您是以原始二进制值还是ASCII编码值发送数据,都可以使用调用和响应。

有时接收器读取的串行数据比发送者发送的慢。例如,如果您有一个执行大量图形工作的程序,它可能每隔几毫秒才会读取一次串行数据。例如,如果您正在使用P5.js,您可能会注意到在draw()循环中使用println()将导致草图减速。在这种情况下,串行缓冲区将变满,您会发现响应时间滞后。这是切换到呼叫和响应方法的好时机。

进一步的工作

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多