上一章实验中已经成功驱动 SD 卡,并可对 SD 卡进行读写操作,但读写 SD 卡时都是直接读出或写入二进制数据,这样使用起来显得十分不方便,因此本章将介绍 SPIFFS,SPIFFS是一个用于 SPI NOR flash 设备的嵌入式文件系统,支持磨损均衡以及文件系统一致性检查等功能。通过本章的学习,读者将学习到 SPIFFS 的基本使用。

SPIFFS 介绍

SPIFFS 是一个用于嵌入式目标上的 SPI NOR flash 设备的文件系统,并且有如下特点:
⚫ 小目标,没有堆的少量 RAM
⚫ 只有大范围的数据块才能被擦除
⚫ 擦除会将块中的所有位重置为 1
⚫ 写操作将 1 置 0
⚫ 0 只能被擦除成 1
⚫ 磨损均衡
以上几点是 SPIFFS 的特点,下面则说明了 SPIFFS 具体能做些什么:
⚫ 专门为低 ram 使用而设计
⚫ 使用静态大小的 ram 缓冲区,与文件的数量无关
⚫ 类可移植操作系统接口:打开、关闭、读、写、查找、统计等
⚫ 它可以在任何 NOR 闪存上运行,不仅是 SPI 闪存,理论上也可以在微处理器的嵌入式闪存
上运行
⚫ 多个 spiffs 配置可以在相同的目标上运行—甚至可以在相同的 SPI 闪存设备上运行
⚫ 实现静态磨损调平(也就是 flash 的寿命维护)
⚫ 内置文件系统一致性检查
⚫ 高度可配置的

SPIFFS 函数解析

SPIFFS 涉及到的文件并不算多,主要调用到了 C 库的函数,关于 C 库的函数我们在这里就
不过多介绍了,主要介绍一下调用到 ESP32 IDF 库中的函数。
注册装载 SPIFFS
该函数使用给定的路径前缀将 SPIFFS 注册并装载到 VFS,其函数原型如下所示:

esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf);

在这里插入图片描述
该函数使用 esp_vfs_spiffs_conf_t 类型的结构体变量传入,该结构体的定义如下所示:
在这里插入图片描述
完成上述结构体参数配置之后,可以将结构传递给esp_vfs_spiffs_register 函数,用以实例化SPIFFS。
获取 SPIFFS 的信息
该函数用于获取 SPIFFS 的信息,其函数原型如下所示:

esp_err_t esp_spiffs_info(const char* partition_label,size_t *total_bytes,size_t *used_bytes);

在这里插入图片描述
注销和卸载 SPIFFS
该函数从 VFS 注销和卸载 SPIFFS,其函数原型如下所示:

esp_err_t esp_vfs_spiffs_unregister(const char* partition_label);

partitions_example.csv

# Name,   Type, SubType, Offset,  Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs,      data, nvs,     0x9000,  0x6000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 1M,
storage,  data, spiffs,  ,        0xF0000,

main.c

/* SPIFFS filesystem example.
   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_spiffs.h"

static const char *TAG = "example";

void app_main(void)
{
    ESP_LOGI(TAG, "Initializing SPIFFS");

    esp_vfs_spiffs_conf_t conf = {
      .base_path = "/spiffs",   //可以认为挂着点,后续使用C库函数fopen("/spiffs/...")
      .partition_label = NULL,  //指定spiffs分区,如果为NULL,则默认为分区表中第一个spiffs类型的分区
      .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, "Failed to mount or format filesystem");
        } else if (ret == ESP_ERR_NOT_FOUND) {
            ESP_LOGE(TAG, "Failed to find SPIFFS partition");
        } else {
            ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
        }
        return;
    }

    //执行SPIFFS文件系统检查
    ESP_LOGI(TAG, "Performing SPIFFS_check().");
    ret = esp_spiffs_check(conf.partition_label);//操作spiffs文件系统器件断电,可能会导致 SPIFFS 损坏,可通过esp_spiffs_check恢复
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "SPIFFS_check() failed (%s)", esp_err_to_name(ret));
        return;
    } else {
        ESP_LOGI(TAG, "SPIFFS_check() successful");
    }

    //获取SPIFFS可用区域大小
    size_t total = 0, used = 0;
    ret = esp_spiffs_info(conf.partition_label, &total, &used);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s). Formatting...", esp_err_to_name(ret));
        esp_spiffs_format(conf.partition_label);
        return;
    } else {
        ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
    }

    //可用空间异常,执行SPIFFS检查
    if (used > total) {
        ESP_LOGW(TAG, "Number of used bytes cannot be larger than total. Performing SPIFFS_check().");
        ret = esp_spiffs_check(conf.partition_label);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "SPIFFS_check() failed (%s)", esp_err_to_name(ret));
            return;
        } else {
            ESP_LOGI(TAG, "SPIFFS_check() successful");
        }
    }

    //结合VFS,可以使用标准C库函数进行文件读写
    ESP_LOGI(TAG, "Opening file");
    FILE* f = fopen("/spiffs/hello.txt", "w");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    fprintf(f, "Hello World!\n");
    fclose(f);
    ESP_LOGI(TAG, "File written");

    //检查/spiffs/foo.txt这个文件是否存在,如果存在删除它
    struct stat st;
    if (stat("/spiffs/foo.txt", &st) == 0) {
        // 删除/spiffs/foo.txt文件
        unlink("/spiffs/foo.txt");
    }
    //重命名文件
    ESP_LOGI(TAG, "Renaming file");
    if (rename("/spiffs/hello.txt", "/spiffs/foo.txt") != 0) {
        ESP_LOGE(TAG, "Rename failed");
        return;
    }

    //打开foo文件读取一行
    ESP_LOGI(TAG, "Reading file");
    f = fopen("/spiffs/foo.txt", "r");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for reading");
        return;
    }
    char line[64];
    fgets(line, sizeof(line), f);
    fclose(f);
    // strip newline
    char* pos = strchr(line, '\n');
    if (pos) {
        *pos = '\0';
    }
    ESP_LOGI(TAG, "Read from file: '%s'", line);

    //测试完成,卸载
    esp_vfs_spiffs_unregister(conf.partition_label);
    ESP_LOGI(TAG, "SPIFFS unmounted");
}

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐