杰理 SDK 文档中心
首页
首页
  • SDK 概述与入门

    • SDK 总览
    • 支持芯片与蓝牙认证
    • 工程结构导航
  • 开发环境与构建

    • 环境搭建与工具链安装
    • 编译指南与工程选择
    • 烧录与生产工具
  • BLE 透传/数传应用

    • 透传应用框架与处理模块
    • 透传与数传示例
    • 多连接与自定义服务示例
    • FindMy 与查找网络示例
  • HID 人机交互应用

    • 键盘与按键设备示例
    • 鼠标设备示例
    • 遥控器示例
    • HID 蓝牙应用模块
  • 公共 BSP 模块

    • 按键、编码器与红外输入
    • 传感器驱动
    • LED 与显示控制
    • 串口与 USB 通信
    • 存储、参数与时钟
    • 电源与温度管理
    • 消息、内存与系统配置
    • OTA 升级框架
  • 蓝牙协议栈与库

    • BLE 控制器与协议栈适配
    • 经典蓝牙 BR/EDR 支持
    • 第三方蓝牙协议
    • 设备管理框架
    • DUT 测试与射频认证
  • 构建系统与开发工具

    • Makefile 构建系统
    • 固件后处理与配置工具
    • 辅助脚本与库合并
  • 文档与硬件资料

    • AT 命令参考
    • 硬件参考资料
    • SDK 文档与在线资源

存储、参数与时钟

本文档介绍 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 设备中,以下三类需求贯穿整个产品生命周期:

  1. 参数持久化:蓝牙配对信息、设备配置、用户设置需要在掉电后仍然保留,因此必须有一个可靠的 Flash 存储区,并配合擦写保护机制防止误操作破坏固件区。
  2. 实时时钟(RTC):设备需要维护当前时间(年/月/日/时/分/秒),即使进入低功耗模式也要能计时,并在指定时刻触发闹钟事件。
  3. 低功耗唤醒: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.cFlash 擦写保护回调,阻止越过 VM 区界限的写操作
boot_info.vmapps/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.hRTC 硬件驱动:时间/闹钟读写、中断回调注册
rtc_demoapps/app/bsp/common/rtc/rtc_demo.cRTC 模块的应用层示例,展示标准调用序列与测试流程

设计意图说明: 存储路径与时钟路径在系统服务层解耦——存储依赖 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_time2020-01-01 00:00:00rtc_dev_platform_data.default_sys_time 指定,驱动初始化时写入(见 rtc_demo.c)
闹钟回调函数指针rtc_alm_isrrtc_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_timeyear/month/day/hour/min/secRTC 时间载体
struct rtc_dev_platform_datadefault_sys_time、cbfun驱动平台配置
struct rtc_event_tevent(位掩码)中断事件,含 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 对应的电源与烧录章节文档。
Prev
串口与 USB 通信
Next
电源与温度管理