闪存文件系统

基础知识

对于这个开发板,闪存文件系统大小一般是4Mb。其中有1Mb是留给上传程序的,剩下3Mb是文件存储(里面含有一部分是系统文件)
故一般上传的文件总和小于2.5Mb
除了可以存放上传的程序以外,我们还可以将网页文件或者系统配置文件存放在ESP8266的闪存中

在IDE里可以设置开发板闪存系统的大小

通过程序向闪存文件系统写入信息

向NodeMCU的SPIFFS中建立名为notes.txt的文件

如果是新用的,格式化会更保险

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <FS.h>  

String file_name = "/taichi-maker/notes.txt"; //被读取的文件位置和名称

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

Serial.println("SPIFFS format start");
SPIFFS.format(); // 格式化SPIFFS(闪存系统)//将开发板闪存内存系统的内容全部删掉//无需在启动后就能执行
Serial.println("SPIFFS format finish");

if(SPIFFS.begin()){ // 启动SPIFFS
Serial.println("SPIFFS Started.");
} else {
Serial.println("SPIFFS Failed to Start.");
}

File dataFile = SPIFFS.open(file_name, "w");// 建立File对象用于向SPIFFS中的file对象(即/notes.txt)写入信息
dataFile.println("Hello IOT World."); // 向dataFile写入字符串信息
dataFile.close(); // 完成文件写入后关闭文件
Serial.println("Finished Writing data to SPIFFS");
}

void loop() {
}

通过程序从闪存文件系统读取信息

dataFile.read()将会读取dataFile文件内容。每调用一次该含税都会返回dataFile文件中一个字符。再次调用,将会返回下一个字符。以此类推,直到dataFile结尾。通过for循环语句,程序将会依次读取dataFile文件内容,并且将文件内容逐字符输出于串口监视器中

注意此时不要再格式化了!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//setup()里 

//确认闪存中是否有file_name文件
if (SPIFFS.exists(file_name)){
Serial.print(file_name);
Serial.println(" FOUND.");
} else {
Serial.print(file_name);
Serial.print(" NOT FOUND.");
}

//建立File对象用于从SPIFFS中读取文件
//注意操作要对应对象
File dataFile = SPIFFS.open(file_name, "r");

//读取文件内容并且通过串口监视器输出文件信息
for(int i=0; i<dataFile.size(); i++){
Serial.print((char)dataFile.read());//说明print出的是char
}

通过程序向闪存文件系统文件添加信息

注意如果要对文件进行操作,比如说读取,一定要在文件open后,close前

1
2
3
File dataFile = SPIFFS.open(file_name, "a");// 建立File对象用于向SPIFFS中的file对象(即/notes.txt)写入信息
dataFile.println("This is Appended Info."); // 向dataFile添加字符串信息
dataFile.close(); // 完成文件操作后关闭文件

(不知道为什么读取的时候会乱码

通过程序读取目录内容

添加信息是不会删除文件内原有信息,而是在原有信息后面添加新的信息。这与但写入操作是有所区别的。写入操作是将文件内容完全清除,重新写入新信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String file_name = "/taichi-maker/myFile.txt"; //被读取的文件位置和名称
String folder_name = "/taichi-maker"; //被读取的文件夹

//setup()
File dataFile = SPIFFS.open(file_name, "w");// 建立File对象用于向SPIFFS中的file对象(即myFile.txt)写入信息
dataFile.println("Hello Taichi-Maker."); // 向dataFile写入字符串信息
dataFile.close(); // 完成文件写入后关闭文件
Serial.println(F("Finished Writing data to SPIFFS"));

// 显示目录中文件内容以及文件大小
Dir dir = SPIFFS.openDir(folder_name); // 建立“目录”对象

while (dir.next()) { // dir.next()用于检查目录中是否还有“下一个文件”
Serial.println(dir.fileName()); // 输出文件名
}

从闪存文件系统中删除文件

用remove

1
2
3
4
5
6
7
8
9
10
11
12
//(file_name是已经定义好的string)

//从闪存中删除file_name文件
if (SPIFFS.remove(file_name)){

Serial.print(file_name);
Serial.println(" remove sucess");

} else {
Serial.print(file_name);
Serial.println(" remove fail");
}

*显示闪存文件系统信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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开发板搭建网页服务

关于安装闪存文件系统上传文件的插件

我用的IDE是2.3.5,自2.0后上传插件的方法就变了
(预先准备:在github下载littlefs包,然后放在C:\Users\Administrator.arduinoIDE\plugins)
上传方法:打开IDE,shift+ctrl+p,输入upload,点击下载项

一定要关闭串口监视器
upload一次就是上传一次文件

用了littlefs后,每次的命令就不是SPIFFS,而是LittleFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 #include "LittleFS.h"

String file_name = "/test_text.txt";

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

if(!LittleFS.begin()){
Serial.println("An Error has occurred while mounting LittleFS");
return;
}

if (LittleFS.exists(file_name)){
Serial.print(file_name);
Serial.println(" FOUND.");
} else {
Serial.print(file_name);
Serial.print(" NOT FOUND.");
}

File file = LittleFS.open("/test_text.txt", "r");
if(!file){
Serial.println("Failed to open file for reading");
return;
}

Serial.println("File Content:");
while(file.available()){
Serial.write(file.read());
}
file.close();
}

void loop() {
}

在网页中加载闪存文件系统中的图片、CSS和JavaScript

这是重要的控制网页的模板
注意理解代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266WebServer.h>
#include <LittleFS.h>

ESP8266WiFiMulti wifiMulti; // 建立ESP8266WiFiMulti对象

ESP8266WebServer esp8266_server(80); // 建立网络服务器对象,该对象用于响应HTTP请求。监听端口(80)

void setup() {
Serial.begin(9600); // 启动串口通讯
Serial.println("");

wifiMulti.addAP("zzz"); // 将需要连接的一系列WiFi ID和密码输入这里
wifiMulti.addAP("zzz"); // ESP8266-NodeMCU再启动后会扫描当前网络
Serial.println("Connecting ..."); // 则尝试使用此处存储的密码进行连接。

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

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

if(LittleFS.begin()){ // 启动闪存文件系统
Serial.println("LittleFS Started.");
} else {
Serial.println("LittleFS Failed to Start.");
}

esp8266_server.onNotFound(handleUserRequet); // 找不到资源时

esp8266_server.begin(); // 启动网站服务
Serial.println("HTTP server started");
}
////////////////////////////////////////////////////////////////
void loop(void) {
esp8266_server.handleClient(); // 处理用户请求
}
////////////////////////////////////////////////////////////////
// 处理用户浏览器的HTTP访问。这个函数出现在onnotfound那
void handleUserRequet() {

// 存储用户请求网址信息,调用rui函数
String webAddress = esp8266_server.uri();

// 通过handleFileRead函数处处理用户访问
bool fileReadOK = handleFileRead(webAddress);

// 如果在LittleFS无法找到用户访问的资源,则回复404 (Not Found)
if (!fileReadOK){
esp8266_server.send(404, "text/plain", "404 Not Found");
}
}
///////////////////////补充的量/////////////////////////////////////
bool handleFileRead(String path) { //处理浏览器HTTP访问

if (path.endsWith("/")) { // 如果访问地址以"/"为结尾
path = "/index.html"; // 则将访问地址修改为/index.html便于LittleFS访问
}

String contentType = getContentType(path); // 获取文件类型
/////////很重要的,打开后再返回给浏览器
if (LittleFS.exists(path)) { // 如果访问的文件可以在LittleFS中找到
File file = LittleFS.open(path, "r"); // 则尝试打开该文件
esp8266_server.streamFile(file, contentType);// 并且将该文件返回给浏览器
file.close(); // 并且关闭文件
return true; // 返回true
}
return false; // 如果文件未找到,则返回false
}

// 获取文件类型
String getContentType(String filename){
if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
else if(filename.endsWith(".css")) return "text/css";
else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".png")) return "image/png";
else if(filename.endsWith(".gif")) return "image/gif";
else if(filename.endsWith(".jpg")) return "image/jpeg";
else if(filename.endsWith(".ico")) return "image/x-icon";
else if(filename.endsWith(".xml")) return "text/xml";
else if(filename.endsWith(".pdf")) return "application/x-pdf";
else if(filename.endsWith(".zip")) return "application/x-zip";
else if(filename.endsWith(".gz")) return "application/x-gzip";
return "text/plain";
}

通过网页控制ESP8266开发板的引脚

使用ESP8266-NodeMCU建立一个有多个页面的网站。在LED页面中配有可控制LED点亮和熄灭的
按钮。点击首页的LED Page链接进入LED页。点击LED页按钮将控制NodeMCU的内置LED点亮和熄灭。

PS:因为抄的代码是用fs.h,所以替换时要变成LittleFS

PS:不要忘记给LED灯的引脚初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//setup()
//注意在这里把不同请求区别

pinMode(LED_BUILTIN, OUTPUT); // 初始化NodeMCU控制板载LED引脚为OUTPUT

esp8266_server.on("/LED-Control", handleLEDControl); // 告知系统如何处理/LED-Control请求
esp8266_server.onNotFound(handleUserRequest); // 告知系统如何处理其它用户请求

// 处理/LED-Control请求
void handleLEDControl(){
bool ledStatus = digitalRead(LED_BUILTIN); // 此变量用于储存LED状态
ledStatus == HIGH ? digitalWrite(LED_BUILTIN, LOW) : digitalWrite(LED_BUILTIN, HIGH); // 点亮或者熄灭LED

esp8266_server.sendHeader("Location", "/LED.html");
esp8266_server.send(303);
}

// 处理用户浏览器的HTTP访问
void handleUserRequest() {

// 获取用户请求资源(Request Resource)
String reqResource = esp8266_server.uri();
Serial.print("reqResource: ");
Serial.println(reqResource);

// 通过handleFileRead函数处处理用户请求资源
bool fileReadOK = handleFileRead(reqResource);

// 如果在LittleFS无法找到用户访问的资源,则回复404 (Not Found)
if (!fileReadOK){
esp8266_server.send(404, "text/plain", "404 Not Found");
}
}

bool handleFileRead(String resource) { //处理浏览器HTTP访问

if (resource.endsWith("/")) { // 如果访问地址以"/"为结尾
resource = "/index.html"; // 则将访问地址修改为/index.html便于LittleFS访问
}

String contentType = getContentType(resource); // 获取文件类型

if (LittleFS.exists(resource)) { // 如果访问的文件可以在LittleFS中找到
File file = LittleFS.open(resource, "r"); // 则尝试打开该文件
esp8266_server.streamFile(file, contentType);// 并且将该文件返回给浏览器
file.close(); // 并且关闭文件
return true; // 返回true
}
return false; // 如果文件未找到,则返回false
}

// 获取文件类型
String getContentType(String filename){
if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
else if(filename.endsWith(".css")) return "text/css";
else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".png")) return "image/png";
else if(filename.endsWith(".gif")) return "image/gif";
else if(filename.endsWith(".jpg")) return "image/jpeg";
else if(filename.endsWith(".ico")) return "image/x-icon";
else if(filename.endsWith(".xml")) return "text/xml";
else if(filename.endsWith(".pdf")) return "application/x-pdf";
else if(filename.endsWith(".zip")) return "application/x-zip";
else if(filename.endsWith(".gz")) return "application/x-gzip";
return "text/plain";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<!--这是首页-->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>太极创客-零基础入门学用物联网教程</title>
</head>
<body>
<center>
<a href="http://www.taichi-maker.com" target="_blank"><img src="/img/taichi-maker.jpg" alt="太极创客"></a>
<h1>ESP8266 LED 引脚控制</h1>
<p><a href="LED.html">前往LED控制页面</a></p>
<p>此页面用于演示如何通过网页按钮来控制ESP8266开发板引脚。</p>
<p>本教程可在太极创客网站免费获取。太极创客网址: <a href="http://www.taichi-maker.com" target="_blank">www.taichi-maker.com</a>
</center>
</body>

</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

<!--这是LED网页-->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>太极创客-零基础入门学用物联网教程</title>
</head>
<body>
<center>
<a href="http://www.taichi-maker.com" target="_blank"><img src="/img/taichi-maker.jpg" alt="太极创客"></a>
<h1>LED引脚控制</h1>
<p>通过以下按键,您可以控制ESP8266开发板上的内置LED引脚</p>

<!--这里的两个form指的是按键-->
<form action="LED-Control"><input type="submit" value="LED控制">
</form>
<br>
<form action="index.html"><input type="submit" value="返回首页">
</form>

<p>此页面用于演示如何通过网页按钮来控制ESP8266开发板引脚。</p>
<p>本教程可在太极创客网站免费获取。太极创客网址: <a href="http://www.taichi-maker.com" target="_black">www.taichi-maker.com</a>
</center>
</body>

</html>

通过网页文本框控制ESP8266开发板的PWM引脚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void handleLEDControl(){

//"esp8266_server"是服务器对象
// 从浏览器发送的信息中获取PWM控制数值(字符串格式)
String ledPwm = esp8266_server.arg("ledPwm");

// 将字符串格式的PWM控制数值转换为整数
int ledPwmVal = ledPwm.toInt();

// 实施引脚PWM设置
analogWrite(LED_BUILTIN, ledPwmVal);

// 建立基本网页信息显示当前数值以及返回链接
//就是esp8266_server.send,服务器的send,就是相应给浏览器
String httpBody = "Led PWM: " + ledPwm + "<p><a href=\"/LED.html\"><-LED Page</a></p>";
esp8266_server.send(200, "text/html", httpBody);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>太极创客-零基础入门学用物联网教程</title>
</head>
<body>
<center>
<a href="http://www.taichi-maker.com" target="_blank"><img src="/img/taichi-maker.jpg" alt="太极创客"></a>
<form action="/LED-Control">
<!--将输入的数据命名,然后这个就是可以传递到程序中的参数-->
<input type="text" name="ledPwm">
</br>
<input type="submit" value="OK">
</form>
<p>通过本页的文本框输入0 - 1023 数值来控制ESP8266开发板的LED亮度。</p>
<p>本教程可在太极创客网站免费获取。太极创客网址: <a href="http://www.taichi-maker.com" target="_blank">www.taichi-maker.com</a>
</center>
</body>

</html>

通过网页将文件上传到ESP8266开发板闪存文件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
File fsUploadFile;              // 建立文件对象用于闪存文件上传

esp8266_server.on("/upload.html", // 如果客户端通过upload页面
HTTP_POST, // 向服务器发送文件(请求方法POST)
respondOK, // 则回复状态码 200 给客户端
handleFileUpload);// 并且运行处理文件上传函数

// 处理上传文件函数
void handleFileUpload(){

HTTPUpload& upload = esp8266_server.upload();

if(upload.status == UPLOAD_FILE_START){ // 如果上传状态为UPLOAD_FILE_START

String filename = upload.filename; // 建立字符串变量用于存放上传文件名
if(!filename.startsWith("/")) filename = "/" + filename; // 为上传文件名前加上"/"
Serial.println("File Name: " + filename); // 通过串口监视器输出上传文件的名称

fsUploadFile = LittleFS.open(filename, "w"); // 在LittleFS中建立文件用于写入用户上传的文件数据

} else if(upload.status == UPLOAD_FILE_WRITE){ // 如果上传状态为UPLOAD_FILE_WRITE

if(fsUploadFile)
fsUploadFile.write(upload.buf, upload.currentSize); // 向LittleFS文件写入浏览器发来的文件数据

} else if(upload.status == UPLOAD_FILE_END){ // 如果上传状态为UPLOAD_FILE_END
if(fsUploadFile) { // 如果文件成功建立
fsUploadFile.close(); // 将文件关闭
Serial.println(" Size: "+ upload.totalSize); // 通过串口监视器输出文件大小
esp8266_server.sendHeader("Location","/success.html"); // 将浏览器跳转到/success.html(成功上传页面)
esp8266_server.send(303); // 发送相应代码303(重定向到新页面)
} else { // 如果文件未能成功建立
Serial.println("File upload failed"); // 通过串口监视器输出报错信息
esp8266_server.send(500, "text/plain", "500: couldn't create file"); // 向浏览器发送相应代码500(服务器错误)
}
}
}

//回复状态码 200 给客户端
void respondOK(){
esp8266_server.send(200);
}
}