很多时候需要Arduino和其他设备需要通信,用来接受控制命令,返回监测到的数据等等。比如:和电脑主机相连,和手机相连。
相连的方式不外乎以下几种,
- USB连接,通过一根数据线把智能硬件和设备连接起来;
- 蓝牙连接,智能硬件和设备通过蓝牙相连。采用这种方式需要一个蓝牙模块;
- WIFI连接,智能硬件和设备都连接到同一个WIFI路由器上,让彼此通过WIFI网络传输数据。采用这种方式需要一个WIFI模块;
- 红外遥控连接,智能硬件接收遥控器发出的红外信号,实现单方面的数据接收。采用这种方式需要一个红外模块;
7.1 串口通信
在介绍前面列举出的各种通信方案之前,我们先来了解下串口通信,串口通信是Arduino最基本的数据通信方式,Arduino
开发板会提供一个至多个用于通信用的串口接口。
其实我们之前的学习过程中,已经不知不觉使用过了串口通信,比如调试信息的输出、程序部署到开发板。虽然你看到的是我们使用的USB接口,但实际上USB接口是和串口绑定在一起的。所以实际上,使用的还是串口通信。
不同开发板,串口引脚的位置有些不同。不过在电路板上都标注着TX
和RX
标志。TX
表示发送数据的引脚,RX
表示接受数据的引脚。
- UNO的串口硬件位置
USB接口和0、1引脚都是对应着串口-Serial。 - MEGA的串口硬件位置
MEGA有多个串口,
USB接口和0、1引脚对应着串口-Serial
18、19引脚对应着串口1-Serial1
16、17引脚对应着串口2-Serial2
14、15引脚对应着串口3-Serial3
每一个串口都由一个输入引脚(RX)和输出引脚(TX)组成。硬件接收和发送数据的功能,就是通过对应的引脚实现的。
串口通信是最基础的通信方式,Arduino开发板的USB接口就是使用了串口通信,相当于USB接口直接和串口做了连接。
如果要使用外接的wifi和蓝牙模块,也需要将它们的通信引脚与Ardiono上的串口引脚进行连接。它们都是利用了串口通信来实现数据的交换。
可以说串口是所有通信的桥梁,不管用什么模块实现数据的交换,数据最后都要汇聚到串口这里来。
红外遥控接口没有使用串口,我们最后来介绍。
7.1.1 串口数据的读写
虽然连接串口的设备不同(USB口,WIFI,蓝牙),但是读写串口数据的方式都是相同的。
在进行读写之前,要判断一下串口是否可用,
if(Serial.available())
{
//这里面对串口操作才会有意义
}
当我们想在代码中使用这些串口读取数据的时候,可以,
- 读一个字节,```cpp //通过UNO或MEGA的Serial读数据, //读一个字节 int data = Serial.read();
//通过MEGA的Serial2读数据, //读一个字节 int data = Serial2.read();
2. 读到数组缓冲区-`buffer`中,```cpp
//通过UNO或MEGA的Serial读数据,
//读到的数据放在buffer的空间当中,
//返回值nRead表示成功读取到的字节数
int length = 128;
char * buffer = new char[length];
int nRead = Serial.readBytes(buffer, length)
//通过MEGA的Serial2读数据,
//读到的数据放在buffer的空间当中,
//返回值nRead表示成功读取到的字节数
int length = 128;
char * buffer = new char(length);
int nRead = Serial2.readBytes(buffer, length)
当我们想在代码中使用这些串口发送数据的时候,可以,
- 发送一个byte,```cpp //通过UNO或MEGA的Serial发数据 Serial.write(‘A’);
//通过MEGA的Serial2发数据 Serial2.write(‘A’);
2. 发送一个缓冲区的内容,```cpp
//通过UNO或MEGA的Serial发数据
//result表示发送成功的byte数
uint8_t data[] = "hello arduino";
int len = sizeof(data);
int result = Serial.write(data, len);
//通过MEGA的Serial2发数据
//result表示发送成功的byte数
uint8_t data[] = "hello arduino";
int len = sizeof(data);
int result = Serial2.write(data, len);
以上内容是通过串口发送和接受数据的共同方式,请一定要记住,不管最终是通过USB还是蓝牙,还是WIFI模块,都是这样操作的。Serial
的使用方法,可以参考Arduino对应的官方文档章节。
7.2 USB通信
通过USB口进行数据的读写我们已经非常熟悉了。
其实我们之前的学习过程中,已经不知不觉使用过了串口通信,比如调试信息的输出、程序部署到开发板。虽然你看到的是我们使用的USB接口,但实际上USB端口已经在开发板上完成了串口的连接,我们只需要使用就好,不要单独去连线了。所以,这种方案使用的还是串口通信。
7.3 蓝牙通信
蓝牙连接是采用较多的一个方案,主要原因是,这是一个无线通信的方式,没有数据线的牵绊,而且Arduino蓝牙模块的价格相对便宜,实现的成本低,而且编程很简单。
下面展示了HC-05
蓝牙模块的引脚布局,详细各位从引脚的标注也能猜到各个引脚的作用。其中,使用4个引脚(输出TX、输入RX、电源VC、接地GND)连接开发板就能完成组装了。(其他型号的蓝牙模块,也和它差不多)
7.3.1 蓝牙连接
我们经常见到的蓝牙模块有HC-05
HC-06
。还有一种形式的蓝牙模块,它就远不止4个引脚了。
我们上面采用的蓝牙模块,就是功能最为简单的HC-06蓝牙模块
,它只能够被动的接收其它蓝牙模块发起的连接请求(不能主动发起连接),连接时的配对密码默认是1234
。
这里使用了MEGA开发板来进行说明。如果使用UNO开发板原理也是一样的。
根据之前串口连接的原则,我们将蓝牙模块与Arduino开发板用插线连接起来。我们选择将蓝牙模块的RX TX连接到MEGA开发板(或者UNO)的0和1引脚--使用Serial
这个串口。
或者使用扩展板来连接蓝牙模块,这里我们使用HC-06
举例,将HC-06
连接到UNO或MEGA扩展板的Serial接口上,
扩展板引脚 | 连接 | 蓝牙模块引脚 |
---|---|---|
0组V(电源VC) | <—-> | 电源VC |
0组G(接地GND) | <—-> | 接地GND |
1组S(TX) | <—-> | 输入RX |
0组S(RX) | <—-> | 输出TX |
当开发板通电(USB数据线连接上电脑)以后,蓝牙模块的指示灯就亮了。此时它就进入了工作状态:其他蓝牙设备就可以查找并连接这个外接的蓝牙模块了。
*注意开发板和外接通信模块相连的时候后,是TX连着RX,一个发送,另一个当然就是接收了;并且VC/GND和TX/RX不能接反了,不然可能会烧毁蓝牙模块。
电源的两根线不能接到数据发送的两个的引脚上,否则会烧毁蓝牙模块
电源的两根线不能接到数据发送的两个的引脚上,否则会烧毁蓝牙模块
电源的两根线不能接到数据发送的两个的引脚上,否则会烧毁蓝牙模块
不过你一定会遇到一个大坑:当你的蓝牙设备连接到串口引脚后,你会发现程序无法通过串口从电脑端部署到Arduino开发板上了。原因很简单,USB传输数据要使用串口,蓝牙模块传世数据也要使用串口,这个串口太受欢迎了,被两个模块同时占用着。
解决办法有下面几种:
- 部署代码的时候,把蓝牙模块拔掉。部署完成后再把蓝牙模块接回去。
- 更换开发板,把UNO换成MEGA,毕竟MEGA上的串口要多出来好几个,大家各用各的就可以了。
- 使用
软串口
,这个方法我们后面来讲。
7.3.2 蓝牙数据的读写
接下来,我们做个简单的程序:让蓝牙模块接受其他蓝牙设备发送过来的数据,每次收到该数据后,又把数据原封不动的回传给发送设备,
在
setup()
中,初始化好蓝牙设备的波特率,为9600
,因为我们硬件连接的是Serial
,所以就要初始化Serial
。cpp void setup() { //初始化蓝牙使用的串口波特率 Serial.begin(9600); }
在
loop()
中,不停的尝试读取串口(蓝牙设备)收到的消息,一旦收到,就通过串口(蓝牙设备)把收到的内容原封不动的发送出去,```cpp void loop() { //如果可以读取数据 if (Serial.available()) {//创建读取数据用的缓存
int length = 128;
char * buffer = new char[length];
//读取数据到buffer中
int nReadSize = Serial.readBytes(buffer, length);
//如果成功读取到数据,就原封不动的发回去
if(nReadSize > 0)
{
Serial.write(buffer, nReadSize);
}
//删除分配的空间,避免内存泄漏
delete buffer;
} } ```
这个用法其实和使用普通串口没有区别,我们的代码操作的都是串口,至于串口连接的是具体什么设备(蓝牙、WIFI),我们都不用去关注了。
7.3.3 使用软串口
刚才的方案中,蓝牙模块数据传输占用了UNO板唯一的串口,会导致无法通过串口进行调试。为此,我们可以使用软串口
技术,也就是使用其他引脚来接收蓝牙模块的数据,但读写数据的方式又和读写串口的方式保持一致。就好像通过软件的方式创造了一个新的串口来模拟之前的硬件串口。
软件代码模拟的串口的细节不需要我们去理解和深究,直接使用就可以了。对于我们使用者来说,不用去区分它到底是真的从硬件获取的数据,还是从虚拟的软件上获取到的数据。
为此,开发板引脚的连接,就需要做下调整:硬件串口(0、1引脚)保持不被占用,选用其他引脚(比如5和6)作为接收和发送引脚。
扩展板引脚 | 连接 | 蓝牙模块引脚 |
---|---|---|
6组V(电源VC) | <—-> | 电源VC |
6组G(接地GND) | <—-> | 接地GND |
6组S(TX) | <—-> | 输入RX |
5组S(RX) | <—-> | 输出TX |
注意,蓝牙模块的输入输出对应着开发板的输出输入。
在代码端做如下修改,
#include <SoftwareSerial.h>
//初始化蓝牙使用的软串口引脚
//第一个参数:数据接收的引脚
//第二个参数:数据发送的引脚
SoftwareSerial softSerial(5, 6);
void setup()
{
//初始化蓝牙使用的串口波特率
Serial.begin(9600);
//初始化蓝牙使用的软串口波特率
softSerial.begin(9600);
}
void loop()
{
//如果可以读取蓝牙数据
if (softSerial.available())
{
//创建读取数据用的缓存
int length = 128;
char * buffer = new char[length];
//读取数据到buffer中
int nReadSize = softSerial.readBytes(buffer, length);
//如果成功读取到数据,就原封不动的发回去
if(nReadSize > 0)
{
softSerial.write(buffer, nReadSize);
//使用真正的串口打印调试数据
Serial.println("feedback data");
}
//删除分配的空间,避免内存泄漏
delete buffer;
}
}
这里面使用了#include <SoftwareSerial.h>
来使用软串口,这是Arduino自身就提供的一个软件功能,我们拿来用就可以了。
另外,大家也能注意到,软串口的用法和真正硬件串口的用法一致,不需要我们重新去学习,这大大简化了开发者的学习成本。
7.4 WIFI通信
使用WIFI模块来连接,需要进行的操作要稍微复杂些,不过复杂的主要在于WIFI网络的接入,后续的读取和发送数据,与蓝牙模块通过Serial
读写数据是完全一样的。
我们选用的WIFI模块是ESP8266_WIFI模块
。我们这里暂时不介绍了以后专门章节介绍。
7.5 IRDA红外遥控
红外遥控与之前几种数据通信不太一样:
- 不使用串口,而是通过数字接口获取数据;
- 通信是单向的,只能从遥控器发送给接收器;
下图是我们将要使用的红外遥控发送(遥控器)和接收端模块:
当按下遥控器上的一个按钮后,接收器就会收到一段二进制代码,比如:
150CB559BCBBCBCBCBCBCBBCB21B21C20C21B21B21B21B21C20CBC20CBCBCBB21CBCBB21CBB21C20C21BCB21B
DCABB559BCBCBCBCBCBCBCBCB21C20C21B22A21B21C20C21BCB21B21BCBCBCB21BCB21BCBCB21C20C20CBC21B
E86AB358CCBBCBCBCBCBCBBCB21C20C21B21B21B22B20C20CBB22BCB21BCBCB21CBB21CBB21BCB21C20CCB21B
...
将这段代码解码后,我们能得到发送的数据包含了:
- 遥控器的类型(decode type):比如NEC、JVC等,对照表如下:| 遥控器类型 | 对应代码 | 遥控器类型 | 对应代码 | 遥控器类型 | 对应代码 | | —- | —- | —- | —- | —- | —- | | NEC | 1 | SONY | 2 | RC5 | 3 | | RC6 | 4 | DISH | 5 | SHARP | 6 | | PANASONIC | 7 | JVC | 8 | SANYO | 9 | | MITSUBISHI | 10 | UNKNOWN | -1 | | |
- 按钮可能对应的数值(value),这个值对不同的遥控器类型可能是不同的,以下只是列出来供大家感性认识下。具体的值,需要大家在开发的时候查阅手册,或者根据后面的测试代码自己验证。| 按钮名称 | 对应数值 | 按钮名称 | 对应数值 | 按钮名称 | 对应数值 | | —- | —- | —- | —- | —- | —- | | CH- | FFA25D | CH | FF629D | CH+ | | | << | FF22DD | >> | FF02FD | || | FFC23D | | - | FC8FEB4F | + | FFA857 | EQ | FF906F | | 0 | FF6897 | 100+ | FF9867 | 200+ | FFB04F | | 1 | FF30CF | 2 | FF18E7 | 3 | FF7A85 | | 4 | FF10EF | 5 | FF38C7 | 6 | FF5AA5 | | 7 | FF42BD | 8 | FF4AB5 | 9 | FF52AD |
7.5.1 硬件连接
IRDA模块的连接非常简单,接收端可以直接连接在开发板上面。
IRDA模块引脚 | 开发板引脚 |
---|---|
IN | 6组SIG |
VCC | 6组5V |
GND | 6组GND |
如下图所示,
7.5.2 红外数据的读写
红外遥控模块,对数据的读取是通过数据引脚获取的,而且获取的这些数据是原始的二进制码(前面我们已经看到过它的样子了),因此需要做进一步的解析。我们将采用第三方提供的库文件
进行这个操作(实际上是源代码)。
前面的章节介绍了在Arduino IDE中使用第三方库的方式:将库文件放到Arduino IDE认识的Library
路径下。比如下图位置,
使用的时候在.ino
文件顶部,用<>
引用头文件;
#include <IRremote.h>
这里采用一种新的用法:将库文件放到本项目代码所在的目录下,
使用的时候在.ino
文件顶部,用""
引用头文件;
#include "IRremote.h"
本例会将库文件放到项目目录下。然后,修改源码如下:
// 库源码放在本项目所在目录
#include "IRremote.h"
// 设置接收数据的引脚
int RECV_PIN = 6;
// 初始化IRDA模块
IRrecv irrecv(RECV_PIN);
// 准备读取数据的数据结构
decode_results results;
void setup() {
// 初始化引脚
pinMode(RECV_PIN, INPUT);
// 初始化串口
Serial.begin(9600);
// 启动IRDA接收模块,随时接收数据
irrecv.enableIRIn();
}
void loop() {
// 解码成功
if (irrecv.decode(&results)) {
// 解码后的遥控器类型代码
Serial.print("decode type:");
Serial.print(results.decode_type, HEX);
// 解码后的遥控器按钮代码
Serial.print(" value: ");
Serial.print(results.value, HEX);
// 接收到的原始数据内容(没有解码)
Serial.print(" raw data: ");
for(int i=0; i<results.rawlen; i++) {
Serial.print(results.rawbuf[i], HEX);
}
Serial.println("");
// 清空IRDA当前缓存的数据,可以处理下一条接收到的数据
irrecv.resume();
}
}
特别需要注意的是:每处理完一个指令后,一定要使用irrecv.resume()
进行缓存的清空,否则该数据还在,下一条待处理数据仍然是它。
假如你希望更加深入的了解如何读取Arduino开发板引脚的数据,并如何进行解码的,以及想知道decode_results
数据结构中,到底有哪些数据,请参考库文件源码,里面都有很详细的实现和解释。这里附上一段源码中对decode_results
的定义,供大家感性认识:
// Results returned from the decoder
class decode_results {
public:
int decode_type; // NEC, SONY, RC5, UNKNOWN
unsigned int panasonicAddress; // This is only used for decoding Panasonic data
unsigned long value; // Decoded value
int bits; // Number of bits in decoded value
volatile unsigned int *rawbuf; // Raw intervals in .5 us ticks
int rawlen; // Number of records in rawbuf.
};
7.5.3 观察
将程序部署到开发板上,按动遥控器按钮,可以看到串口调试窗口中,会出现对应按钮的数据。
请注意value
为FFFFFFFF
这样的数据,它们是按住某个按钮不放,IRDA接收端收到的数据,表示和之前接收到的数据一样。
至此,Arduino通过外接外接无线模块接收和发送数据的功能都能完整的实现了。