杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD16N 系列芯片与 SDK 能力总览
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建指南
    • 烧录与固件升级
  • SDK 工程架构

    • SDK 目录结构与模块分层
    • 构建系统与批处理工具
    • BSP 板级支持包
  • mbox_flash 小音箱应用

    • 应用初始化与启动流程
    • 应用配置系统
    • 按键、UI 与用户交互
  • 音频子系统

    • 音频解码框架与调度
    • 音频格式解码器实现
    • MIDI 合成与播放
    • 音频编码与录音
    • EQ/DRC 与音效处理
    • DAC/ADC 音频接口与采样
  • 存储与文件系统

    • 媒体 IO 抽象层 MIO
    • 存储设备驱动
    • 文件系统支持
  • 平台系统库

    • 系统基础服务
    • CPU 平台与运行库
    • 固件升级与更新机制
    • 蓝牙与扩展连接接口
  • 电源与低功耗管理

    • 电源管理与低功耗设计
    • 锂电池充电管理
  • 硬件与文档参考

    • SDK 文档中心与版本发布记录
    • 芯片数据手册与硬件设计参考

文件系统支持

本页介绍 fw-AD16N GP-MCU SDK 中的文件系统支持层:从 VFS(虚拟文件系统)抽象、struct vfs_operations 操作表注册机制,到 FAT、nor_fs、sydf 等具体文件系统实现,以及挂载、打开、读写、扫描、格式化等完整 API 的使用方式与底层控制流。

Purpose and Scope

本页覆盖以下内容:

  • VFS 虚拟文件系统层的设计与实现(vfs.c / vfs.h),包括 fs_* 宏对 vfs_* 接口的映射。
  • 文件系统操作表 struct vfs_operations 的注册机制(REGISTER_VFS_OPERATIONS 宏与 .vfs_operations 段)。
  • 内置文件系统实现:FAT(vfs_fat.c)、nor_fs(NOR Flash 文件系统)、sydf 及其资源注册文件。
  • 挂载(mount)、打开(openbypath / openbyindex / openbyfile / openbyclust)、读写、seek、扫描(fscan / fselect)、目录与格式化(vfs_format)等核心接口。
  • 启动阶段与音频解码器中的实际使用流程。

以下主题属于其他目录页,不在本页展开:具体存储设备驱动(Flash / SD 卡底层读写)、VM 掉电保存机制、录音业务逻辑。本页聚焦"文件系统"这一抽象层本身。

概述

在 MCU 嵌入式场景中,不同存储介质(内部 NOR Flash、外部 SD 卡等)需要统一的文件读写能力:音乐播放器要枚举并打开歌曲文件,录音功能要按索引创建/追加文件,系统参数区要按路径读取 VM/EEPROM 数据。为此 SDK 实现了一层 VFS(Virtual File System) 抽象:上层代码只与 fs_* / vfs_* 接口打交道,由 VFS 依据"文件系统类型名"(如 "fat"、"norfs")在注册表中找到对应的操作实现并转发调用。

关键设计点:

  • 注册表驱动:每个文件系统通过 REGISTER_VFS_OPERATIONS 宏把自己的操作函数表放到链接脚本指定的 .vfs_operations 段中,vfs_mount 通过 list_for_each_vfs_operation 遍历该段逐个尝试挂载。新增文件系统无需改动 VFS 核心代码。
  • 句柄分层:struct imount 同时承担"文件系统句柄"(pfs)与"文件句柄"(pfile)两种角色,内部保存 ops 指针,实现多态分发。
  • 双模式编译:HAS_VFS_EN 开启时使用 VFS 完整实现;关闭时 fs_* 宏直接映射到 fat_* API,减小代码体积。
  • IOCTL 扩展:目录浏览、文件过滤、长文件名缓存、卷标、空间查询等高级能力统一通过 vfs_ioctl 的命令码扩展,避免操作表无限膨胀。

架构

flowchart TD
    subgraph sg_App["应用层 (Application)"]
        Startup["flash_init 启动流程"]
        Decoder["decoder_api / mp_io"]
        Player["播放器 / 录音 / 文件浏览"]
    end

    subgraph sg_API["VFS API 层 (fs_* 宏)"]
        VFS["vfs.c 核心分发"]
        IOCTL["vfs_ioctl 命令分发"]
    end

    subgraph sg_Ops["文件系统实现层 (.vfs_operations 段)"]
        FAT["fat (vfs_fat / ff_opr)"]
        NORFS["norfs (nor_fs)"]
        SYDF["sydf"]
    end

    subgraph sg_Dev["存储设备层"]
        Flash["NOR Flash 设备"]
        SD["SD 卡 / 外部存储"]
    end

    Startup --> VFS
    Decoder --> VFS
    Player --> VFS
    VFS --> IOCTL
    VFS --> FAT
    VFS --> NORFS
    VFS --> SYDF
    FAT --> SD
    NORFS --> Flash
    SYDF --> Flash

VFS 是唯一入口:应用层不直接调用 FAT 或 nor_fs 的实现函数,而是调用 fs_mount / fs_openbypath 等宏,由 vfs.c 遍历 .vfs_operations 段中的操作表完成分发。vfs_ioctl 则把目录、过滤、缓存等命令转发给具体文件系统的 ioctl 回调。

主要模块与实现

VFS 核心(vfs.c)

vfs.c 位于 sdk/apps/app/bsp/common/fs/vfs.c,是整个文件系统层的调度中枢。它通过 #pragma code_seg(".vfs.text") 等指令把代码与数据放入独立段,便于链接器布局与裁剪。

初始化:vfs_init() 遍历 .vfs_operations 段,逐个调用已注册文件系统的 init 回调:

void vfs_init(void)
{
    struct vfs_operations *ops;
    list_for_each_vfs_operation(ops) {
        if (NULL != ops->init) {
            log_info("%s ops_init!!!\n", ops->fs_type);
            ops->init();
        }
    }
}

Source: vfs.c

挂载分发:vfs_mount 先为句柄分配内存,然后遍历操作表:若调用方指定了 type(如 "fat"),只匹配同名字符串的文件系统;若 type 为 NULL,则跳过 "norfs"(避免误挂载内部录音文件系统)。某文件系统挂载成功后,其 ops 被记录到句柄中并返回 0;若挂载失败则调用其 close_fs 清理并继续尝试下一个:

u32 vfs_mount(void **ppvfs, void *device, void *type)
{
    if ((void *)NULL == *ppvfs) {
        *ppvfs = vfs_hdl_malloc();
        if ((void *)NULL == *ppvfs) {
            return E_NO_VFS;
        }
    }

    struct imount *pvfs = *ppvfs;
    struct vfs_operations *ops;
    list_for_each_vfs_operation(ops) {
        if (NULL != ops->mount) {
            if (NULL != type) {
                if (0 != strcmp(ops->fs_type, type)) {
                    continue;
                }
            } else {
                if (0 == strcmp(ops->fs_type, "norfs")) {
                    continue;
                }
            }
            u32 err = ops->mount(&(pvfs->pfs), device);
            if (0 == err) {
                pvfs->ops = ops;
                return 0;
            } else {
                if (NULL != ops->close_fs) {
                    ops->close_fs(&pvfs->pfs);
                }
            }
        }
    }
    *ppvfs = vfs_fhdl_free(*ppvfs);
    return E_NO_FS;
}

Source: vfs.c

打开文件分发:vfs_openbypath 同样先分配文件句柄,把文件系统句柄中的 ops 复制给文件句柄,再调用 ops->openbypath 完成实际打开;句柄非法时返回 E_VFS_HDL,缺少回调时返回 E_VFS_OPS:

u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)
{
    if ((void *)NULL == *ppvfile) {
        *ppvfile = vfs_hdl_malloc();
        if ((void *)NULL == *ppvfile) {
            return E_NO_VFS;
        }
    }

    u32 err;
    struct vfs_operations *ops;
    struct imount *p_vfs = pvfs;
    struct imount *p_vfile = *ppvfile;
    if ((void *)NULL == p_vfs) {
        err =  E_VFS_HDL;
        goto __vfs_openbypath;
    }
    p_vfile->ops = p_vfs->ops;
    ops = p_vfs->ops;

    if (NULL != ops->openbypath) {
        err = ops->openbypath(p_vfs->pfs, &p_vfile->pfile, path);
    } else {
        err = E_VFS_OPS;
    }
__vfs_openbypath:
    if (0 != err) {
        // ... 错误处理
    }
    return err;
}

Source: vfs.c

文件系统操作表(struct vfs_operations)

vfs.h 定义了所有文件系统必须实现的操作函数指针表。该表是 VFS 多态分发的核心契约:

struct vfs_operations {
    const char *fs_type;
    void (*init)(void);
    u32(*mount)(void **ppfs, void *p_device);
    u32(*openbypath)(void *pfs, void **ppfile, const char *path);
    u32(*openbyindex)(void *pfs, void **ppfile, u32 index, void *parm);
    u32(*openbyfile)(void *pcvfile, void **ppfile, void *ext_name);
    u32(*openbyclust)(void *pfs, void **ppfile, u32 clust, void *parm);
    u32(*createfile)(void *pfs, void **ppfile, u32 *pindex);
    u32(*read)(void *pfile, void *buff, u32 len);
    u32(*write)(void *pfile, void *buff, u32 len);
    u32(*seek)(void *pfile, u32 offset, u32 mode);
    u32(*close_fs)(void **ppfs);
    u32(*close_file)(void **ppfile);
    u32(*fdelete)(void *);
    int (*fget_attr)(void *, void *attr);
    int (*flen)(void *, u32 *parm);
    int (*ftell)(void *, u32 *parm);
    u32(*name)(void *, void *name, u32 len);
    int (*ioctl)(void *, int cmd, int arg);
    int (*fscan_interrupt)(void *, void *, const char *path, u8 max_deepth, int (*callback)(void));
    void (*fscan_release)(void *);
    int (*fsel)(void *, void *, int sel_mode, void **, int);
    int (*file_crc)(void *pfile);
    int (*format)(void **p_fs_hdl, void *device, u32 clust_size, u8 create_new);
};

Source: vfs.h

注意:fs_type 字符串(如 "fat"、"norfs")既是文件系统标识,也是 vfs_mount 中 type 参数匹配的依据。fscan_interrupt / fsel / format 等为可选能力,VFS 分发层不强制要求全部实现。

注册机制(REGISTER_VFS_OPERATIONS)

文件系统通过如下宏把自己的操作表放入 .vfs_operations 链接段:

#define REGISTER_VFS_OPERATIONS(ops) \
	const struct vfs_operations ops SEC(.vfs_operations)

Source: vfs.h

段首尾符号 vfs_ops_begin[] / vfs_ops_end[] 由链接脚本提供,list_for_each_vfs_operation 宏据此遍历所有已注册文件系统:

#define list_for_each_vfs_operation(ops) \
	for (ops=vfs_ops_begin; ops<vfs_ops_end; ops++)

Source: vfs.h

在 sdk/apps/app/bsp/common/fs/ 目录下,fat_resource.c、nor_fs_resource.c、sydf_resource.c 分别定义并注册对应文件系统的操作表;vfs_resource.c 提供 vfs_resource_init() 完成资源级初始化。这种"段注册"方式使编译期即可静态确定系统支持哪些文件系统,运行时无需动态注册,符合 MCU 裸机/轻量 RTOS 环境的约束。

FAT 文件系统(vfs_fat)

FAT 实现位于 sdk/apps/app/bsp/common/fs/vfs_fat.c,底层基于 fat/ff_opr.h(Petit-FatFs 风格的 FAT 操作)与 fat/tff.h。vfs_fat.h 暴露了 VFS 层的扩展接口,其中最重要的是格式化与目录扫描:

int vfs_format(void **ppvfs, const char *dev_name, const char *type, u32 clust_size, u8 create_new);
int vfs_fget_path(void *pvfile, struct vfscan *fscan, u8 *name, int len, u8 is_relative_path);

Source: vfs_fat.h

vfs_format 的语义值得注意:格式化之后 vfs 指针会被释放,需要重新 mount 才能获得新句柄;clust_size 填 0 表示沿用原簇大小,但 create_new == 1(新建文件系统)时簇大小不能为 0,且必须是 4KB/8KB/16KB/32KB/64KB 之类的对齐值。dev_name 形如 "sd0",type 一般为 "fat"。

目录扫描通过 struct vfscan 完成,配合 VFSCAN_RESET_INFO 缓存扫描结果,避免重复扫描同一设备:

typedef struct vfscan_reset_info {
    u16 file_total; // 当前设备文件总数
    u16 dir_total;  // 当前设备文件夹总数
    u8 active;      // 当前设备是否有效,无效需要扫描。
    u8 scan_over;   // 当前设备之前是否扫描过,没有需要扫描。
} VFSCAN_RESET_INFO;

Source: vfs_fat.h

nor_fs(NOR Flash 文件系统)

nor_fs 是为内部 NOR Flash 上"录音文件按索引存取"场景设计的轻量文件系统,定义于 sdk/apps/include_lib/fs/nor_fs/nor_fs.h。它以固定 magic 字标识文件系统,按扇区组织文件:

#define NORFS_MAGIC_NUM     0X11223344
#define REC_FILE_END 0xFE

//文件索引
typedef struct __RECF_INDEX_INFO {
    u32 index;  //文件索引号
    u16 sector; //文件所在扇区
} RECF_INDEX_INFO ;

#define FLASH_PAGE_SIZE 256
#define NORF_SECTOR_SIZE   FLASH_PAGE_SIZE

//文件系统句柄
typedef struct __RECFILESYSTEM {
    RECF_INDEX_INFO index;
    u16 total_file;
    u16 first_sector;
    u16 last_sector;
    u8 sector_bits;
    void *hdev;
} NORFS, RECFILESYSTEM, *PRECFILESYSTEM ;

//文件句柄
typedef struct __REC_FILE {
    RECF_INDEX_INFO index;
    RECFILESYSTEM *pfs;
    u32 len;
    u32 w_len;
    u32 rw_p;
    u16 sr;
    char name[16];
} NORFILE, REC_FILE;

Source: nor_fs.h

nor_fs 的 API 面向"按文件索引号"操作:norfs_mount 挂载、norfs_createfile 创建新文件(返回索引)、norfs_openbyindex 按索引打开、norfs_read / norfs_write / norfs_seek 读写定位、norfs_save_sr 保存扇区记录。文件名为固定 16 字节缓冲区。这与 FAT 的"路径 + 长文件名"模型完全不同,反映了两种文件系统服务于不同业务:nor_fs 用于录音分段存储,FAT 用于 SD 卡多媒体文件。

sydf 文件系统

syfdf_resource.c 与 sydfile.h 表明 SDK 还支持 sydf 文件系统。sydf 同样面向 Flash 存储场景,注册为 VFS 的一种 fs_type,通过同一套 struct vfs_operations 接入 VFS 层。由于本页源材料有限,其内部实现细节不做展开;从架构上看,它与 FAT、nor_fs 并列,通过段注册机制参与 vfs_mount 的类型匹配与挂载尝试。

fs_* API 宏映射

为了让应用代码不感知 VFS 是否存在,vfs.h 在 HAS_VFS_EN 开启时把 fs_* 全部映射为 vfs_*:

#define fs_resource_init()
#define fs_init               vfs_init
#define fs_type_name          vfs_type_name
#define fs_mount              vfs_mount
#define fs_openbypath         vfs_openbypath
#define fs_openbyindex        vfs_openbyindex
#define fs_openbyfile         vfs_openbyfile
#define fs_openbyclust        vfs_openbyclust
#define fs_createfile         vfs_createfile
#define fs_read               vfs_read
#define fs_write              vfs_write
#define fs_file_close         vfs_file_close
#define fs_fs_close           vfs_fs_close
#define fs_file_name          vfs_file_name
#define fs_get_attrs          vfs_get_attrs
#define fs_get_fsize          vfs_get_fsize
#define fs_ftell              vfs_ftell
#define fs_file_delete        vfs_file_delete
#define fs_fscan_new          vfs_fscan_new
#define fs_fscan              vfs_fscan
#define fs_fscan_release      vfs_fscan_release
#define fs_select             vfs_select
#define fs_ioctl              vfs_ioctl
#define fs_mk_dir             vfs_mk_dir
#define fs_get_encfolder_info vfs_get_encfolder_info
#define fs_seek               vfs_seek

Source: vfs.h

当 HAS_VFS_EN 未定义(裁剪场景)时,fs_* 宏直接映射到 fat_* 函数(如 fs_mount → fat_monut_api、fs_type_name 固定返回 "fat"),从而在关闭 VFS 层的情况下依然可以使用 FAT 文件系统,见 vfs.h。

核心流程

启动阶段:挂载与参数区读取(flash_init.c)

系统启动时,sdk/apps/app/bsp/start/flash_init.c 通过 VFS 读取系统参数区。流程是"挂载 → 打开 → 读取 → 关闭"的典型四步,且会执行两次(一次访问 VM 区,一次访问 EEPROM 区):

err = vfs_mount(&pvfs, (void *)NULL, (void *) NULL);
ASSERT(!err, "fii vfs mount : 0x%x\n", err)

err = vfs_openbypath(pvfs, &pvfile, "/app_area_head/VM");
ASSERT(!err, "fii vfs openbypath : 0x%x\n", err)
// ... 读取 VM 数据 ...
vfs_file_close(&pvfile);
vfs_fs_close(&pvfs);

Source: flash_init.c

sequenceDiagram
    participant Boot as 系统启动
    participant VFS as vfs.c 分发层
    participant Ops as vfs_operations 表
    participant FAT as fat 实现
    participant Flash as Flash/SD 设备

    Boot->>VFS: vfs_mount(&pvfs, NULL, NULL)
    VFS->>Ops: 遍历 .vfs_operations 段,匹配 fs_type
    VFS->>FAT: ops->mount(&pfs, device)
    FAT->>Flash: 读取设备/分区信息
    FAT-->>VFS: 返回 0(挂载成功)
    VFS-->>Boot: 记录 ops 到 imount,返回 0
    Boot->>VFS: vfs_openbypath(pvfs, &pvfile, "/app_area_head/VM")
    VFS->>FAT: ops->openbypath(pfs, &pfile, path)
    FAT-->>VFS: 返回 0(打开成功)
    VFS-->>Boot: 返回文件句柄 pvfile
    Boot->>VFS: vfs_read / vfs_ioctl 读取参数
    Boot->>VFS: vfs_file_close(&pvfile)
    Boot->>VFS: vfs_fs_close(&pvfs)

挂载参数 type = NULL 时,vfs_mount 会自动跳过 "norfs",确保默认挂载到 FAT(或 sydf)文件系统;随后用虚拟路径 /app_area_head/VM、/app_area_head/EEPROM 打开分区文件,说明 FAT 实现支持把 Flash 分区映射为路径。第二次挂载流程(第 107-115 行)与第一次完全相同,只是目标路径换为 /app_area_head/EEPROM,见 flash_init.c。

播放流程:解码器中的文件读写(decoder / mp_io)

音频解码器通过 fs_* 宏访问文件。decoder_api.c 使用 fs_openbyfile 依据已打开的媒体文件句柄打开同名 "mio" 扩展文件(媒体信息文件):

if (pfile) {
    mio_res = fs_openbyfile(pfile, &mio_pfile, "mio");
}

Source: decoder_api.c

底层流式读取在 mp_io.c 中实现:先 fs_seek 定位,再 fs_read 读取并返回实际读到的字节数。注释明确 addr 是相对文件起始位置的偏移:

fs_seek(obj->p_file, addr, SEEK_SET);        //addr为相对文件起始位置的偏移,len为多少个byte
rlen = fs_read(obj->p_file, buf, len);
return rlen;

Source: mp_io.c

flowchart LR
    A["解码器请求数据"] --> B["fs_seek(pfile, addr, SEEK_SET)"]
    B --> C["fs_read(pfile, buf, len)"]
    C --> D{"VFS 分发到 fat 实现"}
    D --> E["返回实际读取字节数 rlen"]
    E --> F["解码器继续解码"]

该流程说明:VFS 把"路径/索引打开"与"流式读写"解耦——打开阶段只发生一次(按路径或索引),而播放过程中的大量小粒度读操作直接走 read/seek 回调,减少路径解析开销,这是音频实时播放场景的关键性能设计。

使用示例

示例 1:挂载文件系统并打开分区文件(启动代码)

err = vfs_mount(&pvfs, (void *)NULL, (void *) NULL);
ASSERT(!err, "fii vfs mount : 0x%x\n", err)

err = vfs_openbypath(pvfs, &pvfile, "/app_area_head/VM");
ASSERT(!err, "fii vfs openbypath : 0x%x\n", err)

挂载时 type 传 NULL 表示"自动选择",vfs_mount 会跳过 norfs 依次尝试其余文件系统;打开路径采用 /分区名/文件 的虚拟路径语法。使用完毕后必须配对调用 vfs_file_close 与 vfs_fs_close 释放句柄。

Source: flash_init.c

示例 2:按已打开文件派生同名扩展文件(解码器)

if (pfile) {
    mio_res = fs_openbyfile(pfile, &mio_pfile, "mio");
}

fs_openbyfile 接收一个已打开的文件句柄和扩展名,用于打开与媒体文件同名的辅助文件(如歌词、MIO 信息文件),避免再次按路径解析。

Source: decoder_api.c

示例 3:流式读取(解码器底层 IO)

fs_seek(obj->p_file, addr, SEEK_SET);        //addr为相对文件起始位置的偏移,len为多少个byte
rlen = fs_read(obj->p_file, buf, len);
return rlen;

SEEK_SET 从文件头开始定位,fs_read 返回实际读取长度,调用方(解码器)据此判断文件是否结束。

Source: mp_io.c

示例 4:注册一个新的文件系统操作表

#define REGISTER_VFS_OPERATIONS(ops) \
	const struct vfs_operations ops SEC(.vfs_operations)

自定义文件系统只需定义 struct vfs_operations 实例(fs_type 填写唯一名字,如 "myfs"),并用此宏放入 .vfs_operations 段,即可被 vfs_init / vfs_mount 自动发现。

Source: vfs.h

配置选项

选项类型默认值说明
HAS_VFS_EN编译宏0/1(由工程配置)是否启用 VFS 层;关闭时 fs_* 宏直接映射到 fat_* API
vfs_mount 的 type 参数void *NULL指定文件系统类型名("fat"/"norfs"/"sydf");NULL 时跳过 norfs 自动选择
vfs_format 的 clust_sizeu320(沿用原簇)新建文件系统时须为非 0 的对齐值(4K/8K/16K/32K/64K)
vfs_format 的 create_newu800=维持原文件系统类型;1=新建文件系统(须配合非 0 簇大小)
FS_IOCTL_SET_LFN_BUFioctl 命令—设置长文件名缓冲(512 字节)
FS_IOCTL_SET_LDN_BUFioctl 命令—设置长目录名缓冲(512 字节)
VFS_FILE_NAME_LEN常量16短文件名缓冲区长度,见 vfs.h
FLASH_PAGE_SIZE常量256nor_fs 的扇区/页大小,见 nor_fs.h

文件属性与定位模式常量

常量值含义
SEEK_SET / SEEK_CUR / SEEK_END0 / 1 / 2seek 定位基准:文件头 / 当前位置 / 文件尾
F_ATTR_RO / F_ATTR_ARC / F_ATTR_DIR / F_ATTR_VOL0x01 / 0x02 / 0x04 / 0x08文件属性:只读 / 归档 / 目录 / 卷标

Source: vfs.h

vfs_ioctl 命令码(FS_IOCTL_*)

命令用途
FS_IOCTL_GET_FILE_NUM获取文件数量
FS_IOCTL_FILE_CHECK文件存在性检查
FS_IOCTL_FREE_CACHE释放文件系统缓存
FS_IOCTL_SET_NAME_FILTER设置文件过滤规则
FS_IOCTL_GET_FOLDER_INFO_3获取文件夹序号与文件数
FS_IOCTL_SET_LFN_BUF / FS_IOCTL_SET_LDN_BUF设置长文件名/目录名缓冲(512B)
FS_IOCTL_SET_EXT_TYPE设置后缀类型过滤
FS_IOCTL_OPEN_DIR / ENTER_DIR / EXIT_DIR / GET_DIR_INFO目录浏览
FS_IOCTL_GETFILE_BYNAME_INDIR由歌曲名获取歌词文件
FS_IOCTL_GET_DISP_INFO长文件名显示信息获取
FS_IOCTL_MK_DIR创建文件夹
FS_IOCTL_GET_ENCFOLDER_INFO获取录音文件夹信息
FS_IOCTL_SET_VOL设置卷标
FS_IOCTL_DIR_FILE_TOTAL / FILE_TOTAL / FS_TOTAL目录/文件/文件系统总数统计
FS_IOCTL_FILE_ATTR / FILE_SYNC / FILE_INDEX / FS_INDEX属性/同步/索引查询
FS_IOCTL_RESET_VFSCAN重置扫描缓存
FS_IOCTL_GET_PARTITION_INFO / GET_FREE_SPACE分区信息(簇大小/容量)与剩余空间
FS_IOCTL_GET_PATH获取相对/绝对路径

Source: vfs.h

API 参考

VFS 核心接口(vfs.h)

函数作用
void vfs_init(void)遍历操作表,调用各文件系统 init 回调
u32 vfs_mount(void **ppvfs, void *device, void *type)挂载文件系统;成功返回 0,失败返回 E_NO_VFS / E_NO_FS
u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)按路径打开文件(如 /app_area_head/VM)
u32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index, void *parm)按索引打开文件(录音/文件列表场景)
u32 vfs_openbyfile(void *pcvfile, void **ppvfile, void *ext_name)由已打开文件派生同名扩展文件
u32 vfs_openbyclust(void *pvfs, void **ppvfile, u32 clust, void *parm)按簇号打开文件
u32 vfs_createfile(void *pvfs, void **ppvfile, u32 *pindex)创建文件并返回索引
u32 vfs_read(void *pvfile, void *buf, u32 len) / vfs_write读写文件,返回实际字节数
u32 vfs_seek(void *pvfile, u32 offset, u32 mode)定位(SEEK_SET/CUR/END)
u32 vfs_file_close(void **ppvfile) / vfs_fs_close(void **ppvfs)关闭文件/文件系统句柄
u32 vfs_file_name(void *pvfile, void *name, u32 len)获取文件名
int vfs_get_attrs(void *pvfile, void *pvfs_attr)获取 struct vfs_attr(属性/大小/簇地址)
int vfs_get_fsize(void *pvfile, void *parm) / vfs_ftell获取文件大小 / 当前偏移
u32 vfs_file_delete(void *pvfile)删除文件
int vfs_ioctl(void *pvfile, int cmd, int arg)扩展命令分发(见上方命令码表)
int vfs_file_crc(void *pfile)计算文件 CRC

扫描与格式化扩展接口(vfs_fat.h)

  • struct vfscan *vfs_fscan(void *pvfs, const char *path, const char *arg, u8 max_deepth, int (*callback)(void)) — 扫描目录,arg 为扩展名过滤,max_deepth 限制递归深度。
  • struct vfscan *vfs_fscan_new(..., struct vfscan *fsn, struct vfscan_reset_info *info) — 可复用扫描句柄并携带复位缓存信息。
  • int vfs_select(void *pvfs, void **ppvfile, struct vfscan *fs, int sel_mode, int arg) — 在扫描结果中选择文件并打开。
  • int vfs_mk_dir(void *pvfs, char *folder, u8 mode) — 创建目录。
  • int vfs_format(void **ppvfs, const char *dev_name, const char *type, u32 clust_size, u8 create_new) — 格式化设备;成功后 vfs 句柄被释放,须重新 mount。

Source: vfs_fat.h

nor_fs 接口(nor_fs.h)

  • void norfs_init(u32 sector_start, u32 sector_end, u32 sector_bit) — 初始化文件系统在 Flash 中的扇区范围。
  • u32 norfs_mount(RECFILESYSTEM **ppfs, void *p_device) — 挂载(校验 NORFS_MAGIC_NUM)。
  • u32 norfs_createfile(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 *pindex) — 创建录音文件,返回索引号。
  • u32 norfs_openbyindex(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 index) — 按索引打开。
  • u16 norfs_read(REC_FILE *pfile, u8 *buff, u16 btr) / u16 norfs_write(REC_FILE *pfile, u8 *buff, u16 btw) — 读写,单位字节。
  • u32 norfs_seek(REC_FILE *pfile, u32 offsize, u32 type) — 定位。
  • u32 norfs_filelen(REC_FILE *pfile, u32 *plen) — 获取文件长度。
  • int norfs_ioctl(REC_FILE *pfile, int cmd, int arg) / u32 norfs_name(REC_FILE *pfile, char *name, u32 len) — 控制命令与文件名。

Source: nor_fs.h

故障模式、边界情况与并发

错误码与句柄生命周期

VFS 层定义了一组错误码,vfs_mount 与 vfs_openbypath 均通过返回值上报:

错误码触发场景
E_NO_VFS句柄内存分配失败(vfs_hdl_malloc 返回 NULL)
E_NO_FS遍历所有已注册文件系统均挂载失败(此时句柄已被 vfs_fhdl_free 释放)
E_VFS_HDL传入的文件系统句柄为 NULL
E_VFS_OPS文件系统操作表中缺少对应回调(如未实现 openbypath)

关键行为:vfs_mount 中某个文件系统挂载失败时,会先调用其 close_fs 做资源清理,再尝试下一个;全部失败后释放并置空句柄。因此调用方不应在失败后继续使用 pvfs。vfs_format 成功返回后 vfs 指针也会被释放,必须重新 mount——这是文档注释中明确强调的约束(见 vfs_fat.h)。

边界情况

  • type 为 NULL 时跳过 norfs:vfs_mount 默认挂载行为刻意排除 "norfs",避免把内部录音文件系统误当主文件系统挂载。若业务需要显式挂载 norfs,必须传入 "norfs" 类型名。
  • 簇大小约束:vfs_format 在 create_new == 1 时若 clust_size == 0 会失败;格式化为既有文件系统(create_new == 0)时 clust_size 可为 0 沿用原值。
  • 长文件名缓冲:LFN/LDN 缓冲通过 FS_IOCTL_SET_LFN_BUF / FS_IOCTL_SET_LDN_BUF 显式设置(各 512 字节),未设置时长文件名/长目录名显示(FS_IOCTL_GET_DISP_INFO)可能截断。
  • 扫描缓存失效:VFSCAN_RESET_INFO.active / scan_over 标志用于避免重复扫描;设备插拔或文件变更后须通过 FS_IOCTL_RESET_VFSCAN 或重新构造 vfscan_reset_info 使缓存失效,否则 vfs_select 可能返回陈旧结果。
  • nor_fs 容量上限:RECFILESYSTEM 中 total_file、first_sector、last_sector 均为 u16,文件总数与扇区寻址受 16 位范围限制;REC_FILE.name[16] 固定短文件名。

并发与可重入性

SDK 为 MCU 裸机/轻量 RTOS 环境设计,VFS 层不提供内部互斥锁:vfs_mount / vfs_openbypath 中的 vfs_hdl_malloc 属于共享资源分配,若多个任务并发挂载或打开文件,需要业务层自行加锁保护。文件句柄 struct imount 保存各自的 ops 与 pfile,同一文件系统句柄可派生出多个文件句柄,但同一文件句柄的 read/write/seek 操作不可并发(内部游标 rw_p、w_len 等状态共享)。音频解码链路(mp_io.c)是单任务顺序读,天然满足该约束。

性能与运维

  • 打开一次、流式读取:播放场景只按路径/索引打开一次文件,随后反复 fs_seek + fs_read 小粒度读取;VFS 分发只做一次 ops 解析,热路径开销可控。
  • 扫描缓存:vfscan + VFSCAN_RESET_INFO 缓存设备文件/目录总数,避免每次浏览都全盘扫描;配合 FS_IOCTL_FREE_CACHE 可在内存紧张时释放缓存。
  • 段布局优化:vfs.c 通过 #pragma code_seg(".vfs.text") 等指令将 VFS 代码/数据集中到专用段,便于链接器按需裁剪(不启用 VFS 时整段剔除),同时改善指令缓存局部性。
  • 双模式裁剪:HAS_VFS_EN = 0 时直接使用 fat_* API,省去 vfs_hdl_malloc 的句柄开销与 ops 遍历,适合对 RAM/Flash 极其敏感的配置。

扩展点

  1. 新增文件系统:定义 struct vfs_operations 实例,fs_type 使用唯一字符串,用 REGISTER_VFS_OPERATIONS 放入 .vfs_operations 段;vfs_init 自动调用其 init,vfs_mount 按其 mount 尝试挂载。无需修改 VFS 核心代码。
  2. 类型化挂载选择:调用 vfs_mount(pvfs, device, "myfs") 即可精确指定文件系统,多个文件系统(FAT + nor_fs + sydf)可共存于同一镜像。
  3. ioctl 命令扩展:文件过滤(FS_IOCTL_SET_NAME_FILTER)、后缀过滤(FS_IOCTL_SET_EXT_TYPE)、录音文件夹信息(FS_IOCTL_GET_ENCFOLDER_INFO)等业务能力通过命令码接入,新增命令只影响具体文件系统实现,不破坏 VFS 契约。
  4. 格式化策略:vfs_format 的 create_new 参数允许在"保留原文件系统"与"强制新建"之间选择,可在此基础上封装上层"出厂格式化/用户格式化"策略。

相关链接

  • vfs.h(VFS API 与 fs_* 宏定义)
  • vfs.c(VFS 核心分发实现)
  • vfs_fat.h(FAT 扫描/格式化扩展接口)
  • vfs_fat.c(FAT 文件系统实现)
  • nor_fs.h(NOR Flash 录音文件系统)
  • flash_init.c(启动挂载流程)
  • decoder_api.c(解码器文件打开)
  • mp_io.c(解码器底层流式读写)
  • FAT 底层协议:见 sdk/apps/include_lib/fs/fat/ 下的 tff.h、ff_opr.h、fat_resource.h
  • 存储设备驱动与 VM 掉电保护:参见同目录下"存储设备"相关页面
Prev
存储设备驱动