杰理 SDK 文档中心
首页
首页
  • 入门指南

    • SDK 概述与芯片平台
    • 环境搭建与开发工具链
    • 编译、烧录与快速开始
  • 应用层开发

    • 语音玩具应用 voice_toy
    • 扩音器应用 voice_enhanced
    • 语音功能状态机 voice_func
    • 应用公共框架与配置
  • 音频子系统

    • 音频解码器与 MIDI 播放
    • 音频编码与录音
    • 音效算法(ANS、变调、变声、混响)
    • 音频输出、功放与硬件重采样
  • 存储与文件系统

    • 文件系统层(FAT、NOR_FS、SYDF 等)
    • 存储设备与设备管理
    • 参数存储 VM 与保留区
  • 系统机制

    • 消息与事件机制
    • 电源管理与低功耗
    • 固件升级机制
    • 外设驱动(按键、红外、SPI、USB)
    • 实时时钟与定时器
  • 构建系统与工具

    • 构建系统(Makefile 与 Code::Blocks)
    • 编译后处理与语音资源打包
  • 硬件平台与文档

    • 芯片平台与启动流程
    • 硬件文档、规格书与原理图

文件系统层(FAT、NOR_FS、SYDF 等)

AD24N SDK 的文件系统层以 VFS(虚拟文件系统)为核心:通过 struct vfs_operations 函数指针表将 FAT、NOR_FS、free_fs 等具体文件系统统一挂载到同一套 vfs_* 接口之下,上层应用无需关心底层介质与格式差异;SYDF 则是独立于 VFS 之外的 boot/uboot 级文件系统,用于固件启动阶段的参数与文件读取。

Purpose and Scope

本页面系统性地介绍 AD24N SDK 中与"文件系统"相关的完整实现,涵盖:

  • VFS 虚拟文件系统层:sdk/app/bsp/common/fs/vfs.c 与 sdk/include_lib/fs/vfs.h 定义的分发表、挂载/打开/读写分发机制、句柄分配与错误码约定。
  • FAT 文件系统:sdk/include_lib/fs/fat/ 下的 Tiny-FAT 移植(tff.h)、句柄资源管理(fat_resource.h)以及 sdk/app/bsp/common/fs/vfs_fat.c 提供的封装辅助函数。
  • NOR_FS 简易文件系统:sdk/include_lib/fs/nor_fs/nor_fs.h 定义的面向 NOR Flash 录音/文本存储的扇区式文件系统。
  • free_fs:sdk/app/bsp/common/fs/free_fs/ 下的简易文件系统。
  • SYDF:sdk/include_lib/common/boot.h 中的 USE_SYDFILE_NEW 开关及其在 my_malloc.h 中对应的内存池(MM_SYDFS / MM_SYDFF),属于 boot/uboot 级文件系统。

以下内容不在本页范围,留给对应的 catalog 页面:底层 Flash/SD 设备驱动、USB 协议栈、以及依赖文件系统的具体业务模块(如 sdk/app/src/voice_func/toy_record/enc_in_norfs.c 的录音编码流程)。

Overview

AD24N 是杰理(Jieli)面向音频/玩具/物联网市场的 MCU SDK。音频播放、录音、固件升级等业务都依赖文件系统读写,但不同介质的文件格式差异很大:

  • FAT(FAT12/16/32):用于 SD 卡、U 盘等可移动介质,便于与 PC 交换文件;
  • NOR_FS:一种极简的扇区式文件系统,专门为 NOR Flash 上的录音文件设计,占用资源极小;
  • SYDF:boot 阶段使用的文件系统,用于存放固件(uboot/应用)与 VM 参数;
  • free_fs:另一个轻量级实现,挂在 VFS 之下。

为了让上层业务用同一套 API 操作所有这些介质,SDK 采用面向对象的分发表模式(dispatch table):每个文件系统实现一个 struct vfs_operations 结构体,通过 REGISTER_VFS_OPERATIONS(ops) 宏放入链接脚本指定的 .vfs_operations 段;vfs.c 在运行时遍历该段,按 fs_type 字符串匹配并调用对应实现。这就是整个文件系统层的核心设计意图:把"介质差异"封装在 VFS 之下,上层只面对统一的句柄与函数签名。

关键概念:

概念说明
struct vfs_operations文件系统实现必须填写的函数指针表(约 20 个操作)
struct imountVFS 句柄,ops 指向实现表,pfs/pfile 指向具体文件系统对象
REGISTER_VFS_OPERATIONS把实现表放入 .vfs_operations 链接段的宏
vfs_ops_begin[] / vfs_ops_end[]链接器生成的段边界符号,用于遍历所有注册的实现
fs_type实现表的字符串标识,如 "norfs"、"freefs",mount 时按此匹配
FS_IOCTL_*通过 vfs_ioctl 透传给具体实现的扩展命令集

Architecture

flowchart TD
    subgraph sg_App["应用层"]
        App["业务模块(播放/录音/升级)"]
    end

    subgraph sg_VFS["VFS 层 vfs.c / vfs.h"]
        VFS["vfs_init / vfs_mount / vfs_openbypath<br/>vfs_read / vfs_write / vfs_seek"]
        Ops["struct vfs_operations 分发表"]
        HDL["struct imount 句柄<br/>vfs_hdl_malloc / vfs_fhdl_free"]
        IOCTL["vfs_ioctl + FS_IOCTL_* 命令集"]
    end

    subgraph sg_Reg["已注册文件系统 (.vfs_operations 段)"]
        FAT["FAT<br/>tff.h / fat_resource / vfs_fat.c"]
        NORFS["NOR_FS<br/>nor_fs.h"]
        FREEFS["free_fs<br/>free_fs.c"]
    end

    subgraph sg_Dev["底层介质"]
        SD["SD / U 盘(块设备)"]
        NOR["NOR Flash(扇区设备)"]
    end

    App -->|"vfs_* 统一接口"| VFS
    VFS --> Ops
    VFS --> HDL
    VFS --> IOCTL
    Ops -->|"ops->mount / openbypath / read..."| FAT
    Ops --> NORFS
    Ops --> FREEFS
    FAT --> SD
    NORFS --> NOR

架构说明:

  • VFS 层是唯一的对外入口。vfs_mount 分配 struct imount 句柄并遍历 .vfs_operations 段找到匹配的实现;vfs_openbypath/vfs_read/vfs_write 等函数则从句柄取出 ops 指针,转发给具体实现(见 vfs.c 的 vfs_init 与 vfs.c 的 vfs_mount)。
  • 分发表(struct vfs_operations)是整个层的"接口契约",定义在 vfs.h。每个文件系统只需实现自己关心的字段,未实现的置 NULL,VFS 层会返回 E_VFS_OPS。
  • 挂载策略:当调用方不指定 type 时,vfs_mount 会跳过 "norfs" 与 "freefs",即默认优先尝试 FAT(见 vfs.c)——这是因为 SD/U 盘场景最常用,且 FAT 挂载失败后才会回退到简易系统。
  • SYDF 不经过 VFS:它属于 boot/uboot 级实现,通过 boot.h 的 USE_SYDFILE_NEW 宏控制数据结构布局,并通过 my_malloc.h 的 MM_SYDFS/MM_SYDFF 内存池与其他文件系统隔离(见 boot.h 与 my_malloc.h)。

VFS 层实现详解

分发表:struct vfs_operations

struct vfs_operations 是文件系统层的核心契约,定义在 vfs.h:

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);
    u32(*openbyfile)(void *pcvfile, void **ppfile, void *ext_name);
    u32(*openbyclust)(void *pfs, void **ppfile, u32 clust);
    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)(struct vfscan *, void *, const char *path, u8 max_deepth, int (*callback)(void));
    void (*fscan_release)(struct vfscan *);
    int (*fsel)(struct vfscan *, void *, int sel_mode, void **, int);
    int (*file_crc)(void *pfile);
};

设计意图:这是一个典型的策略模式。fs_type 是运行时匹配的"名字",其余都是可选的函数指针——某文件系统若不支持某个操作(如 NOR_FS 不支持目录),对应指针置 NULL,VFS 层返回 E_VFS_OPS 即可。文件/目录的通用属性通过 struct vfs_attr(attr/fsize/sclust)传递,文件属性位定义在 vfs.h:F_ATTR_RO(只读)、F_ATTR_ARC(归档)、F_ATTR_DIR(目录)、F_ATTR_VOL(卷标)。

注册机制:REGISTER_VFS_OPERATIONS

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

该宏把实现表放入名为 .vfs_operations 的链接段(见 vfs.h)。链接脚本会生成段边界符号 vfs_ops_begin / vfs_ops_end,vfs.c 用下面的宏遍历所有已注册实现:

extern struct vfs_operations vfs_ops_begin[];
extern struct vfs_operations vfs_ops_end[];

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

(见 vfs.c)这种"编译期登记 + 链接期收集"的方式在 MCU 上非常常见:不需要动态注册表,也不占额外 RAM,新增一个文件系统只需实现 struct vfs_operations 并在某处调用 REGISTER_VFS_OPERATIONS。

vfs_init() 在系统启动时遍历该段,逐个调用非 NULL 的 init()(见 vfs.c)。

挂载流程:vfs_mount

vfs_mount 是文件系统层最重要的入口,其完整逻辑见 vfs.c:

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;
                }
                if (0 == strcmp(ops->fs_type, "freefs")) {
                    continue;
                }
            }
            if (0 == ops->mount(&(pvfs->pfs), device)) {
                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;
}

关键点:

  1. 句柄复用:若 *ppvfs 为 NULL 则先 vfs_hdl_malloc() 分配 struct imount,失败返回 E_NO_VFS;
  2. 按名字匹配:调用方传入 type 时用 strcmp(ops->fs_type, type) 精确匹配;不传 type 时显式跳过 norfs 与 freefs,优先尝试 FAT——这保证了"插上 SD 卡自动挂 FAT"的默认行为;
  3. 失败回退:某个 ops->mount 返回非 0 时调用其 close_fs 清理现场,继续尝试下一个实现;全部失败则释放句柄并返回 E_NO_FS。

挂载后的 imount 结构(见 vfs.h):

struct imount {
    struct vfs_operations *ops;
    union {
        void *pfs;    // 文件系统对象(如 FATFS *)
        void *pfile;  // 文件对象(如 FIL *)
    };
};

这个联合体非常巧妙:同一个 imount 结构既充当"已挂载文件系统句柄"(存 pfs)又充当"已打开文件句柄"(存 pfile),由 ops 指向的实现表决定语义。vfs_openbypath 会先把 p_vfile->ops = p_vfs->ops 复制过来,再调用 ops->openbypath(p_vfs->pfs, &p_vfile->pfile, path)(见 vfs.c),从而让文件句柄自动继承所属文件系统的实现。

挂载时序

sequenceDiagram
    participant App as 应用
    participant VFS as vfs_mount(vfs.c)
    participant Ops as vfs_operations[] (.vfs_operations段)
    participant FAT as FAT mount
    participant NOR as norfs_mount

    App->>VFS: vfs_mount(&pvfs, device, type)
    VFS->>VFS: *ppvfs==NULL ? vfs_hdl_malloc() : 复用
    VFS->>VFS: 失败则返回 E_NO_VFS
    loop list_for_each_vfs_operation
        VFS->>Ops: 检查 ops->mount 与 ops->fs_type
        alt type 非空
            VFS->>VFS: strcmp 不匹配则 continue
        else type 为空
            VFS->>VFS: 跳过 "norfs" / "freefs"
        end
        VFS->>FAT: ops->mount(&pfs, device)
        alt 挂载成功 (返回 0)
            FAT-->>VFS: 0
            VFS->>VFS: pvfs->ops = ops
            VFS-->>App: 返回 0
        else 挂载失败
            FAT-->>VFS: 非 0
            VFS->>FAT: ops->close_fs(&pfs) 清理
        end
    end
    VFS-->>App: 释放句柄,返回 E_NO_FS

句柄分配与内存池

VFS 句柄的内存来自 my_malloc.h 定义的统一内存池枚举(见 my_malloc.h):

MM_VFS,      // VFS 句柄 (imount)
MM_SYDFS,    // SYDF 文件系统对象
MM_SYDFF,    // SYDF 文件对象
...
MM_NORFF,    // NOR_FS 文件对象
MM_FATFS,    // FAT 文件系统对象 (FATFS)
MM_FATFF,    // FAT 文件对象 (FIL)

每一种句柄类型对应独立的内存池,vfs_hdl_malloc/vfs_fhdl_free(VFS)、fat_fshdl_alloc/fat_fshdl_free(FAT,见 fat_resource.h)分别从对应池中取/还内存。这种"按类型分池"的做法可以避免不同文件系统之间的内存碎片相互影响,也是嵌入式系统里常用的资源隔离手段。

各文件系统实现

FAT 文件系统(Tiny-FAT 移植)

FAT 实现位于 sdk/include_lib/fs/fat/,核心头文件是 tff.h(Tiny-FatFS 的杰理定制版)。文件系统对象 struct _FATFS 从 FAT 起始扇区开始记录布局(见 tff.h):

/* File system object structure */
struct _FATFS {
    u32		fatbase;	/* FAT start sector */
    ...
};
typedef struct _FATFS FATFS;

tff.h 同时定义了文件对象(含 FFOBJID obj)、目录对象以及带消息队列的异步文件操作结构(FSMSG fs_msg,见 tff.h 与 tff.h),说明该移植支持通过消息队列做异步读写。

资源管理接口在 fat_resource.h:FATFS *fat_fshdl_alloc(void) 与 FATFS *fat_fshdl_free(FATFS *fshdl)(见 fat_resource.h),配合 MM_FATFS 内存池使用。

vfs_fat.c 为 FAT 提供 VFS 之上的封装辅助函数,例如 vfs_file_delete 演示了"通过句柄取 ops 再转发"的标准模式(见 vfs_fat.c):

u32 vfs_file_delete(void *pvfile)
{
    struct imount *p_vfile = pvfile;
    if ((void *)NULL == p_vfile) {
        return E_FS_PFILE;
    }
    struct vfs_operations *ops;
    ops = p_vfile->ops;
    if (((void *)NULL != ops)  && ((void *)NULL !=  ops->fdelete)) {
        u32 res = E_VFS_OPS;
        if (NULL != (p_vfile->pfile)) {
            res = ops->fdelete(p_vfile->pfile);
            if (0 != res) {
                return E_FS_PFILE;
            }
        }
        return res;
    }
    return  E_VFS_OPS;
}

注意其注释明确说明"SH 系列文件关闭在外面应用,删除接口里面不处理"——删除与关闭解耦,由上层负责 close_file。同文件还提供 vfs_get_fsize(包装 ops->flen)与 vfs_ftell(包装 ops->ftell),以及 __fscan_arg_handler 解析扫描参数:-t 文件类型、-r 包含子目录、-d 扫描文件夹、-a 文件属性、-s 排序方式(按时间 t 或按文件号 n,见 vfs_fat.c)。

NOR_FS 简易文件系统

NOR_FS 是专为 NOR Flash 录音文件设计的极简扇区式文件系统,完整定义在 nor_fs.h:

#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;

设计要点:

  • 扇区即页:NORF_SECTOR_SIZE == FLASH_PAGE_SIZE == 256 字节,文件系统按 256 字节页管理 NOR Flash,天然对齐 Flash 的页擦除/编程粒度;
  • 扁平索引:RECF_INDEX_INFO 只有"索引号 + 扇区号"两个字段,没有目录树,适用于"第 N 段录音"这类顺序访问场景;
  • 魔数校验:NORFS_MAGIC_NUM(0x11223344)配合 norfs_flash_info_t(magic/first_sector/last_sector/sector_size)在挂载时校验 Flash 中是否已有合法文件系统;
  • 句柄内嵌介质指针:hdev 指向底层 Flash 设备,文件系统通过它读写,与具体 Flash 驱动解耦。

数据结构关系:

erDiagram
    RECFILESYSTEM ||--o{ REC_FILE : "打开多个文件"
    RECFILESYSTEM {
        RECF_INDEX_INFO index "当前文件索引"
        u16 total_file "文件总数"
        u16 first_sector "起始扇区"
        u16 last_sector "结束扇区"
        u8 sector_bits "扇区位数"
        void hdev "底层设备"
    }
    REC_FILE {
        RECF_INDEX_INFO index "文件索引"
        RECFILESYSTEM pfs "所属文件系统"
        u32 len "文件长度"
        u32 w_len "已写长度"
        u32 rw_p "读写指针"
        u16 sr "当前扇区"
        char name "文件名 name[16]"
    }
    RECF_INDEX_INFO {
        u32 index "文件索引号"
        u16 sector "文件所在扇区"
    }

NOR_FS 对外 API(见 nor_fs.h):

函数作用
norfs_init_api()注册/初始化底层 Flash 操作
norfs_init(sector_start, sector_end, sector_bit)划定文件系统占用的扇区区间
norfs_mount(RECFILESYSTEM **ppfs, void *p_device)挂载,校验魔数并读取文件总数
norfs_createfile(pfs, &pfile, &pindex)创建新文件,返回索引号
norfs_openbyindex(pfs, &pfile, index)按索引打开文件
norfs_read / norfs_write按扇区读写,btr/btw 为 16 位长度
norfs_seek(pfile, offsize, type)定位读写指针
norfs_closefile / norfs_save_sr关闭文件 / 保存当前扇区
norfs_ioctl / norfs_name控制命令 / 获取文件名

NOR_FS 的读写在 VFS 中暴露为 "norfs" 类型的 vfs_operations 实现;vfs_mount 在未指定 type 时会跳过它,但业务层(如录音模块 enc_in_norfs.c)可通过 norfs_* 直接调用,或显式指定 "norfs" 类型挂载。

free_fs 简易文件系统

free_fs 位于 sdk/app/bsp/common/fs/free_fs/,包含 free_fs.c 与 free_fs_resource.c(头文件见 sdk/include_lib/fs/free_fs/free_fs.h)。它同样以 "freefs" 类型注册到 VFS,vfs_mount 默认跳过它。free_fs 面向"无需目录树、文件数有限"的轻量场景(如芯片内部资源区、调试数据),与 NOR_FS 定位类似但实现独立。

SYDF 文件系统(boot 级)

SYDF(Serial/Simple Data File)是独立于 VFS 的 boot/uboot 级文件系统,主要用于启动阶段读取固件与 VM(参数管理)数据。它的存在由 boot.h 的编译开关控制:

#define USE_SYDFILE_NEW 1

struct vm_info {
#if (USE_SYDFILE_NEW == 1)
    u8 align;			  	//from uboot, 按 n * 256 对齐
#endif
    ...
};

当 USE_SYDFILE_NEW == 1 时,vm_info、bt_mac_addr 等启动期结构体采用新布局(按 n*256 对齐,见 boot.h 与 boot.h)。由于 SYDF 运行在 boot 阶段,它不能依赖应用级 VFS,因此拥有独立的内存池 MM_SYDFS(文件系统对象)与 MM_SYDFF(文件对象)(见 my_malloc.h)。

SYDF 与 VFS 体系的分工可以概括为:boot 阶段用 SYDF 完成最小化文件读取(固件、MAC、VM 参数)→ 进入应用后由 VFS 挂载 FAT/NOR_FS/free_fs 提供完整文件服务。

使用示例

示例一:通过 VFS 挂载并打开文件

以下是 VFS 的标准调用序列(挂载 → 打开 → 读写),对应 vfs_mount 与 vfs_openbypath 的实现(见 vfs.c):

struct imount *pvfs = NULL;
struct imount *pvfile = NULL;

// 1. 挂载:type 传 NULL 时默认优先尝试 FAT
u32 err = vfs_mount(&pvfs, device, NULL);
if (0 != err) {
    return err;   // E_NO_VFS / E_NO_FS
}

// 2. 按路径打开文件(自动继承 pvfs 的 ops)
err = vfs_openbypath(pvfs, &pvfile, "/music/test.mp3");
if (0 != err) {
    vfs_fs_close(&pvfs);
    return err;   // E_VFS_HDL / E_VFS_OPS / 底层错误
}

// 3. 读写/关闭由 ops 分发
// vfs_read(pvfile, buf, len);
// vfs_write(pvfile, buf, len);
vfs_file_close(&pvfile);
vfs_fs_close(&pvfs);

打开失败时 vfs_openbypath 内部会自动调用 vfs_file_close(ppvfile) 释放句柄(见 vfs.c),因此调用方只需在返回非 0 时处理 pvfile 即可。

示例二:NOR_FS 直接调用(录音场景)

NOR_FS 不依赖 VFS 时可直接调用其 API,先 norfs_init 划定扇区区间,再 norfs_mount 挂载(见 nor_fs.h):

NORFS *pfs = NULL;
REC_FILE *pfile = NULL;
u32 file_index = 0;

norfs_init_api();
norfs_init(0, 1023, 0);          // 扇区 0~1023,每扇区 256B
norfs_mount(&pfs, nor_device);   // 校验 NORFS_MAGIC_NUM

norfs_createfile(pfs, &pfile, &file_index);  // 创建新录音文件
norfs_write(pfile, pcm_buf, 512);
norfs_closefile(&pfile);          // 写满/停止录音时关闭

录音业务侧的完整用法可参考 enc_in_norfs.c(编码数据直接写入 NOR_FS)。

示例三:注册一个新的文件系统实现

任何文件系统接入 VFS 只需实现 struct vfs_operations 并用宏登记(见 vfs.h):

static u32 myfs_mount(void **ppfs, void *device) { /* ... */ }
static u32 myfs_read(void *pfile, void *buff, u32 len) { /* ... */ }

const struct vfs_operations myfs_ops = {
    .fs_type     = "myfs",
    .mount       = myfs_mount,
    .openbypath  = myfs_openbypath,
    .read        = myfs_read,
    /* 其余置 NULL 即可,VFS 返回 E_VFS_OPS */
};
REGISTER_VFS_OPERATIONS(myfs_ops);

配置选项

配置项位置类型/默认值说明
USE_SYDFILE_NEWboot.h宏,默认 1启用新版 SYDF 数据结构布局(vm_info 按 n*256 对齐)
NORFS_MAGIC_NUMnor_fs.h0x11223344NOR_FS 挂载时的魔数校验
NORF_SECTOR_SIZEnor_fs.h256(=FLASH_PAGE_SIZE)NOR_FS 扇区大小
VFS_FILE_NAME_LENvfs.h16全局短文件名缓冲 g_file_sname 长度
SEEK_SET/CUR/ENDvfs.h0/1/2seek 模式常量
内存池枚举my_malloc.hMM_VFS 等各文件系统句柄独立内存池

API 参考

VFS 层(vfs.h)

函数签名说明
void vfs_init(void)遍历 .vfs_operations 段调用各实现 init()
u32 vfs_mount(void **ppvfs, void *device, void *type)挂载;type==NULL 时跳过 norfs/freefs;返回 0 或 E_NO_VFS/E_NO_FS
u32 vfs_openbypath(void *pvfs, void **ppvfile, const char *path)按路径打开文件,失败自动关闭句柄
u32 vfs_openbyindex(void *pvfs, void **ppvfile, u32 index)按索引打开(NOR_FS 类系统常用)
u32 vfs_createfile(void *pvfs, void **ppvfile, u32 *pindex)创建文件并返回索引
u32 vfs_read/vfs_write/vfs_seek(...)转发到 ops->read/write/seek
u32 vfs_file_close(void **ppvfile) / vfs_fs_close(void **ppvfs)关闭文件/文件系统
u32 vfs_file_name(void *pvfile, void *name, u32 len)获取文件名(转发 ops->name)
int vfs_get_attrs(void *pvfile, void *pvfs_attr)获取 struct vfs_attr(属性/大小/簇地址)
int vfs_ioctl(void *pvfile, int cmd, int arg)透传 FS_IOCTL_* 命令(见下方)
int vfs_delete_dir(void *pvfs, char *path)删除目录
struct imount *vfs_hdl_malloc(void) / vfs_fhdl_free(...)句柄分配/释放(MM_VFS 池)

FS_IOCTL 命令集(vfs.h)

FS_IOCTL_GET_FILE_NUM、FS_IOCTL_FILE_CHECK、FS_IOCTL_FREE_CACHE、FS_IOCTL_SET_NAME_FILTER、FS_IOCTL_GET_FOLDER_INFO、FS_IOCTL_SET_LFN_BUF(512)、FS_IOCTL_SET_LDN_BUF(512)、FS_IOCTL_SET_EXT_TYPE、FS_IOCTL_OPEN_DIR/ENTER_DIR/EXIT_DIR、FS_IOCTL_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、FS_IOCTL_FILE_SYNC、FS_IOCTL_FILE_INDEX/FS_INDEX、FS_IOCTL_RESET_VFSCAN、FS_IOCTL_GET_PARTITION_INFO(簇大小/容量)、FS_IOCTL_GET_FREE_SPACE。

NOR_FS 层(nor_fs.h)

函数参数要点返回
norfs_init(u32 sector_start, u32 sector_end, u32 sector_bit)划定 Flash 扇区区间void
norfs_mount(RECFILESYSTEM **ppfs, void *p_device)校验魔数、读取文件总数u32,0 成功
norfs_createfile(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 *pindex)创建文件,输出索引号u32
norfs_openbyindex(RECFILESYSTEM *pfs, REC_FILE **ppfile, u32 index)按索引打开u32
u16 norfs_read(REC_FILE *pfile, u8 *buff, u16 btr)读,返回实际字节数u16
u16 norfs_write(REC_FILE *pfile, u8 *buff, u16 btw)写,返回实际字节数u16
u32 norfs_seek(REC_FILE *pfile, u32 offsize, u32 type)定位读写指针u32
u32 norfs_closefile(REC_FILE **ppfile)关闭文件u32
void norfs_save_sr(REC_FILE *pfile, u16 sr)保存当前扇区号void

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

错误码约定

VFS 层使用 errno-base.h 中的错误码,挂载/打开路径上的关键错误:

错误码触发场景
E_NO_VFSvfs_hdl_malloc() 分配 imount 句柄失败(MM_VFS 池耗尽)
E_NO_FS遍历完所有 vfs_operations 仍无实现挂载成功(介质不可读/格式不识别)
E_VFS_HDLvfs_openbypath 收到 NULL 文件系统句柄
E_VFS_OPS文件系统未实现对应操作(ops 指针为 NULL)
E_FS_PFILE文件句柄/文件对象为 NULL(如 vfs_file_delete 删除失败)

边界与回退行为

  • 默认挂载顺序:vfs_mount 未指定 type 时跳过 norfs/freefs,意味着只插了录音 NOR Flash、没插 SD 卡的设备默认挂不上简易系统——业务方必须显式传 "norfs" 或直接调用 norfs_* API。这是设计上刻意的取舍:FAT 是通用介质格式,优先尝试成本最低。
  • 句柄自清理:vfs_openbypath 失败路径调用 vfs_file_close(ppvfile) 释放句柄(见 vfs.c),避免调用方遗漏释放;但成功路径的句柄必须由调用方显式关闭,否则内存池泄漏。
  • 挂载失败现场清理:某个 ops->mount 失败后 VFS 会调用其 close_fs 再尝试下一个实现(见 vfs.c),防止前一个实现残留状态影响后续挂载。
  • NOR_FS 容量约束:norfs_read/norfs_write 的长度参数是 u16(见 nor_fs.h),单次读写最大 65535 字节;文件总数上限由 u16 total_file 决定。超过这些上限的读写需要调用方分片进行。

并发与重入

  • VFS 层本身不提供锁:struct vfs_operations 是纯函数表,读写并发由底层文件系统(如 FAT 的 FSMSG 消息队列机制)与业务层互斥保证。从 tff.h 中文件对象内嵌 FSMSG(见 tff.h)可以看出 FAT 实现支持异步消息队列,但调用方仍需遵守"同一文件句柄不跨任务并发读写"的约定。
  • 句柄池竞争:vfs_hdl_malloc/fat_fshdl_alloc 等从内存池取句柄,若多个任务同时挂载/打开文件,需由系统内存池分配器保证原子性。

性能与运维注意事项

  • 内存按类型分池(MM_VFS / MM_SYDFS / MM_SYDFF / MM_NORFF / MM_FATFS / MM_FATFF,见 my_malloc.h):每种句柄有独立预算,可通过各池的剩余量定位"句柄泄漏"属于哪个文件系统——运维排查时优先检查 E_NO_VFS/挂载失败是否由池耗尽引起。
  • 长文件名缓冲:FS_IOCTL_SET_LFN_BUF/FS_IOCTL_SET_LDN_BUF 需要调用方提供 512 字节缓冲(见 vfs.h),处理长文件名前必须先设置,否则 FS_IOCTL_GET_DISP_INFO 拿不到完整名称。
  • NOR_FS 无磨损均衡:录音文件固定落在 first_sector..last_sector 区间,高频擦写会加速该区间 Flash 老化;业务层应规划好循环覆盖策略(如按 REC_FILE_END 标记文件结束)。
  • 扫描中断机制:fscan_interrupt 允许在目录扫描期间周期性回调,供上层让出 CPU/处理按键,避免大目录扫描阻塞系统(见 vfs.h)。

扩展点

  1. 新增文件系统:实现 struct vfs_operations(fs_type + 必要函数指针),调用 REGISTER_VFS_OPERATIONS(ops) 登记,即被 vfs_init/vfs_mount 自动发现——不需要修改 vfs.c 任何代码。
  2. 控制面扩展:通过 FS_IOCTL_* 命令集(vfs.h)向具体实现透传私有命令,如分区信息、剩余空间、目录导航、文件过滤等,上层用 vfs_ioctl 统一调用。
  3. 扫描策略定制:__fscan_arg_handler 的 -t/-r/-d/-a/-s 参数体系(见 vfs_fat.c)定义了文件扫描/排序的通用语义,新的文件系统可复用这套参数解析。
  4. boot 级替换:SYDF 的 USE_SYDFILE_NEW 开关(boot.h)允许在新旧结构布局之间切换,升级固件时用于保持与 uboot 的数据兼容。

测试情况

  • 头文件与实现文件未发现独立单元测试工程;文件系统的验证主要依赖板级集成测试:播放器播放 SD 卡 FAT 文件、录音写入 NOR_FS 后断电重启读取、以及 toy_record(enc_in_norfs.c)的端到端录音-回读链路。
  • 内存池枚举(MM_*)在 my_malloc.h 中与 VFS/SYDF/FAT 一一对应,可作为运行时内存统计(各池水位)的检测点,用于定位句柄泄漏类问题。

Related Links

  • VFS 头文件与实现:vfs.h、vfs.c
  • FAT 封装:vfs_fat.c、tff.h、fat_resource.h
  • NOR_FS:nor_fs.h、nor_fs_resource.c
  • free_fs:free_fs.c、free_fs.h
  • SYDF 与内存池:boot.h、my_malloc.h
  • 相关业务示例:enc_in_norfs.c(录音写入 NOR_FS)
Next
存储设备与设备管理