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

    • 项目概述与芯片平台
    • 环境搭建与工具链安装
    • 编译与烧录指南
    • 工程结构总览
  • 应用层与公共模块

    • GP MCU 主应用入口
    • AT 指令与调试模块
    • 电池检测与电源管理
    • EEPROM 与参数存储
    • 按键与 USB 设备驱动
    • 音频解码与 APA 语音播报
  • 外设驱动与示例

    • 高精度 ADC(HADC)
    • 通用 ADC 与定时器
    • UART / SPI / IIC 通信外设
    • MCPWM 与电机控制
    • RTC 与低功耗唤醒
    • 段码 LCD 驱动
    • NOR Flash 与红外编解码组件
  • 显示与 UI 系统

    • LCD 驱动与字库引擎
    • UI 平台与控件绘制
    • UI 工程与资源生成工具
  • 系统底层与芯片平台

    • cd09 芯片平台与预编译库
    • GPIO 与 IIC 底层驱动
    • 系统文件系统与设备模型
  • 启动引导与固件升级

    • UBOOT 引导工程
    • 固件升级机制
  • 开发工具与资源

    • 编译脚本与命令行工具
    • 音频文件转换工具
    • 硬件资料与文档资源

UI 平台与控件绘制

AC82N SDK 的 UI 平台(ui_platform)是运行于 MCU 上的轻量级图形界面框架,提供显存管理、图片控件链表管理、RLE 资源解压与屏幕绘制调度能力;控件(Widget)以图片控件(ui_img_t)为核心,通过链表组织页面与元素,最终由显示驱动推送到 LCD。

Purpose and Scope

本文档介绍 AC82N SDK 中 UI 平台与控件绘制的完整机制,包括:

  • UI 平台的分层架构(应用层 → 核心层 → 资源层 → 硬件层)
  • 核心数据结构:显存 ui_disp_draw_buf_t、显示驱动 disp_drv_t、图片控件 ui_img_t、RLE 解压进度 ui_jlp_t
  • 控件状态机(ui_obj_state_t)与绘制调度(ui_draw())
  • 图片资源(JL 格式资源 / 未压缩图片)的加载与解码路径
  • 公开 API、配置项、失败模式与性能考量

相关但属于其他目录页的内容:

  • LCD 屏体底层驱动(ui_ll.c 中的寄存器操作、SPI 推屏时序)属于显示驱动主题,本页仅说明其与 UI 平台的接口(SetDrawArea / Draw 回调)。
  • PC 端"图片资源生成工具"(doc/软件工具/图片资源生成工具)生成 all_image_info.h / all_image_id.h 等资源头文件,属于资源构建工具链主题,本页只说明其产物如何被消费。
  • 具体业务的 UI 页面(如模式界面)属于应用层代码,不在本页展开。

Overview

在资源受限的 MCU 环境(无操作系统级窗口系统、无 GPU)中,AC82N 的 UI 平台采用"双缓冲显存 + 图片控件链表 + 按需重绘"的经典嵌入式 GUI 设计:

  1. 显存双缓冲:ui_disp_draw_buf_t 持有 buf1 / buf2 两块缓冲,buf_act 指向当前活动缓冲,绘制与推屏可以错峰进行,避免撕裂(tearing)。
  2. 控件即图片:UI 元素统一抽象为图片控件 ui_img_t(链表节点),一个页面(ui_page_t)就是一张 ui_img_t 链表;控件支持隐藏、按下状态、位置移动(rect)与多帧图片切换(num 索引)。
  3. 资源按需解压:JL 格式 UI 资源采用 RLE 分块压缩,ui_jlp_t 保存 rgb / alpha 两个通道的分块解压进度,实现"边解压边绘制",显著降低内存峰值。
  4. 显示驱动可插拔:disp_drv_t 通过函数指针 SetDrawArea 与 Draw 抽象具体 LCD 屏,UI 核心层与具体屏幕解耦。

该平台面向的场景包括:蓝牙音箱/耳机等产品的点阵屏或小尺寸 TFT 屏界面、菜单导航、状态图标切换、按键反馈(PRESS 态)等。

Architecture

下图展示 UI 平台各模块的依赖关系(节点名与源码文件/结构体一一对应):

flowchart TD
    subgraph sg_App["应用层 (apps)"]
        UIDemo["ui_demo.c<br/>示例程序"]
        UIAPI["ui_api.c<br/>对外 API 封装"]
    end

    subgraph sg_Core["UI 平台核心 (ui_platform/core)"]
        UICore["ui_core.c<br/>控件链表/显存管理"]
        UIDraw["ui_draw.c<br/>绘制调度 ui_draw()"]
        UILL["ui_ll.c<br/>底层显示驱动"]
        UIColor["ui_color.h<br/>颜色定义 ui_color_t"]
    end

    subgraph sg_Res["UI 资源层 (ui/resource)"]
        ResFile["ui_resfile.c<br/>资源文件解析"]
        RLE["rle.c<br/>RLE 分块解压"]
        ImgDraw["ui_image.c<br/>图片绘制"]
    end

    subgraph sg_HW["硬件层"]
        FB[("显存 buf1/buf2<br/>ui_disp_draw_buf_t")]
        LCD["LCD 屏"]
    end

    UIDemo --> UIAPI
    UIAPI --> UICore
    UIAPI --> UIDraw
    UICore -->|"ui_img_t 链表"| UIDraw
    UIDraw --> ImgDraw
    ImgDraw --> RLE
    ResFile --> RLE
    UIDraw --> UILL
    UILL --> FB
    FB --> LCD
    UILL --> LCD
    UIColor -.-> UIDraw

分层职责说明:

  • 应用层:ui_demo.c 提供使用示例;ui_api.c 向上层业务封装控件添加/删除、绘制触发等操作。
  • 核心层:ui_core.c 负责 ui_img_t 链表的增删(ui_obj_img_add / ui_obj_img_del)与显示驱动初始化;ui_draw.c 的 ui_draw() 是绘制总入口,遍历当前页面的控件链表逐项绘制;ui_ll.c 对接具体 LCD 屏的底层操作。
  • 资源层:ui_resfile.c 解析资源文件,rle.c 提供 RLE 分块解压(配合 ui_jlp_t 的进度暂存),ui_image.c 按控件类型将像素写入显存。
  • 硬件层:显存 buffer 与 LCD 屏体;核心层通过 disp_drv_t 中的函数指针回调访问硬件,实现驱动可替换。

说明:ui_ll.c、ui_api.c、ui_demo.c、ui_resfile.c、rle.c、ui_image.c 的实现细节未在本次文档编写中逐行读取,其职责根据文件命名、头文件声明与调用关系推断;ui_core.h 与 ui_draw.h 中的内容均为逐行验证。

核心数据结构

UI 平台的根基是 ui_core.h 中定义的一组结构体。理解它们之间的关系是理解整个绘制机制的前提:

classDiagram
    class ui_disp_draw_buf_t {
        +void* buf1
        +void* buf2
        +void* buf_act
        +u32 buf_size
    }
    class disp_drv_t {
        +u8 bulk_index
        +u8 bulk_num
        +u8 refr_line
        +u16 disp_width
        +u16 disp_height
        +u32 buf_offset
        +ui_disp_draw_buf_t* buf
        +ui_color_t bg_color
        +ui_img_t* disp_page
        +volatile u8 flushing
        +SetDrawArea() int
        +Draw() int
    }
    class ui_img_t {
        +u8 img_type
        +u8 state
        +u8 act_flag
        +void* user_data
        +pImageInfo_t pic
        +ui_jlp_t jlp
        +uint16_t num
        +Rect_t rect
        +ui_img_t* next
    }
    class ui_jlp_t {
        +u32 rgb_result
        +u32 alpha_result
    }
    disp_drv_t --> ui_disp_draw_buf_t : "buf 指向"
    disp_drv_t --> ui_img_t : "disp_page 指向头节点"
    ui_img_t --> ui_jlp_t : "jlp 内嵌"
    ui_img_t --> ui_img_t : "next 链表"

图片控件 ui_img_t(链表节点)

控件在 UI 平台中的唯一抽象是图片控件 ui_img_t(内部类型名 Node_img),每个控件即链表中的一个节点,页面(ui_page_t)即链表头:

// 定义链表节点结构
typedef struct Node_img {
    u8 img_type;            //0: JL格式ui资源  1: 未经压缩打包图片
    u8 state;               //标识是否需要隐藏该控件,1:隐藏,0:不隐藏
    u8 act_flag;            //标识是否需要更新
    void *user_data;        //raw data地址,暂未支持

    /* jl框架相关 */
    pImageInfo_t pic;       //控件原始信息
    ui_jlp_t jlp;           //分块解压缩相关
    uint16_t num;           //标识该图片控件需要显示的图片列表里的哪张图片

    Rect_t rect;            //坐标信息,用于移动控件
    struct Node_img *next;
} ui_img_t;

Source: ui_core.h

关键字段的设计意图:

  • img_type:区分资源的两种格式——0 为 JL 格式 UI 资源(需经 RLE 解压),1 为未压缩打包图片。绘制时依据该字段走不同的解码路径(见 ui_image.c / rle.c)。
  • state:与 ui_obj_state_t 枚举对应,标记控件是否隐藏(UI_OBJ_STATE_HIDDEN)、是否处于按下态(UI_OBJ_STATE_PRESS)。
  • act_flag:脏标记(dirty flag),标识该控件是否需要重绘。这是按需重绘机制的核心——只有标记为需要更新的控件才会被重绘,避免全屏刷新。
  • pic(pImageInfo_t):指向资源构建工具生成的图片信息(来自 all_image_info.h),描述图片的尺寸、数据地址、RLE 分块信息等。
  • jlp(ui_jlp_t):保存 rgb 与 alpha 两个通道的分块解压进度,支持"边解压边绘制"。
  • num:图片列表索引,使一个控件可在多张图片(如不同状态图标)间切换,无需重复创建控件。
  • rect(Rect_t):控件在屏幕上的坐标,移动控件只需修改 rect 并置位 act_flag。
  • next:链表指针,页面中的所有控件通过它串成单链表。

显存 buffer ui_disp_draw_buf_t

/* 显存buffer */
typedef struct {
    void *buf1;
    void *buf2;
    void *buf_act;
    u32 buf_size;
} ui_disp_draw_buf_t;

Source: ui_core.h

buf1 与 buf2 构成双缓冲:绘制线程写入非活动缓冲,buf_act 指向当前正在显示/推屏的缓冲;推屏完成后交换,从而避免绘制过程中的画面撕裂。buf_size 为单块缓冲大小。

显示驱动 disp_drv_t(硬件抽象)

/* 显存结构体定义 */
typedef struct disp_drv_t {
    u8 bulk_index;                  //标识显存块序号
    u8 bulk_num;                    //标识显存块总数
    u8 refr_line;                   //刷新行数
    u16 disp_width;                 //lcd屏幕水平分辨率
    u16 disp_height;                //lcd屏幕垂直分辨率
    u32 buf_offset;                 //显存buffer偏移量
    ui_disp_draw_buf_t *buf;        //显存buffer地址
    ui_color_t bg_color;            //背景颜色
    ui_img_t *disp_page;            //需要显示的page,指向img控件头节点
    volatile u8 flushing;           //1: 是否有更新disp buf
    int (*SetDrawArea)(int xs, int xe, int ys, int ye);  //显示区域接口
    int (*Draw)(char *buf, u32 len, u8 wait);            //推屏接口
} disp_drv_t;

Source: ui_core.h

设计意图:

  • 屏幕参数(disp_width / disp_height / refr_line)描述目标 LCD 的几何与刷新粒度;refr_line 表示每次刷新多少行,配合分块显存(bulk_index / bulk_num / buf_offset)支持"显存块"局部刷新的低内存方案。
  • flushing 使用 volatile 修饰:它可能被中断/推屏流程与绘制流程并发访问,volatile 保证每次读取都取最新值,防止编译器优化导致的状态不同步。
  • 函数指针回调(SetDrawArea / Draw)是驱动插拔点:不同的 LCD 驱动只需实现这两个函数并填充 disp_drv_t,核心绘制逻辑无需改动。Draw 的 wait 参数控制推屏是否阻塞等待。

控件状态 ui_obj_state_t

/* 控件状态 */
typedef enum {
    UI_OBJ_STATE_HIDDEN = 0,    //BIT(0)
    UI_OBJ_STATE_PRESS,

    UI_OBJ_STATE_MAX = 7,       //BIT(7)
} ui_obj_state_t;

Source: ui_core.h

状态以位图方式组织(HIDDEN 对应 BIT(0),PRESS 对应 BIT(1),上限 BIT(7)),意味着 state 字段未来可以扩展为多个状态位组合(例如"隐藏 + 按下"),单个 u8 即可容纳。ui_img_t.state 直接引用该枚举。

RLE 解压进度 ui_jlp_t

/* rle结构体定义 */
typedef struct {
    u32 rgb_result;        //分块解压进度暂存
    u32 alpha_result;      //分块解压进度暂存
} ui_jlp_t;

Source: ui_core.h

JL 格式 UI 资源将 RGB 与 Alpha 通道分别做 RLE 压缩并按块组织。rgb_result / alpha_result 记录两个通道的解压进度,使绘制循环可以"解压一块、绘制一块、暂停/恢复",从而把大图的瞬时内存占用压缩到单块大小——这是该平台能在 KB 级 RAM 的 MCU 上显示全屏位图的关键设计。

绘制流程(Core Flow)

ui_draw()(声明于 ui_draw.h)是绘制调度的总入口,其执行路径如下:

sequenceDiagram
    participant App as 应用 (ui_api.c)
    participant Core as ui_core.c 控件管理
    participant Draw as ui_draw.c ui_draw()
    participant Img as ui_image.c / rle.c
    participant Drv as ui_ll.c disp_drv_t
    participant LCD as LCD 屏

    App->>Core: ui_obj_img_add(head, id) 创建控件
    Core->>Core: 分配 ui_img_t 并挂入链表
    App->>Draw: ui_draw() 触发重绘
    Draw->>Draw: 从 disp_page 遍历控件链表
    loop 每个 act_flag 置位的控件
        Draw->>Img: 按 img_type 选择解码路径
        Img->>Img: rle 分块解压 rgb/alpha<br/>(进度存于 ui_jlp_t)
        Img-->>Draw: 像素写入非活动显存 buf
    end
    Draw->>Drv: SetDrawArea(xs, xe, ys, ye)
    Draw->>Drv: Draw(buf, len, wait) 推屏
    Drv->>LCD: 刷新指定区域
    Drv-->>Draw: 完成,交换 buf_act

流程要点:

  1. 控件构建:应用通过 ui_obj_img_add() 创建控件并加入页面链表(ui_img_t **head 指向链表头)。
  2. 脏标记驱动:ui_draw() 只重绘 act_flag == 1 的控件;控件状态变化(按下、切换图片、移动)时由应用或事件处理置位 act_flag。
  3. 解码绘制:按 img_type 分流——JL 资源走 RLE 分块解压(进度存 jlp),未压缩图片直接拷贝;结果写入当前非活动显存缓冲。
  4. 区域推屏:所有脏控件绘制完成后,通过 disp_drv_t.SetDrawArea 限定脏区域,再用 Draw 将对应缓冲推给 LCD,最后交换 buf_act。
  5. 并发保护:推屏期间 flushing 置 1,绘制逻辑据此避免写入正在推送的缓冲。

资源加载与解码

UI 图片资源的生命周期分为离线构建与运行时消费两个阶段:

flowchart LR
    subgraph sg_Offline["离线(PC 工具)"]
        Tool["图片资源生成工具<br/>(ResBuilder / ui-tools)"]
        Header["all_image_info.h<br/>all_image_id.h"]
        ResFile2["资源文件<br/>(RLE 压缩)"]
    end
    subgraph sg_Runtime["运行时(MCU)"]
        Core["ui_core.h 引用资源头"]
        ResLoad["ui_resfile.c 资源文件解析"]
        RLE["rle.c RLE 解压"]
        Img["ui_image.c 像素写入显存"]
    end
    Tool --> Header
    Tool --> ResFile2
    Header --> Core
    ResFile2 --> ResLoad
    ResLoad --> RLE
    Core --> Img
    RLE --> Img
  • 离线阶段:doc/软件工具/图片资源生成工具 下的 PC 工具(ui-tools.exe / ResBuilder.exe)将图片工程编译为 JL 格式压缩资源,并生成 all_image_info.h(pImageInfo_t 图片信息表)与 all_image_id.h(图片 ID 宏)。ui_core.h 通过 #include "all_image_info.h" 与 #include "all_image_id.h" 直接引用这些产物。
  • 运行时阶段:ui_resfile.c 负责资源文件(如 uitoolbin.bin)的解析与定位;rle.c 实现 RLE 分块解压,进度由 ui_img_t.jlp(ui_jlp_t)保存;ui_image.c 将解码后的像素按控件坐标绘制到显存。

说明:ui_resfile.c、rle.c、ui_image.c 的函数签名与实现细节未在本次编写中逐行验证,以上为基于文件职责与 ui_jlp_t 字段的推断。

控件生命周期与状态迁移

控件从创建到销毁经历如下状态迁移(state 为位图,可多状态共存):

stateDiagram-v2
    [*] --> Visible: ui_obj_img_add(id)
    Visible --> Press: 按下事件 (UI_OBJ_STATE_PRESS)
    Press --> Visible: 释放事件
    Visible --> Hidden: state 置 UI_OBJ_STATE_HIDDEN
    Press --> Hidden: 按下后隐藏
    Hidden --> Visible: 清除 HIDDEN 位
    Hidden --> [*]: ui_obj_img_del(head, id)
    Visible --> [*]: ui_obj_img_del(head, id)

要点:

  • 创建:ui_obj_img_add(ui_img_t **head, obj_id_t id) 按资源 ID(obj_id_t,即 u32)查找图片信息并分配链表节点。
  • 隐藏/显示:修改 state 的 UI_OBJ_STATE_HIDDEN 位即可;隐藏控件在 ui_draw() 遍历时被跳过。
  • 按下反馈:UI_OBJ_STATE_PRESS 配合 num 索引可切换为按下态图片,实现按键视觉反馈。
  • 销毁:ui_obj_img_del(ui_img_t **head, obj_id_t id) 从链表中摘除节点并释放。

配置选项

以下宏定义于 ui_draw.h,用于绘制性能测量:

宏类型默认值说明
TIME_DET_DEBUG宏开关0置 1 启用 GPIO 电平翻转测时;置 0 时 TIME_DET_START/TIME_DET_END 展开为空操作(零开销)
TIME_REFR_TEST_PORTGPIO 宏IO_PORTA_07屏幕刷新时间测量端口(同时复用于解压缩时间测量)
TIME_PUSH_TEST_PORTGPIO 宏IO_PORTA_06推屏(Draw 回调)时间测量端口
TIME_DECODER_TEST_PORTGPIO 宏IO_PORTA_07解压缩时间测量端口

TIME_DET_START(a) / TIME_DET_END(a) 宏在使能时通过 gpio_hw_set_direction(IO_PORT_SPILT(a), 0) 将端口设为输出,再以 gpio_hw_write(a, 1/0) 产生电平翻转,供逻辑分析仪测量各阶段耗时。设计意图:测时逻辑默认完全关闭,不引入任何运行时开销,仅在性能调优时打开。

API Reference

以下 API 均声明于 ui_core.h 与 ui_draw.h(ui_core.c / ui_api.c 中的实现细节未逐行验证,签名以头文件为准)。

void ui_disp_driver_init(void)

初始化显示驱动:配置 disp_drv_t(屏幕分辨率、显存 buffer、SetDrawArea/Draw 回调等),是调用其他 UI API 前必须先执行的初始化函数。

int ui_obj_img_add(ui_img_t **head, obj_id_t id)

向页面链表添加一个图片控件。

参数:

  • head(ui_img_t **):页面链表头指针的地址;head 指向 NULL 时创建新页面(头节点)。
  • id(obj_id_t,即 u32):图片资源 ID(来自 all_image_id.h)。

返回:

  • 0:成功;非零:失败(如资源不存在或内存不足)。

void ui_obj_img_del(ui_img_t **head, obj_id_t id)

从页面链表中删除指定 ID 的图片控件,并释放其内存。

参数:

  • head(ui_img_t **):页面链表头指针的地址。
  • id(obj_id_t):要删除的图片资源 ID。

disp_drv_t *ui_get_disp_drv(void)

获取全局显示驱动结构体指针,供读取屏幕参数、背景色或直接操作显存 buffer 使用。

返回:

  • disp_drv_t *:显示驱动实例指针。

void ui_draw(void)

绘制调度总入口:遍历 disp_drv_t.disp_page 控件链表,对 act_flag 置位的控件执行解码绘制,随后通过 SetDrawArea / Draw 回调推屏。通常由应用在主循环或按键事件后调用。

使用示例

以下示例均提取自实际源码(ui_core.h / ui_draw.h),展示核心数据结构的典型用法。

示例 1:配置显示驱动(disp_drv_t)

应用初始化时填充驱动结构体,SetDrawArea 与 Draw 由具体 LCD 驱动(ui_ll.c)实现:

/* 显存结构体定义 */
typedef struct disp_drv_t {
    u8 bulk_index;                  //标识显存块序号
    u8 bulk_num;                    //标识显存块总数
    u8 refr_line;                   //刷新行数
    u16 disp_width;                 //lcd屏幕水平分辨率
    u16 disp_height;                //lcd屏幕垂直分辨率
    u32 buf_offset;                 //显存buffer偏移量
    ui_disp_draw_buf_t *buf;        //显存buffer地址
    ui_color_t bg_color;            //背景颜色
    ui_img_t *disp_page;            //需要显示的page,指向img控件头节点
    volatile u8 flushing;           //1: 是否有更新disp buf
    int (*SetDrawArea)(int xs, int xe, int ys, int ye);  //显示区域接口
    int (*Draw)(char *buf, u32 len, u8 wait);            //推屏接口
} disp_drv_t;

Source: ui_core.h

初始化后通过 ui_get_disp_drv() 获取指针,设置 disp_width/disp_height/bg_color 等参数;disp_page 指向当前页面的控件链表头,绘制循环据此遍历。

示例 2:构建控件链表(ui_img_t)

页面即 ui_img_t 链表;控件节点保存资源信息、状态与解压进度:

// 定义链表节点结构
typedef struct Node_img {
    u8 img_type;            //0: JL格式ui资源  1: 未经压缩打包图片
    u8 state;               //标识是否需要隐藏该控件,1:隐藏,0:不隐藏
    u8 act_flag;            //标识是否需要更新
    void *user_data;        //raw data地址,暂未支持

    /* jl框架相关 */
    pImageInfo_t pic;       //控件原始信息
    ui_jlp_t jlp;           //分块解压缩相关
    uint16_t num;           //标识该图片控件需要显示的图片列表里的哪张图片

    Rect_t rect;            //坐标信息,用于移动控件
    struct Node_img *next;
} ui_img_t;

Source: ui_core.h

典型的页面构建流程:ui_img_t *page = NULL; → 对每个要显示的图片调用 ui_obj_img_add(&page, IMG_ID_XXX) → 需要更新时通过 ui_get_disp_drv()->disp_page = page 挂接页面 → 调用 ui_draw()。

示例 3:性能测量开关(ui_draw.h)

/* 测试屏幕刷新时间(PA07)、推屏时间(PA06)使能、解压缩时间(PA07) */
#define TIME_DET_DEBUG 			0
#define TIME_REFR_TEST_PORT     IO_PORTA_07
#define TIME_PUSH_TEST_PORT     IO_PORTA_06
#define TIME_DECODER_TEST_PORT  IO_PORTA_07
#if TIME_DET_DEBUG
#define TIME_DET_START(a)  {gpio_hw_set_direction(IO_PORT_SPILT(a), 0); gpio_hw_write(a, 1);}
#define TIME_DET_END(a)    {gpio_hw_write(a, 0);}
#else
#define TIME_DET_START(a)
#define TIME_DET_END(a)
#endif

Source: ui_draw.h

调试时把 TIME_DET_DEBUG 改为 1,在 ui_draw() 的刷新/推屏/解压代码段前后插入 TIME_DET_START(port) / TIME_DET_END(port),即可用逻辑分析仪测量各阶段耗时;发布时保持 0,宏展开为空,零开销。

示例 4:工具宏(ARRAY_LEN / abs)

#ifndef ARRAY_LEN
#define ARRAY_LEN(array)	(sizeof(array) / sizeof(array[0]))
#endif

#ifndef abs
#define abs(x)  ((x)>0?(x):-(x) )
#endif

Source: ui_core.h

ARRAY_LEN 常用于遍历资源 ID 表以批量创建控件;abs 用于坐标差计算(如控件移动距离判定)。二者均带 #ifndef 保护,避免与系统头文件重复定义冲突。

失败模式、边界情况与并发

  • 控件查找失败:ui_obj_img_add / ui_obj_img_del 按 obj_id_t 定位资源。若 all_image_id.h 中的 ID 与资源文件不一致(离线工具版本不匹配),控件创建失败或绘制异常。这要求资源头文件与 uitoolbin.bin 必须同批生成、同步更新。
  • 内存不足:ui_img_t 节点动态分配,RAM 紧张的 MCU 上控件过多会导致添加失败(返回非零)。应控制单页面控件数量,并复用 num 索引切换图片而非重复建控件。
  • 双缓冲撕裂:buf_act 指向当前活动缓冲;若绘制线程与推屏(可能由中断/定时器触发)同时访问同一缓冲,会产生撕裂。flushing(volatile)作为软件互斥标志:推屏期间置 1,绘制方应等待或跳过,避免写正在推送的缓冲。volatile 保证中断与主循环间的可见性。
  • RLE 解压中断恢复:大图解压耗时较长,可能被更高优先级任务打断;ui_jlp_t 的 rgb_result / alpha_result 进度暂存使解压可分段进行、断点续传,保证最终画面完整。
  • 状态位图扩展:UI_OBJ_STATE_MAX = 7 预留了最多 7 个状态位(BIT(0)~BIT(7)),当前仅使用 HIDDEN 与 PRESS;新增状态时需同步处理绘制遍历中的过滤逻辑,避免遗漏。

性能与运维考量

  • 按需重绘:act_flag 脏标记机制保证 ui_draw() 只处理变化的控件,是性能的核心。应用应避免在无变化时频繁调用 ui_draw()。
  • 局部刷新:bulk_index / bulk_num / buf_offset + SetDrawArea 支持只推脏区域,显著降低 SPI 总线与 LCD 的刷新负担。
  • 测时手段:TIME_DET_DEBUG 提供 GPIO 级测时钩子,可精确测量刷新、推屏、解压三个热点耗时,用于定位性能瓶颈。
  • 资源体积:RLE 分块压缩在保证解压速度的同时减小资源体积;ui_jlp_t 将内存峰值限制在单块大小,避免大图撑爆 RAM。

扩展点

  1. 替换 LCD 驱动:实现 disp_drv_t 的 SetDrawArea / Draw 回调(参考 ui_ll.c),即可适配不同分辨率、不同接口(SPI/并口)的屏幕,核心绘制层零改动。
  2. 新增资源格式:ui_img_t.img_type 目前定义 0(JL 格式)与 1(未压缩)两种;新增格式只需扩展 img_type 取值并在 ui_image.c 的解码分流中增加分支。
  3. 新增控件状态:在 ui_obj_state_t 的 UI_OBJ_STATE_MAX 之前添加状态位,并配合 ui_img_t.state 位操作实现自定义交互(如选中态、禁用态)。
  4. 扩展图片切换:通过 num 字段指向图片列表中的任意帧,可低成本实现动画帧切换。

相关链接

  • ui_core.h(核心结构与 API 声明)
  • ui_draw.h(绘制入口与测时宏)
  • ui_core.c(控件链表与显存管理实现)
  • ui_draw.c(绘制调度实现)
  • ui_ll.c(底层显示驱动)
  • ui_api.c(对外 API 封装)
  • ui_demo.c(UI 示例程序)
  • ui_resfile.c(资源文件解析)
  • rle.c(RLE 分块解压)
  • ui_image.c(图片绘制)
  • 图片资源生成工具(离线资源构建)
Prev
LCD 驱动与字库引擎
Next
UI 工程与资源生成工具