- 物联网ESP32教程
- 首页
- 物联网概述
- ESP32简介
- 在Arduino IDE中安装ESP32板
- 设置RTOS以实现双核和多线程操作
- ESP32与MPU6050接口
- ESP32与模拟传感器接口
- ESP32首选项
- ESP32 SPIFFS存储(芯片本身中的迷你SD卡)
- ESP32与OLED显示屏接口
- ESP32上的WiFi
- 使用HTTP通过WiFi传输数据
- 使用HTTPS通过WiFi传输数据
- 使用MQTT通过WiFi传输数据
- 通过蓝牙传输数据
- 使用NTP客户端获取当前时间
- 执行ESP32固件的(OTA)更新
- ESP32的应用
- 作为开发人员的后续步骤
- 物联网ESP32有用资源
- 快速指南
- 有用资源
- 讨论
ESP32中的SPIFFS
在上一章中,我们了解了首选项作为一种在非易失性存储器中存储数据的方式,并了解了如何使用它们来存储键值对。在本节中,我们将介绍SPIFFS(SPI闪存文件存储),它用于以文件形式存储更大的数据。可以将SPIFFS视为ESP32芯片本身上的一个非常小的SD卡。默认情况下,大约1.5 MB的片上闪存分配给SPIFFS。您可以通过“工具”->“分区方案”来查看。
您可以看到还有其他几个分区选项可用。但是,现在我们先不要深入研究。对于大多数应用程序,更改分区方案都是不需要的。本教程中的所有章节都可以在默认分区方案下正常工作。
现在,让我们通过一个示例了解创建、修改、读取和从SPIFFS删除文件的过程。
代码演练
我们将再次使用提供的示例代码。转到“文件”->“示例”->“SPIFFS”->“SPIFFS_Test”。此代码非常适合了解SPIFFS可能进行的所有文件操作。它也可以在GitHub上找到。
我们从包含两个库开始:FS.h和SPIFFS.h。FS代表文件系统。
#include "FS.h" #include "SPIFFS.h"
接下来,您将看到一个宏定义FORMAT_SPIFFS_IF_FAILED。有一个关联的注释建议您仅在第一次运行测试时才需要格式化SPIFFS。这意味着您可以在第一次运行后将此宏的值设置为false。格式化SPIFFS需要时间,并且不需要每次运行代码时都执行。因此,人们采用的做法是为格式化SPIFFS编写单独的代码,并在刷新主代码之前刷新它。主代码不包含格式化命令。但是,在本示例中,为了完整起见,此宏已保留为true。
/* You only need to format SPIFFS the first time you run a test or else use the SPIFFS plugin to create a partition https://github.com/me−no−dev/arduino−esp32fs−plugin */ #define FORMAT_SPIFFS_IF_FAILED true
接下来,您可以看到已为不同的文件系统操作定义了许多函数。它们是:
listDir - 列出所有目录
readFile - 读取特定文件
writeFile - 写入文件(这会覆盖文件中原有的内容)
appendFile - 将内容追加到文件(当您想要添加到现有内容而不是覆盖它时使用)
renameFile - 更改文件名
deleteFile - 删除文件
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.name(), levels -1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print("\tSIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
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());
}
}
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("− frite failed");
}
}
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");
}
}
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");
}
}
请注意,以上所有函数都没有要求提供文件名。它们要求提供完整的文件路径。因为这是一个文件系统。您可以在这些子目录中拥有目录、子目录和文件。因此,ESP32需要知道您要操作的文件的完整路径。
接下来是一个不完全是文件操作函数的函数 - testFileIO。这更像是一个时间基准测试函数。它执行以下操作:
将大约1 MB(2048 * 512字节)的数据写入您提供的文件路径,并测量写入时间
读取相同的文件并测量读取时间
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");
}
}
请注意,buf数组从未初始化任何值。我们很可能会将垃圾字节写入文件。这没关系,因为该函数的目的是测量写入时间和读取时间。
在定义完函数后,我们将继续进行设置,其中显示了每个函数的调用。
void setup(){
Serial.begin(115200);
if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){
Serial.println("SPIFFS Mount Failed");
return;
}
listDir(SPIFFS, "/", 0);
writeFile(SPIFFS, "/hello.txt", "Hello ");
appendFile(SPIFFS, "/hello.txt", "World!\r\n");
readFile(SPIFFS, "/hello.txt");
renameFile(SPIFFS, "/hello.txt", "/foo.txt");
readFile(SPIFFS, "/foo.txt");
deleteFile(SPIFFS, "/foo.txt");
testFileIO(SPIFFS, "/test.txt");
deleteFile(SPIFFS, "/test.txt");
Serial.println( "Test complete" );
}
设置基本上执行以下操作:
它首先使用SPIFFS.begin()初始化SPIFFS。此处使用了开头定义的宏。为true时,它会格式化SPIFFS(耗时);为false时,它会在不格式化的前提下初始化SPIFFS。
然后,它列出根级别上的所有目录。请注意,我们已将级别指定为0。因此,我们没有列出目录内的子目录。您可以通过增加levels参数来增加嵌套级别。
然后,它将“Hello”写入根目录中的hello.txt文件。(如果文件不存在,它将被创建)
然后,它读取回hello.txt
然后,它将hello.txt重命名为foo.txt
然后,它读取foo.txt以查看重命名是否成功。您应该看到打印出“Hello”,因为这就是存储在文件中的内容。
然后,它删除foo.txt
然后,它在新的文件test.txt上执行testFileIO例程
执行完例程后,它删除test.txt
就是这样。此示例代码很好地列出了并测试了您可能想要与SPIFFS一起使用的所有函数。您可以继续修改此代码,并尝试不同的函数。
由于我们不想在这里执行任何重复活动,因此循环为空。
void loop(){
}
串行监视器中显示的输出可能类似于下图:
注意 - 如果在运行草图时出现“SPIFFS Mount Failed”,请将FORMAT_SPIFFS_IF_FAILED的值设置为false,然后重试。