UI 资源工程与打包
本文档说明 AC79NN AIoT SDK 中 UI 资源工程的目录约定、资源文件格式(.sty / .res / .str / .fon)、资源打包后在 Flash/SD 卡上的存放路径(RES_PATH 体系)、运行时加载流程以及相关的配置开关,帮助开发者理解「UI 工程如何被编译打包为资源文件、又如何被固件按路径加载」的完整链路。
Purpose and Scope
本页覆盖:
- UI 资源在文件系统中的路径规划(
RES_PATH、UPGRADE_PATH、FONT_PATH的宏定义与四种存放策略); - UI 资源包的文件类型与二进制格式(
.sty工程描述文件中的ui_file_head/window_head,.str字符串文件中的res_head_t/res_entry_t/res_infor_t); - UI 运行时如何打开与解析资源(
ui_load_info_table、open_resource_file()、select_strfile()/select_resfile()); - 与打包/升级相关的配置宏(
TCFG_USE_SD_ADD_UI_FILE、CONFIG_UI_FILE_SAVE_IN_RESERVED_ZONE、UI_WATCH_RES_ENABLE、UI_UPGRADE_RES_ENABLE等)。
本页不涉及 LCD 驱动寄存器配置(见 LCD 驱动 相关页面)、AWTK 等第三方 UI 框架(见其独立页面),也不展开具体表盘业务逻辑。
Overview
在 AC79NN 这类带屏 AIoT 芯片上,UI 开发遵循「资源与代码分离」的经典设计:界面布局、位图、多语言字符串、字库等数据由上位机 UI 工程工具编译打包为独立的资源文件,固件侧只保留一套 UI 引擎(apps/common/ui/),在运行时按固定路径从文件系统(Flash 或 SD 卡)打开资源文件并解析加载。
这一设计的核心价值:
- 固件与 UI 解耦:更换皮肤/布局/语言只需替换资源文件,无需重新编译固件;
- 多项目共存:一个固件可携带多个 UI 工程(如 JL、watch、watch1…watch5),通过
ui_load_info_table按索引切换; - 资源可升级:通过
UPGRADE_PATH(RES_PATH"ui_upgrade/")支持 UI 升级界面资源的热更新; - 内存可控:资源按需打开、按窗口解析,配合
br23_malloc/br23_free统一分配,便于统计 UI 内存占用。
资源路径的最终值由 res_config.h 中的条件编译宏决定,开发者根据量产方案(资源放 SD 卡、放 Flash 常规区、还是放保留区)选择不同的宏组合。
Architecture
flowchart TD
subgraph sg_Config["资源配置层 res_config.h"]
RES_PATH["RES_PATH 宏"]
UPGRADE["UPGRADE_PATH (ui_upgrade/)"]
FONT["FONT_PATH"]
end
subgraph sg_Storage["存储介质"]
SD["SD 卡 ui_res/"]
FLASH["Flash mnt/sdfile/res/ui_res/"]
RZONE["保留区 uipackres/ui/"]
end
subgraph sg_Files["UI 资源包内容"]
STY[".sty 工程描述"]
RES[".res 位图/控件资源"]
STR[".str 多语言字符串"]
FON[".fon 字库"]
end
subgraph sg_Runtime["UI 运行时 ui_platform.c"]
TABLE["ui_load_info_table"]
OPEN["open_resource_file()"]
PARSE["window_head / ui_file_head 解析"]
MALLOC["br23_malloc / br23_free"]
end
RES_PATH --> SD
RES_PATH --> FLASH
RES_PATH --> RZONE
SD --> STY
FLASH --> STY
RZONE --> STY
STY --> TABLE
TABLE --> OPEN
OPEN --> PARSE
PARSE --> MALLOC
UPGRADE --> STY
FONT --> FON
架构分层说明:
- 资源配置层(
res_config.h):唯一权威地定义资源在文件系统中的挂载根路径。它根据编译期宏选择 SD 卡、Flash 常规目录或保留分区目录,并派生升级路径与字库路径。 - 存储介质层:资源包(
ui_res/或uipackres/ui/目录)实际存放的位置,由量产烧录/打包工具预先写入。 - UI 资源包:同一工程在打包时产出
.sty(工程描述)、.res(图片/控件数据)、.str(多语言字符串)、.fon(字库)等文件,运行时按需分别打开。 - UI 运行时(
ui_platform.c):通过ui_load_info_table声明可加载的.sty工程列表,open_resource_file()打开文件,解析ui_file_head/window_head后按窗口分配内存并注册到 UI 引擎。
资源路径体系(RES_PATH)
RES_PATH 是整套资源打包与加载约定的核心宏,位于 res_config.h:
#if TCFG_USE_SD_ADD_UI_FILE
#define RES_PATH CONFIG_ROOT_PATH"ui_res/"
#else //USE_FLASH_ADD_UI_FILE
#if defined CONFIG_UI_FILE_SAVE_IN_RESERVED_EXPAND_ZONE
#define RES_PATH "mnt/sdfile/EXT_RESERVED/uipackres/ui/"
#elif defined CONFIG_UI_FILE_SAVE_IN_RESERVED_ZONE
#define RES_PATH "mnt/sdfile/app/uipackres/ui/"
#else
#define RES_PATH "mnt/sdfile/res/ui_res/"
#endif
#endif//TCFG_USE_SD_ADD_UI_FILE
#define UPGRADE_PATH RES_PATH"ui_upgrade/"
#define FONT_PATH RES_PATH
Source: res_config.h
设计意图与选择依据:
TCFG_USE_SD_ADD_UI_FILE置 1 时,资源放在 SD 卡CONFIG_ROOT_PATH"ui_res/"下,适合资源量大、需要用户可替换的场景(如 TF 卡升级皮肤);- 资源放 Flash 时,默认落在
mnt/sdfile/res/ui_res/(普通文件系统目录); - 若定义
CONFIG_UI_FILE_SAVE_IN_RESERVED_ZONE,则打包资源写入 Flash 的保留分区mnt/sdfile/app/uipackres/ui/——uipackres即 "UI pack resources"(UI 打包资源),量产时作为独立分区镜像烧录,避免与固件升级区互相覆盖; - 若定义
CONFIG_UI_FILE_SAVE_IN_RESERVED_EXPAND_ZONE,则使用扩展保留区mnt/sdfile/EXT_RESERVED/uipackres/ui/,适用于保留区容量不足、需要外扩分区的方案; UPGRADE_PATH固定为资源根下的ui_upgrade/子目录,配合UI_UPGRADE_RES_ENABLE宏实现升级界面资源,使「升级动画/文案」也能作为资源随包更新;FONT_PATH与RES_PATH相同,字库文件与 UI 资源同目录存放,fontinit.c通过包含res_config.h来定位字库。
UI 资源文件格式
.sty 工程描述文件
.sty 是每个 UI 工程的「入口描述文件」,由上位机工具打包生成。运行时通过 open_resource_file() 打开后,按以下两个结构体解析,定义于 ui_platform.c:
struct window_head {
u32 offset;
u32 len;
u32 ptr_table_offset;
u16 ptr_table_len;
u16 crc_data;
u16 crc_table;
u16 crc_head;
};
struct ui_file_head {
u8 res[16];
u8 type;
u8 window_num;
u16 prop_len;
u8 rotate;
u8 rev[3];
};
Source: ui_platform.c
字段含义与设计意图:
ui_file_head:文件级头部。res[16]为资源标识/校验串,type区分资源类型,window_num声明本工程包含的窗口数量,prop_len为属性段长度,rotate记录默认旋转方向,rev[3]保留字节用于版本兼容。window_head:窗口级头部。offset/len定位该窗口数据在文件中的位置与长度;ptr_table_offset/ptr_table_len定位窗口内控件指针表;三个 CRC 字段(crc_data、crc_table、crc_head)分别对数据段、指针表和头部做校验,保证资源文件在传输/烧录过程中未损坏——这是资源热替换场景下的关键可靠性设计。
.res 位图/控件资源与 .str 多语言字符串
.res(图片资源)与 .str(字符串资源)同样遵循「文件头 + 条目表 + 数据区」的布局。字符串文件在 lcd_simple/ui.c 中被定位读取:
res_infor_t stringinfor;
...
fseek(file, sizeof(res_head_t) + 2 * sizeof(res_entry_t) + BMPID_SUM * sizeof(res_infor_t) + ((StringID - 1)*LANGUAGEID_SUM + LanguageMode - 1)*sizeof(res_infor_t), SEEK_SET);
fread(file, (u8 *)&stringinfor, sizeof(res_infor_t));
Source: lcd_simple/ui.c
该 fseek 偏移表达式揭示了 .str 文件的完整布局(顺序排列):
res_head_t:一个文件头;2 * sizeof(res_entry_t):两个条目表项(通常为「资源表」与「字符串表」两个 entry);BMPID_SUM * sizeof(res_infor_t):位图信息表,每个位图一条res_infor_t;- 随后通过
(StringID-1)*LANGUAGEID_SUM + LanguageMode-1计算出「第 StringID 条字符串 × 第 LanguageMode 种语言」的线性偏移,直接fread出该字符串的res_infor_t(内含偏移/长度/属性)。
这种「一次 fseek + 一次 fread」的随机访问方式,避免把整个字符串文件载入内存,符合资源常驻 Flash、按需读取的嵌入式设计原则。
运行时加载流程
ui_load_info_table:工程清单
UI 平台层用一个静态表声明「当前固件可加载哪些 UI 工程」,定义于 ui_platform.c:
struct ui_load_info ui_load_info_table[] = {
{1, NULL, NULL},
{2, RES_PATH"prj2.sty", NULL},//by yyj
{-1, NULL, NULL},
};
Source: ui_platform.c
- 每个表项包含
{id, 路径, 附加信息};-1作为表结束哨兵; - 路径统一由
RES_PATH前缀拼接,因此只要RES_PATH编译正确,表项无需随存放介质改动; res_config.h中被注释掉的UI_STY_CHECK_PATH/UI_RES_CHECK_PATH/UI_STR_CHECK_PATH展示了多工程(JL、watch、watch1…watch5)同时存在时的扩展写法:每个工程一组.sty/.res/.str路径,运行时按索引切换。
打开与解析序列
sequenceDiagram
participant APP as 应用初始化
participant PLAT as ui_platform.c
participant FS as 文件系统
participant UI as UI 引擎
APP->>PLAT: 初始化 UI 平台
PLAT->>PLAT: 遍历 ui_load_info_table
PLAT->>FS: open(RES_PATH + "prj2.sty")
FS-->>PLAT: FILE*
PLAT->>FS: fread(ui_file_head)
PLAT->>FS: fread(window_head)
PLAT->>PLAT: 校验 CRC / 按窗口分配内存
PLAT->>UI: 注册窗口与控件
APP->>PLAT: select_strfile(语言索引)
PLAT->>FS: fseek 到字符串表偏移
FS-->>PLAT: res_infor_t 数据
PLAT-->>APP: 字符串/位图句柄
关键函数(声明见 ui_platform.c):
open_resource_file():静态函数,负责按ui_load_info_table打开工程文件并解析头部;select_strfile(u8 index):按索引切换字符串资源文件(多语言切换入口);select_resfile(u8 index):按索引切换图片资源文件。
内存管理
UI 引擎的所有动态内存统一经由平台层封装,见 ui_platform.c:
void *br23_malloc(int size)
{
void *buf;
malloc_cnt++;
buf = (void *)malloc(size);
#ifdef UI_BUF_CALC
struct buffer *new = (struct buffer *)malloc(sizeof(struct buffer));
new->buf = buf;
new->size = size;
list_add_tail(new, &buffer_used);
printf("platform_malloc : 0x%x, %d\n", buf, size);
...
#endif
return buf;
}
Source: ui_platform.c
设计要点:
- 通过
malloc_cnt统计未释放次数,可快速发现内存泄漏; - 定义
UI_BUF_CALC后,每次分配/释放都会把缓冲区登记到链表并打印累计占用,用于调试 UI 内存峰值——这是资源包较大时评估 RAM 预算的标准手段; - 资源按窗口加载、窗口切换即释放,使多个工程的资源可以分时复用同一块内存,而不是同时常驻。
Configuration Options
UI 资源工程与打包相关的全部配置集中在 res_config.h 与工程级 app_config.h 中,以编译期宏的形式生效:
| 配置宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_USE_SD_ADD_UI_FILE | 开关宏 | 0(未定义) | 置 1 时 UI 资源从 SD 卡 CONFIG_ROOT_PATH"ui_res/" 加载,否则从 Flash 加载 |
CONFIG_UI_FILE_SAVE_IN_RESERVED_ZONE | 开关宏 | 未定义 | 资源打包写入 Flash 保留分区 mnt/sdfile/app/uipackres/ui/ |
CONFIG_UI_FILE_SAVE_IN_RESERVED_EXPAND_ZONE | 开关宏 | 未定义 | 资源打包写入扩展保留区 mnt/sdfile/EXT_RESERVED/uipackres/ui/ |
UPGRADE_PATH | 派生宏 | RES_PATH"ui_upgrade/" | UI 升级界面资源的存放目录 |
FONT_PATH | 派生宏 | RES_PATH | 字库文件存放目录(与 UI 资源同目录) |
UI_UPGRADE_RES_ENABLE | 开关宏 | 未定义(注释态) | 使能升级界面资源功能 |
UI_WATCH_RES_ENABLE | 开关宏 | 未定义(注释态) | 使能表盘功能(watch face),见 lcd_ui_api.c 中的使用 |
UI_USED_DOUBLE_BUFFER | 开关宏 | 未定义(注释态) | 使能双缓冲推屏,改善刷新流畅度 |
UI_BUF_CALC | 调试宏 | 未定义 | 使能 UI 内存分配/释放追踪打印 |
路径选择优先级(互斥组合)说明:当 TCFG_USE_SD_ADD_UI_FILE 未定义(即 USE_FLASH_ADD_UI_FILE)时,再按「扩展保留区 → 保留区 → 普通目录」的顺序条件编译确定 RES_PATH。选择保留区方案时,量产工具需将 uipackres/ui/ 打包为独立分区镜像,与固件区分离,从而支持 UI 资源单独升级而不触碰固件。
API Reference
以下为 UI 平台层暴露给上层/UI 引擎的关键接口(原型见 ui_platform.c):
void select_strfile(u8 index)
按索引切换当前使用的多语言字符串资源文件。
参数:
index(u8):字符串资源文件在工程清单中的索引(对应语言/字符串表编号)。
说明: 用于多语言切换;切换后对 select_strfile 的调用会配合 LanguageMode 重算 .str 文件中的字符串偏移。
void select_resfile(u8 index)
按索引切换当前使用的图片/控件资源文件。
参数:
index(u8):资源文件在工程清单中的索引。
static int open_resource_file()
按 ui_load_info_table 打开当前选中的 .sty 工程文件并解析文件头/窗口头。
返回:
0表示成功,非零表示打开或解析失败(文件不存在、头部 CRC 校验失败等)。
说明: 为静态函数,仅平台层内部调用;ui_load_info_table 中 {1, NULL, NULL} 等空路径表项用于占位或跳过加载。
void *br23_malloc(int size) / void br23_free(void *buf)
平台层统一内存分配/释放接口,供 UI 引擎所有动态对象使用。
参数:
size(int):申请字节数;buf(void *):待释放指针。
说明: 内部维护 malloc_cnt 计数;定义 UI_BUF_CALC 时登记 struct buffer 链表并打印每次分配/释放及累计占用,用于内存峰值与泄漏分析。
Usage Examples
示例 1:配置资源存放介质(工程配置)
在工程 app_config.h 中选择 SD 卡方案,并开启保留区打包:
#define TCFG_USE_SD_ADD_UI_FILE 1 // UI 资源放 SD 卡
// 或(Flash 方案)
// #define CONFIG_UI_FILE_SAVE_IN_RESERVED_ZONE 1 // 打包到保留分区 uipackres/ui/
Source: res_config.h
示例 2:注册/更换 UI 工程清单
在 ui_load_info_table 中追加新的 .sty 工程,实现一固件多主题:
struct ui_load_info ui_load_info_table[] = {
{1, NULL, NULL},
{2, RES_PATH"prj2.sty", NULL},//by yyj
{-1, NULL, NULL},
};
Source: ui_platform.c
示例 3:运行时按偏移读取某语言的字符串
fseek 直接定位到 (StringID, LanguageMode) 对应条目,只读一条 res_infor_t:
res_infor_t stringinfor;
...
fseek(file, sizeof(res_head_t) + 2 * sizeof(res_entry_t) + BMPID_SUM * sizeof(res_infor_t) + ((StringID - 1)*LANGUAGEID_SUM + LanguageMode - 1)*sizeof(res_infor_t), SEEK_SET);
fread(file, (u8 *)&stringinfor, sizeof(res_infor_t));
Source: lcd_simple/ui.c
Failure Modes, Edge Cases & Concurrency
资源文件缺失或路径不匹配
- 现象:
open_resource_file()打开RES_PATH"prj2.sty"失败。 - 根因:最常见是
RES_PATH编译宏与量产烧录布局不一致——例如固件按TCFG_USE_SD_ADD_UI_FILE编译但资源实际烧在 Flash,或保留区方案下分区未写入uipackres/ui/。 - 处理:
ui_load_info_table用{1, NULL, NULL}空项占位、-1结束,加载失败时引擎可跳过该工程回退到默认界面;排查时应先确认RES_PATH展开值与实际文件系统路径完全一致。
资源损坏与 CRC 校验
.sty 的 window_head 内置 crc_data/crc_table/crc_head 三份校验值。资源在传输、OTA、SD 卡热插拔过程中损坏时,解析层应校验失败并拒绝加载该窗口,避免脏数据进入 UI 引擎导致花屏或越界。.res/.str 文件头同样通过 res_head_t/res_entry_t 的条目表约束长度,读取越界前应做边界检查。
字符串越界与语言回退
.str 文件按「(StringID-1)*LANGUAGEID_SUM + LanguageMode-1」线性寻址,若打包时 LANGUAGEID_SUM(语言总数)与实际写入的语言数不一致,或传入非法 LanguageMode,fseek 会落在错误偏移。设计上要求打包工具与固件保持同一组 BMPID_SUM/LANGUAGEID_SUM 常量;运行时建议对 LanguageMode 做范围钳制并回退到默认语言(如 LanguageMode = 1)。
并发与中断上下文
- UI 渲染与资源加载运行在主线程/UI 任务中,
lcd_ui_api.c在UI_WATCH_RES_ENABLE场景下使用local_irq_disable()保护关键段(见 lcd_ui_api.c),表盘唤醒/休眠切换时短暂关中断防止推屏撕裂; - 资源文件句柄(
ui_file/ui_file1/ui_file2)为全局静态变量,多任务同时访问时需串行化,避免 fseek/fread 交错导致文件指针错乱; - 内存分配集中在
br23_malloc/br23_free,若 UI 任务与其它任务共享堆,需保证堆互斥(SDK 默认内存管理已加锁)。
Performance & Operational Notes
- 按需随机读取而非整体载入:
.str字符串、.res位图均通过fseek + fread定位读取,资源常驻 Flash,RAM 只保存当前窗口数据;因此资源包大小主要受限于 Flash 容量而非 RAM。 - 内存峰值评估:开发阶段打开
UI_BUF_CALC,利用br23_malloc的链表登记打印(used buffer size:%d)统计各窗口切换时的内存水位,为窗口数/位图尺寸的设计提供依据。 - 双缓冲与旋转:
UI_USED_DOUBLE_BUFFER与ui_file_head.rotate/ui_rotate配合决定推屏方式与方向,开启双缓冲会增加一份屏幕尺寸的 RAM 开销,需在流畅度与内存之间权衡。 - 多工程资源分时复用:
ui_load_info_table多表项方案下,切换工程会释放旧窗口资源再加载新窗口,同一时刻仅一个工程常驻,这是多主题方案能跑在小内存 MCU 上的关键。 - 打包与升级:UI 升级资源存放于
UPGRADE_PATH(ui_upgrade/),UI_UPGRADE_RES_ENABLE使能后升级界面文案/动画可随升级包替换,不影响主 UI 资源。
Extension Points
- 新增 UI 工程/主题:在
ui_load_info_table追加{id, RES_PATH"xxx.sty", NULL}表项,并将xxx.sty/.res/.str按RES_PATH布局打包即可,无需改动引擎代码。 - 多语言扩展:调整打包工具侧的
LANGUAGEID_SUM,同时保持固件侧常量一致,即可在.str中增加语言列。 - 字库替换:
FONT_PATH指向资源根目录,替换/新增字库文件(fontinit.c按res_config.h定位)即可实现字体升级。 - UI 升级资源:通过
UI_UPGRADE_RES_ENABLE+UPGRADE_PATH扩展升级流程界面;表盘功能通过UI_WATCH_RES_ENABLE扩展(配合ui_show_main/ui_hide_curr_main的页面生命周期管理)。 - 调试扩展:
UI_BUF_CALC提供内存追踪钩子,可在此基础上扩展为按窗口统计的资源占用报表。
Related Links
- res_config.h(资源配置头文件)
- ui_platform.c(UI 平台层:资源打开/解析/内存管理)
- lcd_simple/ui.c(.str 字符串资源定位读取)
- lcd_ui_api.c(UI 显示生命周期与表盘/升级界面控制)
- fontinit.c(字库初始化,依赖 FONT_PATH)
- 相关目录:LCD 驱动配置与移植见 LCD 驱动页面;AWTK 等第三方 UI 框架见其独立文档页。