存储、参数与时钟
本文档介绍 AW33N BLE SDK 中的 Flash 参数存储(VM 存储系统)、参数读写机制以及 RTC 实时时钟/闹钟模块的实现原理、使用方式与配置选项。
Purpose and Scope
本页面向「存储、参数与时钟」这一子系统,覆盖以下内容:
- Flash 存储分区与 VM 参数区:启动阶段如何通过 VFS 定位
/app_area_head/VM分区、记录起始地址与大小、注册 Flash 写保护回调,为参数持久化奠定基础。 - 参数存储机制:VM(Value Management)存储系统如何承载用户参数(配对信息、配置项等)。
- RTC 实时时钟:系统时间初始化、读写时间/闹钟接口、闹钟中断回调以及低功耗定时唤醒(LPTMR)的工作方式。
本页不涉及以下内容,它们属于其他目录页:
- 具体蓝牙协议栈(GATT/GAP)行为——见对应蓝牙协议章节;
- 通用文件系统(如 nor_fs)的完整实现——本页只涉及 VM 参数区相关的挂载与属性获取;
- 电源管理整体的低功耗策略——本页仅说明与 RTC 唤醒相关的接口调用方式。
Overview
在嵌入式 BLE 设备中,以下三类需求贯穿整个产品生命周期:
- 参数持久化:蓝牙配对信息、设备配置、用户设置需要在掉电后仍然保留,因此必须有一个可靠的 Flash 存储区,并配合擦写保护机制防止误操作破坏固件区。
- 实时时钟(RTC):设备需要维护当前时间(年/月/日/时/分/秒),即使进入低功耗模式也要能计时,并在指定时刻触发闹钟事件。
- 低功耗唤醒:RTC 常作为低功耗场景下的定时唤醒源,配合软关机(soft poweroff)实现"定时开机"类功能。
SDK 将这三者组织为紧密协作的层次:flash_init.c 在启动早期完成 VM 参数区的定位与保护配置;vm/vm_sfc 提供参数读写服务;rtc_demo.c 展示了 RTC 驱动在应用层的标准用法(初始化、读写时间/闹钟、闹钟回调、定时唤醒)。
从源码观察到的关键设计意图:
- 启动时序前置:
flash_info_init()在系统早期执行,通过 VFS 打开逻辑路径/app_area_head/VM而非直接使用物理地址,屏蔽了不同 Flash 布局差异(见 flash_init.c)。 - 写保护回调:
flash_code_protect_callback以 VM 起始地址为界限,任何越过该界限的擦写操作都会被拒绝,从驱动层杜绝固件区被误擦(见 flash_init.c)。 - RTC 首次上电才写时间:
rtc_demo通过复位源判断(is_reset_source(P33_VDDIO_POR_RST))只在首次上电时写入默认时间/闹钟,避免每次复位都覆盖用户设置的时间(见 rtc_demo.c)。
Architecture
下图展示了存储与时钟两个子系统的层次结构与依赖关系:
flowchart TD
subgraph sg_App["应用层 (apps/app)"]
RtcDemo["rtc_demo (TCFG_APP_RTC_EN)"]
FlashInit["flash_info_init (启动早期)"]
ParamUser["业务参数读写 (配对/配置)"]
end
subgraph sg_Sys["系统服务层"]
Vfs["VFS 文件系统"]
BootInfo["boot_info.vm 分区信息"]
TZFlash["tzflash 安全分区"]
PowerMgr["电源管理 (power_interface)"]
end
subgraph sg_Drv["驱动层"]
RtcDev["rtc_dev 驱动 (rtc/rtc_dev.h)"]
VmSfc["vm_sfc 存储"]
SfcDev["SFC Flash 设备"]
end
FlashInit -->|"vfs_mount / vfs_openbypath('/app_area_head/VM')"| Vfs
Vfs --> VmSfc
VmSfc --> SfcDev
FlashInit -->|"FS_IOCTL_FILE_ATTR -> vm_attr"| BootInfo
FlashInit -->|"IOCTL_SET_PROTECT_INFO"| SfcDev
FlashInit -->|"IOCTL_SET_PART_INFO"| TZFlash
ParamUser -->|"vm 读写接口"| VmSfc
RtcDemo -->|"rtc_init / rtc_read_time / rtc_write_alarm"| RtcDev
RtcDemo -->|"rtc_lptmr_wakeup_enable"| PowerMgr
RtcDemo -->|"power_set_soft_poweroff"| PowerMgr
各组成部分职责:
| 组件 | 来源文件 | 职责 |
|---|---|---|
flash_info_init() | apps/app/bsp/start/flash_init.c | 启动早期完成 VFS 挂载、定位 VM 分区、注册写保护、设置安全分区信息 |
flash_code_protect_callback() | apps/app/bsp/start/flash_init.c | Flash 擦写保护回调,阻止越过 VM 区界限的写操作 |
boot_info.vm | apps/app/bsp/start/flash_init.c | 记录 VM 参数区物理起始地址 vm_saddr 与大小 vm_size |
vm_sfc 存储 | apps/app/bsp/start/flash_init.c(含 vm_sfc.h) | 基于 SFC Flash 的键值参数存储,受 VM_SFC_ENABLE 宏控制 |
rtc_dev 驱动 | apps/include_lib/device/rtc/rtc_dev.h | RTC 硬件驱动:时间/闹钟读写、中断回调注册 |
rtc_demo | apps/app/bsp/common/rtc/rtc_demo.c | RTC 模块的应用层示例,展示标准调用序列与测试流程 |
设计意图说明: 存储路径与时钟路径在系统服务层解耦——存储依赖 VFS 抽象(逻辑路径映射到物理 Flash 分区),时钟依赖设备驱动抽象(rtc_dev)。应用层只需面对 vm_* 接口与 rtc_* 接口,无需关心底层 Flash 地址与硬件寄存器,这为不同 Flash 容量/布局与不同 RTC 外设的产品复用提供了可能。
存储子系统:Flash 分区与 VM 参数区
启动期 VM 分区定位
VM 参数区不是由应用直接计算地址得到的,而是通过 VFS 的逻辑路径 /app_area_head/VM 打开一个"文件",再读取其属性获得物理信息。这样 Flash 布局调整(例如增加固件区、调整分区表)时,应用代码无需改动:
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)
err = vfs_ioctl(pvfile, FS_IOCTL_FILE_ATTR, (int)&vm_attr);
ASSERT(!err, "fii vfs ioctl : 0x%x\n", err)
log_info("file size : 0x%x\nfile sclust : 0x%x\n", vm_attr.fsize, vm_attr.sclust);
Source: flash_init.c
关键点:
vfs_openbypath返回的pvfile句柄在完成FS_IOCTL_FILE_ATTR后立即关闭(vfs_file_close/vfs_fs_close),说明 VM 分区定位是一次性启动动作,不长期占用文件句柄。struct vfs_attr中的sclust(起始簇/起始地址)与fsize(大小)随后被写入boot_info.vm,供整个系统的存储服务引用:
boot_info.vm.vm_saddr = vm_attr.sclust;
boot_info.vm.vm_size = vm_attr.fsize;
Source: flash_init.c
擦写保护:flash_code_protect_callback
这是存储子系统最关键的安全设计。回调以 VM 区起始地址 vm_attr.sclust 为下限、以整片 Flash 大小为上限,任何越过该范围的擦写请求都会被拒绝:
AT_SPI_CODE/*该函数放置段不可更改*/
u32 flash_code_protect_callback(u32 offset, u32 len)
{
u32 limit_addr = vm_attr.sclust;
/* log_info("0x%x 0x%x", limit_addr, offset); */
if ((offset < limit_addr) || ((offset + len) > boot_info.flash_size)) {
/* 超过正常擦写区域,不进行擦写操作 */
return 1;
} else {
/* 进行擦写操作 */
return 0;
}
}
Source: flash_init.c
设计意图:
- 函数声明使用
AT_SPI_CODE段属性,注释明确"该函数放置段不可更改"——因为写保护回调必须常驻内存(或在固定段),不能因 Flash 擦写导致自身代码不可访问,这是一个针对自擦除风险(self-erase)的防御性设计。 - 回调通过
dev_ioctl(device, IOCTL_SET_PROTECT_INFO, (u32)flash_code_protect_callback)注册到 SFC 设备(__SFC_NANE),保护在驱动层生效,任何上层模块(包括参数服务本身)都无法绕过(见 flash_init.c)。
安全分区信息
除写保护外,flash_info_init 还会把 VM 区及固件区之外的区域配置为非加密分区(IOCTL_SET_PART_INFO),并与 OTP/INI 配置区信息(sydf_get_top_area_info("otp_cfg", ...))配合,确保带安全引擎(tzflash)的平台能正确区分加密/非加密存储域:
u32 unenc_part[2];
unenc_part[0] = boot_info.vm.vm_saddr;
unenc_part[1] = FLASH_CPU_ADDR_END - boot_info.sfc.app_addr + boot_info.sfc.sfc_base_addr;
tzflash_ioctl(NULL, IOCTL_SET_PART_INFO, (u32)unenc_part);
Source: flash_init.c
VM 存储使能与访问入口
VM(Value Management)是基于 Flash 的键值存储服务。从源码可见其使能与配置通过编译宏控制:
flash_init.c同时包含vm.h与vm_sfc.h,其中vm_sfc为基于 SFC(SPI Flash Controller)的 VM 实现;apps/app/bsp/device/device_list.c中以#ifdef VM_SFC_ENABLE/#if VM_SFC_ENABLE条件编译,控制设备列表中的存储设备注册(见 device_list.c)。
分区初始化流程可总结为下图:
flowchart TD
Start(["上电启动"]) --> Mount["vfs_mount 挂载文件系统"]
Mount --> Open["vfs_openbypath('/app_area_head/VM')"]
Open --> Ioctl["vfs_ioctl(FS_IOCTL_FILE_ATTR)"]
Ioctl --> Attr["获取 vm_attr: sclust 起始地址 / fsize 大小"]
Attr --> Boot["写入 boot_info.vm.vm_saddr / vm_size"]
Boot --> Prot["注册 flash_code_protect_callback 写保护"]
Prot --> Part["tzflash 设置非加密分区 / 读取 otp_cfg"]
Part --> Done(["VM 参数区就绪,业务可读写"])
Done --> VMRead["vm 键值读写 (vm_sfc)"]
参数持久化的使用场景
VM 参数区通常承载以下数据(依据 SDK 典型用法与设备列表注册推断,具体键值定义在业务层):
- 蓝牙配对信息(对端地址、配对记录、连接密钥);
- 用户配置(音量、语言、工作模式等);
- 工厂校准数据与 OTP 配置(与
otp_cfg/ini_cfg区域配合,见sdfile_get_votp_cfg_addr_len与sdfile_get_ini_cfg_info,flash_init.c)。
时钟子系统:RTC 实时时钟
模块使能与数据结构
RTC 模块整体由宏 TCFG_APP_RTC_EN 控制,未定义时整个 rtc_demo.c 不参与编译(见 rtc_demo.c)。
时间通过 struct sys_time 传递,包含年/月/日/时/分/秒六个字段。示例代码定义了三个时间对象:默认系统时间、写时钟测试时间、写闹钟测试时间:
static struct sys_time def_sys_time = { //初始化系统时间
.year = 2020,
.month = 1,
.day = 1,
.hour = 0,
.min = 0,
.sec = 0,
};
struct sys_time rtc_time_test = { //写时钟接口测试
.year = 2024,
.month = 11,
.day = 30,
.hour = 23,
.min = 59,
.sec = 50,
};
struct sys_time alarm_time_test = { //写闹钟接口测试
.year = 2024,
.month = 12,
.day = 1,
.hour = 0,
.min = 0,
.sec = 10,
};
Source: rtc_demo.c
初始化配置:rtc_dev_platform_data
驱动初始化接受一个平台配置结构,其中 default_sys_time 指定默认系统时间,cbfun 为闹钟事件回调:
void rtc_alm_isr(const struct rtc_event_t *ev) //闹钟回调函数测试
{
if (ev->event & BIT(RTC_ALARM_EVENT)) {
log_info("alarm_timeout!!!\n\n");
}
}
struct rtc_dev_platform_data rtc_config_test = {
.default_sys_time = &def_sys_time, //配置默认系统时钟
.cbfun = rtc_alm_isr,
};
Source: rtc_demo.c
设计意图:
- 回调通过
struct rtc_event_t的事件位(BIT(RTC_ALARM_EVENT))区分中断来源,而不是直接绑定单一回调,为将来扩展(如秒中断、日期变更事件)预留了空间; - 默认时间注入放在平台配置中,使驱动本身不硬编码时间,产品可在不改驱动的情况下定制出厂时间。
时间/闹钟读写与首次上电判断
rtc_demo() 是完整的驱动验收流程。其核心逻辑是只在首次上电(POR 复位)时写入测试时间与闹钟,之后每次复位只读取,避免覆盖用户设置:
//只在第一次上电配置
if (is_reset_source(P33_VDDIO_POR_RST)) {
void reset_source_dump();
reset_source_dump();
/********测试系统时钟读写接口********/
rtc_read_time(&tmp_time);
log_info("rtc_read_time_before: %d-%d-%d %d:%d:%d\n", ...);
rtc_write_time(&rtc_time_test);
rtc_read_time(&tmp_time);
log_info("rtc_read_time_after: %d-%d-%d %d:%d:%d\n", ...);
/********测试系统闹钟读写接口********/
rtc_read_alarm(&tmp_time); //读当前alarm时间
log_info("rtc_read_alarm_before: ...");
rtc_alarm_switch(1);
rtc_write_alarm(&alarm_time_test); //修改alarm时间
rtc_read_alarm(&tmp_time); //读当前alarm时间
log_info("rtc_read_alarm_after: ...");
}
sys_timer_add(NULL, rtc_dump_test, 1000);
Source: rtc_demo.c
要点:
is_reset_source(P33_VDDIO_POR_RST)判断复位源是否为上电复位——若为看门狗/软复位等来源则跳过写入,这是保证用户时间不被复位流程覆盖的关键;rtc_alarm_switch(1)先使能闹钟,再rtc_write_alarm写入闹钟时间,顺序不可颠倒;sys_timer_add(NULL, rtc_dump_test, 1000)注册 1 秒周期的系统定时器,周期打印当前时间/闹钟(见rtc_dump_test,rtc_demo.c)。
低功耗定时唤醒
RTC 之外,示例还演示了 LPTMR(低功耗定时器)唤醒路径——先使能 30 秒定时唤醒,再软关机:
#if SET_TIME_WAKEUP_RTC
rtc_lptmr_wakeup_enable(30000);
power_set_soft_poweroff();
#endif
Source: rtc_demo.c
设计意图:SET_TIME_WAKEUP_RTC 宏(默认 0)将"定时唤醒 + 软关机"作为一个独立测试开关,与正常 RTC 演示解耦——因为进入软关机后无法继续打印,两者在同一函数中互斥执行更利于验证。
核心流程
RTC 完整使用时序
以下时序图展示了从初始化到闹钟触发、再到低功耗唤醒的完整调用链(依据 rtc_demo.c 的真实调用顺序):
sequenceDiagram
participant App as rtc_demo (应用层)
participant Rtc as rtc_dev 驱动
participant Hw as RTC 硬件
participant Pwr as 电源管理
App->>Rtc: rtc_init(&rtc_config_test)
Rtc->>Hw: 写入默认系统时间 def_sys_time
App->>Rtc: rtc_is_alarm_en()
Rtc-->>App: 闹钟使能状态
App->>Rtc: rtc_read_time(&tmp_time)
Rtc-->>App: 当前 sys_time
Note over App,Hw: 复位源判断: is_reset_source(P33_VDDIO_POR_RST)
alt 首次上电 (POR)
App->>Rtc: rtc_write_time(&rtc_time_test)
App->>Rtc: rtc_alarm_switch(1)
App->>Rtc: rtc_write_alarm(&alarm_time_test)
Hw-->>Rtc: 闹钟超时 (RTC_ALARM_EVENT)
Rtc-->>App: rtc_alm_isr(ev) 回调
else 非首次上电
Note over App: 跳过时间/闹钟写入,保留用户设置
end
App->>Pwr: rtc_lptmr_wakeup_enable(30000)
App->>Pwr: power_set_soft_poweroff()
存储子系统启动时序
sequenceDiagram
participant Boot as 启动代码
participant Vfs as VFS
participant Sfc as SFC 设备
participant TZ as tzflash
Boot->>Vfs: vfs_mount
Boot->>Vfs: vfs_openbypath("/app_area_head/VM")
Boot->>Vfs: vfs_ioctl(FS_IOCTL_FILE_ATTR, &vm_attr)
Vfs-->>Boot: vm_attr.sclust / fsize
Boot->>Boot: boot_info.vm = (vm_saddr, vm_size)
Boot->>Sfc: dev_open(__SFC_NANE)
Boot->>Sfc: dev_ioctl(IOCTL_SET_PROTECT_INFO, flash_code_protect_callback)
Boot->>TZ: tzflash_ioctl(IOCTL_SET_PART_INFO, unenc_part)
Note over Sfc: 后续所有擦写均经保护回调校验
使用示例
示例 1:RTC 初始化并读取时间(应用入口)
以下代码是 RTC 模块的标准初始化序列——配置平台数据、初始化驱动、读取当前时间并查询闹钟使能状态:
rtc_init(&rtc_config_test);
log_info("rtc_is_alm_en:%d\n", rtc_is_alarm_en()); //获取闹钟是否使能
struct sys_time tmp_time;
rtc_read_time(&tmp_time);
log_info("rtc_read_init_time: %d-%d-%d %d:%d:%d\n",
tmp_time.year, tmp_time.month, tmp_time.day, tmp_time.hour, tmp_time.min, tmp_time.sec);
Source: rtc_demo.c
示例 2:周期读取时间与闹钟(定时器回调)
rtc_dump_test 每 1 秒被系统定时器调用一次,读取当前时间与闹钟并打印;计数超过 10 次后软关机,用于验证长时间运行与关机路径:
void rtc_dump_test()
{
//时间打印
struct sys_time tmp_time;
rtc_read_time(&tmp_time);
printf("rtc_rtc_read_time: %d-%d-%d %d:%d:%d\n", tmp_time.year, tmp_time.month, tmp_time.day, tmp_time.hour, tmp_time.min, tmp_time.sec);
rtc_read_alarm(&tmp_time); //读当前alarm时间
printf("rtc_rtc_read_alarm: %d-%d-%d %d:%d:%d\n", tmp_time.year, tmp_time.month, tmp_time.day, tmp_time.hour, tmp_time.min, tmp_time.sec);
rtc_num++;
if (rtc_num > 10) {
//soff
power_set_soft_poweroff();
}
}
Source: rtc_demo.c
示例 3:闹钟回调与事件位判断
闹钟超时后驱动通过中断回调上报事件,应用通过事件位掩码识别闹钟事件:
void rtc_alm_isr(const struct rtc_event_t *ev) //闹钟回调函数测试
{
if (ev->event & BIT(RTC_ALARM_EVENT)) {
log_info("alarm_timeout!!!\n\n");
}
}
Source: rtc_demo.c
示例 4:获取 VM 分区属性(存储层)
存储侧启动代码通过 VFS 获取 VM 分区的物理属性,这是所有参数持久化的前置条件:
struct vfs_attr *get_vm_attr_p(void)
{
return &vm_attr;
}
Source: flash_init.c
vm_attr 在 flash_info_init() 中填充(FS_IOCTL_FILE_ATTR),任何需要访问 VM 分区信息的模块(含写保护回调)都通过该访问器读取,避免重复解析。
配置选项
存储与时钟子系统的可配置项如下(均来自实际源码):
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_APP_RTC_EN | 宏 | 未定义 | RTC 模块总开关,定义后 rtc_demo.c 参与编译;未定义时 RTC 代码整体排除(见 rtc_demo.c) |
SET_TIME_WAKEUP_RTC | 宏 | 0 | 单独测试定时唤醒路径的开关;置 1 时执行 rtc_lptmr_wakeup_enable(30000) + 软关机(见 rtc_demo.c) |
VM_SFC_ENABLE | 宏 | 条件编译 | 控制基于 SFC 的 VM 存储设备是否在设备列表中注册(见 device_list.c) |
CONFIG_CPU_BD47 | 宏 | 条件编译 | 存储分区初始化代码(sydf_cfg、vm_attr 等)在对应 CPU 平台下编译(见 flash_init.c) |
HAS_NORFS_EN | 宏 | 条件编译 | 是否引入 nor_fs.h 通用文件系统支持(见 flash_init.c) |
CONFIG_DEBUG_ENABLE | 宏 | 条件编译 | 定义时 rtc_demo 日志通过 printf 输出,否则日志为空实现(见 rtc_demo.c) |
| 默认系统时间 | struct sys_time | 2020-01-01 00:00:00 | rtc_dev_platform_data.default_sys_time 指定,驱动初始化时写入(见 rtc_demo.c) |
| 闹钟回调 | 函数指针 | rtc_alm_isr | rtc_dev_platform_data.cbfun,闹钟事件触发时被驱动调用(见 rtc_demo.c) |
API 参考
以下接口签名依据 rtc_demo.c 中的实际调用方式归纳;完整声明以 apps/include_lib/device/rtc/rtc_dev.h 为准(示例中的头文件引用:rtc_demo.c)。
rtc_init(const struct rtc_dev_platform_data *config)
初始化 RTC 驱动并写入默认系统时间。
参数:
config(const struct rtc_dev_platform_data *):平台配置,包含default_sys_time(默认时间)与cbfun(闹钟回调)。
返回值: 无(示例中直接调用,未检查返回值)。
rtc_read_time(struct sys_time *t)
读取当前系统时间。
参数:
t(struct sys_time *):输出参数,填充当前年/月/日/时/分/秒。
示例: rtc_read_time(&tmp_time);
rtc_write_time(const struct sys_time *t)
写入系统时间。
参数:
t(const struct sys_time *):待写入的时间。
调用位置: 首次上电(POR)时执行,见 rtc_demo.c。
rtc_read_alarm(struct sys_time *t)
读取当前闹钟时间。
参数:
t(struct sys_time *):输出参数,填充闹钟时间。
rtc_write_alarm(const struct sys_time *t)
写入闹钟时间。需先通过 rtc_alarm_switch(1) 使能闹钟。
参数:
t(const struct sys_time *):闹钟触发时间。
rtc_alarm_switch(u8 enable)
使能或关闭闹钟。
参数:
enable(整数):非 0 使能,0 关闭。
rtc_is_alarm_en(void)
查询闹钟是否使能。
返回: 非 0 表示已使能。
rtc_lptmr_wakeup_enable(u32 ms)
使能 LPTMR 低功耗定时唤醒。
参数:
ms(u32):唤醒延时(毫秒),示例中为30000(30 秒)。
相关结构体
| 结构体 | 字段 | 说明 |
|---|---|---|
struct sys_time | year/month/day/hour/min/sec | RTC 时间载体 |
struct rtc_dev_platform_data | default_sys_time、cbfun | 驱动平台配置 |
struct rtc_event_t | event(位掩码) | 中断事件,含 RTC_ALARM_EVENT 位(BIT(RTC_ALARM_EVENT) 判断) |
存储侧关键接口
| 接口 | 说明 |
|---|---|
get_vm_attr_p(void) | 返回 struct vfs_attr *,指向启动期填充的 VM 分区属性(sclust/fsize),见 flash_init.c |
flash_code_protect_callback(offset, len) | Flash 写保护回调,越界返回 1(拒绝),合法返回 0(放行),见 flash_init.c |
sdfile_get_votp_cfg_addr_len(addr, len) | 获取 OTP 配置区地址与长度,见 flash_init.c |
sdfile_get_ini_cfg_info(addr, len) | 获取 INI 配置区地址与长度,见 flash_init.c |
说明:VM 键值读写接口(
vm_open/vm_read/vm_write等)由vm.h/vm_sfc.h声明,本次文档未读取其实现细节,具体签名请查阅 SDK 头文件;本页仅确认其编译入口(flash_init.c)与使能宏。
失败模式、边界情况与并发
Flash 擦写越界与保护
- 越界擦写:任何模块若尝试擦写
offset < vm_attr.sclust或越过boot_info.flash_size的区域,flash_code_protect_callback返回 1 拒绝操作。这是对固件区/引导区最后的防线,设计上不允许绕过。 - 启动 ASSERT:
flash_info_init()中vfs_mount、vfs_openbypath、vfs_ioctl任一失败都会触发ASSERT(!err, ...)(见 flash_init.c)。这类错误通常意味着 Flash 布局与镜像不匹配(例如 VM 分区未烧录或路径错误),属于启动期致命错误,系统会停在 ASSERT 便于排查,而非带病运行。 - 设备打开失败:
dev_open(__SFC_NANE)失败同样 ASSERT——SFC 是 VM 存储的物理载体,缺失则整个参数系统不可用。
RTC 时间保持与复位
- 复位源判断:
is_reset_source(P33_VDDIO_POR_RST)用于区分"首次上电"与"运行中复位"。设计意图是:只有冷启动才允许写入默认/测试时间,热复位(看门狗、软复位)必须保留用户已设置的时间,否则会静默丢失用户配置。 - 闹钟使能顺序:必须先
rtc_alarm_switch(1)再rtc_write_alarm(...),否则闹钟可能处于关闭状态而不触发。示例代码的顺序即正确用法(见 rtc_demo.c)。
并发与中断上下文
- 闹钟回调
rtc_alm_isr运行在中断上下文,示例中仅做日志打印。业务代码若在该回调中执行耗时操作或调用非中断安全 API,将影响系统实时性甚至导致死锁——这是示例刻意保持回调极简的原因。 rtc_dump_test通过sys_timer_add在系统定时器上下文中每 1 秒执行一次,与中断回调互不嵌套;时间读写接口需保证在两者并发时的一致性(由驱动内部加锁/临界区保证,本页未读取驱动实现细节)。- 存储侧:
flash_code_protect_callback被驱动擦写流程调用,可能处于低优先级线程或中断上下文;函数放置于AT_SPI_CODE段且不做日志输出,正是为了避免上下文切换与自擦除风险。
边界情况
- 示例中
rtc_dump_test计数超过 10 次即power_set_soft_poweroff()——演示了 RTC 测试循环的退出条件,同时验证软关机路径可用。 rtc_lptmr_wakeup_enable(30000)与SET_TIME_WAKEUP_RTC互斥:打开该开关时跳过正常 RTC 演示,直接进入唤醒+关机流程,避免两种测试互相干扰。
性能与运维注意事项
- 启动开销:VM 分区定位仅做一次 VFS 打开/IOCTL/关闭,属于启动早期一次性成本;业务运行期参数读写直接走 VM 服务,不再经过 VFS 路径。
- 擦写均衡与磨损:VM 系统基于 Flash 键值存储设计,天然将参数写入分散到整个 VM 区以延长 Flash 寿命;越界保护同时限制了可擦写范围,防止磨损集中到固件区。具体磨损均衡算法在
vm_sfc实现中(本页未展开)。 - 调试手段:
- 打开
CONFIG_DEBUG_ENABLE后,rtc_demo通过[RTC_DEMO]前缀输出时间/闹钟日志,便于定位时间不同步问题; - 启动日志中的
file size / file sclust / boot_info.vm.vm_saddr / vm_size(flash_init.c)用于核对 VM 分区是否与烧录布局一致。
- 打开
- 低功耗验证:
rtc_lptmr_wakeup_enable+power_set_soft_poweroff组合用于验证 RTC 唤醒通路;量产时需确认唤醒源配置与外部 32K 时钟(若使用)的稳定性,避免低功耗下计时漂移。
扩展点
- RTC 事件扩展:
struct rtc_event_t.event采用位掩码设计(示例使用RTC_ALARM_EVENT),驱动后续可在不改变回调签名的情况下增加新事件位(如秒中断、日期变更),应用侧只需增加对应的BIT()判断。 - 默认时间定制:产品无需修改驱动,只需自定义
def_sys_time与rtc_dev_platform_data即可定制出厂时间与闹钟回调行为。 - 存储设备可插拔:
VM_SFC_ENABLE宏与设备列表条件编译使平台可在 SFC VM 与其它存储实现间切换;get_vm_attr_p()作为统一访问器,隔离了分区属性来源(当前为 VFS 文件属性)。 - 安全分区配置:
tzflash_ioctl(IOCTL_SET_PART_INFO)允许平台按需调整加密/非加密分区边界,为需要 Flash 加密的产品提供接入点。
相关链接
- flash_init.c(启动期存储初始化)
- rtc_demo.c(RTC 驱动示例)
- rtc_dev.h(RTC 设备接口头文件)
- device_list.c(VM_SFC_ENABLE 设备注册)
- 电源管理细节(软关机/低功耗)与 Flash 烧录布局说明,见 SDK 对应的电源与烧录章节文档。