API GUIDE / SFC / MIDDLEWARE
SFC Middleware 接口
sfc mid api
本文面向 BSP 和应用开发者,说明如何通过 SFC 中间件访问 NOR/NAND Flash、如何通过 DFS 挂载文件系统,并提供公开 API 参考。内部控制器时序、CDT 表构造、NAND 器件表和 HAL 实现不在本文范围内。
API 头文件:
code/middleware/sfc/include/im_sfc_device.hcode/middleware/sfc/include/im_sfc.hcode/middleware/sfc/include/im_sfc_nor.hcode/middleware/sfc/include/im_sfc_nand.h
DFS 挂载相关接口来自 code/middleware/filesystem/include/filesystem_init.h 和 code/middleware/filesystem/include/dfs_fs.h,不属于 SFC 中间件 API。
同一构建只能选择 NOR 或 NAND 一种设备中间件。
1. 模块概览
1.1 软件分层
| 组件 | 作用 |
|---|---|
im_sfc |
Common SFC 控制器层,以及编译期选中的 NOR 或 NAND 中间件 |
im_mtd |
把 NOR/NAND 分区发布为统一 MTD 对象 |
im_sfc_device |
设备层,保存 BSP 配置,管理 NOR/NAND、MTD 和生命周期 |
dfs_mtd |
把 MTD 分区注册为 DFS 设备,属于 DFS 侧组件 |
BSP board_sfc.c |
描述板级 Flash 配置并注册设备,不直接访问 Flash |
普通应用推荐使用 im_sfc_device:BSP 注册一次,DFS、日志、Shell 和应用共享同一个已启动设备。应用也可以绕过设备层,直接创建 NOR/NAND/Common SFC 对象,但需要自行管理初始化和生命周期。
1.2 运行时关系
BSP board_sfc.c
-> im_sfc_device_register()
DFS file_system_init() [CONFIG_DFS]
-> im_sfc_device_start()
-> NOR/NAND 初始化
-> MTD 挂接
-> dfs_mtd 把分区注册为 DFS 设备
-> dfs_init()
-> 自动挂载 / 应用调用 mount_fs()
应用或 Shell
-> im_sfc_device_start()
-> im_sfc_device_nor_get() / nand_get() / mtd_get()
-> NOR/NAND/MTD API
-> 文件路径访问(DFS 已挂载时)
设备层只负责启动 SFC 并发布 MTD;DFS 设备注册和文件系统挂载由 DFS 完成,不由 im_sfc_device 完成。
1.3 头文件选择
| 使用方式 | 包含 |
|---|---|
| 通过设备层访问 | <im_sfc_device.h> |
| 直接使用 NOR | <im_sfc_nor.h> |
| 直接使用 NAND | <im_sfc_nand.h> |
| 直接使用 Common SFC 事务 | <im_sfc.h> |
im_sfc_nor.h 和 im_sfc_nand.h 已包含 im_sfc.h,不要重复依赖内部头文件。
1.4 使用约定
- 返回
error_code_t的接口以IM_OK表示成功。失败时不要使用本次调用写出的数据,除非接口另有说明。 timeout_ms == 0使用默认超时 3000 ms。NOR 多块擦除、NAND 多次状态等待可能累计多个超时周期。IM_ERR_TIMEOUT不表示已经发出的 Flash 命令被撤销。编程、擦除或页读可能仍在器件内执行。- NOR 使用 Flash 字节地址。NAND 使用
block/page/column,长度和列地址的单位都是字节,列范围包含 spare 区。 name[32]等定长字符串不保证以NUL结尾,打印或比较时按字段长度处理。- 初始化、读写、擦除、时钟切换和 XIP 操作都是同步阻塞接口,只能在任务上下文调用。
- NOR/NAND 高层会串行化同一设备上的完整 Flash 操作;Common SFC 对象不串行化并发调用。
- NOR/NAND 高层按每个数据 phase 的长度自动选择数据通路:达到 1024 字节使用 DMA,否则使用 CPU FIFO;不满足 DMA 对齐要求的缓冲区由高层自动 bounce。
- 直接调用 Common 事务接口并指定 DMA 时,对齐、物理连续和 cache 维护由调用方负责。
- 多页写入、多块擦除不是事务,失败时已完成的部分不会自动回滚。
2. 典型流程
2.1 Kconfig 配置
SFC 的配置项位于 code/middleware/sfc/Kconfig,配置菜单路径为 Middleware -> SFC Middleware。选择前需要先满足两个依赖:
Drivers -> G32S10 Drivers -> SFC driver:对应CONFIG_DRIVER_SFC=y。OS -> Build OS API:对应CONFIG_OS_API=y,默认已启用。
依赖不满足时,SFC middleware 选项不可见。启用 CONFIG_MIDDLEWARE_SFC 后,继续选择 NOR 或 NAND;二者是同一个 choice,只能启用一个,默认选择 NOR。
| 配置项 | 默认值 | 说明 |
|---|---|---|
CONFIG_MIDDLEWARE_SFC |
n |
启用 SFC 中间件 |
CONFIG_MIDDLEWARE_SFC_NOR |
y |
编译 NOR 中间件,与 NAND 二选一 |
CONFIG_MIDDLEWARE_SFC_NAND |
n |
编译 NAND 中间件,与 NOR 二选一 |
CONFIG_MIDDLEWARE_SFC_NOR_PARAM_OFFSET |
0x6800 |
NOR Burner 参数块在 Flash 中的偏移 |
CONFIG_MIDDLEWARE_SFC_NAND_PARAM_OFFSET |
0x6800 |
NAND Burner 参数块在 Flash 中的偏移 |
CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK |
n |
NAND 跳过坏块检查 |
CONFIG_MIDDLEWARE_SFC_NOR_PARAM_OFFSET 只会在选择 NOR 后出现;NAND 偏移和跳过坏块选项只会在选择 NAND 后出现。CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK 只适用于已知良品或测试场景,不建议在量产配置中启用。
使用 menuconfig 选择
在 SDK 工程根目录执行:
source tools/imde/env.sh
imde list-defconfigs
imde use-defconfig g32s10 g32s10_v1_mini_defconfig --reconfigure
imde menuconfig --configure-if-needed
第一次选择板级配置时使用上面的 use-defconfig。如果工程中已有 .config,不要再次执行 use-defconfig,否则会覆盖当前配置;直接运行 imde menuconfig --configure-if-needed。
在 menuconfig 中依次完成:
- 在
Drivers -> G32S10 Drivers中启用SFC driver。 - 在
OS中确认Build OS API已启用。 - 在
Middleware -> SFC Middleware中启用SFC middleware。 - 在
SFC device type中选择NOR Flash或NAND Flash。 - 按实际板级烧录布局设置参数块偏移,然后保存并退出。
menuconfig 检测到 .config 变化后会自动同步生成配置并重新执行 CMake configure,随后直接构建:
imde build
defconfig 关键项
NOR 配置至少应包含:
CONFIG_DRIVER_SFC=y
CONFIG_OS_API=y
CONFIG_MIDDLEWARE_SFC=y
CONFIG_MIDDLEWARE_SFC_NOR=y
# CONFIG_MIDDLEWARE_SFC_NAND is not set
CONFIG_MIDDLEWARE_SFC_NOR_PARAM_OFFSET=0x6800
NAND 配置至少应包含:
CONFIG_DRIVER_SFC=y
CONFIG_OS_API=y
CONFIG_MIDDLEWARE_SFC=y
# CONFIG_MIDDLEWARE_SFC_NOR is not set
CONFIG_MIDDLEWARE_SFC_NAND=y
CONFIG_MIDDLEWARE_SFC_NAND_PARAM_OFFSET=0x6800
# CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK is not set
批量维护时可以直接修改 defconfig,然后重新选择并配置:
imde use-defconfig <source> <config> --reconfigure
imde build
参数块偏移
NOR/NAND 初始化时从参数块偏移处读取 Burner 写入的 Flash 参数。0x6800 是 SDK 默认值,不是分区起点,也不应仅根据 Flash 容量修改。
修改该值时必须同步更新 Burner/烧录工具中的参数块位置,否则设备初始化会读取错误数据。BSP 的 im_sfc_device_config_t.param_offset 为 0 时沿用 Kconfig 配置;非 0 时由 BSP 值覆盖。
配置保存与启动方式
CONFIG_DFS 与 SFC 配置相互独立。同时启用 SFC 和 DFS(Middleware -> Filesystem -> Device filesystem)时,file_system_init() 会启动 SFC 并注册 MTD 分区;只使用裸 Flash 接口时不需要启用 DFS,应用在使用前调用 im_sfc_device_start(),已有自动启动路径时除外。启动方式的差异见 2.4 节,文件系统配置和挂载见 2.5 节。
BSP 组件应显式依赖 im_sfc_device。启用 CONFIG_DFS 时,DFS 组件也需要依赖 im_sfc_device,以便通过设备层启动 Flash。
2.2 BSP 注册设备
BSP 提供一份板级配置并注册到设备层。注册阶段只复制配置,不访问 Flash。
#include <im_sfc_device.h>
static const im_sfc_device_config_t s_board_sfc_config = {
.board = {
.sample_delay_init = 4U,
.sample_delay_work = 4U,
.ce_inactive_level = HAL_SFC_LEVEL_HIGH,
.hold_inactive_level = HAL_SFC_LEVEL_HIGH,
.wp_inactive_level = HAL_SFC_LEVEL_HIGH,
},
.clock_rate_hz = 0U,
.param_offset = 0U,
};
error_code_t board_sfc_register_devices(void)
{
error_code_t ret;
ret = im_sfc_device_register(IM_SFC_DEVICE_ID_DEFAULT,
&s_board_sfc_config);
return (ret == IM_ERR_ALREADY_REGISTERED) ? IM_OK : ret;
}
启用 CONFIG_MIDDLEWARE_SFC 后,core_v1_early_init() 会调用当前 BSP 的 board_sfc_register_devices()。
2.3 启动并访问设备
DFS 会自动启动设备。没有 DFS、也没有其他自动启动路径时,应用在使用 Flash 前调用一次 im_sfc_device_start():
#include <im_sfc_device.h>
static error_code_t app_read(uint32_t address, uint8_t *buffer, uint32_t length)
{
im_sfc_nor_t *nor;
error_code_t ret;
ret = im_sfc_device_start(IM_SFC_DEVICE_ID_DEFAULT);
if (ret != IM_OK)
return ret;
nor = im_sfc_device_nor_get(IM_SFC_DEVICE_ID_DEFAULT);
if (nor == NULL)
return IM_ERR_NOT_READY;
return im_sfc_nor_read(nor, address, length, buffer, 0U);
}
NAND 构建把 im_sfc_device_nor_get() 和 im_sfc_nor_read() 换成对应的 NAND 接口。
设备层规则:
device_id使用IM_SFC_DEVICE_ID_DEFAULT。im_sfc_device_start()可重复调用,已启动时返回IM_OK。im_sfc_device_nor_get()/nand_get()/mtd_get()返回借用指针,不能释放。- 不要再对借用对象调用
init();由设备层统一启动和停止。 im_sfc_device_stop()后指针立即失效,再次启动后要重新获取。
2.4 自动启动与手动启动
| 场景 | 启动方式 |
|---|---|
启用 CONFIG_DFS |
file_system_init() 启动设备、注册 DFS 设备,并执行已配置的自动挂载 |
| Flash raw 日志 | 日志后端首次访问时调用 im_sfc_device_start() |
| Shell/USB | 使用前调用 im_sfc_device_start() |
| 普通应用 | 没有自动启动路径时自行调用 im_sfc_device_start() |
| 直接使用 NOR/NAND/Common | 调用方自行初始化,不经过设备层 |
如果 DFS 已挂载分区,不要直接 im_sfc_device_stop()。必须先卸载文件系统并停止所有访问者。stop() 不会注销 dfs_mtd 创建的 DFS 设备。
file_system_init() 初始化失败后(例如 SFC 启动、DFS 设备注册或 rootfs 挂载失败),会进入固定的 FAILED 状态;后续调用直接返回同一错误,不会自动重试。应用应在处理故障后重新初始化文件系统。
2.5 DFS 文件系统挂载
SFC 只负责把 Flash 分区发布为 MTD。应用要按文件路径访问 Flash,还需要启用 DFS,并把 MTD 分区注册为 DFS 设备后挂载文件系统:
挂载会把一个 DFS 设备(分区)绑定到目录;挂载完成后访问 /data/... 就是访问该分区中的文件系统。
挂载前提是 Burner 参数块中已经有合法分区;SFC/MTD 不创建分区,分区名就是 DFS 设备名。
file_system_init()
-> im_sfc_device_start()
-> MTD 分区
-> dfs_mtd 注册 DFS 设备(设备名 = 分区名)
-> dfs_init()
-> 已配置的自动挂载
应用(未自动挂载时)
-> im_dfs_mount_point_create()
-> mount_fs()
-> /<分区名>/...
NOR 使用 FAT 文件系统,DFS 后端名为 "elm";NAND 使用 YAFFS2,文件系统类型名为 "yaffs"。不要用 "elm" 挂载 NAND 分区。配置入口为 Middleware -> Filesystem:先启用 Device filesystem,再在 Filesystem Types 中选择对应后端。
相关 DFS Kconfig 配置项:
| 配置项 | 说明 |
|---|---|
CONFIG_DFS |
启用 DFS;依赖 CONFIG_OS_API |
CONFIG_DFS_ELMFAT |
NOR 使用的 ELM FatFs 后端 |
CONFIG_DFS_YAFFS |
NAND 使用的 YAFFS2 后端;依赖 CONFIG_MIDDLEWARE_SFC_NAND |
CONFIG_DFS_ELM_MAX_SECTOR_SIZE |
FatFs 最大扇区大小,默认 8192;按 NOR 最大擦除块设置,例如 4 KiB 器件用 4096,32 KiB 器件用 32768 |
CONFIG_DFS_ROOT_DEVICE_RAMDISK |
/ 使用 RAM disk,掉电不保留;NOR 的启动挂载默认使用该方式 |
CONFIG_DFS_ROOT_DEVICE_FLASH |
/ 挂载 NOR 参数块中名为 rootfs 的 FAT 分区;需启用 ELMFAT,且仅适用于 NOR |
CONFIG_DFS_ELMFAT_MOUNT_PARTITIONn / ..._NAME |
RAM disk 根模式下,启动时把指定 NOR 分区挂载到 /<分区名> |
CONFIG_DFS_YAFFS_MOUNT_PARTITIONn / ..._NAME |
启动时把指定 NAND 分区挂载到 /<分区名> |
CONFIG_DFS_MOUNT_AUTO_FORMAT |
卷没有可用文件系统时允许自动格式化;默认关闭 |
开机自动挂载
NOR 使用 RAM disk 作为根目录,并把 data 分区自动挂载到 /data:
CONFIG_DFS=y
CONFIG_DFS_ELMFAT=y
CONFIG_DFS_ELM_MAX_SECTOR_SIZE=32768
CONFIG_DFS_ROOT_DEVICE_RAMDISK=y
CONFIG_DFS_ELMFAT_MOUNT_PARTITION0=y
CONFIG_DFS_ELMFAT_MOUNT_PARTITION0_NAME="data"
NAND 自动挂载 YAFFS2 分区:
CONFIG_DFS=y
CONFIG_DFS_YAFFS=y
CONFIG_DFS_YAFFS_MOUNT_PARTITION0=y
CONFIG_DFS_YAFFS_MOUNT_PARTITION0_NAME="data"
自动挂载规则:
PARTITIONn中的n是挂载槽位 0~7,不是参数块中的分区序号。..._NAME必须与 Burner 参数块中的分区名完全一致,挂载点固定为/<分区名>。- NOR 选择
CONFIG_DFS_ROOT_DEVICE_FLASH时,只自动挂载名为rootfs的根分区;其他 NOR 分区需要手动挂载。rootfs不存在,或默认关闭自动格式化时rootfs未格式化,都会使file_system_init()失败。 - NAND 不要求提供
rootfs分区;只启用 YAFFS2 时,根目录由 root-stub 提供,NAND 分区仍可自动或手动挂载到/<分区名>。 - 自动挂载不会默认格式化卷。卷没有文件系统时会挂载失败;此时先执行一次
dfs_mkfs();启用CONFIG_DFS_SHELL_COMMANDS时也可使用 Shellmkfs <fs> <device>(如 NOR 的mkfs elm data、NAND 的mkfs yaffs data),格式化会清除分区数据。 - 普通分区自动挂载失败只打印日志,
file_system_init()仍可能返回IM_OK;rootfs根分区挂载失败会使初始化返回错误。应检查日志,并在启用CONFIG_DFS_SHELL_COMMANDS时使用df确认挂载结果。
手动挂载
未配置自动挂载,或需要挂载其他分区时,先创建挂载点目录,再调用 mount_fs():
#include <im_config.h>
#include <im_log.h>
#include <filesystem_init.h>
#include <dfs_device.h>
#include <dfs_fs.h>
#define APP_FS_DEVICE "data"
#define APP_FS_MOUNT "/data"
#if defined(CONFIG_MIDDLEWARE_SFC_NOR)
#define APP_FS_TYPE "elm"
#elif defined(CONFIG_MIDDLEWARE_SFC_NAND)
#define APP_FS_TYPE "yaffs"
#endif
error_code_t app_mount_data(void)
{
error_code_t ret;
int status;
ret = file_system_init();
if (ret != IM_OK)
return ret;
ret = im_dfs_mount_point_create(APP_FS_MOUNT);
if ((ret != IM_OK) && (ret != IM_ERR_ALREADY_EXISTS))
return ret;
status = mount_fs(APP_FS_MOUNT, APP_FS_DEVICE, APP_FS_TYPE,
0U, NULL, FORMAT_FILESYSTEM_TYPE_NO_FORMAT);
if (status != 0) {
IM_LOGE("mount %s on %s failed errno=%d",
APP_FS_DEVICE, APP_FS_MOUNT, im_dfs_get_errno());
return IM_ERR_IO;
}
return IM_OK;
}
说明:
- 当前固件启动流程已在
CONFIG_DFS下调用file_system_init();成功后的重复调用返回IM_OK,失败状态不会自动重试。自定义启动流程必须自行调用。 - 该示例适用于未自动挂载
data的场景;同一分区不要重复挂载。 mount_fs()使用FORMAT_FILESYSTEM_TYPE_NO_FORMAT时只做挂载,不会格式化。传入FORMAT_FILESYSTEM_TYPE_FAT(NOR)或FORMAT_FILESYSTEM_TYPE_GENERIC(NAND),并开启CONFIG_DFS_MOUNT_AUTO_FORMAT后,才会在卷不可用时自动格式化并重试。- 首次使用或文件系统损坏时,先卸载文件系统并关闭所有访问者,再调用
dfs_mkfs(APP_FS_TYPE, APP_FS_DEVICE)。格式化会清除该分区全部内容。 - 如果绕过
file_system_init()自行启动 SFC,必须按顺序调用dfs_device_init(),把 MTD 分区注册成 DFS 设备(NOR 使用im_mtd_block_devices_register(),NAND 使用im_mtd_nand_devices_register()),再调用dfs_init()并准备根目录/挂载点;否则mount_fs()找不到设备或无法解析路径。
挂载成功后,/data 下的文件由 DFS 转发到 Flash。应用可以使用 <dfs_posix.h> 提供的 open() / read() / write() / stat(),也可以使用标准 fopen() / fread() / fwrite()。
卸载时先关闭文件和其他访问者,再执行:
if (dfs_unmount(APP_FS_MOUNT) != 0)
IM_LOGE("unmount %s failed", APP_FS_MOUNT);
(void)im_dfs_mount_point_remove(APP_FS_MOUNT);
必须卸载所有基于 SFC 分区的文件系统并停止访问后,才能调用 im_sfc_device_stop()。
2.6 直接初始化 NOR/NAND
绕过设备层时,NOR/NAND 对象由调用方分配,并且必须零初始化:
im_sfc_nor_t nor;
im_sfc_nor_config_t config;
error_code_t ret;
memset(&nor, 0, sizeof(nor));
ret = im_sfc_nor_config_default(&config);
if (ret == IM_OK)
ret = im_sfc_nor_init(&nor, &config);
if (ret == IM_OK)
ret = im_sfc_nor_read(&nor, address, length, buffer, 0U);
(void)im_sfc_nor_deinit(&nor);
直接使用低层对象时,应用自行负责时钟、并发和生命周期;不会自动获得设备层的板级配置、MTD 挂接或 DFS 注册。
3. 配置和关键类型
3.1 状态枚举
typedef enum {
IM_SFC_STATE_UNINITIALIZED = 0U,
IM_SFC_STATE_READY = 1U,
IM_SFC_STATE_BUSY = 2U,
IM_SFC_STATE_WORKING = 3U,
IM_SFC_STATE_ERROR = 4U,
IM_SFC_STATE_XIP = 5U,
} im_sfc_state_t;
typedef enum {
IM_SFC_NOR_STATE_UNINITIALIZED = 0U,
IM_SFC_NOR_STATE_READY = 1U,
IM_SFC_NOR_STATE_BUSY = 2U,
IM_SFC_NOR_STATE_ERROR = 3U,
IM_SFC_NOR_STATE_XIP = 4U,
} im_sfc_nor_state_t;
typedef enum {
IM_SFC_NAND_STATE_UNINITIALIZED = 0U,
IM_SFC_NAND_STATE_READY = 1U,
IM_SFC_NAND_STATE_BUSY = 2U,
IM_SFC_NAND_STATE_ERROR = 3U,
} im_sfc_nand_state_t;
应用通常只需判断对象是否为 READY。出现 ERROR 时调用对应的 deinit(),成功后再重新初始化。XIP 状态下不能执行普通读写。
恢复建议:
ERROR:先deinit(),再重新初始化。deinit()失败时不要继续使用对象。XIP:普通读写擦除返回IM_ERR_BUSY。NOR 应通过im_sfc_nor_xip_disable()退出。stop()或deinit()失败可能只完成部分清理,不要继续使用借用指针或器件快照。
3.2 设备层配置
typedef struct {
uint8_t sample_delay_init;
uint8_t sample_delay_work;
hal_sfc_level_t ce_inactive_level;
hal_sfc_level_t hold_inactive_level;
hal_sfc_level_t wp_inactive_level;
} im_sfc_board_config_t;
typedef struct {
im_sfc_board_config_t board;
uint32_t clock_rate_hz;
uint32_t param_offset;
} im_sfc_device_config_t;
im_sfc_device_config_default() 的默认值:
| 字段 | 默认值 | 说明 |
|---|---|---|
sample_delay_init |
4 | 初始化时钟下的采样延时 |
sample_delay_work |
4 | 工作时钟下的采样延时 |
ce_inactive_level / hold_inactive_level / wp_inactive_level |
HAL_SFC_LEVEL_HIGH |
引脚空闲电平 |
clock_rate_hz |
0 | 使用 NOR/NAND 默认工作时钟(100 MHz) |
param_offset |
0 | 使用 Kconfig 中的参数块偏移 |
clock_rate_hz 和 param_offset 的 0 都是“使用下层默认值”的哨兵。设备层无法表达“不切换时钟”或“参数块位于偏移 0”;需要这两种行为时,请直接使用 NOR/NAND 层。
im_sfc_device_register() 会校验采样延时和三个空闲电平,并在注册时复制配置。注册完成后没有注销或重新配置接口;需要变更配置时必须重新上电或重新构建。
3.3 Common SFC 配置
typedef struct {
hal_sfc_device_config_t device_config;
hal_sfc_clock_config_t clock;
uint16_t poll_time;
bool use_irq;
} im_sfc_config_t;
普通应用不需要手工填写该结构。使用设备层时由 BSP 配置覆盖板级相关字段;直接使用 Common SFC 时,先调用 im_sfc_config_default(),再只修改需要的字段。
默认值:
| 字段 | 默认值 |
|---|---|
device_config |
SDK 通用 SFC 配置,采样延时 4,CE/HOLD/WP 空闲高电平 |
clock |
hal_sfc_clock_default_get(),默认 50 MHz |
poll_time |
0 |
use_irq |
FreeRTOS 构建为 true,其他构建为 false |
注意:
use_irq = true需要平台支持 IRQ;CONFIG_IRQ_SIMPLE_DISPATCH构建中必须设为false。im_sfc_deinit()不关闭 SFC 时钟,时钟生命周期由调用方管理。poll_time和device_config属于高级配置,取值范围见hal_sfc.h。
3.4 时钟和 XIP 配置
typedef struct {
uint32_t hold_ns;
uint32_t setup_ns;
uint32_t deselect_ns;
uint8_t sample_delay;
} im_sfc_clock_timing_t;
typedef struct {
hal_sfc_direct_phase_config_t read_phase;
uint8_t mode_bits;
uint8_t preset_cdt_index;
uint16_t exit_command;
} im_sfc_xip_config_t;
| 字段 | 说明 |
|---|---|
read_phase |
连续读命令、地址宽度、dummy 和数据传输模式 |
mode_bits |
连续读模式位,默认 0xA0 |
preset_cdt_index |
重入 XIP 时使用的 preset CDT 槽位,不能为 0 |
exit_command |
退出连续读的命令,默认 0xFF;需要两字节命令时设为 0xFFFF |
im_sfc_clock_timing_t 中的 hold/setup/deselect 单位为 ns;sample_delay 是新时钟下使用的采样延时编码。
普通 NOR XIP 使用 im_sfc_nor_xip_enable(nor, NULL) 即可,默认命令为 0xEB、4 个 dummy、quad I/O。只有器件需要不同读命令或退出命令时,才需要自行填写 im_sfc_xip_config_t。
直接使用 Common XIP API 时,除了调用 im_sfc_xip_config_default(),还必须填写有效的 read_phase.phase.address_width 和 preset_cdt_index。NOR 包装层会自动补齐这两个字段。
3.5 NOR 配置和快照
typedef struct {
im_sfc_config_t sfc_config;
uint32_t clock_rate_hz;
uint32_t param_offset;
uint8_t sample_delay_work;
} im_sfc_nor_config_t;
im_sfc_nor_config_default() 默认使用 100 MHz 工作时钟、Kconfig 参数块偏移和采样延时 4。
配置字段:
| 字段 | 说明 |
|---|---|
sfc_config |
传给内部 Common SFC 的配置 |
clock_rate_hz |
匹配器件后的工作时钟;0 表示保持初始化时钟 |
param_offset |
NOR Burner 参数块在 Flash 中的偏移 |
sample_delay_work |
工作时钟下的采样延时;0 使用默认值 4,超过 HAL 上限时初始化返回 IM_ERR_INVALID_PARAM |
初始化成功后,可以从 im_sfc_nor_t 读取以下只读快照:
| 字段 | 说明 |
|---|---|
flash_info(im_sfc_nor_flash_info_t) |
器件命令、时序、容量、页大小和擦除大小 |
partitions(im_sfc_nor_partitions_t) |
Burner 参数块中的分区表 |
chip_id |
读到的 24 位 JEDEC ID |
quad_enabled |
是否使用 quad 命令 |
address_4byte |
是否进入 4-byte 地址模式 |
常用 flash_info 字段:
| 字段 | 说明 |
|---|---|
chip_size |
Flash 总容量,单位字节 |
page_size |
编程页大小 |
erase_size |
擦除粒度,不一定等于 4 KiB |
id |
参数块声明的 JEDEC ID |
NOR 没有内置器件表。im_sfc_nor_init() 从 param_offset 读取 im_sfc_nor_burner_params_t,其内容依次为 magic、version、flash_info、partitions 和 private_data。初始化要求:
magic为IM_SFC_NOR_MAGIC,version为IM_SFC_NOR_VERSION。flash_info.id等于实际读到的 JEDEC ID。param_offset + sizeof(im_sfc_nor_burner_params_t)不超过 16 MiB。flash_info中的读、写、擦除和 busy 轮询命令完整有效。- 分区数量不超过 10;分区越界、重叠和对齐由烧录工具或上层存储代码验证。
3.6 NAND 配置和快照
typedef struct {
im_sfc_config_t sfc_config;
uint32_t clock_rate_hz;
uint32_t param_offset;
uint8_t sample_delay_work;
} im_sfc_nand_config_t;
im_sfc_nand_config_default() 默认使用 100 MHz 工作时钟、Kconfig 参数块偏移和采样延时 4。
im_sfc_nand_config_t 的字段含义与 NOR 相同。clock_rate_hz = 0 保持初始化时钟,param_offset 指向 NAND Burner 参数块。
初始化成功后,可以从 im_sfc_nand_t 读取以下只读快照:
| 字段 | 说明 |
|---|---|
flash_info(im_sfc_nand_flash_info_t) |
匹配到的内置器件表项 |
partitions(im_sfc_nand_partitions_t) |
Burner 参数块中的分区表,读取失败时全 0 |
chip_id |
厂商 ID 和器件 ID 的组合 |
quad_enabled |
是否使用 quad 读写命令 |
ecc_status |
最近一次读操作的原始 ECC 状态 |
常用 flash_info 字段:
| 字段 | 说明 |
|---|---|
page_size / spare_size |
每页数据区和 spare 区大小 |
pages_per_block |
每块页数 |
block_count |
总块数 |
NAND 器件参数来自内置器件表。Burner 参数块只提供分区信息;参数块读取失败或格式非法时只打印警告,初始化仍可成功,此时分区表为全 0。NAND 层不检查分区是否越界、重叠或按块对齐,使用前必须由上层验证。
im_sfc_nand_burner_params_t 由 magic、partition_num 和最多 10 个分区项组成。合法参数块要求 magic 为 IM_SFC_NAND_MAGIC,partition_num 在 0~10 之间。
直接使用 NOR/NAND 层时,clock_rate_hz = 0 表示保持初始化时钟;通过设备层时该字段为 0 表示使用 100 MHz 默认工作时钟。
3.7 分区和 ECC 类型
NOR/NAND 分区表最多包含 10 项(im_sfc_nor_partitions_t / im_sfc_nand_partitions_t),每项(im_sfc_nor_partition_t / im_sfc_nand_partition_t)包含:
char name[32];
uint32_t size;
uint32_t offset;
uint32_t mask_flags;
uint32_t manager_mode;
name 不保证以 NUL 结尾,打印或比较时按最多 32 字节处理。mask_flags 和 manager_mode 是上层元数据,SFC 和 MTD 分区构造不解释。
NAND ECC 状态快照:
typedef struct {
uint8_t status;
uint8_t count;
} im_sfc_nand_ecc_status_t;
中间件只返回原始、按器件定义的 ECC 状态,不判断是否可纠正,也不自动纠正数据。
3.8 常用常量
| 宏 | 值 | 说明 |
|---|---|---|
IM_SFC_DEFAULT_TIMEOUT_MS |
3000 | 默认事务超时 |
IM_SFC_DEFAULT_SAMPLE_DELAY |
4 | 默认采样延时编码 |
IM_SFC_CLOCK_MAX_QUAD_HZ |
104 MHz | quad 模式时钟上限 |
IM_SFC_CLOCK_MAX_STANDARD_HZ |
50 MHz | 标准 SPI 时钟上限 |
IM_SFC_XIP_DEFAULT_COMMAND |
0xEB |
默认 XIP 读命令 |
IM_SFC_XIP_DEFAULT_DUMMY_CYCLES |
4 | 默认 XIP dummy cycles |
IM_SFC_NOR_DEFAULT_CLOCK_HZ |
100 MHz | NOR 默认工作时钟 |
IM_SFC_NAND_DEFAULT_CLOCK_HZ |
100 MHz | NAND 默认工作时钟 |
IM_SFC_DEVICE_ID_DEFAULT |
0 | 默认设备 ID |
3.9 MTD/DFS 边界
- 设备层启动时会把 NOR/NAND 挂接到内部 MTD,并可通过
im_sfc_device_mtd_get()取得借用指针。 dfs_mtd把 NOR 分区注册为 DFS block 设备,把 NAND 分区注册为 DFS MTD 设备;设备名等于 Burner 参数块中的分区名。- 自动挂载、手动挂载、格式化与卸载流程见 2.5 节;是否挂载由应用和 DFS 配置决定。
- MTD 挂接时会跳过空名、越界、未按擦除块/block 对齐或与其他分区重叠的项,这些分区不会出现在 DFS 中。
- MTD 分区表在挂接时生成,之后只读。
im_sfc_device_stop()只解挂 MTD,不注销 DFS 设备,也不卸载文件系统。
3.10 公开类型索引
普通应用通常只需要本文已展开的配置、状态和快照类型。以下类型由中间件或 Burner 工具填充,应用不应改写:
| 类型 | 用途 |
|---|---|
im_sfc_dma_segment_t |
描述符 DMA 的单个数据段 |
im_sfc_data_buffers_t |
Common 事务的缓冲区或 DMA 段 |
im_sfc_cdt_transfer_t |
CDT 事务请求 |
im_sfc_direct_transfer_t |
direct 事务请求 |
im_sfc_transfer_context_t |
内部事务上下文,不要读写 |
im_sfc_dma_descriptor_t |
不透明 DMA 描述符,不要分配或修改 |
im_sfc_nor_cmd_info_t / im_sfc_nor_st_info_t |
NOR 命令和状态寄存器命令描述 |
im_sfc_nor_flash_info_t |
NOR 器件参数 |
im_sfc_nor_partition_t / im_sfc_nor_partitions_t |
NOR 分区项和分区表 |
im_sfc_nor_private_data_t |
NOR Burner 私有参数 |
im_sfc_nor_burner_params_t |
NOR Burner 参数块 |
im_sfc_nand_feature_t / im_sfc_nand_ecc_field_t |
NAND feature 和 ECC 字段描述 |
im_sfc_nand_ecc_layout_t |
NAND ECC 状态布局 |
im_sfc_nand_flash_info_t |
NAND 内置器件表项 |
im_sfc_nand_partition_t / im_sfc_nand_partitions_t |
NAND 分区项和分区表 |
im_sfc_nand_burner_params_t |
NAND Burner 参数块 |
Burner 参数块、命令描述和器件描述必须保持头文件定义的二进制布局,不要重排字段或使用 #pragma pack 改变布局。NOR/NAND 的底层命令码和 CDT 槽位定义在对应头文件中,扩展驱动时再查阅。
3.11 常见错误码
| 错误码 | 常见原因 |
|---|---|
IM_ERR_INVALID_PARAM |
空指针、长度为零、地址或长度越界、擦除未对齐、NAND 跨页、DMA 缓冲区不合格 |
IM_ERR_NOT_READY |
对象未初始化、设备未启动、systick 未初始化 |
IM_ERR_TIMEOUT |
等待事务完成或 Flash 状态超时 |
IM_ERR_BUSY |
对象正在操作、处于 XIP,或控制器尚未释放 |
IM_ERR_NO_MEMORY |
DMA 描述符、bounce buffer 或临时缓冲区分配失败 |
IM_ERR_NOT_FOUND |
设备未注册,或 NOR 参数块中的器件 ID 与实际 ID 不一致 |
IM_ERR_ALREADY_EXISTS |
NOR/NAND 对象已经初始化 |
IM_ERR_UNSUPPORTED |
当前器件或构建不支持该功能 |
IM_ERR_ALREADY_REGISTERED |
同一设备 ID 已经注册 |
IM_ERR_IO |
FIFO 搬运不完整、NAND 编程/擦除失败、数据校验失败 |
IM_ERR_NO_DEVICE |
设备 ID 非法、NOR 参数块非法,或 NAND 器件不在内置表 |
IM_ERR_INVALID_STATE |
对象处于 ERROR,或当前状态不允许该操作 |
调用方应按“非 IM_OK 即失败”处理,并保留原始错误码用于日志。硬件错误码可能由 HAL、CPM、IRQ 或缓存层返回。
3.11.1 常见问题定位
| 现象 | 优先检查 |
|---|---|
NOR 初始化返回 IM_ERR_NO_DEVICE |
参数块偏移、magic、version、分区布局,以及参数块是否位于前 16 MiB |
NOR 初始化返回 IM_ERR_NOT_FOUND |
参数块中的 flash_info.id 与实际 JEDEC ID 是否一致 |
NAND 初始化返回 IM_ERR_NO_DEVICE |
芯片 ID 是否在内置器件表中 |
| 读数据不一致 | 地址和长度是否越界、写入前是否擦除、采样延时和时钟是否正确、XIP cache 是否已失效 |
NAND 读返回 IM_ERR_IO |
NAND OIP/P_FAIL/E_FAIL 状态、ECC 状态和坏块情况 |
| XIP 读校验失败 | 读命令、dummy、mode bits、地址宽度、窗口映射和 cache 属性 |
file_system_init() 成功但看不到 /data |
是否启用自动挂载、..._NAME 是否与参数块分区名一致;NOR 使用 Flash 根分区时是否只配置了 rootfs |
mount_fs() 失败 |
分区设备是否存在、分区是否按擦除块/block 对齐、elm/yaffs 是否与 Flash 类型匹配、卷是否已格式化、DFS_ELM_MAX_SECTOR_SIZE 是否覆盖 NOR 擦除块 |
| DFS 场景停止设备失败或挂载异常 | 是否先卸载文件系统,以及是否仍有其他访问者持有 MTD/DFS 设备 |
4. API 参考
4.1 API 总览
im_sfc_device.h
| API | 用途 |
|---|---|
im_sfc_device_config_default() |
填充设备层默认配置 |
im_sfc_device_config_get() |
读取已注册设备的配置副本 |
im_sfc_device_register() |
注册一个板级 SFC 设备 |
im_sfc_device_start() |
启动设备并初始化 NOR/NAND、挂接 MTD |
im_sfc_device_stop() |
解挂 MTD 并反初始化 NOR/NAND |
im_sfc_device_nor_get() |
取得内部 NOR 对象的借用指针 |
im_sfc_device_nand_get() |
取得内部 NAND 对象的借用指针 |
im_sfc_device_mtd_get() |
取得内部 MTD 对象的借用指针 |
im_sfc.h
| API | 用途 |
|---|---|
im_sfc_config_default() |
填充 Common SFC 默认配置 |
im_sfc_init() |
初始化控制器、时钟、DMA 资源和可选 IRQ |
im_sfc_clock_config() |
切换总线时钟并应用 CE/采样时序 |
im_sfc_cdt_entry_config() |
配置一个 CDT 表项 |
im_sfc_cdt_transfer() |
执行一条 CDT 命令链 |
im_sfc_direct_transfer() |
使用临时 phase 表执行一次事务 |
im_sfc_dma_buffer_alignment_is_valid() |
检查缓冲区是否满足 DMA 对齐要求 |
im_sfc_data_path_select() |
按数据长度选择 DMA 或 CPU FIFO |
im_sfc_dma_bounce_resize() |
扩容内部 bounce buffer |
im_sfc_xip_config_default() |
填充默认 XIP 配置 |
im_sfc_xip_enable() |
进入控制器 XIP 连续读模式 |
im_sfc_xip_disable() |
退出 XIP 模式并恢复普通事务 |
im_sfc_deinit() |
释放 DMA/IRQ 资源并软复位控制器 |
im_sfc_get_state() |
读取控制器状态快照 |
im_sfc_nor.h
| API | 用途 |
|---|---|
im_sfc_nor_config_default() |
填充 NOR 默认配置 |
im_sfc_nor_init() |
初始化 NOR 和内部 Common SFC |
im_sfc_nor_deinit() |
反初始化 NOR 和内部 Common SFC |
im_sfc_nor_clock_config() |
切换 NOR 工作时钟 |
im_sfc_nor_get_state() |
读取 NOR 状态快照 |
im_sfc_nor_read() |
从 Flash 字节地址读取数据 |
im_sfc_nor_write() |
向 NOR 写入数据 |
im_sfc_nor_erase() |
按擦除大小擦除 NOR |
im_sfc_nor_xip_enable() |
进入 NOR XIP 连续读模式 |
im_sfc_nor_xip_disable() |
退出 NOR XIP 并恢复普通命令模式 |
im_sfc_nand.h
| API | 用途 |
|---|---|
im_sfc_nand_config_default() |
填充 NAND 默认配置 |
im_sfc_nand_init() |
初始化 NAND 和内部 Common SFC |
im_sfc_nand_deinit() |
反初始化 NAND 和内部 Common SFC |
im_sfc_nand_clock_config() |
切换 NAND 工作时钟 |
im_sfc_nand_get_state() |
读取 NAND 状态快照 |
im_sfc_nand_read() |
从一页的指定列读取数据 |
im_sfc_nand_write() |
向一页的指定列写入数据 |
im_sfc_nand_erase() |
擦除一个 block |
im_sfc_nand_is_bad_block() |
检查出厂坏块标记 |
im_sfc_nand_get_ecc_status() |
读取最近一次页读锁存的 ECC 状态 |
4.2 通用约定
- 返回
error_code_t的接口以IM_OK表示成功,其他值表示失败。 timeout_ms == 0使用IM_SFC_DEFAULT_TIMEOUT_MS,当前为 3000 ms。- 所有 Flash 操作都是同步阻塞接口,只能在任务上下文调用。
- 所有输出参数必须指向有效内存;允许为
NULL的接口会单独说明。 - NOR/NAND/Common 对象由调用方分配时,必须先零初始化。
- 设备层返回的 NOR/NAND/MTD 指针是借用指针,不能释放,也不能跨
stop()/ 重新start()使用。
4.3 设备层 API
当前只有一个设备槽位,device_id 必须为 IM_SFC_DEVICE_ID_DEFAULT;其他 ID 返回 IM_ERR_NO_DEVICE。
im_sfc_device_config_default()
error_code_t im_sfc_device_config_default(im_sfc_device_config_t *config_out);
填充设备层默认配置。config_out 不能为 NULL。返回 IM_OK 或 IM_ERR_INVALID_PARAM。
im_sfc_device_config_get()
error_code_t im_sfc_device_config_get(uint32_t device_id,
im_sfc_device_config_t *config_out);
读取已注册设备的配置副本,不启动设备,也不访问硬件。config_out == NULL 或设备 ID 非法返回 IM_ERR_INVALID_PARAM / IM_ERR_NO_DEVICE;尚未注册返回 IM_ERR_NOT_FOUND。
im_sfc_device_register()
error_code_t im_sfc_device_register(
uint32_t device_id,
const im_sfc_device_config_t *config);
注册一个板级 SFC 设备,只复制配置,不访问 Flash。device_id 使用 IM_SFC_DEVICE_ID_DEFAULT。config == NULL 或字段非法返回 IM_ERR_INVALID_PARAM;同一 ID 已注册返回 IM_ERR_ALREADY_REGISTERED。
im_sfc_device_start()
error_code_t im_sfc_device_start(uint32_t device_id);
启动已注册设备:应用板级配置,初始化 NOR/NAND,并挂接 MTD。已启动时重复调用返回 IM_OK。未注册返回 IM_ERR_NOT_FOUND;启动失败时返回底层第一个错误,并清理本次资源,后续可以重试。
im_sfc_device_stop()
error_code_t im_sfc_device_stop(uint32_t device_id);
解挂 MTD,并反初始化 NOR/NAND。已停止时重复调用返回 IM_OK。停止后设备仍保持注册状态,可以再次启动。失败时返回清理过程中的第一个错误,并可能只完成部分清理,此时不要继续使用借用指针。该接口不会注销 DFS 设备,也不会卸载文件系统。
im_sfc_device_nor_get()
im_sfc_nor_t *im_sfc_device_nor_get(uint32_t device_id);
NOR 构建可用。设备处于 READY 时返回内部 NOR 对象,否则返回 NULL。
im_sfc_device_nand_get()
im_sfc_nand_t *im_sfc_device_nand_get(uint32_t device_id);
NAND 构建可用。设备处于 READY 时返回内部 NAND 对象,否则返回 NULL。
im_sfc_device_mtd_get()
im_mtd_t *im_sfc_device_mtd_get(uint32_t device_id);
设备处于 READY 时返回内部 MTD 对象,否则返回 NULL。MTD 对象只在当前一次启动生命周期内有效。
4.4 Common SFC API
Common SFC 接口主要用于自定义命令、DMA 和控制器级 XIP。普通 NOR/NAND 应用优先使用高层 API。Common SFC 对象不串行化并发调用;NOR/NAND 高层会串行化各自的完整 Flash 操作,直接使用内嵌 sfc 时需要外部加锁。
Common API 的状态错误约定:未初始化返回 IM_ERR_NOT_READY,对象处于 ERROR 返回 IM_ERR_INVALID_STATE,正在执行事务或处于 XIP 返回 IM_ERR_BUSY,等待硬件超时返回 IM_ERR_TIMEOUT。
im_sfc_config_default()
error_code_t im_sfc_config_default(im_sfc_config_t *config_out);
填充 Common SFC 默认配置。config_out == NULL 时返回 IM_ERR_INVALID_PARAM。
im_sfc_init()
error_code_t im_sfc_init(im_sfc_t *sfc, const im_sfc_config_t *config);
初始化控制器、时钟、DMA 资源和可选 IRQ。sfc 必须零初始化,config 的内容会被复制。成功后状态为 READY。
重复初始化前必须先成功 im_sfc_deinit()。常见错误包括 IM_ERR_INVALID_PARAM、IM_ERR_INVALID_STATE、IM_ERR_BUSY、IM_ERR_NO_MEMORY 和 IRQ/CPM 错误。CONFIG_IRQ_SIMPLE_DISPATCH 构建中 use_irq 必须为 false。
im_sfc_clock_config()
error_code_t im_sfc_clock_config(im_sfc_t *sfc,
uint32_t rate_hz,
const im_sfc_clock_timing_t *timing,
uint32_t *actual_hz_out);
切换 SFC 总线时钟并应用 CE/采样时序。rate_hz == 0 时不修改 CPM 频率;timing == NULL 时保持当前 CE/采样编码。actual_hz_out 可为 NULL。普通 NOR/NAND 应优先使用各自的 clock_config()。
im_sfc_cdt_entry_config()
error_code_t im_sfc_cdt_entry_config(
im_sfc_t *sfc,
uint8_t entry_index,
const hal_sfc_cdt_entry_config_t *config);
配置一个 CDT 表项。NOR/NAND 中间件会占用各自的 CDT 槽位,应用不要覆盖;自定义命令应使用空闲槽位并自行保证串行化。
im_sfc_cdt_transfer()
error_code_t im_sfc_cdt_transfer(im_sfc_t *sfc,
const im_sfc_cdt_transfer_t *transfer,
uint32_t timeout_ms);
执行一条 CDT 命令链。数据 phase 根据方向使用 tx_buffer 或 rx_buffer,也可以按 dma_segments 使用描述符 DMA。调用期间缓冲区必须由 SFC 独占。
execution.data_enabled == false 时不搬运数据,buffers 应保持全 0。
DMA 缓冲区要求:
- 直接 DMA:缓冲区物理连续,起始地址按 cache line 对齐,长度为 cache line 的整数倍。
- 描述符 DMA:
tx_buffer/rx_buffer为空,dma_segments非空;1~64 个物理连续段,每段按 cache line 对齐且长度为整数倍,data_transfer.transfer_length填 0,由中间件按段长求和。 - 非描述符模式:
dma_segments == NULL,段数为 0。 - 普通 NOR/NAND 高层接口会自动处理不满足对齐要求的缓冲区。
im_sfc_direct_transfer()
error_code_t im_sfc_direct_transfer(im_sfc_t *sfc,
const im_sfc_direct_transfer_t *transfer,
uint32_t timeout_ms);
使用请求中临时提供的 phase 表执行一次事务,不依赖 CDT 中预存的命令链。缓冲区和 DMA 规则与 im_sfc_cdt_transfer() 相同。
im_sfc_dma_buffer_alignment_is_valid()
bool im_sfc_dma_buffer_alignment_is_valid(const void *buffer, uint32_t length);
检查缓冲区是否按 cache line 对齐且长度为 cache line 的整数倍。返回 true 表示可以直接交给 DMA。
im_sfc_data_path_select()
hal_sfc_operation_mode_t im_sfc_data_path_select(uint32_t length);
按长度选择数据通路:长度大于等于 1024 字节返回 DMA,否则返回 CPU FIFO。普通应用不需要调用。
im_sfc_dma_bounce_resize()
error_code_t im_sfc_dma_bounce_resize(im_sfc_t *sfc, uint32_t min_size);
扩容内部 bounce buffer。该接口主要供 NAND 层在识别器件后使用,普通应用不需要调用。
im_sfc_xip_config_default()
error_code_t im_sfc_xip_config_default(im_sfc_xip_config_t *config_out);
填充默认 XIP 配置。config_out == NULL 时返回 IM_ERR_INVALID_PARAM。直接使用 Common XIP 时,调用方还必须填写 read_phase.phase.address_width 和 preset_cdt_index。
im_sfc_xip_enable()
error_code_t im_sfc_xip_enable(im_sfc_t *sfc, const im_sfc_xip_config_t *config);
让控制器进入 XIP 连续读模式。sfc 必须处于 READY。成功后状态为 XIP,普通事务接口返回 IM_ERR_BUSY。NOR 应用建议使用 im_sfc_nor_xip_enable()。
失败时不要调用 xip_disable() 做回滚;对象通常已回到 READY 或进入 ERROR,应按 im_sfc_get_state() 清理。
im_sfc_xip_disable()
error_code_t im_sfc_xip_disable(im_sfc_t *sfc, uint32_t timeout_ms);
退出控制器 XIP 模式并回到 READY。该函数必须在非 XIP 内存中执行。失败时可能保持 XIP 或进入 ERROR;应根据 im_sfc_get_state() 的结果重试或调用 im_sfc_deinit()。
im_sfc_deinit()
error_code_t im_sfc_deinit(im_sfc_t *sfc);
释放 DMA/IRQ 资源并软复位控制器。可以从 ERROR 状态重试。若对象仍在 XIP,应先通过 NOR 接口发送 Flash XIP 退出命令;Common 层 deinit() 不会发送该退出帧。deinit() 不关闭 SFC 时钟。
im_sfc_get_state()
im_sfc_state_t im_sfc_get_state(const im_sfc_t *sfc);
返回当前状态快照;sfc == NULL 时返回 IM_SFC_STATE_UNINITIALIZED。
4.5 NOR API
NOR 读、写、擦除要求对象处于 READY。未初始化返回 IM_ERR_NOT_READY,ERROR 返回 IM_ERR_INVALID_STATE,对象忙或处于 XIP 返回 IM_ERR_BUSY。
im_sfc_nor_config_default()
error_code_t im_sfc_nor_config_default(im_sfc_nor_config_t *config_out);
填充 NOR 默认配置:100 MHz、Kconfig 参数块偏移、采样延时 4。config_out 不能为 NULL。
im_sfc_nor_init()
error_code_t im_sfc_nor_init(im_sfc_nor_t *nor,
const im_sfc_nor_config_t *config);
初始化 NOR 和内部 Common SFC,读取 Burner 参数块,校验 JEDEC ID,设置工作时钟,并进入 quad/4-byte 模式。成功后状态为 READY。
常见错误:参数非法返回 IM_ERR_INVALID_PARAM;参数块 magic/version 错误返回 IM_ERR_NO_DEVICE;器件 ID 不匹配返回 IM_ERR_NOT_FOUND;已初始化返回 IM_ERR_ALREADY_EXISTS。
im_sfc_nor_deinit()
error_code_t im_sfc_nor_deinit(im_sfc_nor_t *nor);
反初始化 NOR 和内部 Common SFC。可以从 XIP 状态强制退出。Flash 器件本身保持当前模式,不会自动恢复到标准 SPI 模式。
im_sfc_nor_clock_config()
error_code_t im_sfc_nor_clock_config(im_sfc_nor_t *nor,
uint32_t clock_rate_hz,
uint8_t sample_delay,
uint32_t *actual_hz_out);
切换 NOR 工作时钟。频率会按当前器件配置截断到允许上限,actual_hz_out 返回实际生效频率,可为 NULL。clock_rate_hz == 0 表示保持当前频率。
im_sfc_nor_get_state()
im_sfc_nor_state_t im_sfc_nor_get_state(const im_sfc_nor_t *nor);
返回 NOR 状态快照;nor == NULL 时返回 IM_SFC_NOR_STATE_UNINITIALIZED。
im_sfc_nor_read()
error_code_t im_sfc_nor_read(im_sfc_nor_t *nor,
uint32_t address,
uint32_t length,
uint8_t *buffer,
uint32_t timeout_ms);
从 Flash 字节地址读取数据。address + length 不能超过 flash_info.chip_size。接口根据当前模式自动选择 quad 或标准读命令,并自动处理 DMA 对齐。
length 必须非零,buffer 必须有效。
im_sfc_nor_write()
error_code_t im_sfc_nor_write(im_sfc_nor_t *nor,
uint32_t address,
uint32_t length,
const uint8_t *buffer,
uint32_t timeout_ms);
向 NOR 写入数据。单次页编程不会跨页,但一次调用可以跨多个页。写入前必须擦除目标范围。整次调用不是原子操作,中途失败时前面的页可能已经写入。
length 必须非零,buffer 必须有效;timeout_ms 约束每一页的编程等待。
im_sfc_nor_erase()
error_code_t im_sfc_nor_erase(im_sfc_nor_t *nor,
uint32_t address,
uint32_t length,
uint32_t timeout_ms);
按 flash_info.erase_size 擦除 NOR。address 和 length 都必须按擦除大小对齐。timeout_ms 约束每个擦除块,多块擦除可能等待多个超时周期。中途失败时不回滚已擦除的块。
length 必须非零且为擦除大小的整数倍;不支持通过该接口执行整片擦除。
im_sfc_nor_xip_enable()
error_code_t im_sfc_nor_xip_enable(im_sfc_nor_t *nor,
const im_sfc_xip_config_t *config);
进入 NOR XIP 连续读模式。config == NULL 时固定使用 0xEB、4 个 dummy、quad I/O,不检查 quad_enabled;非 quad 或特殊器件必须传入显式配置。
成功只说明控制器侧已进入 XIP。调用方必须读取 XIP 窗口并校验数据,失败时立即退出 XIP。
enable 失败时 NOR 对象不会停留在 XIP,不要用 disable() 回滚;应先按 im_sfc_nor_get_state() 清理,再重新初始化或重新上电。
im_sfc_nor_xip_disable()
error_code_t im_sfc_nor_xip_disable(im_sfc_nor_t *nor, uint32_t timeout_ms);
发送退出帧并恢复普通命令模式。必须从非 XIP 内存调用,且不能再从 XIP 窗口取指或访问数据。返回失败时按 im_sfc_nor_get_state() 处理:仍在 XIP 可以重试,进入 ERROR 后调用 im_sfc_nor_deinit()。
4.6 NAND API
NAND 读、写、擦除要求对象处于 READY。未初始化返回 IM_ERR_NOT_READY,ERROR 返回 IM_ERR_INVALID_STATE,对象忙返回 IM_ERR_BUSY。
im_sfc_nand_config_default()
error_code_t im_sfc_nand_config_default(im_sfc_nand_config_t *config_out);
填充 NAND 默认配置:100 MHz、Kconfig 参数块偏移、采样延时 4。config_out 不能为 NULL。
im_sfc_nand_init()
error_code_t im_sfc_nand_init(im_sfc_nand_t *nand,
const im_sfc_nand_config_t *config);
初始化 NAND 和内部 Common SFC:复位、读取 ID、匹配内置器件表、配置时钟、QE/ECC_EN,并尝试读取分区参数块。成功后状态为 READY。
器件不在内置表中时返回 IM_ERR_NO_DEVICE;参数非法返回 IM_ERR_INVALID_PARAM;已初始化返回 IM_ERR_ALREADY_EXISTS。当前只支持单 die 器件。
im_sfc_nand_deinit()
error_code_t im_sfc_nand_deinit(im_sfc_nand_t *nand);
反初始化 NAND 和内部 Common SFC。可以从 ERROR 状态调用。失败时按返回值重试,不要继续使用对象。
im_sfc_nand_clock_config()
error_code_t im_sfc_nand_clock_config(im_sfc_nand_t *nand,
uint32_t clock_rate_hz,
uint8_t sample_delay,
uint32_t *actual_hz_out);
切换 NAND 工作时钟。频率会按当前器件配置截断到允许上限,actual_hz_out 可为 NULL。clock_rate_hz == 0 表示保持当前频率。
im_sfc_nand_get_state()
im_sfc_nand_state_t im_sfc_nand_get_state(const im_sfc_nand_t *nand);
返回 NAND 状态快照;nand == NULL 时返回 IM_SFC_NAND_STATE_UNINITIALIZED。
im_sfc_nand_read()
error_code_t im_sfc_nand_read(im_sfc_nand_t *nand,
uint32_t block,
uint32_t page,
uint32_t column,
uint8_t *buffer,
uint32_t length,
uint32_t timeout_ms);
从一页的指定列读取数据。必须满足:
block < flash_info.block_countpage < flash_info.pages_per_blockcolumn + length <= flash_info.page_size + flash_info.spare_size
一次调用不能跨页。数据读取完成后,中间件会尝试锁存本次原始 ECC 状态;不自动纠正数据。
length 必须非零,buffer 必须有效;timeout_ms 只约束本次页读主流程,后续 ECC 状态读取使用默认超时。
im_sfc_nand_write()
error_code_t im_sfc_nand_write(im_sfc_nand_t *nand,
uint32_t block,
uint32_t page,
uint32_t column,
const uint8_t *buffer,
uint32_t length,
uint32_t timeout_ms);
向一页的指定列写入数据,约束与 im_sfc_nand_read() 相同。接口不执行 read-modify-write;写部分页时,调用方必须自行保留未写区域,或确保目标页已经按预期擦除。
length 必须非零,buffer 必须有效;timeout_ms 约束本次页编程主流程。
im_sfc_nand_erase()
error_code_t im_sfc_nand_erase(im_sfc_nand_t *nand,
uint32_t block,
uint32_t timeout_ms);
擦除一个 block。擦除会破坏出厂坏块标记;需要保留坏块信息时,应在擦除前调用 im_sfc_nand_is_bad_block() 并自行记录。
block 必须小于 flash_info.block_count;timeout_ms 约束擦除主流程,后续状态确认使用默认超时。
im_sfc_nand_is_bad_block()
error_code_t im_sfc_nand_is_bad_block(im_sfc_nand_t *nand,
uint32_t block,
bool *bad_out);
检查出厂坏块标记,结果写入 bad_out。block 必须小于 flash_info.block_count。中间件检查 page 0 的第一个 spare 字节,读取失败时回退到 page 1;任何非 0xFF 值都视为坏块。打开 CONFIG_MIDDLEWARE_SFC_NAND_SKIP_BADBLOCK 后不访问硬件,始终报告为好块。
im_sfc_nand_get_ecc_status()
error_code_t im_sfc_nand_get_ecc_status(im_sfc_nand_t *nand,
im_sfc_nand_ecc_status_t *status_out);
返回最近一次 NAND 读操作锁存的原始 ECC 状态,不访问 Flash。器件不支持 ECC 状态寄存器时返回 IM_ERR_UNSUPPORTED。状态字段的语义由具体器件定义。
5. 典型完整示例
文件系统挂载和文件访问见 2.5 节;本节示例均为裸 Flash 访问。
5.1 NOR 读写擦除
以下示例只示意调用顺序。DEMO_FLASH_ADDR 必须是产品预留区域,不能覆盖 SPL、参数块、固件或文件系统。
#include <im_sfc_nor.h>
#include <string.h>
#define DEMO_FLASH_ADDR (0x100000U)
#define DEMO_LENGTH (256U)
static im_sfc_nor_t s_nor;
static error_code_t nor_demo(void)
{
im_sfc_nor_config_t config;
uint8_t pattern[DEMO_LENGTH];
uint8_t verify[DEMO_LENGTH];
uint32_t erase_size;
uint32_t address = DEMO_FLASH_ADDR;
error_code_t ret;
memset(&s_nor, 0, sizeof(s_nor));
ret = im_sfc_nor_config_default(&config);
if (ret != IM_OK)
return ret;
ret = im_sfc_nor_init(&s_nor, &config);
if (ret != IM_OK)
return ret;
erase_size = s_nor.flash_info.erase_size;
if ((erase_size == 0U) || (DEMO_LENGTH > erase_size)) {
ret = IM_ERR_INVALID_PARAM;
goto out;
}
address -= address % erase_size;
memset(pattern, 0x5A, sizeof(pattern));
ret = im_sfc_nor_erase(&s_nor, address, erase_size, 0U);
if (ret != IM_OK)
goto out;
ret = im_sfc_nor_write(&s_nor, address, sizeof(pattern), pattern, 0U);
if (ret != IM_OK)
goto out;
memset(verify, 0, sizeof(verify));
ret = im_sfc_nor_read(&s_nor, address, sizeof(verify), verify, 0U);
if ((ret == IM_OK) && (memcmp(pattern, verify, sizeof(pattern)) != 0))
ret = IM_ERR_IO;
out:
(void)im_sfc_nor_deinit(&s_nor);
return ret;
}
5.2 NAND 页读写
NAND 示例只使用一个已擦除的安全测试 block。实际产品必须结合坏块管理和文件系统分区策略。
#include <im_sfc_nand.h>
#include <string.h>
#define DEMO_NAND_BLOCK (16U)
#define DEMO_NAND_PAGE (0U)
#define DEMO_NAND_LEN (64U)
static im_sfc_nand_t s_nand;
static error_code_t nand_demo(void)
{
im_sfc_nand_config_t config;
im_sfc_nand_ecc_status_t ecc;
uint8_t pattern[DEMO_NAND_LEN];
uint8_t verify[DEMO_NAND_LEN];
bool bad = false;
error_code_t ret;
memset(&s_nand, 0, sizeof(s_nand));
ret = im_sfc_nand_config_default(&config);
if (ret != IM_OK)
return ret;
ret = im_sfc_nand_init(&s_nand, &config);
if (ret != IM_OK)
return ret;
if (DEMO_NAND_BLOCK >= s_nand.flash_info.block_count) {
ret = IM_ERR_INVALID_PARAM;
goto out;
}
ret = im_sfc_nand_is_bad_block(&s_nand, DEMO_NAND_BLOCK, &bad);
if (ret != IM_OK)
goto out;
if (bad) {
ret = IM_ERR_UNSUPPORTED;
goto out;
}
memset(pattern, 0x3C, sizeof(pattern));
ret = im_sfc_nand_erase(&s_nand, DEMO_NAND_BLOCK, 0U);
if (ret != IM_OK)
goto out;
ret = im_sfc_nand_write(&s_nand, DEMO_NAND_BLOCK, DEMO_NAND_PAGE,
0U, pattern, sizeof(pattern), 0U);
if (ret != IM_OK)
goto out;
memset(verify, 0, sizeof(verify));
ret = im_sfc_nand_read(&s_nand, DEMO_NAND_BLOCK, DEMO_NAND_PAGE,
0U, verify, sizeof(verify), 0U);
if (ret != IM_OK)
goto out;
if (memcmp(pattern, verify, sizeof(pattern)) != 0) {
ret = IM_ERR_IO;
goto out;
}
ret = im_sfc_nand_get_ecc_status(&s_nand, &ecc);
if (ret == IM_ERR_UNSUPPORTED)
ret = IM_OK;
out:
(void)im_sfc_nand_deinit(&s_nand);
return ret;
}
5.3 NOR XIP 读取
假设 s_nor 已通过 im_sfc_nor_init() 初始化。示例使用 0x16000000 作为窗口基址;产品必须使用自己的地址映射,并确认 cache 属性。
推荐执行顺序:
- 停止所有 Flash 访问、DMA 和中断处理。
- 确保后续代码不从待使用的 XIP 窗口取指。
- 用普通读接口取得参考数据。
- 调用
im_sfc_nor_xip_enable()。 - 失效 XIP 窗口对应的 D-cache,读取窗口并校验数据。
- 从非 XIP 内存调用
im_sfc_nor_xip_disable(),再用普通读接口复读校验。
#include <im_cache.h>
#include <im_sfc_nor.h>
#include <string.h>
#define XIP_WINDOW_BASE (0x16000000UL)
#define XIP_FLASH_OFFSET (0x400000UL)
#define XIP_TEST_LENGTH (64U)
static error_code_t xip_demo(void)
{
const volatile uint8_t *window;
uint8_t expected[XIP_TEST_LENGTH];
uint8_t observed[XIP_TEST_LENGTH];
error_code_t ret;
error_code_t verify_ret;
window = (const volatile uint8_t *)(XIP_WINDOW_BASE + XIP_FLASH_OFFSET);
ret = im_sfc_nor_read(&s_nor, XIP_FLASH_OFFSET,
sizeof(expected), expected, 0U);
if (ret != IM_OK)
return ret;
ret = im_sfc_nor_xip_enable(&s_nor, NULL);
if (ret != IM_OK)
return ret;
sys_cache_data_invalidate_by_addr((const void *)(uintptr_t)window,
sizeof(observed));
memcpy(observed, (const void *)(uintptr_t)window, sizeof(observed));
verify_ret = memcmp(expected, observed, sizeof(observed)) == 0 ?
IM_OK : IM_ERR_IO;
ret = im_sfc_nor_xip_disable(&s_nor, 0U);
if (ret != IM_OK)
return ret;
return verify_ret;
}
XIP 期间不能执行普通 NOR 读写擦除,也不能从正在禁用的 XIP 窗口取指。
6. 使用约束和检查清单
6.1 关键约束
- NOR 和 NAND 不能在同一构建中同时启用。
- 所有 Flash 操作只能在任务上下文调用,不能从 ISR 调用。
- NOR 擦除必须按
flash_info.erase_size对齐;NAND 以整个 block 擦除。 - NOR 写入前必须先擦除。多页写入和多块擦除都不是原子操作。
- NAND 一次读或写不能跨页;部分页写入不做 read-modify-write。
- NAND 擦除会破坏出厂坏块标记,需要时先读取并保存坏块信息。
- NOR 参数块必须位于前 16 MiB,且 magic、version、命令表、时序、容量和器件 ID 必须完整有效。
- SFC 不检查分区越界、重叠和对齐;MTD 会跳过明显非法的项,应用仍需校验完整分区布局。
- NOR 文件系统使用
elm,NAND 文件系统使用yaffs;DFS 设备名必须与参数块分区名一致。 dfs_mkfs()和自动格式化都会清除分区内容;量产流程不要依赖自动格式化。- XIP 只适用于 NOR。进入后必须读回校验,退出时必须在非 XIP 内存执行。
- DFS 挂载期间不要停止设备。停止前先卸载文件系统并确认没有其他访问者。
- 不要长期缓存设备层返回的 NOR/NAND/MTD 指针。
6.2 首次集成检查
- 已启用
CONFIG_DRIVER_SFC、CONFIG_OS_API,并选择CONFIG_MIDDLEWARE_SFC及 NOR/NAND 之一。 - BSP 已提供合法的
sample_delay_*、CE/HOLD/WP 空闲电平和时钟配置。 -
board_sfc_register_devices()调用im_sfc_device_register(),并接受IM_ERR_ALREADY_REGISTERED。 - NOR Burner 参数块位于前 16 MiB,且与烧录工具、分区布局一致。
- NAND 器件 ID 在内置表中;需要分区时参数块格式正确。
- DFS 场景已确认
file_system_init()、DFS 设备注册和文件系统挂载成功;无 DFS 场景已自行调用im_sfc_device_start()。 - DFS 后端、根目录模式和分区名正确;NOR 使用
elm,NAND 使用yaffs。 - 首次挂载前已格式化分区,或明确启用了自动格式化并接受数据被清除。
- 已检查
im_sfc_device_nor_get()/nand_get()/mtd_get()的返回值和生命周期。 - 读、写、擦除只在预留测试区域进行。
- NOR 擦写后已回读校验;NAND 已检查坏块和 ECC 状态。
- 使用 XIP 时已确认窗口地址、cache 属性、退出命令和回读校验。
- 关机或重新初始化前已关闭文件、卸载文件系统,并按层级执行
stop()/deinit()。