ESP32 文件系统(LittleFS/FatFS/SPIFFS)对文件的读取写入列表删除操作
目录
实验效果
凌顺实验室(lingshunlab.com)本次示例主要讲解LittleFS的使用方法,并且把示例程序上传到ESP32,看其输出。实现ESP32文件系统的读取,写入,创建目录,列表文件等操作。
现在文件系统的库兼容性非常好,对于FatFS和SPIFFS库的使用,和LittleFS基本无差别,通过切换不同库的头文件,就可以实现无痛无缝切换。但各子的文件系统里的文件,并不相通,清注意这一点。切换了使用的文件系统,之前保存的文件很有可能会丢失。
以下图片是ESP32运行「LittleFS_test」的串口输出信息:
可以看出,
把1048576 bytes的数据写入的时间,需要 11358 ms,
而读取1048576 bytes的数据的时间,需要 530 ms。
LittleFS介绍
这个库现在是Arduino esp32 core v2的一部分。
https://github.com/espressif/arduino-esp32/tree/master/libraries/LittleFS
注意,现在已经从LITTLEFS改名为LittleFS,请在那里(Arduino esp32 core v2)发表你的问题。这里是为Arduino esp32 core 1.x而保留的,已经停止更新了。
- 该功能与FAT,SPIFFS类似,基本可以通过修改头文件即可切换使用。
ESP32功能模块图:
SPIFFS 是原始文件系统,非常适合空间和 RAM 受限的应用程序,这些应用程序使用许多小文件,关心静态和动态磨损均衡并且不需要真正的目录支持。闪存上的文件系统开销也很小。
LittleFS 专注于更高的性能和目录支持,但具有更高的文件系统和每个文件的开销(最小 4K 与 SPIFFS 的 256 字节最小文件分配单元相比)。
在这个闪存中,ESP 存储了程序。除了该程序,您还可以将文件存储在上面。此内存的限制是它只有 10000(一万)个写周期。
与 SPIFFS 的区别
- LittleFS有文件夹,除非你设置,否则你需要迭代文件夹中的文件
#define CONFIG_LITTLEFS_SPIFFS_COMPAT 1
open
方法的行为与 SPIFFS 不同。当您遍历 a 时,SPIFFS 会返回“子目录”中的文件File::openNextFile()
(因为它们不是子目录,而是名称中带有“/”的文件),而 LittleFS 只会返回指定子目录中的文件。这模仿了大多数 C 程序员习惯的目录遍历的POSIX 行为。- 在根目录下有一个“/folder”=“folder”
- 需要挂载点标签,NULL 无效。推荐使用默认的 LITTLEFS.begin()
- maxOpenFiles 参数未使用,保留兼容性
- LITTLEFS.mkdir(path) 和 LITTLEFS.rmdir(path) 可用
- file.seek() 的行为类似于 FFat 查看更多详细信息
- 分区空间结束时的 file.write() 和 file.print() 可能返回不同于实际写入的字节(在其他 FS 上也不一致)。
- 基于LittleFS_test.ino草图的速度比较(对于 1048576 字节的文件):
文件系统 | 读取时间 [ms] | 写入时间[ms] |
---|---|---|
FAT | 276 | 14493 |
LITTLEFS | 446* | 16387 |
SPIFFS | 767 | 65622 |
*以上测试数据仅供参考,以实际为准。
配置储存空间
可以在上传代码中配置LittleFS的储存空间,
库安装
如果是使用Arduino ESP core v2.0之后的版本,LittleFS已经默认安装好。
使用方法
使用与SPIFFS相同的方法的LITTLEFS,加上mkdir()和rmdir()。
在你现有的代码基础上快速启动,你可也以重新定义 SPIFFS
#define USE_LittleFS
#include <FS.h>
#ifdef USE_LittleFS
#define SPIFFS LITTLEFS
#include<LITTLEFS.h>,否则
#else
#include <SPIFFS.h
#endif
注意,如果你的草图使用了其他自己使用SPIFFS的库,这可能就不奏效。
参见esp_partition.h 。LITTLEFS重新使用与SPIFFS相同的类型和子类型。
属性与方法
有一些标准命令或者方法可用于此文件系统
LITTLEFS.begin()
此方法安装 LITTLEFS 文件系统,并且必须在使用任何其他 FS API 之前调用它。如果文件系统挂载成功则返回真;否则为假。
LITTLEFS.format()
格式化文件系统。如果格式化成功则返回 true。
LITTLEFS.open(path, mode)
打开一个文件。路径应该是绝对路径,以斜杠开头(例如,/dir/filename.txt)。Mode 是指定访问模式的字符串。它可以是“r”、“w”、“a”之一。这些模式的含义与fopen C 函数的含义相同。
返回文件对象。要检查文件是否已成功打开,请使用布尔运算符。
LITTLEFS.exists(path)
如果给定路径的文件存在则返回真;否则为假。
LITTLEFS.remove(path)
删除给定绝对路径的文件。如果文件删除成功则返回真。
LITTLEFS.rename(pathFrom, pathTo)
将文件从 pathFrom 重命名为 pathTo。如果文件重命名成功,则返回 true。路径必须是绝对的。
LITTLEFS .mkdir(path):
创建一个新文件夹。 如果目录创建成功则 返回 *true ,否则*返回 false 。
LITTLEFS .rmdir(path):
*删除目录。 如果目录已成功删除,则返回 true ;*否则为假。
LITTLEFS.totalBytes()
返回在 LITTLEFS 上启用的总字节数。返回字节。
LITTLEFS.usedBytes()
返回在 LITTLEFS 上启用的总使用字节数。返回字节。
file.seek(offset, mode)
此函数的行为类似于 fseek C 函数。根据模式的值,它移动文件中的当前位置如下:
- 如果模式是 SeekSet,则位置设置为从开头偏移字节。
- 如果模式是 SeekCur,则当前位置移动偏移量字节。
- 如果模式为 SeekEnd,则位置设置为距文件末尾的字节偏移量。
- 如果位置设置成功则返回真。
file.position()
返回文件中的当前位置,以字节为单位。
file.size()
返回文件大小,以字节为单位。
file.name()
返回文件名,作为 const char*。
file.close()
关闭文件。
file.getLastWrite()
最后一次写入的纪元时间(使用内部时间来管理日期)。
file.isDirectory()
如果是目录则返回
file.openNextFile()
在目录中设置下一个文件指针。
file.rewindDirectory()
重新启动指向目录第一个文件的指针。
示例代码
打开示例代码,菜单栏「File」->「Examples」->「LittleFS」->「LittleFS_test」
// welcome to www.lingshunlab.com
#include <Arduino.h>
#include "FS.h"
#include <LittleFS.h>
/* You only need to format LittleFS the first time you run a
test or else use the LITTLEFS plugin to create a partition
https://github.com/lorol/arduino-esp32littlefs-plugin
If you test two partitions, you need to use a custom
partition.csv file, see in the sketch folder */
//#define TWOPART
#define FORMAT_LITTLEFS_IF_FAILED true
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\r\n", dirname);
File root = fs.open(dirname);
if(!root){
Serial.println("- failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println(" - not a directory");
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
Serial.print(" DIR : ");
Serial.println(file.name());
if(levels){
listDir(fs, file.path(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print("\tSIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void createDir(fs::FS &fs, const char * path){
Serial.printf("Creating Dir: %s\n", path);
if(fs.mkdir(path)){
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
void removeDir(fs::FS &fs, const char * path){
Serial.printf("Removing Dir: %s\n", path);
if(fs.rmdir(path)){
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
void readFile(fs::FS &fs, const char * path){
Serial.printf("Reading file: %s\r\n", path);
File file = fs.open(path);
if(!file || file.isDirectory()){
Serial.println("- failed to open file for reading");
return;
}
Serial.println("- read from file:");
while(file.available()){
Serial.write(file.read());
}
file.close();
}
void writeFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Writing file: %s\r\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("- failed to open file for writing");
return;
}
if(file.print(message)){
Serial.println("- file written");
} else {
Serial.println("- write failed");
}
file.close();
}
void appendFile(fs::FS &fs, const char * path, const char * message){
Serial.printf("Appending to file: %s\r\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file){
Serial.println("- failed to open file for appending");
return;
}
if(file.print(message)){
Serial.println("- message appended");
} else {
Serial.println("- append failed");
}
file.close();
}
void renameFile(fs::FS &fs, const char * path1, const char * path2){
Serial.printf("Renaming file %s to %s\r\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("- file renamed");
} else {
Serial.println("- rename failed");
}
}
void deleteFile(fs::FS &fs, const char * path){
Serial.printf("Deleting file: %s\r\n", path);
if(fs.remove(path)){
Serial.println("- file deleted");
} else {
Serial.println("- delete failed");
}
}
// SPIFFS-like write and delete file, better use #define CONFIG_LITTLEFS_SPIFFS_COMPAT 1
void writeFile2(fs::FS &fs, const char * path, const char * message){
if(!fs.exists(path)){
if (strchr(path, '/')) {
Serial.printf("Create missing folders of: %s\r\n", path);
char *pathStr = strdup(path);
if (pathStr) {
char *ptr = strchr(pathStr, '/');
while (ptr) {
*ptr = 0;
fs.mkdir(pathStr);
*ptr = '/';
ptr = strchr(ptr+1, '/');
}
}
free(pathStr);
}
}
Serial.printf("Writing file to: %s\r\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("- failed to open file for writing");
return;
}
if(file.print(message)){
Serial.println("- file written");
} else {
Serial.println("- write failed");
}
file.close();
}
void deleteFile2(fs::FS &fs, const char * path){
Serial.printf("Deleting file and empty folders on path: %s\r\n", path);
if(fs.remove(path)){
Serial.println("- file deleted");
} else {
Serial.println("- delete failed");
}
char *pathStr = strdup(path);
if (pathStr) {
char *ptr = strrchr(pathStr, '/');
if (ptr) {
Serial.printf("Removing all empty folders on path: %s\r\n", path);
}
while (ptr) {
*ptr = 0;
fs.rmdir(pathStr);
ptr = strrchr(pathStr, '/');
}
free(pathStr);
}
}
void testFileIO(fs::FS &fs, const char * path){
Serial.printf("Testing file I/O with %s\r\n", path);
static uint8_t buf[512];
size_t len = 0;
File file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("- failed to open file for writing");
return;
}
size_t i;
Serial.print("- writing" );
uint32_t start = millis();
for(i=0; i<2048; i++){
if ((i & 0x001F) == 0x001F){
Serial.print(".");
}
file.write(buf, 512);
}
Serial.println("");
uint32_t end = millis() - start;
Serial.printf(" - %u bytes written in %u ms\r\n", 2048 * 512, end);
file.close();
file = fs.open(path);
start = millis();
end = start;
i = 0;
if(file && !file.isDirectory()){
len = file.size();
size_t flen = len;
start = millis();
Serial.print("- reading" );
while(len){
size_t toRead = len;
if(toRead > 512){
toRead = 512;
}
file.read(buf, toRead);
if ((i++ & 0x001F) == 0x001F){
Serial.print(".");
}
len -= toRead;
}
Serial.println("");
end = millis() - start;
Serial.printf("- %u bytes read in %u ms\r\n", flen, end);
file.close();
} else {
Serial.println("- failed to open file for reading");
}
}
void setup(){
Serial.begin(115200);
#ifdef TWOPART
if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED, "/lfs2", 5, "part2")){
Serial.println("part2 Mount Failed");
return;
}
appendFile(LittleFS, "/hello0.txt", "World0!\r\n");
readFile(LittleFS, "/hello0.txt");
LittleFS.end();
Serial.println( "Done with part2, work with the first lfs partition..." );
#endif
if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)){
Serial.println("LittleFS Mount Failed");
return;
}
Serial.println( "SPIFFS-like write file to new path and delete it w/folders" );
writeFile2(LittleFS, "/new1/new2/new3/hello3.txt", "Hello3");
listDir(LittleFS, "/", 3);
deleteFile2(LittleFS, "/new1/new2/new3/hello3.txt");
listDir(LittleFS, "/", 3);
createDir(LittleFS, "/mydir");
writeFile(LittleFS, "/mydir/hello2.txt", "Hello2");
listDir(LittleFS, "/", 1);
deleteFile(LittleFS, "/mydir/hello2.txt");
removeDir(LittleFS, "/mydir");
listDir(LittleFS, "/", 1);
writeFile(LittleFS, "/hello.txt", "Hello ");
appendFile(LittleFS, "/hello.txt", "World!\r\n");
readFile(LittleFS, "/hello.txt");
renameFile(LittleFS, "/hello.txt", "/foo.txt");
readFile(LittleFS, "/foo.txt");
deleteFile(LittleFS, "/foo.txt");
testFileIO(LittleFS, "/test.txt");
deleteFile(LittleFS, "/test.txt");
Serial.println( "Test complete" );
}
void loop(){
}
参考:
https://www.mischianti.org/2021/04/01/esp32-integrated-littlefs-filesystem-5/