1. 前言
????在前面博文关于ESP8266WiFiWebServer的例程中,大家可以发现,博主基本上都是手动拼装html内容返回,html的内容被固定写在我们的Arduino ESP代码中。
????那么这样就有两点弊端:
- ESP8266代码相当臃肿
- 为了开发方便,web server网页除了自身的html内容之外,还包括一些css文件,甚至引入了JQuery库以及一些图片相关资源。如果把这些内容也直接写入到ESP8266代码中,会导致8266整体代码变大,甚至可能超过flash规定的大小;
- 业务职责分离不明确
- 一般来说,在一个开发团队中,有人负责开发ESP8266业务需求,有人负责开发WebServer网页内容,有人负责硬件部分。直接把html的内容直接写入到ESP8266代码中,就会导致业务职责混乱,并且如果要修改html内容的时候还得一个个改掉arduino的文件,也有可能改错标识符之类的。理想情况应该是,只需要更新web server的html文件就好,原来的esp8266 arduino逻辑不用更新;
????基于以上两点弊端,正式引入本篇章需要研究的ESP8266 文件系统(SPI Flash FileSystem,简称为SPIFFS)。
????先来看一个概念图:
????这个文件系统可以帮助我们存储一些变更频率不频繁的文件例如网页、配置或者是某些固化的数据等。
????其实,我们用得更多的是存储网页,将网页和相关资源(如:图片、html、css、javaScript)存入到flash的SPIFFS区域。
????原理如下图:
2. FLASH存储分配
????在讲解SPIFFS之前,我们来看看在Arduino环境下ESP8266的flash存储分配,请看下图:
????具体可以分为几部分:
- 代码区
- 又叫做程序存储区,其中又区分为当前代码区(current Sketch),更新代码区(OTA update);
- 文件系统
- 这个就是我们这节重点讲解的SPI Flash File System,简称SPIFFS闪存文件系统。
- 即使文件系统与程序存储在同一个闪存芯片上,烧入新的代码也不会修改文件系统内容。这允许使用文件系统来存储Web服务器的代码数据、配置文件或内容。而这个SPIFFS文件系统的大小可以通过烧写环境来配置,目前一般有1M,2M,3M等等。博主建议如果是NodeMcu板子,可以配置成3M;
- 为了使用文件系统,需要把下面的头文件包含在代码中:
#include <FS.h>
- EEPROM
- 具体讲解请回顾 ESP8266开发之旅 基础篇④ ESP8266与EEPROM
- WiFi Config
- 这个区域就是我们设置WiFi模块配置的时候存储的数据。
3. SPIFFS文件系统
3.1 文件系统限制
????ESP8266的文件系统实现必须满足芯片的限制,其中最重要是有限的RAM。SPIFFS之所以被ESP8266选择作为文件系统,是因为它是为小型系统专门设计的,同时是以一些简化和限制为代价的。
????首先,SPIFFS不支持目录,它只存储一个“扁平化”的文件列表。但是与传统的文件系统相反,斜杠字符“/”在文件名中是允许的,因此处理目录列表的函数(例如,openDir("/website"))基本上只是过滤文件名,并保留以前缀(/website/)开始的那些文件。
????然后,对于文件名,总共有32个字符限制。一个“\0”字符被保留用于c字符串终止符,因此留给我们31个可用字符长度。
????综合起来,这意味着建议保持短文件名,不要使用深嵌套的目录,因为每个文件的完整路径(包括目录、“/”字符、基本名称、点和扩展名)最多只能是31个字符长度。例如,/website/images/bird_thumbnail.jpg 达到了34个字符长度,如果使用它,将导致一些问题。
????警告:这个限制很容易达到,如果忽略,问题可能会被忽略,因为在编译和运行时不会出现错误信息。
3.2 文件系统文件添加方式
????使用文件系统目的就是为了存储文件,那么存储文件的方式其实可以分为3种:
- 直接代码中调用FS提供的API在SPIFFS上创建文件;
- 通过 ESP8266FS 工具把文件上传到SPIFFS;
- 通过OTA Update的方式上传到SPIFFS;
????本质上,无论是通过ESP8266FS或者OTA Update的方式把文件上传到SPIFFS,其底层都是通过调用FS提供的API去完成,所以我们只需要了解FS常用API即可。
4. SPIFFS库
????了解一下SPIFFS文件系统常用的操作方法,以下是博主总结的百度脑图:
????方法分为3大类:
- SPIFFS专用方法
- Dir对象专用方法
- File对象专用方法
4.1 SPIFFS专用方法
4.1.1 begin —— 挂载SPIFFS文件系统
函数说明:
/**
* 挂载SPIFFS文件系统
* @return bool 如果文件系统挂载成功,返回true,否则返回false
*/
bool begin();
注意点:
- 它必须在其他任何FS API被调用之前先调用;
- Arduino IDE配置时需要启用SPIFFS;
4.1.2 format —— 格式化文件系统
函数说明:
/**
* 格式化文件系统
* @return bool 如果格式化成功则返回true
*/
bool format();
注意点:
- 可以在执行begin()之前或者之后调用
4.1.3 open —— 打开文件
函数说明:
/**
* 打开文件,某种模式下会创建文件
* @param path 文件路径
* @param mode 存取模式
* @return File 返回一个File对象
*/
File open(const char* path, const char* mode);
File open(const String& path, const char* mode);
注意点:
- 路径必须是以斜线开头的绝对路径(如:/dir/filename.txt);
- 模式参数是个用字符串指定的存取模式,其值为“r”、“w”、“a”、“r+”、“w+”和“a+”之中的一个。
- r 以只读方式操作文件,读位置在文件的开始位置,文件不存在返回空对象;
- r+ 以可读可写方式打开文件,读写位置在文件的开始位置,文件不存在返回空对象;
- w 截取文件长度到0或者创建新文件,只能写操作,写位置在文件的开始位置;
- w+ 截取文件长度到0或者创建新文件,可读可写操作,写位置在文件的开始位置;
- a 在文件末尾追加内容或者文件不存在就创建新文件,追加位置在当前文件的末尾,只能写操作;
- a+ 在文件末尾追加内容或者文件不存在就创建新文件,追加位置在当前文件的末尾,可读写操作;
如果要检查文件是否打开成功,请使用以下代码:
File f = SPIFFS.open("/f.txt", "w");
if (!f) {
Serial.println("file open failed");
}
4.1.4 exists —— 路径是否存在
函数说明:
/**
* 路径是否存在
* @param path 文件路径
* @return bool 如果指定的路径存在,则返回true,否则返回false
*/
bool exists(const char* path);
bool exists(const String& path);
4.1.5 openDir —— 打开绝对路径文件夹
函数说明:
/**
* 打开绝对路径文件夹
* @param path 文件路径
* @return Dir 打开绝对路径文件夹,返回一个Dir对象
*/
Dir openDir(const char* path);
Dir openDir(const String& path);
4.1.6 remove —— 删除绝对路径的文件
函数说明:
/**
* 删除绝对路径的文件
* @param path 文件路径
* @return bool 如果删除成功则返回true,否则返回false
*/
bool remove(const char* path);
bool remove(const String& path);
4.1.7 rename —— 重新命名文件
函数说明:
/**
* 重新命名文件
* @param pathFrom 原始路径文件名
* @param pathTo 新路径文件名
* @return bool 如果重新命名成功则返回true,否则返回fals
*/
bool rename(const char* pathFrom, const char* pathTo);
bool rename(const String& pathFrom, const String& pathTo);
4.1.8 info —— 获取文件系统的信息
函数说明:
/**
* 获取文件系统的信息,存储在FSInfo对象
* @param info FSInfo对象
* @return bool 是否获取成功
*/
bool info(FSInfo& info);
FSInfo定义如下:
struct FSInfo {
size_t totalBytes;//整个文件系统的大小
size_t usedBytes;//文件系统所有文件占用的大小
size_t blockSize;//SPIFFS块大小
size_t pageSize;//SPIFFS逻辑页数大小
size_t maxOpenFiles;//能够同时打开的文件最大个数
size_t maxPathLength;//文件名最大长度(包括一个字节的字符串结束符)
};
4.2 Dir对象专用方法
在上面的方法中,我们可以获取到Dir对象,那么看看Dir对象定义是什么?
class Dir {
public:
Dir(DirImplPtr impl = DirImplPtr()): _impl(impl) { }
File openFile(const char* mode);//打开文件
String fileName();//获取文件名字
size_t fileSize();//文件大小
bool next();//下一个文件
protected:
DirImplPtr _impl;
};
注意点:
- Dir对象的作用主要是遍历文件夹里的所有文件;
- 文件夹并不是真正意义上的文件夹,文件都是平铺的;
4.2.1 openFile —— 打开文件
函数说明:
/**
* 打开文件
* @param mode 打开模式,请参考open方法
* @return File 返回一个File对象
*/
File openFile(const char* mode);
4.2.2 fileName —— 获取文件名字
函数说明:
/**
* 获取文件大小
* @return size_t 文件大小
*/
size_t fileSize();
4.2.3 next —— 是否还有下一个文件
函数说明:
/**
* 是否还有下一个文件
* @return bool true 表示还有文件
*/
bool next();
注意点:
- 其实这里用到了遍历;
- 只要还有文件,dir.next()就会返回true,这个方法必须在fileName()和openFile()方法之前调用。
4.3 File对象专用方法
那么,我们来看看File对象结构:
class File : public Stream
{
public:
File(FileImplPtr p = FileImplPtr()) : _p(p) {}
// Print methods:
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
// Stream methods:
int available() override;
int read() override;
int peek() override;
void flush() override;
size_t readBytes(char *buffer, size_t length) override {
return read((uint8_t*)buffer, length);
}
size_t read(uint8_t* buf, size_t size);
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
void close();
operator bool() const;
const char* name() const;
protected:
FileImplPtr _p;
};
File对象支持Stream的所有方法,因此可以使用readBytes、findUntil、parseInt、printIn以及其他stream方法。以下是File对象特有的一些方法:
4.3.1 seek —— 文件偏移位置
函数说明:
/**
* 设置文件位置偏移
* @param pos 偏移量
* @param mode 偏移模式
* @return bool 如果移动成功,则返回true,否则返回false
*/
bool seek(uint32_t pos, SeekMode mode);
bool seek(uint32_t pos) {
return seek(pos, SeekSet);
}
注意点:
- 如果模式值是 SeekSet,则从文件开头移动指定的偏移量。
- 如果模式值是 SeekCur,则从目前的文件位置移动指定的偏移量。
- 如果模式值是 SeekEnd,则从文件结尾处移动指定的偏移量。
4.3.2 position —— 返回目前在文件中的位置
函数说明:
/**
* 返回目前在文件中的位置
* @return size_t 当前位置
*/
size_t position();
4.3.3 size —— 返回文件大小
函数说明:
/**
* 返回文件大小
* @return size_t 文件大小
*/
size_t size();
4.3.4 name —— 返回文件名字
函数说明:
/**
* 返回文件名字
* @return const char* 文件名字
*/
const char* name();
4.3.5 close —— 关闭文件
函数说明:
/**
* 关闭文件
*/
void close();
注意点:
- 执行这个方法之后,就不能在该文件上执行其他操作。
5. 实例
5.1 文件操作
实例说明:
spiffs文件操作常见方法使用,包括文件查找、创建、打开、关闭、删除
实例源码:
/**
* 功能描述:spiffs文件操作常见方法使用,包括文件查找、创建、打开、关闭、删除
*/
#include <FS.h>
//以下三个定义为调试定义
#define DebugBegin(baud_rate) Serial.begin(baud_rate)
#define DebugPrintln(message) Serial.println(message)
#define DebugPrint(message) Serial.print(message)
#define myFileName "mydemo.txt"
void setup(){
DebugBegin(9600);
DebugPrintln("Check Start SPIFFS...");
//启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
if(!SPIFFS.begin()){
DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
return;
}
DebugPrintln("Start SPIFFS Done.");
//判断文件是否存在
if(SPIFFS.exists(myFileName)){
DebugPrintln("mydemo.txt exists.");
}else{
DebugPrintln("mydemo.txt not exists.");
}
File myFile;
//打开文件 不存在就创建一个 可读可写
myFile = SPIFFS.open(myFileName,"w+");
//关闭文件
myFile.close();
//再次判断文件是否存在
if(SPIFFS.exists(myFileName)){
DebugPrintln("mydemo.txt exists.");
}else{
DebugPrintln("mydemo.txt not exists.");
}
//删除文件
DebugPrintln("mydemo.txt removing...");
SPIFFS.remove(myFileName);
//再次判断文件是否存在
if(SPIFFS.exists(myFileName)){
DebugPrintln("mydemo.txt exists.");
}else{
DebugPrintln("mydemo.txt not exists.");
}
}
void loop(){
}
实验结果:
5.2 文件列表
实例说明:
查看spiffs文件系统列表
实例准备:
- NodeMcu开发板
- 烧录配置需要开启SPIFFS
实例源码:
/**
* 功能描述:查看spiffs文件系统列表
*/
#include <FS.h>
//以下三个定义为调试定义
#define DebugBegin(baud_rate) Serial.begin(baud_rate)
#define DebugPrintln(message) Serial.println(message)
#define DebugPrint(message) Serial.print(message)
void setup(){
DebugBegin(9600);
DebugPrintln("Check Start SPIFFS...");
//启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
if(!SPIFFS.begin()){
DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
return;
}
DebugPrintln("Start SPIFFS Done.");
File myFile;
//打开文件 不存在就创建一个 可读可写
myFile = SPIFFS.open("/myDemo.txt","w+");
//关闭文件
myFile.close();
//打开文件 不存在就创建一个 可读可写
myFile = SPIFFS.open("/myDemo.jpg","w+");
//关闭文件
myFile.close();
//打开文件 不存在就创建一个 可读可写
myFile = SPIFFS.open("/myDemo.html","w+");
//关闭文件
myFile.close();
Dir dir = SPIFFS.openDir("/");
while(dir.next()){
String fileName = dir.fileName();
size_t fileSize = dir.fileSize();
Serial.printf("FS File:%s,size:%d\n",fileName.c_str(),fileSize);
}
DebugPrintln("Setup Done!");
}
void loop(){
}
实验结果:
5.3 文件读写
实例说明:
往文件myDemo.txt中写入“单片机菜鸟博哥666”并读取出来显示。
实例源码:
/**
* 功能描述:演示文件读写功能
*/
#include <FS.h>
//以下三个定义为调试定义
#define DebugBegin(baud_rate) Serial.begin(baud_rate)
#define DebugPrintln(message) Serial.println(message)
#define DebugPrint(message) Serial.print(message)
void setup(){
DebugBegin(9600);
DebugPrintln("Check Start SPIFFS...");
//启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
if(!SPIFFS.begin()){
DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
return;
}
DebugPrintln("Start SPIFFS Done.");
File myFile;
//打开文件 不存在就创建一个 可读可写
myFile = SPIFFS.open("myDemo.txt","w+");
if(myFile){
DebugPrintln("Writing something to myDemo.txt...");
myFile.println("单片机菜鸟博哥666");
myFile.close();
DebugPrintln("Writing Done.");
}else{
DebugPrintln("Open File Failed.");
}
//打开文件 可读
myFile = SPIFFS.open("myDemo.txt","r");
if(myFile){
DebugPrintln("Reading myDemo.txt...");
while(myFile.available()){
//读取文件输出
Serial.write(myFile.read());
}
myFile.close();
}else{
DebugPrintln("Open File Failed.");
}
DebugPrintln("Setup Done!");
}
void loop(){
}
实验结果:
5.4 烧写文件
实验说明:
????在上面的例子中,我们都是自己手动在SPIFFS文件系统中创建或者写入文件,但是对于习惯web开发的人员来说,肯定是直接把写好的web程序(html、css、js、资源文件等)直接烧入文件系统更加令人容易接受。所以本例子主要是讲解如何往SPIFFS里面烧写文件。
????这个例子是重点,因为绝大部分的web开发(web配网、web页面等)都是常用烧写文件的方式,请读者仔细阅读。
????要存入SPIFFS区域的文件,都得事先放在代码目录里的“data”目录(请自行新增“data”目录)。
????例如,存在一个项目工程叫做espStaticWeb,其文件结构如下:
????负责将文件上传到SPIFFS的工具叫做 ESP8266FS。ESP8266FS是一个集成到Arduino IDE中的工具,它将一个菜单项添加到工具菜单,用于将skench data目录的内容上传到ESP8266 Flash文件系统中。
????这个工具需要另外安装,整个上传文件步骤如下:
- 下载 ESP8266FS工具
- 将下载到的文件解压到Arduino IDE安装路径下的tools文件夹(如果不存在这个文件夹,请自行增加)。参考下图:
- 重启Arduino IDE
- 打开一个Sketch工程(新建或者打开最近的工程),去到Sketch工程目录下创建一个data目录(不存在该目录),然后把你需要放到文件系统的文件copy到这里。
- 确保你选择了正确的板子、com口,关闭掉串口监视器。
- 选择 工具 ESP8266 Sketch Data Upload
然后就会开始上传文件到ESP8266 flash文件系统。
当IDE显示“SPIFFS Image Uploaded”,代表上传完毕。
????那么接下来说明一下本例子内容:
- 往8266 SPIFFS文件系统中上传一个config.txt文件(请读者自行创建,然后放在data目录,上传到ESP8266),然后读取出来。文件内容包括:
{"name":"esp8266","flash":"QIO","board":"NodeMcu"}
实验准备:
- 往8266 SPIFFS文件系统中上传一个config.txt文件(请读者自行创建,然后放在data目录,上传到ESP8266)
- NodeMcu开发板
实验源码:
/**
* 功能描述:演示上传文件并读取文件内容
* 前提:需要先往SPIFFS里面上传config.txt文件
*/
#include <FS.h>
//以下三个定义为调试定义
#define DebugBegin(baud_rate) Serial.begin(baud_rate)
#define DebugPrintln(message) Serial.println(message)
#define DebugPrint(message) Serial.print(message)
void setup(){
DebugBegin(9600);
DebugPrintln("Check Start SPIFFS...");
//启动SPIFFS,如果下载配置没有配置SPIFFS,返回false
if(!SPIFFS.begin()){
DebugPrintln("Start SPIFFS Failed!please check Arduino Download Config.");
return;
}
DebugPrintln("Start SPIFFS Done.");
File myFile;
//打开文件 不存在就创建一个 可读可写
myFile = SPIFFS.open("/config.txt","r");
if(myFile){
//打印文件大小
int size = myFile.size();
Serial.printf("Size=%d\r\n", size);
//读取文件内容
DebugPrintln(myFile.readString());
myFile.close();
DebugPrintln("Reading Done.");
}else{
DebugPrintln("Open File Failed.");
}
}
void loop(){
}
实验结果:
6. 总结
SPIFFS文件系统属于非常重要的一篇,希望读者可以认真理解使用。
原文地址:https://www.cnblogs.com/danpianjicainiao/p/11048396.html