前文中:https://blog.csdn.net/m0_50114967/article/details/126910392

已经创建了一个AP和STA模式共存的WEB服务器,在保存html网页代码时使用的是SPIFFS的文件系统,而不是更有效率的LittleFS文件系统,在之前已经简单地介绍了SPIFFS文件系统,在后续的WEB服务器的搭建里,将会更多地使用到ESP32的文件系统,比如保存或读取一些设置参数,这里会更详细地介绍SPIFFS文件系统,同时也会详细地介绍一下LittleFS文件系统。

在ESP32中使用闪存有两种文件系统,SPIFFS和LittleFS。

SPIFFS是比较原始的文件系统,支持静态和动态磨损均衡,没有目录系统,就是说所有文件都保存在根目录上。对于小文件比较多的情况下,对于内存空间的利用比较理想(最小256字节的分配)。

LittleFS是新增加的一种文件系统,性能方面比SPIFFS较高,带有目录系统,但对于小文件比较多的情况下,对于内存空间的利用更多(最小4K字节的分配)。

他们共享一套文件操作的兼容API,但对于闪存的操作并不兼容,在使用中,最好只选择其中一种。如果在SPIFFS下挂载LittleFS系统,可能会导致文件的丢失。反之也是一样。所以同一项目中,最不要两种系统混用。

虽然对于闪存的操作并不兼容,但在使用中所用的方法都是高度一致的。

目录

文件系统对象:

setConfig

begin

end

format

open

exists

mkdir

rmdir

opendir

remove

rename

gc

check

info

info64

目录对象

.next()

.fileName

.fileSize

.fileTime

.fileCreationTime

.isFile

.isDirectory

.openFile

.rewind

setTimeCallback(time_t(*cb)(void))

seek

position

size

name

fullName

getLastWrite

getCreationTime

isFile

isDirectory

close

 文件读写操作

read

readBytes

readBytesUntil

readString

readStringUntil

find

findUntil

peek

parseInt

parseFloat

使用更有效率的LittleFS文件系统


文件系统对象:

setConfig

SPIFFSConfig cfg;
cfg.setAutoFormat(false);
SPIFFS.setConfig(cfg);

 默认情况下,如果文件系统挂载失败,会自动格式化文件系统。该方法将文件系统设置为禁用挂载失败时自动格式化文件系统。

begin

挂载文件系统:

SPIFFS.begin()

 对应

LittleFS.begin()

请注意,如果已经挂载了SPIFFS,又再挂载LittleFS,会丢失所有的在该系统上的所有文件。

end

卸载文件系统:

SPIFFS.end()

对应

LittleFS.end()

format

格式化文件系统:

SPIFFS.format()

对应

LittleFS.format()

open

打开文件

SPIFFS.open(path, mode)

对应

LittleFS.open(path, mode)

参数:

        path:        字符串,文件所在的绝对路径

        mode:        字符串,指定访问权限,可以是“r”、“w”、“a”、“r+”、“w+”、“a+”之一

                        r:        只读模式,文件流在文件开头。

                        r+:        读写模式,文件流在文件开头。

                        w:        写入模式,如果对应的文件已存在,会被清零并新建该文件,文件流在文件开头。

                        w+:        读写模式,如果文件不存在,则新建该文件,存在则会被清零,文件流在文件开头。

                        a:           追加模式,如果文件不存在,则新建该文件,文件流在文件结尾。

                        a+:        读取和追加模式,如果文件不存在,则新建该文件,在读取时,会从文件开头读取,但写入时会从文件结尾追加。

函数返回一个文件对象,用布尔值来检查文件是否被打开。



File f = <SPIFFS or LittleFS>.open("/f.txt", "w");
if (!f) {
    Serial.println("file open failed");
}

exists

检查文件是否存在,返回true或false

SPIFFS.exists(path)

对应

LittleFS.exists(path)

mkdir

新建一个目录,成功返回true,失败返回false,SPIFFS没有目录系统

LittleFS.rmdir(path)

rmdir

删除目录,成功返回true,失败返回flase,SPIFFS没有目录系统

LittleFS.rmdir(path)

opendir

打开一个目录,因为SPIFFS没有目录系统,所以会返回带有/字符的文件

SPIFFS.openDir(path)

对应

LittleFS.openDir(path)

remove

删除文件,成功返回true,失败返回flase

SPIFFS.remove(path)

对应

LittleFS.remove(path)

rename

重命名文件,pathFrom重命名为pathTo,路径都必须为绝对路径,成功返回true,失败返回false

SPIFFS.rename(pathFrom, pathTo)

对应

LittleFS.rename(pathFrom, pathTo)

gc

只能用在SPIFFS系统,垃圾回收操作,可以使之后的写入更快

SPIFFS.gc()

check

只能用在SPIFFS系统,尝试修复文件系统,不能保证实际修复效果

SPIFFS.begin();
SPIFFS.check();

info

设置文件系统信息

FSInfo fs_info;
SPIFFS.info(fs_info);
or LittleFS.info(fs_info);

文件系统信息结构:

struct FSInfo {
    size_t totalBytes;       //文件系统可用数据总大小
    size_t usedBytes;        //文件使用的字节数
    size_t blockSize;        //文件系统块的大小
    size_t pageSize;         //文件系统逻辑页面大小
    size_t maxOpenFiles;     //可同时打开的文件数
    size_t maxPathLength;    //最大文件名长度(包知一个字节的\0终止符)
};

info64

与info的操作相同,但允许操作大于4GB的文件系统

setTimeCallback(time_t(*cb)(void))

设置文件时间戳的回调,

time_t myTimeCallback() {
    return 1455451200; 
}
void setup () {
    LittleFS.setTimeCallback(myTimeCallback);
}

目录对象

遍历目录中的文件:

Dir dir = SPIFFS.openDir("/data");
// or Dir dir = LittleFS.openDir("/data");
while (dir.next()) {
    Serial.print(dir.fileName());
    if(dir.fileSize()) {
        File f = dir.openFile("r");
        Serial.println(f.size());
    }
}

.next()

当目录中有文件时返回true

.fileName

返回当前指向的文件的名称

.fileSize

返回当前指向的文件的大小

.fileTime

返回当前指向的文件的写入时间

.fileCreationTime

返回当前指向的文件的创建时间

.isFile

如果当前指向的文件是一个文件

.isDirectory

如果当前指向的文件是一个目录

.openFile

该方法和SPIFFS/LittleFS.open()一样的mode参数

.rewind

将指针重置为目录的开头

setTimeCallback(time_t(*cb)(void))

设置时间戳的回调

文件对象

.open方法返回一个File对象,该对象支持Stream流

seek

移动文件流的位置

file.seek(offset, mode);

参数:

        offset:        偏移值

        mode:        偏移位置设置

                SeekSet:        从文件开始偏移offset

                SeekCur:        从当前位置开始偏移offset

                SeekEnd:        从文件末尾开始偏移offset

成功返回true,失败返回false

position

返回文件流的位置

file.position();

size

返回文件大小

file.size();

name

返回不带路径的文件名

String name = file.name();

fullName

返回包括目录的文件名

String name = file.fullName();

getLastWrite

返回文件上次写入时间,仅对只读方式打开的文件有效,如果在写入模式,返回的时间是不确定的

time_t t=file.getLastWrite();

getCreationTime

返回文件创建时间

time_t t=file.getCreationTime();

isFile

如果File指向文件,返回true

if(file.isFile()) {
    Serial.println("这是一个文件");
}else {
    Serial.println("这不是一个文件");
}

isDirectory

如果File指向文件夹,返回true

if(file.isDirectory()) {
    Serial.println("这是一个文件夹");
}else {
    Serial.println("这不是一个文件夹");
}

close

关闭文件

file.close();

 文件读写操作

read

读取一个字节(以二进制读取)的数据

Serial.println(file.read());

读取出的数据为ASCII的编码,比如文本文件内只保存有一个字符'a',读取出来的数据为'61'。对应的编码可以自行查找ASCII的编码表。

readBytes

读取指定长度的数据

  size_t len = 20;
  char buffer[len];
  file.readBytes(buffer, len);
  Serial.print("FILE:");
  for(int i=0; i<len; i++){
    Serial.println(buffer[i]);
  }


readBytesUntil

读取指定长度或直到指定结束符的数据

  char terminateChar = 'x';
  size_t len = 20;
  char buffer[len];
  file.readBytesUntil(terminateChar,buffer, len);
  Serial.print("FILE:");
  for(int i=0; i<len; i++){
    Serial.println(buffer[i]);
  }


readString

读取字符串

Serial.println(file.readString());


readStringUntil

读取直到指定结束符的字符串

Serial.println(file.readStringUntil('\n'));


find

搜索指定字符串,找到指定字符串时退出函数并返回true

  if(file.find("x")) {
    Serial.print("Great! found "); Serial.println("x"); 
  } else {
    Serial.print("Sorry can't find "); Serial.println("x"); 
  }  


findUntil

搜索指字字符串,找到指定字符串时退出函数并返回true,读取到终止字符时退出函数并返回

if(file.findUntil("x", "STOP")) {
    Serial.print("Great!  found "); Serial.println("x");
} else {
    Serial.print("Sorry  can't find "); Serial.println("x");
} 

peek

用于读取一个字节的数据。但是与read函数不同的是,使用peek函数读取数据后,被读取的数据不会从数据流中消除。这就导致每一次调用peek函数,只能读取数据流中的第一个字符。然而每一次调用read函数读取数据时,被读取的数据都会从数据流中删除

Serial.println(file.peek());


parseInt

用于从读取到的的数据中寻找整数数值

Serial.print("FILE:");Serial.println(file.parseInt());


parseFloat

用于从读取到的的数据中寻找浮点数值

Serial.print("FILE:");Serial.println(file.parseFloat());

print

写入字符串

file.print();

println

写入一行字符串

file.println();

还有一些兼容的方法,不推荐使用,这里不作介绍。

以上翻译的说明来自官方对这两种文件系统介绍,可能会有所错漏

官方说明地址:https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#

使用更有效率的LittleFS文件系统

以上说明可以证实,如果使用LittleFS文件系统,除了拥有目录系统,读取效率也更高,在官方说明中,也提示了SPIFFS将被弃用的警告,原文

SPIFFS Deprecation Warning

SPIFFS is currently deprecated and may be removed in future releases of the core. Please consider moving your code to LittleFS. SPIFFS is not actively supported anymore by the upstream developer, while LittleFS is under active development, supports real directories, and is many times faster for most operations.

ESPAsyncWebServer库虽然并未宣称支持LittleFS文件系统,但通过测试,可以发现ESPAsyncWebServer是可以使用LittleFS文件系统的。

LittleFS文件的上传,需要另外的arduino IDE插件,arduino-esp32fs-plugin,该插件可同时支持SPIFFS和LittleFS文件系统的上传,项目地址:

https://github.com/lorol/arduino-esp32fs-plugin

下载以下文件

把下载到的文件"esp32fs.jar"复制arduino安装目录下的<arduino>\tools\ESP32FS\tool文件夹下,如果文件夹不存在就新建一个。重启arduino IDE,如果安装成功,工具菜单下将出现如下选项:


 点击就可以把项目文件夹下的data文件夹的所有文件上传到ESP32,注意上传前必须关闭"串口监视器"。

对应两个文件系统的区别,我们只要增加LittleFS的头文件引用和把使用SPIFFS的地方修改成使用LittleFS就可以了。修改后的代码:

#include <WiFi.h>
#include <LittleFS.h>
#include <DNSServer.h>
#include "ESPAsyncWebServer.h"


DNSServer dnsserver;
AsyncWebServer server(80);

//连接WIFI
void connect_wifi(){
  const byte DNS_PORT = 53;                     //DNS端口
  const String url = "ESPAP.com";               //域名
  IPAddress APIp(10,0,10,1);                    //AP IP
  IPAddress APGateway(10,0,10,1);               //AP网关
  IPAddress APSubnetMask(255,255,255,0);        //AP子网掩码
  const char* APSsid = "esp32_AP";              //AP SSID
  const char* APPassword = "12345678";          //AP wifi密码
  
  const char* wifi_ssid = "ESP32";
  const char* wifi_password = "12345678";
  Serial.begin(9600);
  WiFi.mode(WIFI_AP_STA);                             //打开AP和STA共存模式
  WiFi.softAPConfig(APIp, APGateway, APSubnetMask);   //设置AP的IP地址,网关和子网掩码
  WiFi.softAP(APSsid, APPassword, 6);                 //设置AP模式的登陆名和密码
  dnsserver.start(DNS_PORT, url, APIp);               //设置DNS的端口、网址、和IP
  WiFi.begin(wifi_ssid, wifi_password);               //连接WIFI
  Serial.print("Connected");
  //循环,直到连接成功
  while(WiFi.status() != WL_CONNECTED){
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  IPAddress local_IP = WiFi.localIP();
  Serial.print("WIFI is connected,The local IP address is "); //连接成功提示
  Serial.println(local_IP); 
}

void web_server(){
  if(!LittleFS.begin(true)){
    Serial.println("An Error has occurred while mounting LittleFS");
    return;
  }
  server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
  server.begin();                       //初始化
}

void setup() {
  connect_wifi();
  web_server();
}

void loop() {
  dnsserver.processNextRequest();
}
之后的文章中,将介绍如何从文件系统中读取服务器的设置,以及把服务器的设置保存在文件系统中

更多推荐