文件系统与 XIAO ESP32S3 Sense
在本教程中,我们将重点介绍 XIAO ESP32S3 文件系统的使用,主要是 Sense 版本的 microSD 卡插槽的使用。同时,我们还将介绍官方的 ESP 文件系统 SPIFFS、芯片内置的 Flash 等。
本教程的 microSD 卡部分仅适用于 XIAO ESP32S3 Sense,其余内容适用于 ESP32-S3 芯片,因此除了 microSD 卡之外,您可以在 XIAO ESP32S3 上运行这些示例。
入门指南
由于本教程将使用 microSD 卡,我们需要提前安装 Sense 扩展板并准备好 microSD 卡。
扩展板安装(适用于 Sense)
安装扩展板非常简单,您只需要将扩展板上的连接器与 XIAO ESP32S3 上的 B2B 连接器对齐,用力按下并听到"咔嗒"声,安装就完成了。

准备 microSD 卡
XIAO ESP32S3 Sense 支持最大 32GB 的 microSD 卡,因此如果您准备为 XIAO 购买 microSD 卡,请参考此规格。在使用 microSD 卡之前,请将 microSD 卡格式化为 FAT32 格式。

格式化后,您可以将 microSD 卡插入 microSD 卡槽。请注意插入方向,金手指一面应朝内。

如果 microSD 卡无法被 ESP32S3 识别但能被您的计算机识别,并且错误看起来像:
[ 7273][E][sd_diskio.cpp:200] sdCommand(): Card Failed! cmd: 0x00
[ 7274][E][sd_diskio.cpp:759] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
[ 7588][E][sd_diskio.cpp:200] sdCommand(): Card Failed! cmd: 0x00
Card Mount Failed
执行以下步骤:
- 使用 Windows 格式化工具

- 使用 SD Card Formatter(第三方软件)

注意:
-
此过程将比快速格式化花费更长的时间。
-
当您重复使用之前用于其他目的的 microSD 卡时会出现这些情况(即当包含 Linux 操作系统的 microSD 卡被重复使用时)。
扩展板的卡槽电路设计
XIAO ESP32S3 Sense 卡槽占用 ESP32-S3 的 4 个 GPIO,占用的引脚详细信息如下表所示。
ESP32-S3 GPIO | microSD 卡槽 |
---|---|
GPIO21 | CS |
D8 / A8 / Qt7 / GPIO7 | SCK |
D9 / A9 / Qt8 / GPIO8 | MISO |
D10 / A10 / Qt9 / GPIO9 | MOSI |
这也意味着如果您选择使用扩展板的microSD卡功能,就不能同时使用XIAO ESP32S3的SPI功能。您可以通过连接/切断J3焊盘来开启/关闭microSD卡功能。
如果您想使用SPI引脚 / 禁用扩展板的SD卡 | 如果您想启用扩展板上的SD卡 / 禁用SPI引脚 |
---|---|
![]() | |
沿着白色细线切断以断开焊盘连接。 | 将两个焊盘焊接在一起。 |
默认情况下,安装扩展板后会启用 microSD 卡功能。
修改 microSD 卡中的文件
本节仅适用于 XIAO ESP32S3 Sense。
Arduino IDE 中有几个示例展示了如何使用 XIAO ESP32S3 处理 microSD 卡上的文件。在 Arduino IDE 中,转到 File > Examples > SD > SD_Test,或复制以下代码。

#include "FS.h"
#include "SD.h"
#include "SPI.h"
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
Serial.printf("Listing directory: %s\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(" SIZE: ");
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\n", path);
File file = fs.open(path);
if(!file){
Serial.println("Failed to open file for reading");
return;
}
Serial.print("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\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\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\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\n", path);
if(fs.remove(path)){
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void testFileIO(fs::FS &fs, const char * path){
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if(file){
len = file.size();
size_t flen = len;
start = millis();
while(len){
size_t toRead = len;
if(toRead > 512){
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %u ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if(!file){
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for(i=0; i<2048; i++){
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
file.close();
}
void setup(){
Serial.begin(115200);
while(!Serial);
if(!SD.begin(21)){
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}
void loop(){
}
请注意,您不能直接使用示例程序,您需要对程序进行轻微修改以确保其正常工作。修改是在 Setup()
函数中将原始代码从 SD.begin()
更改为 SD.begin(21)
。指定用于初始化的引脚号。
将程序上传到 XIAO ESP32S3 Sense,打开串口监视器,您将看到文件创建过程和写入过程。您也可以使用 microSD 转 USB 通过计算机访问新创建的文件和内容。
![]() | ![]() |
程序注释
首先,您需要包含以下库:FS.h 用于处理文件,SD.h 用于与 microSD 卡接口,SPI.h 用于使用 SPI 通信协议。
-
在
setup()
中,以下代码行使用SD.begin(21)
初始化 microSD 卡。这里,我们需要为SD.begin()
传递一个参数,即 CS 引脚。对于扩展板的 microSD 卡设计,CS 引脚连接到 GPIO 21。如果您使用的是 Round Display for XIAO,那么传入的参数应该是 D2。 -
以下代码行在串口监视器上打印 microSD 卡类型。
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
- You can get the microSDcard size by calling the
cardSize()
method:
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
该示例提供了几个函数来处理microSD卡上的文件。
-
listDir()
函数列出SD卡上的目录。该函数接受文件系统(SD)、主目录名称和进入目录的级别作为参数。以下是如何调用此函数的示例。
/
对应microSD卡的根目录。
listDir(SD, "/", 0);
createDir()
函数创建一个新目录。将SD
文件系统和目录名路径作为参数传递。例如,以下命令在根目录下创建一个名为mydir
的新目录。
createDir(SD, "/mydir");
- 要从microSD卡中删除目录,请使用
removeDir()
函数,并将SD文件系统和目录名路径作为参数传递。
removeDir(SD, "/mydir");
readFile()
函数读取文件内容并在串口监视器中打印内容。与之前的函数一样,传递SD
文件系统和文件路径作为参数。例如,以下行读取hello.txt
文件的内容。
readFile(SD, "/hello.txt")
- 要向文件写入内容,您可以使用
writeFile()
函数。传递SD
文件系统、文件路径和消息作为参数。以下行在hello.txt
文件中写入Hello
。
writeFile(SD, "/hello.txt", "Hello ");
- 同样,您可以使用
appendFile()
函数向文件追加内容(不覆盖之前的内容)。下面这行代码在hello.txt
文件中追加消息World!\n
。\n
表示下次向文件写入内容时,将在新行中写入。
appendFile(SD, "/hello.txt", "World!\n");
- 您可以使用
renameFile()
函数重命名文件。将 SD 文件系统、原始文件名和新文件名作为参数传递。以下代码行将hello.txt
文件重命名为foo.txt
。
renameFile(SD, "/hello.txt", "/foo.txt");
- 使用
deleteFile()
函数删除文件。将 SD 文件系统和要删除的文件路径作为参数传递。以下代码行从 microSD 卡中删除foo.txt
文件。
deleteFile(SD, "/foo.txt");
testFileIO()
函数显示读取文件内容需要多长时间。以下函数测试 test.txt 文件。
testFileIO(SD, "/test.txt");
基于气体数据记录的MicroSD卡应用
本节仅适用于XIAO ESP32S3 Sense。
本项目展示了如何使用XIAO ESP32S3 Sense将带有时间戳的数据记录到TF卡中。作为示例,我们将每10分钟记录一次来自多通道气体传感器的温度读数。XIAO ESP32S3在每次读数之间将处于深度睡眠模式,并且它将使用网络时间协议(NTP)请求日期和时间。

要完成此项目,您需要提前准备以下硬件。
Seeed Studio XIAO ESP32S3 Sense | Seeed Studio XIAO扩展底板配Grove OLED | Grove - 多通道气体传感器v2 |
---|---|---|
![]() | ![]() | ![]() |
对于软件,您需要提前为 Arduino IDE 安装以下库。
- Taranais 分支的 NTPClient 库
- 多通道气体传感器库
以下是完整的示例程序。在程序中我们需要使用网络对时功能,因此您需要将程序中的 WiFi 名称和密码更改为您的。
#include "FS.h"
#include "SD.h"
#include <SPI.h>
//multichannel gas libraries
#include <Multichannel_Gas_GMXXX.h>
#include <Wire.h>
// Libraries to get time from NTP Server
#include <NTPClient.h>
#include <WiFiUdp.h>
#include "WiFi.h"
// Define deep sleep options
uint64_t uS_TO_S_FACTOR = 1000000; // Conversion factor for micro seconds to seconds
// Sleep for 10 minutes = 600 seconds
uint64_t TIME_TO_SLEEP = 600;
// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Define CS pin for the SD card module
#define SD_CS 21
// Save reading number on RTC memory
RTC_DATA_ATTR int readingID = 0;
String dataMessage;
// Gas Sensor variables
int NO2_val, C2H5CH_val, VOC_val, CO_val;
GAS_GMXXX<TwoWire> gas;
// Define NTP Client to get time
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
// Variables to save date and time
String formattedDate;
String dayStamp;
String timeStamp;
void setup() {
// Start serial communication for debugging purposes
Serial.begin(115200);
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
// Initialize a NTPClient to get time
timeClient.begin();
// Set offset time in seconds to adjust for your timezone, for example:
// GMT +1 = 3600
// GMT +8 = 28800
// GMT -1 = -3600
// GMT 0 = 0
timeClient.setTimeOffset(3600);
// Initialize SD card
SD.begin(SD_CS);
if(!SD.begin(SD_CS)) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("Initializing SD card...");
if (!SD.begin(SD_CS)) {
Serial.println("ERROR - SD card initialization failed!");
return; // init failed
}
// If the data.txt file doesn't exist
// Create a file on the SD card and write the data labels
File file = SD.open("/data.txt");
if(!file) {
Serial.println("File doens't exist");
Serial.println("Creating file...");
writeFile(SD, "/data.txt", "Reading ID, Date, Hour, NO2, C2H5CH, VOC, CO \r\n");
}
else {
Serial.println("File already exists");
}
file.close();
// Start the gas sensor
gas.begin(Wire, 0x08); // use the hardware I2C
getReadings();
getTimeStamp();
logSDCard();
// Increment readingID on every new reading
readingID++;
// Start deep sleep
Serial.println("DONE! Going to sleep now.");
// Enable Timer wake_up
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
}
void loop() {
// The ESP32 will be in deep sleep
// it never reaches the loop()
}
// Function to get temperature
void getReadings(){
// GM102B NO2 sensor ppm
NO2_val = gas.getGM102B();
// GM302B C2H5CH sensor ppm
C2H5CH_val = gas.getGM302B();
// GM502B VOC sensor
VOC_val = gas.getGM502B();
// GM702B CO sensor
CO_val = gas.getGM702B();
Serial.print("NO2 Value is: ");
Serial.println(NO2_val);
Serial.print("C2H5CH Value is: ");
Serial.println(C2H5CH_val);
Serial.print("VOC Value is: ");
Serial.println(VOC_val);
Serial.print("CO Value is: ");
Serial.println(CO_val);
}
// Function to get date and time from NTPClient
void getTimeStamp() {
while(!timeClient.update()) {
timeClient.forceUpdate();
}
// The formattedDate comes with the following format:
// 2018-05-28T16:00:13Z
// We need to extract date and time
formattedDate = timeClient.getFormattedDate();
Serial.println(formattedDate);
// Extract date
int splitT = formattedDate.indexOf("T");
dayStamp = formattedDate.substring(0, splitT);
Serial.println(dayStamp);
// Extract time
timeStamp = formattedDate.substring(splitT+1, formattedDate.length()-1);
Serial.println(timeStamp);
}
// Write the sensor readings on the SD card
void logSDCard() {
dataMessage = String(readingID) + "," + String(dayStamp) + "," + String(timeStamp) + "," +
String(NO2_val) + "," + String(C2H5CH_val) + "," + String(VOC_val) + "," +
String(CO_val) + "\r\n";
Serial.print("Save data: ");
Serial.println(dataMessage);
appendFile(SD, "/data.txt", dataMessage.c_str());
}
// Write to the SD card (DON'T MODIFY THIS FUNCTION)
void writeFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Writing file: %s\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();
}
// Append data to the SD card (DON'T MODIFY THIS FUNCTION)
void appendFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Appending to file: %s\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();
}
编译并上传程序,然后打开串口监视器。如果程序运行顺利,您将看到串口监视器输出以下信息。

您可以随时取出microSD卡,通过读卡器访问保存的传感器数据。

为了便于测试,演示效果显示每分钟保存一次数据,实际提供的示例代码是每十分钟保存一次数据。
关于这个项目有以下几点需要注意:
- 多通道气体传感器需要一段预热时间才能获得准确的数值。因此,如果误差较大,可以考虑丢弃前几组记录的数据。
- 串口监视器只会输出一次保存信息,因为这个示例使用了深度睡眠功能,相当于唤醒后重置,也就是说您需要重新打开Arduino的串口才能看到下一次的调试信息。但请放心,如果卡没有问题,传感器数据会在您设定的时间准时采集。
程序注释
在这个示例中,XIAO ESP32S3在每次读取之间都处于深度睡眠模式。在深度睡眠模式下,您的所有代码都应该放在setup()
函数中,因为XIAO ESP32S3永远不会到达loop()
。
这个示例使用了从微秒到秒的转换因子,这样您就可以在TIME_TO_SLEEP
变量中以秒为单位设置睡眠时间。在这种情况下,我们设置XIAO ESP32S3睡眠10分钟(600秒)。如果您希望XIAO ESP32S3睡眠不同的时间段,您只需要在TIME_TO_SLEEP
变量中输入深度睡眠的秒数。
// Define deep sleep options
uint64_t uS_TO_S_FACTOR = 1000000; // Conversion factor for micro seconds to seconds
// Sleep for 10 minutes = 600 seconds
uint64_t TIME_TO_SLEEP = 600;
Next, define the microSD card CS pin. In this case, it is set to GPIO 21.
#define SD_CS 21
创建一个名为 readingID
的变量来保存读数 ID。这是一种组织读数的方法。为了在深度睡眠期间保存变量值,我们可以将其保存在 RTC 内存中。要在 RTC 内存中保存数据,您只需要在变量定义前添加 RTC_DATA_ATTR
。
创建一个 String 变量来保存要保存到 microSD 卡上的数据。
以下两行定义了一个 NTPClient 来从 NTP 服务器请求日期和时间。
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
接下来,在 Setup()
函数中初始化 NTP 客户端,从 NTP 服务器获取日期和时间。您可以使用 setTimeOffset(<time>)
方法来调整您所在时区的时间。
在完成所有初始化后,我们可以获取读数、时间戳,并将所有内容记录到 microSD 卡中。
为了使代码更容易理解,我们创建了以下函数:
getReadings()
:从多通道气体传感器读取气体值;getTimeStamp()
:从 NTP 服务器获取日期和时间;logSDcard()
:将前述数据记录到 microSD 卡。
最后,ESP32 开始深度睡眠。
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();
我们建议您将这两个功能一起使用。确保 XIAO 在设置唤醒时间后能够尽快进入深度睡眠模式。
串行外设接口闪存文件系统 (SPIFFS)
本节适用于 XIAO ESP32C3、XIAO ESP32S3 或 XIAO ESP32S3 Sense,但本节不支持 Arduino IDE 2.X。
ESP32 包含一个串行外设接口闪存文件系统 (SPIFFS)。SPIFFS 是为微控制器创建的轻量级文件系统,具有通过 SPI 总线连接的闪存芯片,如 ESP32 闪存。在本文中,我们将展示如何使用 Arduino IDE 插件轻松将文件上传到 ESP32 文件系统。

SPIFFS 让您可以像在计算机上的普通文件系统中一样访问闪存,但更简单且更有限。您可以读取、写入、关闭和删除文件。在撰写本文时,SPIFFS 不支持目录,因此所有内容都保存在扁平结构中。
在 XIAO ESP32 上使用 SPIFFS 特别适用于:
- 创建带有设置的配置文件
- 永久保存数据
- 创建文件来保存少量数据,而不是使用 microSD 卡
- 保存 HTML 和 CSS 文件来构建 Web 服务器
- 保存图像、图形和图标
安装 Arduino ESP32 文件系统上传器
您可以通过在 Arduino IDE 上编写代码来创建、保存和写入文件到 ESP32 文件系统。这不是很有用,因为您必须在 Arduino 草图中输入文件的内容。
幸运的是,Arduino IDE 有一个插件,允许您直接从计算机上的文件夹将文件上传到 ESP32 文件系统。这使得处理文件变得非常简单。让我们安装它。
注意:在撰写本文时,ESP32 文件系统上传器插件不支持 Arduino 2.0。
Windows
步骤 1. 转到发布页面并点击 ESP32FS-1.1.zip 文件进行下载。
步骤 2. 找到您的草图本位置。在您的 Arduino IDE 中,转到 File > Preferences 并检查您的草图本位置。在我的情况下,它在以下路径:C:\Users\mengd\Documents\Arduino
。

步骤 3. 转到草图本位置,并创建一个 tools 文件夹。

步骤 4. 解压下载的 .zip 文件夹。打开它并将 ESP32FS 文件夹复制到您在上一步中创建的 tools 文件夹中。您应该有类似的文件夹结构:
<Sketchbook-location>/tools/ESP32FS/tool/esp32fs.jar

步骤 5. 最后,重启您的 Arduino IDE。
要检查插件是否成功安装,请打开您的 Arduino IDE。选择 XIAO ESP32S3 或 XIAO ESP32C3,转到 Tools 并检查您是否有 ESP32 Sketch Data Upload 选项。

MacOS
步骤 1. 转到发布页面并点击 ESP32FS-1.1.zip 文件进行下载。
步骤 2. 解压文件。
步骤 3. 在 /Documents/Arduino/
中创建一个名为 tools 的文件夹。
步骤 4. 将解压的 ESP32FS 文件夹复制到 tools 目录。您应该有类似的文件夹结构。
~Documents/Arduino/tools/ESP32FS/tool/esp32fs.jar

步骤 5. 最后,重启您的 Arduino IDE。
要检查插件是否成功安装,请打开您的 Arduino IDE。选择 XIAO ESP32S3 或 XIAO ESP32C3,转到 Tools 并检查您是否有 ESP32 Sketch Data Upload 选项。

使用文件系统上传器上传文件
要将文件上传到 ESP32 文件系统,请按照以下说明操作。
步骤 6. 创建一个 Arduino 草图并保存它。为了演示目的,您可以保存一个空草图。
步骤 7. 然后,打开草图文件夹。您可以转到 Sketch > Show Sketch Folder。保存草图的文件夹应该会打开。
步骤 8. 在该文件夹内,创建一个名为 data 的新文件夹。

步骤 9. 在 data 文件夹内是您应该放置要保存到 ESP32 文件系统中的文件的地方。作为示例,创建一个包含一些文本的 .txt 文件,名为 test_example。

步骤 10. 然后,要上传文件,在 Arduino IDE 中,您只需转到 Tools > ESP32 Sketch Data Upload。
上传器将覆盖您已经保存在文件系统中的任何内容。
当您看到消息 SPIFFS Image Uploaded 时,文件已成功上传到 ESP32 文件系统。

测试上传器
现在,让我们检查文件是否实际保存到了 ESP32 文件系统中。只需将以下代码上传到您的 ESP32 开发板。
#include "SPIFFS.h"
void setup() {
Serial.begin(115200);
while(!Serial);
if(!SPIFFS.begin(true)){
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
File file = SPIFFS.open("/test_example.txt");
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() {
}
上传后,以115200的波特率打开串口监视器。它应该会在串口监视器上打印你的_.txt_文件的内容。

Flash数据存储
本节适用于XIAO ESP32C3、XIAO ESP32S3或XIAO ESP32S3 Sense。
当我们使用开发板时,许多人都希望能够使用芯片上的闪存来存储一些重要数据。这需要一种存储方法,确保即使在开发板异常情况下也不会丢失数据。
本教程将介绍如何通过以下两种不同的存储方法在XIAO ESP32闪存上存储重要数据:
-
第一个指南展示如何使用Preferences.h库在ESP32闪存上永久保存数据。存储在闪存中的数据在重置或断电后仍然保持。使用Preferences.h库对于保存网络凭据、API密钥、阈值或甚至GPIO的最后状态等数据很有用。你将学习如何从闪存中保存和读取数据。
-
第二个指南解释了什么是XIAO ESP32C3 EEPROM以及它的用途。我们还将向你展示如何从EEPROM写入和读取数据,并构建一个项目示例来将所学概念付诸实践。
本节是为XIAO ESP32C3编写的,完全兼容新的XIAO ESP32S3,所以你可以直接使用这里的例程,因此我们不会在这里重复介绍。
故障排除
引用与参考
本文借鉴了**Random Nerd Tutorials**关于ESP32的文件系统内容,并在Seeed Studio XIAO ESP32S3 Sense上进行了验证使用。
特别感谢Random Nerd Tutorials作者们的辛勤工作!
以下是原文的参考链接,欢迎通过以下原文链接了解更多关于ESP32文件系统的内容。
- ESP32: Guide for MicroSD Card Module using Arduino IDE
- ESP32 Data Logging Temperature to MicroSD Card
- Install ESP32 Filesystem Uploader in Arduino IDE
有关使用ESP32开发板的更多信息,请阅读Random Nerd Tutorials官方网站。
技术支持与产品讨论
感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。