开始前适用开发板:ESP32 系列 · 开发框架:ESP-IDF
文档类型:ESP-IDF 通用 API 教程,不绑定单一开发板。
板级例程见 热门例程详解。
目录
SPIFFS 简介
SPIFFS (SPI Flash File System) 是一种专为嵌入式设备上的 SPI Flash 设计的轻量级文件系统。它具有以下特点:
- 专为 Flash 存储器设计,考虑了擦写均衡
- 支持基本的文件操作(读、写、删除等)
- 占用内存少,适合资源受限的嵌入式系统
- 支持目录结构(但有限制)
- 适合存储配置文件、网页、字体等静态数据
基本使用
1. 初始化 SPIFFS
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_spiffs.h"
#include "esp_err.h"
static const char *TAG = "spiffs_example";
void app_main(void)
{
ESP_LOGI(TAG, "初始化 SPIFFS");
// 配置 SPIFFS
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs", // 文件系统挂载点
.partition_label = NULL, // 使用默认分区
.max_files = 5, // 最大打开文件数
.format_if_mount_failed = true // 如果挂载失败,则格式化
};
// 注册并挂载 SPIFFS 文件系统
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "挂载或格式化文件系统失败");
} else if (ret == ESP_ERR_NOT_FOUND) {
ESP_LOGE(TAG, "找不到 SPIFFS 分区");
} else {
ESP_LOGE(TAG, "注册 SPIFFS 失败 (%s)", esp_err_to_name(ret));
}
return;
}
// 获取 SPIFFS 分区信息
size_t total = 0, used = 0;
ret = esp_spiffs_info(NULL, &total, &used);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "获取 SPIFFS 分区信息失败");
} else {
ESP_LOGI(TAG, "分区大小: 总计: %d 字节, 已使用: %d 字节", total, used);
}
ESP_LOGI(TAG, "SPIFFS 挂载成功");
}
2. 基本文件操作
void file_operations_example(void)
{
// 写入文件
ESP_LOGI(TAG, "写入文件");
FILE *f = fopen("/spiffs/hello.txt", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行写入");
return;
}
fprintf(f, "Hello SPIFFS!\n");
fclose(f);
// 读取文件
ESP_LOGI(TAG, "读取文件");
f = fopen("/spiffs/hello.txt", "r");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行读取");
return;
}
char buf[64];
memset(buf, 0, sizeof(buf));
fread(buf, 1, sizeof(buf), f);
fclose(f);
ESP_LOGI(TAG, "读取的内容: %s", buf);
// 检查文件是否存在
if (access("/spiffs/hello.txt", F_OK) == 0) {
ESP_LOGI(TAG, "文件存在");
}
// 删除文件
ESP_LOGI(TAG, "删除文件");
unlink("/spiffs/hello.txt");
}
3. 目录操作
void directory_operations_example(void)
{
// 创建目录
ESP_LOGI(TAG, "创建目录");
mkdir("/spiffs/mydir", 0755);
// 写入文件到目录
FILE *f = fopen("/spiffs/mydir/test.txt", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法创建目录中的文件");
return;
}
fprintf(f, "文件在子目录中\n");
fclose(f);
// 列出目录内容
ESP_LOGI(TAG, "列出目录内容");
DIR *dir = opendir("/spiffs");
if (dir == NULL) {
ESP_LOGE(TAG, "无法打开目录");
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
ESP_LOGI(TAG, "找到文件: %s", entry->d_name);
}
closedir(dir);
}
CMakeLists.txt 配置
要在 ESP-IDF 项目中使用 SPIFFS,需要在 CMakeLists.txt 中进行适当的配置,特别是将数据文件嵌入到 SPIFFS 分区中。
1. 基本 CMakeLists.txt 配置
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(spiffs_example)
2. 添加 SPIFFS 数据文件
# 指定 SPIFFS 数据目录
set(SPIFFS_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/spiffs_data")
# 创建 SPIFFS 镜像
spiffs_create_partition_image(storage ${SPIFFS_DATA_DIR} FLASH_IN_PROJECT)
3. 完整的 CMakeLists.txt 示例
cmake_minimum_required(VERSION 3.5)
# 设置 SPIFFS 数据目录
set(SPIFFS_DATA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/spiffs_data")
# 包含 ESP-IDF 项目 CMake 文件
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# 定义项目
project(spiffs_example)
# 创建 SPIFFS 镜像并烧录到 Flash
spiffs_create_partition_image(storage ${SPIFFS_DATA_DIR} FLASH_IN_PROJECT)
4. 项目结构示例
spiffs_example/
├── CMakeLists.txt
├── main/
│ ├── CMakeLists.txt
│ └── main.c
├── spiffs_data/
│ ├── index.html
│ ├── style.css
│ └── config.json
└── partitions.csv
5. main 目录下的 CMakeLists.txt
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
6. 分区表配置 (partitions.csv)
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, spiffs, , 0x100000,
高级用法
1. 使用 SPIFFS 存储网页文件
#include "esp_http_server.h"
// 处理 HTTP GET 请求
static esp_err_t http_get_handler(httpd_req_t *req)
{
char filepath[64];
// 获取请求的 URI
const char *uri = req->uri;
// 如果请求根目录,则返回 index.html
if (strcmp(uri, "/") == 0) {
uri = "/index.html";
}
// 构建文件路径
sprintf(filepath, "/spiffs%s", uri);
// 打开文件
FILE *file = fopen(filepath, "r");
if (file == NULL) {
httpd_resp_send_404(req);
return ESP_FAIL;
}
// 获取文件大小
fseek(file, 0, SEEK_END);
long fsize = ftell(file);
fseek(file, 0, SEEK_SET);
// 设置内容类型
if (strstr(filepath, ".html")) {
httpd_resp_set_type(req, "text/html");
} else if (strstr(filepath, ".css")) {
httpd_resp_set_type(req, "text/css");
} else if (strstr(filepath, ".js")) {
httpd_resp_set_type(req, "application/javascript");
} else if (strstr(filepath, ".png")) {
httpd_resp_set_type(req, "image/png");
} else if (strstr(filepath, ".jpg")) {
httpd_resp_set_type(req, "image/jpeg");
}
// 分配缓冲区
char *buffer = malloc(fsize);
if (buffer == NULL) {
fclose(file);
httpd_resp_send_500(req);
return ESP_FAIL;
}
// 读取文件内容
fread(buffer, fsize, 1, file);
fclose(file);
// 发送响应
httpd_resp_send(req, buffer, fsize);
// 释放缓冲区
free(buffer);
return ESP_OK;
}
2. 使用 SPIFFS 存储配置文件
#include "cJSON.h"
// 从 SPIFFS 读取 JSON 配置
bool read_config_from_spiffs(void)
{
FILE *f = fopen("/spiffs/config.json", "r");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开配置文件");
return false;
}
// 获取文件大小
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
// 分配缓冲区
char *buffer = malloc(fsize + 1);
if (buffer == NULL) {
ESP_LOGE(TAG, "内存分配失败");
fclose(f);
return false;
}
// 读取文件内容
fread(buffer, fsize, 1, f);
buffer[fsize] = 0;
fclose(f);
// 解析 JSON
cJSON *root = cJSON_Parse(buffer);
free(buffer);
if (root == NULL) {
ESP_LOGE(TAG, "JSON 解析失败");
return false;
}
// 读取配置项
cJSON *wifi_ssid = cJSON_GetObjectItem(root, "wifi_ssid");
cJSON *wifi_password = cJSON_GetObjectItem(root, "wifi_password");
if (cJSON_IsString(wifi_ssid) && cJSON_IsString(wifi_password)) {
ESP_LOGI(TAG, "WiFi SSID: %s", wifi_ssid->valuestring);
ESP_LOGI(TAG, "WiFi 密码: %s", wifi_password->valuestring);
// 这里可以保存配置到全局变量或应用到系统
}
cJSON_Delete(root);
return true;
}
// 保存配置到 SPIFFS
bool save_config_to_spiffs(const char *ssid, const char *password)
{
// 创建 JSON 对象
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "wifi_ssid", ssid);
cJSON_AddStringToObject(root, "wifi_password", password);
// 转换为字符串
char *json_str = cJSON_Print(root);
cJSON_Delete(root);
if (json_str == NULL) {
ESP_LOGE(TAG, "JSON 创建失败");
return false;
}
// 写入文件
FILE *f = fopen("/spiffs/config.json", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开配置文件进行写入");
free(json_str);
return false;
}
fprintf(f, "%s", json_str);
fclose(f);
free(json_str);
ESP_LOGI(TAG, "配置已保存到 SPIFFS");
return true;
}
常见问题
1. 挂载失败
如果 SPIFFS 挂载失败,可能的原因和解决方法:
-
分区表配置错误:
- 确认分区表中包含 SPIFFS 分区
- 检查分区大小是否足够
-
Flash 损坏:
- 设置
format_if_mount_failed = true重新格式化 - 使用
esptool.py erase_flash完全擦除 Flash
- 设置
-
内存不足:
- 减少
max_files参数 - 优化内存使用
- 减少
2. 文件操作失败
如果文件操作失败,可能的原因和解决方法:
-
路径错误:
- 确保路径以挂载点开头(如
/spiffs/) - 检查文件名大小写(SPIFFS 区分大小写)
- 确保路径以挂载点开头(如
-
文件系统已满:
- 删除不需要的文件
- 增加分区大小
-
文件句柄耗尽:
- 增加
max_files参数 - 确保正确关闭文件
- 增加
3. 性能问题
如果遇到性能问题,可能的原因和解决方法:
-
频繁小文件读写:
- 合并小文件操作
- 使用缓冲区减少 I/O 操作
-
大文件操作:
- 分块处理大文件
- 考虑使用 FATFS 替代 SPIFFS
性能优化
1. 缓冲区优化
void buffered_file_write_example(void)
{
FILE *f = fopen("/spiffs/data.bin", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件");
return;
}
// 设置文件缓冲区
char *buf = malloc(4096);
if (buf != NULL) {
setvbuf(f, buf, _IOFBF, 4096);
}
// 写入大量数据
for (int i = 0; i < 1000; i++) {
uint8_t data[16] = {0};
// 填充数据
for (int j = 0; j < 16; j++) {
data[j] = (i + j) % 256;
}
fwrite(data, 1, 16, f);
}
fclose(f);
free(buf);
}
2. 使用 mmap 映射文件
#include "esp_partition.h"
#include "esp_spi_flash.h"
void mmap_file_example(void)
{
// 获取 SPIFFS 分区
const esp_partition_t *partition = esp_partition_find_first(
ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, NULL);
if (partition == NULL) {
ESP_LOGE(TAG, "找不到 SPIFFS 分区");
return;
}
// 映射分区
spi_flash_mmap_handle_t handle;
const void *data;
esp_err_t err = spi_flash_mmap(partition->address, partition->size,
SPI_FLASH_MMAP_DATA, &data, &handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "映射 SPIFFS 分区失败");
return;
}
// 现在可以直接访问映射的内存
// 注意:这种方法只适用于只读访问
// 使用完毕后取消映射
spi_flash_munmap(handle);
}
总结
SPIFFS 是 ESP-IDF 中一个强大的文件系统组件,适合存储配置文件、网页、字体等静态数据。通过本教程,您应该已经掌握了:
- SPIFFS 的基本使用方法
- 如何在 CMakeLists.txt 中配置 SPIFFS
- 文件和目录操作
- 高级应用场景
- 常见问题的解决方法
- 性能优化技巧
在实际应用中,应根据需求选择合适的文件系统。如果需要存储大文件或需要频繁写入,可以考虑使用 FATFS;如果只需要存储少量静态数据,SPIFFS 是一个很好的选择。