编辑
2021-10-26
浅尝嵌入式开发
00
请注意,本文编写于 706 天前,最后修改于 47 天前,其中某些信息可能已经过时。

目录

ESP8266 引脚对应图
ESP8266 引脚说明
可用引脚
电压限制
引脚特殊情况
上/下拉电阻
模拟输入引脚
通信引脚
开发环境搭建
接入点模式(Access Point)
无线终端模式(Station)
ESP8266 HTTP服务器
ESP8266 闪存文件系统
写入 & 读取文件
读取目录下全部文件
删除指定的文件
显示闪存文件系统信息
ArduinoIDE插件上传文件
ESP8266 客户端操作
ESP8266HTTPClient
WiFiClient
Stream
JSON处理
ESP8266闪存存储的JSON解析
ESP8266 自动配网
ESP8266 多任务处理
启动任务与停止任务
向定时调用函数传递参数
ESP8266 OTA操作(不常用)

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

c
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网络并进行连接 。

c
#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连接无线路由器的模式相同,这就是无线终端模式。

c
// 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。

c
// 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服务器

c
// 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了,代码如下:

c
// 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页面:

c
// 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)进行文件读取和修改呢?

写入 & 读取文件

c
#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() { }

读取目录下全部文件

c
#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() { }

删除指定的文件

c
#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() { }

显示闪存文件系统信息

c
#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

c
#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的亮灭:

c
#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数据。

c
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数据:

SerialSerial
SoftwareSerialSoftwareSerial
EhternetEthernetClient
ESP8266FSFile
SDFile
WireWire
GSMGSMClient
WifiClientWiFiClient
WiFiServerWiFiServer
WiFiUDPWiFiUDP
WiFiClientSecureWiFiClientSecure

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

c
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

下载安装这个依赖库:

c
#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的示例:

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

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

代码如下:

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

c
#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网络配置:

c
#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的定时器是一样的原理:

c
#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*

c
#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上传程序。

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

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

本文作者:Tim

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!