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闪烁程序可以正常运行,那么证明环境是没问题的:

void setup() {
  // put your setup code here, to run once:
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

接入点模式(Access Point)

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

#include <ESP8266WiFi.h> // ESP8266WiFi库

// WIFI名称
const char *ssid = "ESP8266-WIFI";

// WIFI密码(空字符串则表示无需密码)
const char *password = "12345678";
                                     
void setup() {
  // 启动串口通讯(波特率9600)
  Serial.begin(9600);              

  // 用于启动NodeMCU的AP模式
  WiFi.softAP(ssid, password);

  // 串口输出名称和密码
  Serial.println(ssid);
  Serial.println(WiFi.softAPIP());
}

void loop() { 

}

无线终端模式(Station)

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

// NodeMCU无线终端模式连接WiFi
#include <ESP8266WiFi.h>

// 将要连接的WiFi和密码
const char* ssid = "TP-LINK";
const char* password = "xpassword";

void setup() {
  Serial.begin(9600);
  // 启动网络连接
  WiFi.begin(ssid, password);
  // 告知用户NodeMCU正在尝试WiFi连接           
  Serial.print("Connecting to ");
  Serial.print(ssid); Serial.println(" ...");

  // 如果WiFi连接成功则返回值为WL_CONNECTED
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(i++); Serial.print(' ');
  }
  
  Serial.println("");
  Serial.println("Connection success!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}
 
void loop() {                                   
}

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

// NodeMCU无线终端模式连接WiFi
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

ESP8266WiFiMulti wifiMulti;

void setup() {
  Serial.begin(9600);
  
  // 将要连接的WiFi和密码
  wifiMulti.addAP("TP-LINK0", "xpassword");
  wifiMulti.addAP("TP-LINK1", "xpassword");
  wifiMulti.addAP("TP-LINK2", "xpassword");
  
  Serial.println("Connecting ...");
  int i = 0;
  // 如果WiFi连接成功则返回值为WL_CONNECTED
  // wifiMulti.run()会在当前环境中搜索并连接信号最强的WIFI
  while (wifiMulti.run() != WL_CONNECTED) {
    delay(1000);
    Serial.print('.');
  }
    
  Serial.println("");
  Serial.println("Connection success!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}
 
void loop() {                                   
}

ESP8266 HTTP服务器

// NodeMCU无线终端模式连接WiFi
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// 将要连接的WiFi和密码
const char* ssid = "TP-LINK";
const char* password = "xpassword";

// 设置Web服务器端口
ESP8266WebServer esp8266_server(8080);

void setup() {
  Serial.begin(9600);
  // 启动网络连接
  WiFi.begin(ssid, password);
  // 告知用户NodeMCU正在尝试WiFi连接           
  Serial.print("Connecting to ");
  Serial.print(ssid); Serial.println(" ...");

  // 如果WiFi连接成功则返回值为WL_CONNECTED
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(i++); Serial.print(' ');
  }
  
  Serial.println("");
  Serial.println("Connection success!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());


  // ---- WebServer ---------
  esp8266_server.begin();
  esp8266_server.on("/", handleRoot);
  esp8266_server.onNotFound(handleNotFound);
  // ---- WebServer ---------
}

void handleRoot() {   //处理网站根目录“/”的访问请求 
  esp8266_server.send(200, "text/plain", "Hello from ESP8266");
}

// 设置处理404情况的函数'handleNotFound'
void handleNotFound(){
  esp8266_server.send(404, "text/plain", "404: Not found");
}
 
void loop() {
  esp8266_server.handleClient();
}

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

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

// NodeMCU无线终端模式连接WiFi
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// 将要连接的WiFi和密码
const char* ssid = "TP-LINK";
const char* password = "xpassword";

// 设置Web服务器端口
ESP8266WebServer esp8266_server(8080);

void setup() {
  Serial.begin(9600);
  // 启动网络连接
  WiFi.begin(ssid, password);
  // 告知用户NodeMCU正在尝试WiFi连接           
  Serial.print("Connecting to ");
  Serial.print(ssid); Serial.println(" ...");

  // 如果WiFi连接成功则返回值为WL_CONNECTED
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(i++); Serial.print(' ');
  }
  
  Serial.println("");
  Serial.println("Connection success!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());


  // ---- WebServer ---------
  esp8266_server.begin();
  esp8266_server.on("/", handleRoot);
  esp8266_server.onNotFound(handleNotFound);
  esp8266_server.on("/LED", handleLED);
  // ---- WebServer ---------

  pinMode(LED_BUILTIN, OUTPUT);
}

void handleRoot() {   //处理网站根目录“/”的访问请求 
  esp8266_server.send(200, "text/html", "<form action=\"/LED\" method=\"POST\"><input type=\"submit\" value=\"Toggle LED\"></form>");
}

// 设置处理404情况的函数'handleNotFound'
void handleNotFound(){
  esp8266_server.send(404, "text/plain", "404: Not found");
}

void handleLED() {
  digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
  esp8266_server.sendHeader("Location","/");
  esp8266_server.send(303); 
}

void loop() {
  esp8266_server.handleClient();
}

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

// NodeMCU无线终端模式连接WiFi
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>

// 将要连接的WiFi和密码
const char* ssid = "TP-LINK";
const char* password = "192.168.1.1";

// 设置Web服务器端口
ESP8266WebServer esp8266_server(8080);

// 存储引脚状态用变量,检测按键按下
bool buttonState;

void setup() {
  Serial.begin(9600);
  // 启动网络连接
  WiFi.begin(ssid, password);
  // 告知用户NodeMCU正在尝试WiFi连接           
  Serial.print("Connecting to ");
  Serial.print(ssid); Serial.println(" ...");

  // 如果WiFi连接成功则返回值为WL_CONNECTED
  int i = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(i++); Serial.print(' ');
  }
  
  Serial.println("");
  Serial.println("Connection success!");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());


  // ---- WebServer ---------
  esp8266_server.begin();
  esp8266_server.on("/", handleRoot);
  esp8266_server.onNotFound(handleNotFound);
  // ---- WebServer ---------

  // 设置为输入上拉模式 
  pinMode(LED_BUILTIN, INPUT_PULLUP);
}

void handleRoot() {
  String htmlCode = "<html><head><meta http-equiv='refresh' content='1'/></head><body>";
  if(buttonState){
    htmlCode +="<p>Button Status: HIGH</p>\n";
  }else{
    htmlCode +="<p>Button Status: LOW</p>\n";
  }
  htmlCode +="</body></html>\n";
  esp8266_server.send(200, "text/html", htmlCode);  
}

void handleNotFound(){
  esp8266_server.send(404, "text/plain", "404: Not found");
}

void loop() {
  esp8266_server.handleClient();
  buttonState = digitalRead(D3); // 获取引脚状态
}

ESP8266 闪存文件系统

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

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

写入 & 读取文件

#include <FS.h>
 
String file_name = "/myfile.txt";

void setup() {
  Serial.begin(9600);
  
  // ----------- 写入操作 ---------
  SPIFFS.format(); // 格式化SPIFFS(删除全部用户文件)
  Serial.println("format fs success.");
  
  if(SPIFFS.begin()){ // 启动SPIFFS
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }

  // 写入字符串
  File dataFile = SPIFFS.open(file_name, "w");
  dataFile.println("Hello IOT World.");
  dataFile.close();

  Serial.println("Finished Writing data to SPIFFS");

  // 追加写入字符串(a->append代表向该文件添加信息)
  File dataFile = SPIFFS.open(file_name, "a");
  dataFile.println("This is Appended Info.");
  dataFile.close();
  Serial.println("Finished append writing data to SPIFFS");

  // ---------- 读取操作 ---------
  // 确认闪存中是否有file_name文件
  if (SPIFFS.exists(file_name)){
    Serial.print(file_name);
    Serial.println(" FOUND.");
  } else {
    Serial.print(file_name);
    Serial.print(" NOT FOUND.");
  }
  
  File readDataFile = SPIFFS.open(file_name, "r");
  // 读取文件内容并且通过串口监视器输出文件信息
  for(int i=0; i<readDataFile.size(); i++){
    Serial.print((char)readDataFile.read());
  }
  //完成文件读取后关闭文件
  readDataFile.close();
}

void loop() {
}

读取目录下全部文件

#include <FS.h>
String folder_name = "/myDir";

void setup() {
  Serial.begin(9600);
  
  SPIFFS.format(); // 格式化SPIFFS(删除全部用户文件)
  Serial.println("format fs success.");
  
  if(SPIFFS.begin()){ // 启动SPIFFS
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }
  
  // 显示目录中文件内容以及文件大小
  Dir dir = SPIFFS.openDir(folder_name);
  // 串口输出目录的全部文件
  while (dir.next()) {
    Serial.println(dir.fileName());
  }
}

void loop() {
}

删除指定的文件

#include <FS.h>
 
String folder_name = "/myfile.txt";

void setup() {
  Serial.begin(9600);
  Serial.println("");
  
  if(SPIFFS.begin()){ // 启动闪存文件系统
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }
  
  //从闪存中删除file_name文件
  if (SPIFFS.remove(file_name)){
    
    Serial.print(file_name);
    Serial.println(" remove sucess");
    
  } else {
    Serial.print(file_name);
    Serial.println(" remove fail");
  }                       
}

void loop() {
}

显示闪存文件系统信息

#include <FS.h>
 
FSInfo fs_info;

void setup() {
  Serial.begin(9600);
 
  SPIFFS.begin();//启动SPIFFS
  Serial.println("");
  Serial.println("SPIFFS Started.");
 
  // 闪存文件系统信息
  SPIFFS.info(fs_info);
 
  // 可用空间总和(单位:字节)
  Serial.print("totalBytes: ");     
  Serial.print(fs_info.totalBytes); 
  Serial.println(" Bytes"); 
 
  // 已用空间(单位:字节)
  Serial.print("usedBytes: "); 
  Serial.print(fs_info.usedBytes);
  Serial.println(" Bytes"); 
 
  // 最大文件名字符限制(含路径和'\0')
  Serial.print("maxPathLength: "); 
  Serial.println(fs_info.maxPathLength);
 
  // 最多允许打开文件数量
  Serial.print("maxOpenFiles: "); 
  Serial.println(fs_info.maxOpenFiles);
 
  // 存储块大小
  Serial.print("blockSize: "); 
  Serial.println(fs_info.blockSize);
 
  // 存储页大小
  Serial.print("pageSize: ");
  Serial.println(fs_info.pageSize);
}

void loop() {
}

对于我手上这块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

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>

#define URL "http://www.example.com"

const char* ssid = "TP-LINK";
const char* password = "192.168.1.1";

void setup() {
  //初始化串口
  Serial.begin(9600);
 
  //设置ESP8266工作模式为无线终端模式
  WiFi.mode(WIFI_STA);
 
  //开始连接wifi
  WiFi.begin(ssid, password);
 
  //等待WiFi连接,连接成功打印IP
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("WiFi Connected!");
  
  httpClientRequest();
}

void loop() {
}

void httpClientRequest(){
 
  // 创建 HTTPClient 对象
  HTTPClient httpClient;
  WiFiClient client;

  // 通过begin函数配置请求地址
  httpClient.begin(client, URL);
  Serial.print("URL: "); Serial.println(URL);
 
  // 通过GET函数启动连接并发送HTTP请求
  int httpCode = httpClient.GET();
  Serial.print("Send GET request to URL: ");
  Serial.println(URL);
  
  // 判断HTTP_CODE_OK(200)
  if (httpCode == HTTP_CODE_OK) {
    String responsePayload = httpClient.getString();
    Serial.println("Server Response Payload: ");
    Serial.println(responsePayload);
  } else {
    Serial.println("Server Respose Code:");
    Serial.println(httpCode);
  }
 
  // 断开链接
  httpClient.end();
}

WiFiClient

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

#include <ESP8266WiFi.h>

const char* ssid = "TP-LINK";
const char* password = "192.168.1.1";

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  
  //初始化串口
  Serial.begin(9600);
 
  //设置ESP8266工作模式为无线终端模式
  WiFi.mode(WIFI_STA);
 
  //开始连接wifi
  WiFi.begin(ssid, password);
 
  //等待WiFi连接,连接成功打印IP
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("WiFi Connected!");
  
  wifiClientRequest();
}

void loop() {
}

void wifiClientRequest(){
 
  // 创建 WiFiClient 对象
  WiFiClient client;

  String low = "LOW";
  String high = "HIGH";
  
  if (client.connect("192.168.199.201", 8080)){
    while (client.connected() || client.available()){
      if (client.available()){
        // 读取服务器返回的字符串直到读取到换行符
        String line = client.readStringUntil('\n');
        if(line.equals(low)) {
          digitalWrite(LED_BUILTIN, LOW);
        }else if(line.equals(high)) {
          digitalWrite(LED_BUILTIN, HIGH);
        }
        Serial.println(line);
      }
    }

    client.stop(); 
    Serial.print("Disconnected!");
  }else {
    Serial.println(" connection failed!");
    client.stop();
  }
}

Stream

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

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

void setup() {
  // 启动串口通讯
  Serial.begin(9600); 
  Serial.println();
}
 
void loop() {
  // 当串口接收到信息
  if (Serial.available()){
    // 将接收到的信息使用readString()存储于serialData变量
    String serialData = Serial.readString();  
    Serial.print(serialData);
  }
}

另外,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数据中寻找整数数值:

void setup() {
  Serial.begin(9600);
  Serial.println("");
  Serial.println("Please enter input...");
}
 
void loop() {
  while(Serial.available()){
    if(Serial.find("ok")){
      Serial.println("Found ok in user input.");
 
      int serialParseInt = Serial.parseInt();
      Serial.print("serialParseInt = ");
      Serial.println(serialParseInt);
      
      String serialInput = Serial.readString();
      Serial.print("serialInput = ");
      Serial.println(serialInput);
    }
  }
}

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

JSON处理

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

下载安装这个依赖库:

#include <ArduinoJson.h>

void setup() {
  Serial.begin(9600);
  Serial.println("");
 
  // DynamicJsonDocument对象
  const size_t capacity = JSON_OBJECT_SIZE(2) + 30;
  DynamicJsonDocument doc(capacity);
 
  // 即将解析的JSON
  String json = "{\"name\":\"Tim\",\"number\":252}";

  // 反序列化数据
  deserializeJson(doc, json);
 
  // 获取解析后的数据信息
  String nameStr = doc["name"].as<String>();
  int numberInt = doc["number"].as<int>();
 
  // 通过串口监视器输出解析后的数据信息
  Serial.print("nameStr = ");Serial.println(nameStr);
  Serial.print("numberInt = ");Serial.println(numberInt);
}

void loop() {}

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

下面是解析JSONArray的示例:

void setup() {
  Serial.begin(9600);
 
  const size_t capacity = JSON_ARRAY_SIZE(2) + 2*JSON_OBJECT_SIZE(1) + 60;
  DynamicJsonDocument doc(capacity);
  
  String json = "[{\"name\":\"Tim\"},{\"website\":\"zouchanglin.cn\"}]";
 
  // 反序列化数据
  deserializeJson(doc, json);
 
 
  String nameStr = doc[0]["name"].as<String>();
  String websiteStr = doc[1]["website"].as<String>();
 
  // 通过串口监视器输出解析后的数据信息
  Serial.print("nameStr = ");Serial.println(nameStr);
  Serial.print("websiteStr = ");Serial.println(websiteStr);
}

void 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配置如下:

{
  "wifi": [
	{
		"ssid": "HUAWEI-LINK",
		"password": "12345678"
	},
	{
		"ssid": "TP-LINK",
		"password": "xpassword"
	},
	{
		"ssid": "XiaoMi-WiFi",
		"password": "19274390133"
	}
  ]
}

代码如下:

#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <FS.h>

// 建立ESP8266WiFiMulti对象
ESP8266WiFiMulti wifiMulti;

void setup() {
  Serial.begin(9600);

  // 启动闪存文件系统
  if(SPIFFS.begin()){                      
    Serial.println("SPIFFS Started.");
  } else {
    Serial.println("SPIFFS Failed to Start.");
  }
 
  const size_t capacity = JSON_ARRAY_SIZE(1) + 3*JSON_OBJECT_SIZE(2) + 120;
  DynamicJsonDocument doc(capacity);
  
  // 从闪存文件系统中读取即将解析的json文件
  File file = SPIFFS.open("/config.json", "r");
 
  // 反序列化数据
  deserializeJson(doc, file);
 
  JsonObject results_0 = doc["wifi"][0];
  JsonObject results_1 = doc["wifi"][1];
  JsonObject results_2 = doc["wifi"][2];
 
  // 通过串口监视器输出解析后的数据信息
  wifiMulti.addAP(results_0["ssid"], results_0["password"]);
  wifiMulti.addAP(results_1["ssid"], results_1["password"]);
  wifiMulti.addAP(results_2["ssid"], results_2["password"]);

    Serial.println("Connecting ..."); 
 
  int i = 0;  
  while (wifiMulti.run() != WL_CONNECTED) { // 尝试进行wifi连接。
    delay(1000);
    Serial.print(i++); Serial.print(' ');
  }

  // WiFi连接成功后将通过串口监视器输出连接成功信息 
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID()); // WiFi名称
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP()); // IP
}

void 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连接信息。

#include <ESP8266WiFi.h>          
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>         
 
void setup() {
  Serial.begin(9600);       
  // 建立WiFiManager对象
  WiFiManager wifiManager;
  
  // 清除ESP8266所存储的WiFi连接信息以便测试WiFiManager工作效果
  wifiManager.resetSettings();
  Serial.println("ESP8266 WiFi Settings Cleared");
}

void loop() {}

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

#include <ESP8266WiFi.h>          
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>         
 
void setup() {
    Serial.begin(9600);       
    // 建立WiFiManager对象
    WiFiManager wifiManager;
    
    // 自动连接WiFi。以下语句的参数是连接ESP8266时的WiFi名称
    wifiManager.autoConnect("AutoConnectAP");
    
    // 如果您希望该WiFi添加密码,可以使用以下语句:
    // wifiManager.autoConnect("AutoConnectAP", "12345678");
    
    // WiFi连接成功后将通过串口监视器输出连接成功信息 
    Serial.println(""); 
    Serial.print("ESP8266 Connected to ");
    Serial.println(WiFi.SSID()); // WiFi名称
    Serial.print("IP address:\t");
    Serial.println(WiFi.localIP()); // IP
}
 
void loop() {}

ESP8266 多任务处理

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

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

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

启动任务与停止任务

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

#include <Ticker.h>
 
Ticker ticker;// 建立Ticker用于实现定时功能
int count;    // 计数用变量
 
void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
 
  // 每隔二秒钟调用sayHi函数一次
  // 参数是控制定时间隔的变量,单位为秒;以及定时执行的函数名称
  ticker.attach(1, sayHi);
}
 
void loop() {
  // 用LED呼吸灯效果来演示在Tinker对象控制下,ESP8266可以定时执行其它任务
  for (int fadeValue = 0 ; fadeValue <= 1023; fadeValue += 5) {
    analogWrite(LED_BUILTIN, fadeValue);
    delay(10);
  }
 
  for (int fadeValue = 1023 ; fadeValue >= 0; fadeValue -= 5) {
    analogWrite(LED_BUILTIN, fadeValue);
    delay(10);
  }
  delay(3000);
}
 
// 在Tinker对象控制下,此函数将会定时执行。
void sayHi(){
  count++;
  Serial.print("Hi ");
  Serial.println(count);
    
  // 当定时调用了6次后,停止定时调用函数
  if (count >= 6) {
    ticker.detach();  // 使用detach来停止ticker对象定时调用函数
    Serial.print("ticker.detach()");
  }
}

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

向定时调用函数传递参数

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

#include <Ticker.h>
 
Ticker ticker;
 
int count;
 
void setup() {
  Serial.begin(9600);
  pinMode(LED_BUILTIN, OUTPUT);
  
  ticker.attach(1, sayHi, 8);
}
 
void loop() {
  for (int fadeValue = 0 ; fadeValue <= 1023; fadeValue += 5) {
    analogWrite(LED_BUILTIN, fadeValue);
    delay(10);
  }
 
  for (int fadeValue = 1023 ; fadeValue >= 0; fadeValue -= 5) {
    analogWrite(LED_BUILTIN, fadeValue);
    delay(10);
  }
  delay(3000);
}
 
void sayHi(int hiTimes){
  count++;
  Serial.print("Hi ");
  Serial.println(count);
 
  if (count >= hiTimes) {
    ticker.detach();
    Serial.print("ticker.detach();");
  }
}

ESP8266 OTA操作(不常用)

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

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

#include <ESP8266WiFi.h>
#include <ArduinoOTA.h>
#include <Ticker.h>
 
// 闪烁时间间隔(秒)
const int blinkInterval = 2; 
 
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "TP-LINK";
const char* password = "192.168.1.1";
 
Ticker ticker;
 
void setup() {
  Serial.begin(9600);            
  Serial.println("");
  pinMode(LED_BUILTIN, OUTPUT);
 
  ticker.attach(blinkInterval, tickerCount);  // 设置Ticker对象
  
  connectWifi();
 
  // OTA设置并启动
  ArduinoOTA.setHostname("ESP8266");
  ArduinoOTA.setPassword("12345678");
  ArduinoOTA.begin();
  
  Serial.println("OTA ready");
}
void loop() {
  ArduinoOTA.handle();
}
 
// 在Tinker对象控制下,此函数将会定时执行。
void tickerCount(){
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
 
void connectWifi(){
  //开始连接wifi
  WiFi.begin(ssid, password);
 
  //等待WiFi连接,连接成功打印IP
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected!");  
  Serial.print("IP address:\t");            
  Serial.println(WiFi.localIP());          
}

第二步:通过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,最新的引脚图如下: