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,最新的引脚图如下: