ESP8266 NodeMCU

ESP8266-NodeMCU是一个开源硬件开发板,由于它支持WIFI功能,所以在IOT领域,Arduino开发板最大的对手之一就是ESP8266-NodeMCU开发板。ESP8266-NodeMCU尺寸与Nano类似,他并不是Arduino团队开发的,但是我们也可以使用Arduino IDE 对其进行开发,NodeMCU是以ESP8266芯片为核心的开发板,无论是芯片价格还是开发板的价格都是比较实惠的,总体来说ESP8266作为IOT的接入设备是非常划算的。

ESP8266 引脚对应图

NodeMCU开发板上的两排插针与ESP8266芯片的引脚相连。有了开发板上的两排插针,我们就可以很轻松的使用杜邦线将芯片的引脚接到实验电路中:

所以,当我们编写 digitalWrite(4, HIGH) 通过digitalWrite函数将引脚4设置为高电平,则意味着我们将GPIO4置为高电平,是开发板的D2引脚,而不是开发板的D4引脚。 这与digitalWrite(D2, HIGH)是等效的。

ESP8266 引脚说明

可用引脚

ESP8266芯片有17个GPIO引脚(GPIO0 - GPIO16)。这些引脚中的GPIO6 - GPIO 11被用于连接开发板的闪存(Flash Memory)。如果在实验电路中使用GPIO6 - GPIO11,NodeMCU开发板将无法正常工作。因此建议不要使用 GPIO6 - GPIO11

电压限制

NodeMCU开发板引脚的输入输出电压限制是3.3 V。如果向引脚施加3.6V以上的电压就有可能对芯片电路造成损坏。同时请注意,这些引脚的最大输出电流是12mA。

引脚特殊情况

GPIO2引脚在NodeMCU开发板启动时是不能连接低电平的。

GPIO15引脚在开发板运行中一直保持低电平状态。因此不要使用GPIO15引脚来读取开关状态或进行I²C通讯。

GPIO0引脚在开发板运行中需要一直保持高电平状态。否则ESP8266将进入程序上传工作模式也就无法正常工作了。其实无需对GPIO0引脚进行额外操作,因为NodeMCU的内置电路可以确保GPIO0引脚在工作时连接高电平而在上传程序时连接低电平。

上/下拉电阻

GPIO 0 -15引脚都配有内置上拉电阻。这一点与Arduino十分类似。GPIO16引脚配有内置下拉电阻。

模拟输入引脚

ESP8266 只有一个模拟输入引脚(该引脚通过模拟- 数字转换将引脚上的模拟电压数值转化为数字量)。此引脚可以读取的模拟电压值为 0 – 1.0V。所以ESP8266 芯片模拟输入引脚连接在1.0V以上电压可能损坏ESP8266芯片。以上所描述的是针对ESP8266芯片的引脚。

而对于NodeMCU开发板引脚,情况就不同了,NodeMCU开发板配有降压电路。可以用NodeMCU开发板的模拟输入引脚读取0-3.3V的模拟电压信号。

通信引脚

串口

ESP8266有2个硬件串行端口(UART)。

串行端口0(UART0)使用GPIO1和GPIO3引脚。其中GPIO1引脚是TX0,GPIO3是RX0。

串行端口1(UART1)使用GPIO2和GPIO8引脚。其中GPIO2引脚是TX1,GPIO8是RX1。请注意,由于GPIO8被用于连接闪存芯片,串行端口1只能使用GPIO2来向外发送串行数据。

I2C

ESP8266只有软件模拟的I²C端口,没有硬件I²C端口。也就是说我们可以使用任意的两个GPIO引脚通过软件模拟来实现I²C通讯。ESP8266的数据表(datasheet)中,GPIO2标注为SDA,GPIO14标注为SCL。

SPI

GPIO14 — CLK、GPIO12 — MISO、GPIO13 — MOSI、GPIO 15 — CS(SS)

开发环境搭建

安装开发板驱动程序,安装 Arduino IDE , 通过开发板管理器安装ESP8266插件,插件也可以离线安装。

如果下面这段LED闪烁程序可以正常运行,那么证明环境是没问题的:

 1void setup() {
 2  // put your setup code here, to run once:
 3  pinMode(LED_BUILTIN, OUTPUT);
 4}
 5
 6void loop() {
 7  // put your main code here, to run repeatedly:
 8  digitalWrite(LED_BUILTIN, HIGH);
 9  delay(1000);
10  digitalWrite(LED_BUILTIN, LOW);
11  delay(1000);
12}

接入点模式(Access Point)

NodeMCU可以建立WiFi网络供其它设备连接。当NodeMCU以此模式运行时,我们可以使用手机搜索NodeMCU所发出的WiFi网络并进行连接 。

 1#include <ESP8266WiFi.h> // ESP8266WiFi库
 2
 3// WIFI名称
 4const char *ssid = "ESP8266-WIFI";
 5
 6// WIFI密码(空字符串则表示无需密码)
 7const char *password = "12345678";
 8                                     
 9void setup() {
10  // 启动串口通讯(波特率9600)
11  Serial.begin(9600);              
12
13  // 用于启动NodeMCU的AP模式
14  WiFi.softAP(ssid, password);
15
16  // 串口输出名称和密码
17  Serial.println(ssid);
18  Serial.println(WiFi.softAPIP());
19}
20
21void loop() { 
22
23}

无线终端模式(Station)

ESP8266可通过WiFi连接无线路由器,这与手机通过WiFi连接无线路由器的模式相同,这就是无线终端模式。

 1// NodeMCU无线终端模式连接WiFi
 2#include <ESP8266WiFi.h>
 3
 4// 将要连接的WiFi和密码
 5const char* ssid = "TP-LINK";
 6const char* password = "xpassword";
 7
 8void setup() {
 9  Serial.begin(9600);
10  // 启动网络连接
11  WiFi.begin(ssid, password);
12  // 告知用户NodeMCU正在尝试WiFi连接           
13  Serial.print("Connecting to ");
14  Serial.print(ssid); Serial.println(" ...");
15
16  // 如果WiFi连接成功则返回值为WL_CONNECTED
17  int i = 0;
18  while (WiFi.status() != WL_CONNECTED) {
19    delay(1000);
20    Serial.print(i++); Serial.print(' ');
21  }
22  
23  Serial.println("");
24  Serial.println("Connection success!");
25  Serial.print("IP address: ");
26  Serial.println(WiFi.localIP());
27}
28 
29void loop() {                                   
30}

如果NodeMCU需要在多个地方使用,这时候就需要它能存储多个地点的WiFi信息。通过以下示例程序,NodeMCU可以在它所处的网络环境里搜索预先存储好的WiFi。一旦找到预存的WiFi名称,NodeMCU将会使用预存的密码信息尝试连接该WiFi。如果同时找到多个预存WiFi,NodeMCU将会尝试连接信号最强的WiFi。

 1// NodeMCU无线终端模式连接WiFi
 2#include <ESP8266WiFi.h>
 3#include <ESP8266WiFiMulti.h>
 4
 5ESP8266WiFiMulti wifiMulti;
 6
 7void setup() {
 8  Serial.begin(9600);
 9  
10  // 将要连接的WiFi和密码
11  wifiMulti.addAP("TP-LINK0", "xpassword");
12  wifiMulti.addAP("TP-LINK1", "xpassword");
13  wifiMulti.addAP("TP-LINK2", "xpassword");
14  
15  Serial.println("Connecting ...");
16  int i = 0;
17  // 如果WiFi连接成功则返回值为WL_CONNECTED
18  // wifiMulti.run()会在当前环境中搜索并连接信号最强的WIFI
19  while (wifiMulti.run() != WL_CONNECTED) {
20    delay(1000);
21    Serial.print('.');
22  }
23    
24  Serial.println("");
25  Serial.println("Connection success!");
26  Serial.print("IP address: ");
27  Serial.println(WiFi.localIP());
28}
29 
30void loop() {                                   
31}

ESP8266 HTTP服务器

 1// NodeMCU无线终端模式连接WiFi
 2#include <ESP8266WiFi.h>
 3#include <ESP8266WebServer.h>
 4
 5// 将要连接的WiFi和密码
 6const char* ssid = "TP-LINK";
 7const char* password = "xpassword";
 8
 9// 设置Web服务器端口
10ESP8266WebServer esp8266_server(8080);
11
12void setup() {
13  Serial.begin(9600);
14  // 启动网络连接
15  WiFi.begin(ssid, password);
16  // 告知用户NodeMCU正在尝试WiFi连接           
17  Serial.print("Connecting to ");
18  Serial.print(ssid); Serial.println(" ...");
19
20  // 如果WiFi连接成功则返回值为WL_CONNECTED
21  int i = 0;
22  while (WiFi.status() != WL_CONNECTED) {
23    delay(1000);
24    Serial.print(i++); Serial.print(' ');
25  }
26  
27  Serial.println("");
28  Serial.println("Connection success!");
29  Serial.print("IP address: ");
30  Serial.println(WiFi.localIP());
31
32
33  // ---- WebServer ---------
34  esp8266_server.begin();
35  esp8266_server.on("/", handleRoot);
36  esp8266_server.onNotFound(handleNotFound);
37  // ---- WebServer ---------
38}
39
40void handleRoot() {   //处理网站根目录“/”的访问请求 
41  esp8266_server.send(200, "text/plain", "Hello from ESP8266");
42}
43
44// 设置处理404情况的函数'handleNotFound'
45void handleNotFound(){
46  esp8266_server.send(404, "text/plain", "404: Not found");
47}
48 
49void loop() {
50  esp8266_server.handleClient();
51}

ESP8266WebServer就是Web服务器对象,begin方法启动服务器,on方法处理访问路径绑定到处理方法上面,onNotFound用于处理404的情况, handleClient 用于检查有没有设备通过网络向NodeMCU发送请求,如果有的画就会调用对应的处理函数。

那么现在就可以用网页表单来控制开发板的LED了,代码如下:

 1// NodeMCU无线终端模式连接WiFi
 2#include <ESP8266WiFi.h>
 3#include <ESP8266WebServer.h>
 4
 5// 将要连接的WiFi和密码
 6const char* ssid = "TP-LINK";
 7const char* password = "xpassword";
 8
 9// 设置Web服务器端口
10ESP8266WebServer esp8266_server(8080);
11
12void setup() {
13  Serial.begin(9600);
14  // 启动网络连接
15  WiFi.begin(ssid, password);
16  // 告知用户NodeMCU正在尝试WiFi连接           
17  Serial.print("Connecting to ");
18  Serial.print(ssid); Serial.println(" ...");
19
20  // 如果WiFi连接成功则返回值为WL_CONNECTED
21  int i = 0;
22  while (WiFi.status() != WL_CONNECTED) {
23    delay(1000);
24    Serial.print(i++); Serial.print(' ');
25  }
26  
27  Serial.println("");
28  Serial.println("Connection success!");
29  Serial.print("IP address: ");
30  Serial.println(WiFi.localIP());
31
32
33  // ---- WebServer ---------
34  esp8266_server.begin();
35  esp8266_server.on("/", handleRoot);
36  esp8266_server.onNotFound(handleNotFound);
37  esp8266_server.on("/LED", handleLED);
38  // ---- WebServer ---------
39
40  pinMode(LED_BUILTIN, OUTPUT);
41}
42
43void handleRoot() {   //处理网站根目录“/”的访问请求 
44  esp8266_server.send(200, "text/html", "<form action=\"/LED\" method=\"POST\"><input type=\"submit\" value=\"Toggle LED\"></form>");
45}
46
47// 设置处理404情况的函数'handleNotFound'
48void handleNotFound(){
49  esp8266_server.send(404, "text/plain", "404: Not found");
50}
51
52void handleLED() {
53  digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
54  esp8266_server.sendHeader("Location","/");
55  esp8266_server.send(303); 
56}
57
58void loop() {
59  esp8266_server.handleClient();
60}

同时也可以读取开发板的引脚状态展示到Web页面上,实现的复杂点就是用WebSocket,简单点就是自动刷新,这里选择自动刷新网页,读取D3引脚的状态来展示到Web页面:

 1// NodeMCU无线终端模式连接WiFi
 2#include <ESP8266WiFi.h>
 3#include <ESP8266WebServer.h>
 4
 5// 将要连接的WiFi和密码
 6const char* ssid = "TP-LINK";
 7const char* password = "192.168.1.1";
 8
 9// 设置Web服务器端口
10ESP8266WebServer esp8266_server(8080);
11
12// 存储引脚状态用变量,检测按键按下
13bool buttonState;
14
15void setup() {
16  Serial.begin(9600);
17  // 启动网络连接
18  WiFi.begin(ssid, password);
19  // 告知用户NodeMCU正在尝试WiFi连接           
20  Serial.print("Connecting to ");
21  Serial.print(ssid); Serial.println(" ...");
22
23  // 如果WiFi连接成功则返回值为WL_CONNECTED
24  int i = 0;
25  while (WiFi.status() != WL_CONNECTED) {
26    delay(1000);
27    Serial.print(i++); Serial.print(' ');
28  }
29  
30  Serial.println("");
31  Serial.println("Connection success!");
32  Serial.print("IP address: ");
33  Serial.println(WiFi.localIP());
34
35
36  // ---- WebServer ---------
37  esp8266_server.begin();
38  esp8266_server.on("/", handleRoot);
39  esp8266_server.onNotFound(handleNotFound);
40  // ---- WebServer ---------
41
42  // 设置为输入上拉模式 
43  pinMode(LED_BUILTIN, INPUT_PULLUP);
44}
45
46void handleRoot() {
47  String htmlCode = "<html><head><meta http-equiv='refresh' content='1'/></head><body>";
48  if(buttonState){
49    htmlCode +="<p>Button Status: HIGH</p>\n";
50  }else{
51    htmlCode +="<p>Button Status: LOW</p>\n";
52  }
53  htmlCode +="</body></html>\n";
54  esp8266_server.send(200, "text/html", htmlCode);  
55}
56
57void handleNotFound(){
58  esp8266_server.send(404, "text/plain", "404: Not found");
59}
60
61void loop() {
62  esp8266_server.handleClient();
63  buttonState = digitalRead(D3); // 获取引脚状态
64}

ESP8266 闪存文件系统

每一个ESP8266都配有一个闪存,这个闪存类似于硬盘,我们上传的文件就被存放在这个闪存里。这个闪存的全称是Serial Peripheral Interface Flash File System(SPIFFS)。

除了可以存放上传的程序以外,还可以将网页文件或者系统配置文件存放在ESP8266的闪存中。如何利用程序对闪存文件系统(SPIFFS)进行文件读取和修改呢?

写入 & 读取文件

 1#include <FS.h>
 2 
 3String file_name = "/myfile.txt";
 4
 5void setup() {
 6  Serial.begin(9600);
 7  
 8  // ----------- 写入操作 ---------
 9  SPIFFS.format(); // 格式化SPIFFS(删除全部用户文件)
10  Serial.println("format fs success.");
11  
12  if(SPIFFS.begin()){ // 启动SPIFFS
13    Serial.println("SPIFFS Started.");
14  } else {
15    Serial.println("SPIFFS Failed to Start.");
16  }
17
18  // 写入字符串
19  File dataFile = SPIFFS.open(file_name, "w");
20  dataFile.println("Hello IOT World.");
21  dataFile.close();
22
23  Serial.println("Finished Writing data to SPIFFS");
24
25  // 追加写入字符串(a->append代表向该文件添加信息)
26  File dataFile = SPIFFS.open(file_name, "a");
27  dataFile.println("This is Appended Info.");
28  dataFile.close();
29  Serial.println("Finished append writing data to SPIFFS");
30
31  // ---------- 读取操作 ---------
32  // 确认闪存中是否有file_name文件
33  if (SPIFFS.exists(file_name)){
34    Serial.print(file_name);
35    Serial.println(" FOUND.");
36  } else {
37    Serial.print(file_name);
38    Serial.print(" NOT FOUND.");
39  }
40  
41  File readDataFile = SPIFFS.open(file_name, "r");
42  // 读取文件内容并且通过串口监视器输出文件信息
43  for(int i=0; i<readDataFile.size(); i++){
44    Serial.print((char)readDataFile.read());
45  }
46  //完成文件读取后关闭文件
47  readDataFile.close();
48}
49
50void loop() {
51}

读取目录下全部文件

 1#include <FS.h>
 2String folder_name = "/myDir";
 3
 4void setup() {
 5  Serial.begin(9600);
 6  
 7  SPIFFS.format(); // 格式化SPIFFS(删除全部用户文件)
 8  Serial.println("format fs success.");
 9  
10  if(SPIFFS.begin()){ // 启动SPIFFS
11    Serial.println("SPIFFS Started.");
12  } else {
13    Serial.println("SPIFFS Failed to Start.");
14  }
15  
16  // 显示目录中文件内容以及文件大小
17  Dir dir = SPIFFS.openDir(folder_name);
18  // 串口输出目录的全部文件
19  while (dir.next()) {
20    Serial.println(dir.fileName());
21  }
22}
23
24void loop() {
25}

删除指定的文件

 1#include <FS.h>
 2 
 3String folder_name = "/myfile.txt";
 4
 5void setup() {
 6  Serial.begin(9600);
 7  Serial.println("");
 8  
 9  if(SPIFFS.begin()){ // 启动闪存文件系统
10    Serial.println("SPIFFS Started.");
11  } else {
12    Serial.println("SPIFFS Failed to Start.");
13  }
14  
15  //从闪存中删除file_name文件
16  if (SPIFFS.remove(file_name)){
17    
18    Serial.print(file_name);
19    Serial.println(" remove sucess");
20    
21  } else {
22    Serial.print(file_name);
23    Serial.println(" remove fail");
24  }                       
25}
26
27void loop() {
28}

显示闪存文件系统信息

 1#include <FS.h>
 2 
 3FSInfo fs_info;
 4
 5void setup() {
 6  Serial.begin(9600);
 7 
 8  SPIFFS.begin();//启动SPIFFS
 9  Serial.println("");
10  Serial.println("SPIFFS Started.");
11 
12  // 闪存文件系统信息
13  SPIFFS.info(fs_info);
14 
15  // 可用空间总和(单位:字节)
16  Serial.print("totalBytes: ");     
17  Serial.print(fs_info.totalBytes); 
18  Serial.println(" Bytes"); 
19 
20  // 已用空间(单位:字节)
21  Serial.print("usedBytes: "); 
22  Serial.print(fs_info.usedBytes);
23  Serial.println(" Bytes"); 
24 
25  // 最大文件名字符限制(含路径和'\0')
26  Serial.print("maxPathLength: "); 
27  Serial.println(fs_info.maxPathLength);
28 
29  // 最多允许打开文件数量
30  Serial.print("maxOpenFiles: "); 
31  Serial.println(fs_info.maxOpenFiles);
32 
33  // 存储块大小
34  Serial.print("blockSize: "); 
35  Serial.println(fs_info.blockSize);
36 
37  // 存储页大小
38  Serial.print("pageSize: ");
39  Serial.println(fs_info.pageSize);
40}
41
42void loop() {
43}

对于我手上这块ESP8266 NodeMCU来说还剩余1.86M的空间:

ArduinoIDE插件上传文件

这个是插件的下载链接: https://download.fastgit.org/esp8266/arduino-esp8266fs-plugin/releases/download/0.5.0/ESP8266FS-0.5.0.zip ,解压出来的Jar包放在C:\Users\XXX\Documents\Arduino\tools\ESP8266FS\tool即可 ,这个根据项目的不同会有所不同。

先确定插件是否安装成功,再根据上传的文件总大小来设置闪存大小,在把文件放在工程的data目录下即可上传

下面就上传成功了:

ESP8266 客户端操作

ESP8266-Arduino库中有两个库用于控制ESP8266与网络服务器进行通讯。分别是WiFiClient库和ESP8266HTTPClient库。其实就是一个TCPClient,一个HttpClient而已。 在使用ESP8266开发项目时,更多的时候是使用WiFiClient库来实现物联网通讯功能,WiFiClient库的重要性和实用性要高于ESP8266HTTPClient库,如果是HTTP协议通信,那么选ESP8266HTTPClient咯。

ESP8266HTTPClient

 1#include <ESP8266WiFi.h>
 2#include <ESP8266HTTPClient.h>
 3
 4#define URL "http://www.example.com"
 5
 6const char* ssid = "TP-LINK";
 7const char* password = "192.168.1.1";
 8
 9void setup() {
10  //初始化串口
11  Serial.begin(9600);
12 
13  //设置ESP8266工作模式为无线终端模式
14  WiFi.mode(WIFI_STA);
15 
16  //开始连接wifi
17  WiFi.begin(ssid, password);
18 
19  //等待WiFi连接,连接成功打印IP
20  while (WiFi.status() != WL_CONNECTED) {
21    delay(1000);
22    Serial.print(".");
23  }
24  Serial.println("");
25  Serial.print("WiFi Connected!");
26  
27  httpClientRequest();
28}
29
30void loop() {
31}
32
33void httpClientRequest(){
34 
35  // 创建 HTTPClient 对象
36  HTTPClient httpClient;
37  WiFiClient client;
38
39  // 通过begin函数配置请求地址
40  httpClient.begin(client, URL);
41  Serial.print("URL: "); Serial.println(URL);
42 
43  // 通过GET函数启动连接并发送HTTP请求
44  int httpCode = httpClient.GET();
45  Serial.print("Send GET request to URL: ");
46  Serial.println(URL);
47  
48  // 判断HTTP_CODE_OK(200)
49  if (httpCode == HTTP_CODE_OK) {
50    String responsePayload = httpClient.getString();
51    Serial.println("Server Response Payload: ");
52    Serial.println(responsePayload);
53  } else {
54    Serial.println("Server Respose Code:");
55    Serial.println(httpCode);
56  }
57 
58  // 断开链接
59  httpClient.end();
60}

WiFiClient

通过TCP Server发送来的信息就可以控制引脚状态了,比如控制LED的亮灭:

 1#include <ESP8266WiFi.h>
 2
 3const char* ssid = "TP-LINK";
 4const char* password = "192.168.1.1";
 5
 6void setup() {
 7  pinMode(LED_BUILTIN, OUTPUT);
 8  
 9  //初始化串口
10  Serial.begin(9600);
11 
12  //设置ESP8266工作模式为无线终端模式
13  WiFi.mode(WIFI_STA);
14 
15  //开始连接wifi
16  WiFi.begin(ssid, password);
17 
18  //等待WiFi连接,连接成功打印IP
19  while (WiFi.status() != WL_CONNECTED) {
20    delay(1000);
21    Serial.print(".");
22  }
23  Serial.println("");
24  Serial.print("WiFi Connected!");
25  
26  wifiClientRequest();
27}
28
29void loop() {
30}
31
32void wifiClientRequest(){
33 
34  // 创建 WiFiClient 对象
35  WiFiClient client;
36
37  String low = "LOW";
38  String high = "HIGH";
39  
40  if (client.connect("192.168.199.201", 8080)){
41    while (client.connected() || client.available()){
42      if (client.available()){
43        // 读取服务器返回的字符串直到读取到换行符
44        String line = client.readStringUntil('\n');
45        if(line.equals(low)) {
46          digitalWrite(LED_BUILTIN, LOW);
47        }else if(line.equals(high)) {
48          digitalWrite(LED_BUILTIN, HIGH);
49        }
50        Serial.println(line);
51      }
52    }
53
54    client.stop(); 
55    Serial.print("Disconnected!");
56  }else {
57    Serial.println(" connection failed!");
58    client.stop();
59  }
60}

Stream

Stream对于ESP8266-Arduino语言来说指的是数据序列。在C++编程中Stream常被称作流,其实将Stream称为数据序列更加直观。因为数据序列这一概念有两个很关键特点。 第一个特点是“序”,即数据序列不能是杂乱无章的数据罗列,第二个特点是“列”,即数据序列是排成一列的。

在ESP8266-Arduino中,第一个接触到的处理Stream的库就是Serial,用于处理串口通信中的各种数据序列。下面的程序就是使用了Serial.available来判断ESP8266开发板是否接收到串口数据。这里的开发板通过串口所接收到的数据就是Stream数据。另外,程序通过Serial.println语句将接收到的Stream数据通过串口输出并显示在串口监视器中,这里ESP8266通过串口所输出的数据也是Stream数据。换句话说,ESP8266开发板通过串口收发的数据都是Stream数据。

 1void setup() {
 2  // 启动串口通讯
 3  Serial.begin(9600); 
 4  Serial.println();
 5}
 6 
 7void loop() {
 8  // 当串口接收到信息
 9  if (Serial.available()){
10    // 将接收到的信息使用readString()存储于serialData变量
11    String serialData = Serial.readString();  
12    Serial.print(serialData);
13  }
14}

另外,HTTPClient、WIfiClient等都是Stream数据处理器,还有File的IO操作,操作的都是Stream数据,如果有过C++/Java等编程经验的话Stream其实很容易理解,在此不再赘述了。

Serial库,WiFiClient库,FS库所建立的对象都可以处理Stream数据。另外除了以上这些库以外,以下列表中的库也可以处理Stream数据:

Serial Serial
SoftwareSerial SoftwareSerial
Ehternet EthernetClient
ESP8266FS File
SD File
Wire Wire
GSM GSMClient
WifiClient WiFiClient
WiFiServer WiFiServer
WiFiUDP WiFiUDP
WiFiClientSecure WiFiClientSecure

下面我们来看一个Stream操作的实例,这个实例演示了如何利用find函数配合parseInt函数从ESP8266接收到的Serial数据中寻找整数数值:

 1void setup() {
 2  Serial.begin(9600);
 3  Serial.println("");
 4  Serial.println("Please enter input...");
 5}
 6 
 7void loop() {
 8  while(Serial.available()){
 9    if(Serial.find("ok")){
10      Serial.println("Found ok in user input.");
11 
12      int serialParseInt = Serial.parseInt();
13      Serial.print("serialParseInt = ");
14      Serial.println(serialParseInt);
15      
16      String serialInput = Serial.readString();
17      Serial.print("serialInput = ");
18      Serial.println(serialInput);
19    }
20  }
21}

当ESP8266找到了find函数所指定的参数“ok”后,随即在后续接收到的数据中查找数字信息。一旦找到数字,则通过串口监视器输出。接下来串口监视器还将输出找到数字后剩余的串口输入信息是什么。

JSON处理

解析JSON格式信息是一个较为繁琐的工作,但是可以借助解析Arduino – ESP8266平台中解析JSON格式信息的第三方库——ArduionJson库,该库是目前最受好评的解析JSON信息第三方库。GitHub: https://github.com/bblanchon/ArduinoJson

下载安装这个依赖库:

 1#include <ArduinoJson.h>
 2
 3void setup() {
 4  Serial.begin(9600);
 5  Serial.println("");
 6 
 7  // DynamicJsonDocument对象
 8  const size_t capacity = JSON_OBJECT_SIZE(2) + 30;
 9  DynamicJsonDocument doc(capacity);
10 
11  // 即将解析的JSON
12  String json = "{\"name\":\"Tim\",\"number\":252}";
13
14  // 反序列化数据
15  deserializeJson(doc, json);
16 
17  // 获取解析后的数据信息
18  String nameStr = doc["name"].as<String>();
19  int numberInt = doc["number"].as<int>();
20 
21  // 通过串口监视器输出解析后的数据信息
22  Serial.print("nameStr = ");Serial.println(nameStr);
23  Serial.print("numberInt = ");Serial.println(numberInt);
24}
25
26void loop() {}

先建立DynamicJsonDocument对象,该对象名称为doc。在建立该对象时需要提供一个参数,也就是括号中的参数capacity。这个capacity参数的作用是告诉ESP8266我们所建立的DynamicJsonDocument对象将要占用多大的内存空间。这个空间大小是由语句const size_t capacity = JSON_OBJECT_SIZE(2) + 30; 计算出来的。因为要解析的字符串包含两个数据对,所以JSON_OBJECT_SIZE为2。+30这些额外增加的数值是由于ArduinoJson库在解析信息时,需要额外的空间来复制JSON信息。

下面是解析JSONArray的示例:

 1void setup() {
 2  Serial.begin(9600);
 3 
 4  const size_t capacity = JSON_ARRAY_SIZE(2) + 2*JSON_OBJECT_SIZE(1) + 60;
 5  DynamicJsonDocument doc(capacity);
 6  
 7  String json = "[{\"name\":\"Tim\"},{\"website\":\"zouchanglin.cn\"}]";
 8 
 9  // 反序列化数据
10  deserializeJson(doc, json);
11 
12 
13  String nameStr = doc[0]["name"].as<String>();
14  String websiteStr = doc[1]["website"].as<String>();
15 
16  // 通过串口监视器输出解析后的数据信息
17  Serial.print("nameStr = ");Serial.println(nameStr);
18  Serial.print("websiteStr = ");Serial.println(websiteStr);
19}
20
21void loop() {}

以上JSON信息是一个数组,该数组含有两个元素。所以在计算capacity时首先使用了语句JSON_ARRAY_SIZE(2)来获得含有两个元素的数组所占用内存的大小。

其实,计算capacity可以使用ArduinoJson官网的在线工具 -> Use arduinojson.org/v6/assistant to compute the capacity.

ArduinoJson官网提供了在线工具可帮助我们自动生成JSON解析代码。该工具网址如下: https://arduinojson.org/v6/assistant/

ESP8266闪存存储的JSON解析

有时可能需要ESP8266解析比较大型的存放在闪存中的JSON信息,以下示例程序演示如何使用ESP8266读取并且解析存储在闪存中的JSON。 以解析WIFI的JSON配置为例,链接信号最佳的WIFI,闪存中的WIFI配置如下:

 1{
 2  "wifi": [
 3	{
 4		"ssid": "HUAWEI-LINK",
 5		"password": "12345678"
 6	},
 7	{
 8		"ssid": "TP-LINK",
 9		"password": "xpassword"
10	},
11	{
12		"ssid": "XiaoMi-WiFi",
13		"password": "19274390133"
14	}
15  ]
16}

代码如下:

 1#include <ArduinoJson.h>
 2#include <ESP8266WiFi.h>
 3#include <ESP8266WiFiMulti.h>
 4#include <FS.h>
 5
 6// 建立ESP8266WiFiMulti对象
 7ESP8266WiFiMulti wifiMulti;
 8
 9void setup() {
10  Serial.begin(9600);
11
12  // 启动闪存文件系统
13  if(SPIFFS.begin()){                      
14    Serial.println("SPIFFS Started.");
15  } else {
16    Serial.println("SPIFFS Failed to Start.");
17  }
18 
19  const size_t capacity = JSON_ARRAY_SIZE(1) + 3*JSON_OBJECT_SIZE(2) + 120;
20  DynamicJsonDocument doc(capacity);
21  
22  // 从闪存文件系统中读取即将解析的json文件
23  File file = SPIFFS.open("/config.json", "r");
24 
25  // 反序列化数据
26  deserializeJson(doc, file);
27 
28  JsonObject results_0 = doc["wifi"][0];
29  JsonObject results_1 = doc["wifi"][1];
30  JsonObject results_2 = doc["wifi"][2];
31 
32  // 通过串口监视器输出解析后的数据信息
33  wifiMulti.addAP(results_0["ssid"], results_0["password"]);
34  wifiMulti.addAP(results_1["ssid"], results_1["password"]);
35  wifiMulti.addAP(results_2["ssid"], results_2["password"]);
36
37    Serial.println("Connecting ..."); 
38 
39  int i = 0;  
40  while (wifiMulti.run() != WL_CONNECTED) { // 尝试进行wifi连接。
41    delay(1000);
42    Serial.print(i++); Serial.print(' ');
43  }
44
45  // WiFi连接成功后将通过串口监视器输出连接成功信息 
46  Serial.println("");
47  Serial.print("Connected to ");
48  Serial.println(WiFi.SSID()); // WiFi名称
49  Serial.print("IP address:\t");
50  Serial.println(WiFi.localIP()); // IP
51}
52
53void loop() {}

ESP8266 自动配网

通过WiFiManager库,则无需修改ESP8266的程序,就可以完成ESP8266的WiFi连接设置。这为我们在开发物联网项目提供了很多便利。Github:https://github.com/tzapu/WiFiManager,下面是其工作流程:

ESP8266的WiFi设置是储存在它的闪存系统中的。因此在启动ESP8266并连接WiFi时,它都会尝试使用闪存系统中储存的信息来进行WiFi连接。

在使用WiFiManager库来配置ESP8266的WiFi设置前,需要先清除ESP8266的WiFi连接信息,这样才能看到WiFiManager库的工作效果。(如果ESP8266刚一启动就自动成功连接WiFi了,那么WiFiManager库是不会发挥作用的。可以使用 wifiManager.resetSettings()来实现清除ESP8266的闪存中所存储的WiFi连接信息。

 1#include <ESP8266WiFi.h>          
 2#include <DNSServer.h>
 3#include <ESP8266WebServer.h>
 4#include <WiFiManager.h>         
 5 
 6void setup() {
 7  Serial.begin(9600);       
 8  // 建立WiFiManager对象
 9  WiFiManager wifiManager;
10  
11  // 清除ESP8266所存储的WiFi连接信息以便测试WiFiManager工作效果
12  wifiManager.resetSettings();
13  Serial.println("ESP8266 WiFi Settings Cleared");
14}
15
16void loop() {}

以下示例程序使用了WiFiManager来实现WiFi网络配置:

 1#include <ESP8266WiFi.h>          
 2#include <DNSServer.h>
 3#include <ESP8266WebServer.h>
 4#include <WiFiManager.h>         
 5 
 6void setup() {
 7    Serial.begin(9600);       
 8    // 建立WiFiManager对象
 9    WiFiManager wifiManager;
10    
11    // 自动连接WiFi。以下语句的参数是连接ESP8266时的WiFi名称
12    wifiManager.autoConnect("AutoConnectAP");
13    
14    // 如果您希望该WiFi添加密码,可以使用以下语句:
15    // wifiManager.autoConnect("AutoConnectAP", "12345678");
16    
17    // WiFi连接成功后将通过串口监视器输出连接成功信息 
18    Serial.println(""); 
19    Serial.print("ESP8266 Connected to ");
20    Serial.println(WiFi.SSID()); // WiFi名称
21    Serial.print("IP address:\t");
22    Serial.println(WiFi.localIP()); // IP
23}
24 
25void loop() {}

ESP8266 多任务处理

利用Ticker库,我们可以让ESP8266定时处理任务。主要是学会Ticker的使用方法以及如何利用Ticker库来实现ESP8266的多任务处理。

利用Ticker库,可以让ESP8266定时调用某一个函数。通过以下示例程序我们可以看到,ESP8266将会每隔一秒钟通过串口监视器输出一次信息。我们是通过语句ticker.attach(1, sayHi)来实现这一操作的。

该语句中的attach函数有两个参数。第一个参数可控制调用函数的时间间隔,单位是秒。这里的数字1说明ESP8266将会每隔一秒钟调用一次函数。那么具体调用哪一个函数呢?这个函数名称正是是通过第二个参数来限定的。也就是名称为sayHi的函数。

启动任务与停止任务

其实,这与其他MCU的定时器是一样的原理:

 1#include <Ticker.h>
 2 
 3Ticker ticker;// 建立Ticker用于实现定时功能
 4int count;    // 计数用变量
 5 
 6void setup() {
 7  Serial.begin(9600);
 8  pinMode(LED_BUILTIN, OUTPUT);
 9 
10  // 每隔二秒钟调用sayHi函数一次
11  // 参数是控制定时间隔的变量,单位为秒;以及定时执行的函数名称
12  ticker.attach(1, sayHi);
13}
14 
15void loop() {
16  // 用LED呼吸灯效果来演示在Tinker对象控制下,ESP8266可以定时执行其它任务
17  for (int fadeValue = 0 ; fadeValue <= 1023; fadeValue += 5) {
18    analogWrite(LED_BUILTIN, fadeValue);
19    delay(10);
20  }
21 
22  for (int fadeValue = 1023 ; fadeValue >= 0; fadeValue -= 5) {
23    analogWrite(LED_BUILTIN, fadeValue);
24    delay(10);
25  }
26  delay(3000);
27}
28 
29// 在Tinker对象控制下,此函数将会定时执行。
30void sayHi(){
31  count++;
32  Serial.print("Hi ");
33  Serial.println(count);
34    
35  // 当定时调用了6次后,停止定时调用函数
36  if (count >= 6) {
37    ticker.detach();  // 使用detach来停止ticker对象定时调用函数
38    Serial.print("ticker.detach()");
39  }
40}

当Ticker定时调用某一函数执行到一定次数后,可以使用detach函数来停止定时调用函数。以下示例程序中的语句ticker.detach()将会让ticker对象停止调用函数。

向定时调用函数传递参数

attach函数所能传递的参数最多只有一个。另外该参数仅能是以下类型中的一种:char, short, int, float, void*, char*

 1#include <Ticker.h>
 2 
 3Ticker ticker;
 4 
 5int count;
 6 
 7void setup() {
 8  Serial.begin(9600);
 9  pinMode(LED_BUILTIN, OUTPUT);
10  
11  ticker.attach(1, sayHi, 8);
12}
13 
14void loop() {
15  for (int fadeValue = 0 ; fadeValue <= 1023; fadeValue += 5) {
16    analogWrite(LED_BUILTIN, fadeValue);
17    delay(10);
18  }
19 
20  for (int fadeValue = 1023 ; fadeValue >= 0; fadeValue -= 5) {
21    analogWrite(LED_BUILTIN, fadeValue);
22    delay(10);
23  }
24  delay(3000);
25}
26 
27void sayHi(int hiTimes){
28  count++;
29  Serial.print("Hi ");
30  Serial.println(count);
31 
32  if (count >= hiTimes) {
33    ticker.detach();
34    Serial.print("ticker.detach();");
35  }
36}

ESP8266 OTA操作(不常用)

OTA是Over-The-Air的缩写。有人将其翻译为“空中下载”,也有翻译为“隔空传输”。无论如何翻译,对于ESP2866来说,通过OTA就无需将ESP8266与电脑连接,而仅仅通过WiFi就可以用Arduino IDE向ESP8266上传程序。

第一步:通过数据线上传初始示例程序

 1#include <ESP8266WiFi.h>
 2#include <ArduinoOTA.h>
 3#include <Ticker.h>
 4 
 5// 闪烁时间间隔(秒)
 6const int blinkInterval = 2; 
 7 
 8// 设置wifi接入信息(请根据您的WiFi信息进行修改)
 9const char* ssid = "TP-LINK";
10const char* password = "192.168.1.1";
11 
12Ticker ticker;
13 
14void setup() {
15  Serial.begin(9600);            
16  Serial.println("");
17  pinMode(LED_BUILTIN, OUTPUT);
18 
19  ticker.attach(blinkInterval, tickerCount);  // 设置Ticker对象
20  
21  connectWifi();
22 
23  // OTA设置并启动
24  ArduinoOTA.setHostname("ESP8266");
25  ArduinoOTA.setPassword("12345678");
26  ArduinoOTA.begin();
27  
28  Serial.println("OTA ready");
29}
30void loop() {
31  ArduinoOTA.handle();
32}
33 
34// 在Tinker对象控制下,此函数将会定时执行。
35void tickerCount(){
36  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
37}
38 
39void connectWifi(){
40  //开始连接wifi
41  WiFi.begin(ssid, password);
42 
43  //等待WiFi连接,连接成功打印IP
44  while (WiFi.status() != WL_CONNECTED) {
45    delay(1000);
46    Serial.print(".");
47  }
48  Serial.println("");
49  Serial.println("WiFi Connected!");  
50  Serial.print("IP address:\t");            
51  Serial.println(WiFi.localIP());          
52}

第二步:通过Arduino IDE正确选择OTA端口

程序上传后,请重新启动Arduino IDE。并且通过Arduino IDE正确选择ESP8266的OTA端口。

点击Arduino IDE的上传按钮后, IDE将会弹出对话框让用户输入OTA上传密码。根据示例程序中的setPassword函数所设置的信息来输入密码。完成密码输入后,点击确定程序开始上传。

程序上传结束后,ESP8266将会自动重启开发板,新的程序也将在重启后开始运行。所以OTA缺点也很明显:

1、程序占用空间变大,在OTA上传新程序过程中,ESP8266开发板将会保持旧程序的运行。这将导致ESP8266开发板的程序占用空间翻倍。假如程序非常复杂,占用空间很大,那么使用OTA上传就不太适合了; 2、Arduino IDE无法通过OTA端口与开发板进行串口通讯; 3、使用OTA上传程序的电脑与ESP8266必须连接同一WiFi;

ESP32的引脚图

2022-08-14 更新 最近从ESP8266升级到了ESP32,最新的引脚图如下: