杰理 SDK 文档中心
首页
首页
  • SDK 概述与快速开始

    • SDK 概览与 AC791N 芯片平台
    • 环境搭建与编译指南
    • 烧录与固件升级
    • 工程结构导览
  • 产品方案应用

    • WiFi 摄像头方案
    • WiFi IPC 可视对讲方案
    • WiFi 故事机方案
    • 扫码枪 HID 方案
    • 开发板示例工程
  • 公共应用组件

    • 语音识别 ASR 引擎
    • LLM 与 AI 语音助手接入
    • 摄像头传感器驱动
    • UI 显示框架与驱动
    • USB 主机与设备栈
    • 文件系统与存储管理
    • 系统服务与外设管理
    • 生产测试与射频工具
  • 蓝牙协议栈

    • 经典蓝牙 BR/EDR
    • BLE 低功耗蓝牙
    • 蓝牙 Mesh 网络
    • 蓝牙扩展协议(RCSP/广播/无线麦克风)
  • WiFi 与网络协议栈

    • WiFi 驱动与网络模式
    • lwIP TCP/IP 协议栈
    • 网络安全与加密库
    • 应用层网络协议
    • 流媒体与音视频传输
    • 云平台接入 SDK
    • P2P 远程访问与设备互联
  • 芯片平台与驱动

    • wl82 平台与硬件加速
    • 外设驱动框架
    • 平台配置与固件打包工具
  • 媒体与音频引擎

    • 音频编解码与音源
    • 音效处理引擎
    • 视频与图像处理
  • 操作系统与运行时

    • 实时操作系统与 POSIX 层
    • C/C++ 运行时库
  • 开发资源与文档

    • 文档与规格书
    • 公共示例工程
    • UI 资源工程与打包
    • SDK 辅助工具与脚本

UI 显示框架与驱动

本页介绍 AC79NN SDK 中 apps/common/ui 目录下的 UI 显示框架与 LCD 显示驱动:从板级配置、屏识别、显示缓冲区管理、图像合成双线程推屏,到具体 LCD 驱动芯片适配与 UI/摄像头帧数据的上屏接口,是一套完整的多屏适配显示子系统。

目的与范围

本页覆盖的内容包括:

  • LCD 驱动抽象层:lcd_drive.h / lcd_drive.c 定义的 lcd_device 设备模型、注册与遍历机制、屏识别与初始化流程。
  • 显示数据通路:lcd_data_driver.c 实现的图像合成双线程推屏加速,以及 ui_show_frame() / lcd_show_frame() 等上屏接口。
  • 具体屏驱动:lcd_driver/ 下各芯片驱动(ILI9341、ILI9481、ILI9488、HX8357、ST7735S、ST7789S、ST7789T3、480x272 8bits 等)的适配方式。
  • 配套资源:字体初始化(fontinit.c)、GIF 播放(GUI_GIF.c / gif_api.c)、资源与 UI 动作配置头文件。

以下主题属于其他页面,不在本页展开:具体业务 UI 页面开发(参见 AWTK/LVGL/uGFX 等第三方 GUI 框架示例)、触摸屏驱动协议细节、摄像头采集与编码。本页聚焦"显示"这一条垂直链路。

概述

在 AC79NN 这类带屏 AIoT 芯片上,UI 显示框架需要解决三个核心矛盾:

  1. 多屏适配:同一套固件可能出货给不同分辨率和接口(SPI/EMI/PAP/IMD)的屏幕,驱动层必须做到"一套代码,多屏运行"。
  2. 性能:UI 帧与摄像头预览帧可能同时上屏,需要双线程并行推屏 + 图像合成(画中画)能力,避免阻塞 UI 刷新。
  3. 内存:嵌入式环境内存有限,显示缓冲区按整帧申请,需要精确计算 YUV420 与 RGB565/RGB888 的帧大小并复用三个全局帧缓冲。

SDK 通过 apps/common/ui 下的分层设计解决上述问题:板级配置(ui_devices_cfg)描述引脚,lcd_drive.c 负责识别并初始化屏幕,lcd_data_driver.c 负责把 UI/摄像头数据合成并推到屏幕,具体屏驱动以结构体回调(LCD_Init、SetDrawArea 等)挂接进统一设备链表。

架构

flowchart TD
    subgraph sg_App["应用层"]
        App["业务应用 / Demo"]
        UI_API["ui_api.h 接口<br/>ui_show_frame / play_gif_to_lcd"]
    end

    subgraph sg_Framework["UI 显示框架 (apps/common/ui)"]
        LCD_DRIVE["lcd_drive.c<br/>屏识别 / 初始化 / 旋转"]
        DATA_DRIVER["lcd_data_driver.c<br/>图像合成双线程推屏"]
        BUF["全局帧缓冲<br/>ui_save_buf / camera_save_buf / show_buf"]
        FONT["fontinit.c 字体"]
        GIF["GUI_GIF.c / gif_api.c"]
    end

    subgraph sg_Devices["设备驱动层"]
        DEV_LIST["lcd_device 链表<br/>list_for_each_lcd_driver"]
        ILI9341["lcd_ili9341.c"]
        ILI9481["lcd_ili9481.c"]
        ILI9488["lcd_ili9488.c"]
        HX8357["lcd_hx8357.c"]
        ST77["lcd_st7735s / st7789s / st7789t3"]
        RGB8["lcd_480x272_8bits.c"]
    end

    subgraph sg_HW["硬件层"]
        LCD_PANEL["LCD 屏 / 背光 / TE"]
        SPI_BUS["SPI / EMI 总线"]
    end

    App --> UI_API
    UI_API --> LCD_DRIVE
    LCD_DRIVE --> DATA_DRIVER
    DATA_DRIVER --> BUF
    UI_API --> FONT
    UI_API --> GIF
    LCD_DRIVE -->|"按 ID 匹配"| DEV_LIST
    DEV_LIST --> ILI9341
    DEV_LIST --> ILI9481
    DEV_LIST --> ILI9488
    DEV_LIST --> HX8357
    DEV_LIST --> ST77
    DEV_LIST --> RGB8
    ILI9341 --> LCD_PANEL
    RGB8 --> SPI_BUS
    ILI9481 --> SPI_BUS

架构分层说明:

  • 应用层通过 ui_api.h 暴露的接口(如 ui_show_frame()、play_gif_to_lcd())把 UI 帧数据交给框架,不直接操作具体屏幕。
  • 框架层由 lcd_drive.c(识别与初始化)与 lcd_data_driver.c(数据通路)组成。lcd_drive.c 读取板级配置 ui_cfg_data,确定屏幕型号后调用对应设备驱动的回调;lcd_data_driver.c 内部维护摄像头与 UI 两条数据通道,通过图像合成线程输出到 show_buf。
  • 设备驱动层是注册进 lcd_device 链表的各屏芯片驱动,各自实现 LCD_Init、SetDrawArea、SetColor 等回调,通过链接器区间(lcd_device_begin/lcd_device_end)自动收集,list_for_each_lcd_driver 宏遍历匹配。
  • 硬件层:最终像素数据经 SPI/EMI 等总线写入屏控制器,TE 引脚用于帧同步,背光引脚控制亮灭。

主要实现细节

LCD 驱动抽象:lcd_device 设备模型

apps/common/ui/include/lcd_drive.h 定义了统一的屏幕设备结构。核心成员包括私有协议指针与两个关键回调:

struct lcd_device {
    // ...(成员包括设备名、分辨率、对齐、旋转等字段)
    void *lcd_priv;                 //LCD私有协议,比如RGB屏相关配置
    int (*LCD_Init)(void);
    void (*SetDrawArea)(u16, u16, u16, u16);
};

Source: lcd_drive.h

lcd_priv 的存在让 RGB 屏这类需要私有配置的屏幕可以在不改动框架的前提下携带自己的初始化数据;LCD_Init 与 SetDrawArea 是框架唯一强依赖的两个回调——一个负责上电初始化屏控制器,一个负责划定写入窗口,其余如 SetColor、对齐、旋转字段都由框架按需读取。

所有屏驱动通过链接器段自动注册(lcd_device_begin 到 lcd_device_end),框架用如下宏遍历:

#define list_for_each_lcd_driver(lcd_dev) \
	for (lcd_dev = (struct lcd_device*)lcd_device_begin; lcd_dev < (struct lcd_device*)lcd_device_end; lcd_dev++)

Source: lcd_drive.h

这种"零注册代码"的设计使得新增一块屏只需要在 lcd_driver/ 下新增一个 .c 文件并放入链接器段,框架无需改动。

屏识别与全局参数

lcd_drive.c 中的 lcd_init() 是整套显示子系统的启动入口。它按优先级处理三种屏识别途径:

  1. 触摸识别:当 TCFG_LCD_ILI9341_ENABLE 开启时,先调用 check_gt911() 通过触摸芯片判断是否为 ILI9341 屏(240x320)。
  2. Check IO 识别:利用板级配置的 check_pin_D6/check_pin_D7 上下拉电平(get_check_pin_io_state())区分 ILI9481 与 ILI9488(320x480)。
  3. 强制指定:客户屏(如 TM9486X,480x800)放在最后,直接赋予固定 ID 与分辨率。
u8 lcd_choice = CONFIG_ILI9481_ID;
// ...
#if TCFG_LCD_ILI9341_ENABLE
        int check_gt911(void);//通过触摸来识别屏
        if (check_gt911() == 0) {
            printf("[msg]>>>>>>>>gt911_ok");
            lcd_choice = CONFIG_ILI9341_ID;
            lcd_w = 240;
            lcd_h = 320;
            lcd_bk_on = 1;
            lcd_bk_off = 0;
        }
#endif

Source: lcd_drive.c

识别完成后立即计算全局帧大小并配置数据驱动层:

//初始化全局显示内存配置
lcd_yuv420_data_size = lcd_w * lcd_h * 3 / 2;
#if TCFG_LCD_480x272_8BITS //rgb888
lcd_w = 480;
lcd_h = 272;
lcd_bk_on = 1;
lcd_bk_off = 0;
lcd_data_size = lcd_w * lcd_h * 3;
#else  //rgb565
lcd_data_size = lcd_w * lcd_h * 2;
#endif

//配置lcd_data_driver一些参数
set_lcd_data_driver_lcd_h_w(lcd_w, lcd_h);
set_lcd_data_size(lcd_data_size);
set_lcd_yuv420_data_size(lcd_yuv420_data_size);
set_lcd_bk_on(lcd_bk_on);
set_lcd_te_pgio(lcd_pdata->te_pin);

//申请全局lcd显示buf
extern void malloc_lcd_buf(void);
malloc_lcd_buf();

Source: lcd_drive.c

设计要点:lcd_w/lcd_h/lcd_bk_on/lcd_bk_off 是全局变量,值跟着屏走(见 lcd_drive.h),因此上层代码无需感知当前是哪种屏;YUV420 与 RGB565/RGB888 两种帧格式的大小分别按 3/2 与 2/3 字节每像素计算,malloc_lcd_buf() 一次性申请 ui_save_buf、camera_save_buf、show_buf 三个整帧缓冲。

随后遍历设备链表匹配屏 ID(find_lcd),匹配成功后初始化接口(lcd_interface_init(),根据 lcd_if 选择 SPI/EMI 等总线),调用 lcd_dev->LCD_Init() 点亮屏幕,最后启动图像合成线程:

list_for_each_lcd_driver(lcd_dev) {
    if (lcd_dev && lcd_dev->LCD_Init) {
        if (lcd_dev->name == "ili9341" && lcd_choice == CONFIG_ILI9341_ID) {
            find_lcd = true;
            break;
        }
        // ... ili9481 / ili9488 / HX8357 同理
    }
}
if (!find_lcd) {
    printf("err no lcd_dev\n");
    return -1;
}
if (lcd_interface_init() != 0) {
    printf("err unknow lcd port \n\n\n");
    return -1;
}
if (lcd_dev && lcd_dev->LCD_Init) {
    lcd_dev->LCD_Init();
    //图像合成线程初始化
    picture_compose_task_init();
    return 0;
}

Source: lcd_drive.c

两级初始化入口:user_lcd_init 与 user_ui_lcd_init

框架对外提供两个语义不同的初始化入口:

  • user_lcd_init():只初始化屏(背光、控制器、总线),不涉及 UI 资源——适合纯视频/摄像头预览场景。
  • user_ui_lcd_init():完成 UI 与 LCD 全部初始化;若开启 TCFG_USE_SD_ADD_UI_FILE(UI 资源放在 SD 卡),会先自旋等待存储设备就绪,再调用 lcd_ui_init() 加载 UI 资源:
void user_ui_lcd_init(void)
{
#if TCFG_USE_SD_ADD_UI_FILE
    int storage_device_ready(void);
    while (!storage_device_ready()) {
        os_time_dly(1);
    }
#endif
    extern const struct ui_devices_cfg ui_cfg_data;
    int lcd_ui_init(void *arg);
    lcd_ui_init(&ui_cfg_data);
}

Source: lcd_drive.c

板级引脚配置来自 ui_cfg_data(struct ui_devices_cfg),其 private_data 指向 struct ui_lcd_platform_data——包含 rst_pin、cs_pin、rs_pin、bl_pin、te_pin、check_pin_D6、check_pin_D7、touch_int_pin、touch_reset_pin 等(见 lcd_drive.h)。lcd_init() 中通过 ASSERT(lcd_pdata, "Error! lcd io not config") 强制校验板级配置必须存在。

图像合成与双线程推屏

lcd_data_driver.c 是数据通路的核心。文件头注释直接点明了设计:

*  该文件实现了图像合成双线程推屏加速
*  上层通过lcd_show_frame()到达该文件的camera_send_data_ready()
*  ui数据会自动对接到接口ui_send_data_ready()

Source: lcd_data_driver.c

两条数据通路并行到达:

  • UI 通路:UI 渲染结果(RGB 数据)经 ui_show_frame() 进入 ui_save_buf,触发 ui_send_data_ready();
  • 摄像头通路:摄像头 YUV 数据经 lcd_show_frame() 进入 camera_save_buf,触发 camera_send_data_ready()。

图像合成线程(picture_compose_task_init() 启动)把两路数据按颜色过滤规则合成到 show_buf,再经 SPI/EMI 推送到屏幕。合成时的颜色过滤宏定义在 lcd_drive.h:FILTER_COLOR(纯黑,RGB888)/ FILTER_COLOR_RGB565(纯黑,RGB565)作为透明色被过滤,FILTER_COLOR_OUTER_FRAME(0xffff)作为画中画外框色——这是实现"摄像头预览 + UI 叠加/画中画"的基础。

lcd_drive.h 中还定义了三个全局帧缓冲与旋转/调试相关常量:

//全局显示内存 三个整帧屏显
extern u8 *ui_save_buf;
extern u8 *camera_save_buf;
extern u8 *show_buf;

Source: lcd_drive.h

核心流程

系统启动:从板级配置到首帧上屏

sequenceDiagram
    participant App as 应用 main()
    participant UI as user_ui_lcd_init()
    participant D as lcd_init() / lcd_drive.c
    participant Cfg as ui_cfg_data 板级配置
    participant Dev as lcd_device 链表
    participant DD as lcd_data_driver.c
    participant LCD as 屏控制器

    App->>UI: user_ui_lcd_init()
    UI->>UI: (可选) 等待 storage_device_ready
    UI->>D: lcd_ui_init(&ui_cfg_data)
    D->>Cfg: 读取 private_data 引脚配置
    D->>D: check_gt911 / get_check_pin_io_state 识别屏
    D->>D: 计算 lcd_data_size / lcd_yuv420_data_size
    D->>D: malloc_lcd_buf 申请三帧缓冲
    D->>Dev: list_for_each_lcd_driver 按 ID 匹配
    Dev-->>D: find_lcd = true
    D->>D: lcd_interface_init() 初始化 SPI/EMI
    D->>LCD: lcd_dev->LCD_Init() 点亮屏幕
    D->>DD: picture_compose_task_init() 启动合成线程
    DD-->>App: 就绪,等待帧数据

双路帧数据上屏

sequenceDiagram
    participant UIApp as UI 渲染
    participant Cam as 摄像头
    participant F as 框架接口
    participant DD as lcd_data_driver
    participant Buf as 帧缓冲
    participant LCD as 屏

    UIApp->>F: ui_show_frame(buf, len) (RGB)
    F->>Buf: 写入 ui_save_buf
    Buf-->>DD: ui_send_data_ready()
    Cam->>F: lcd_show_frame(buf, size, w, h) (YUV)
    F->>Buf: 写入 camera_save_buf
    Buf-->>DD: camera_send_data_ready()
    DD->>DD: 合成线程按 FILTER_COLOR 过滤/叠加
    DD->>Buf: 合成到 show_buf
    DD->>LCD: SPI/EMI 非阻塞刷帧 + TE 同步

关键设计:两条通路互不阻塞(双线程推屏),合成线程只读两个输入帧缓冲、写一个输出帧缓冲,UI 刷新率不受摄像头帧率影响;dev_ioctl(lcd_hdl, IOCTL_SPI_WRITE_NON_BLOCK_FLUSH, 0)(见 lcd_drive.c)等待上一次发包完成,保证 SPI 写时序不乱序。

使用示例

示例 1:只初始化屏幕(视频/预览场景)

void user_lcd_init(void)
{
    if (!lcd_hdl) {
        extern const struct ui_devices_cfg ui_cfg_data;
        lcd_init(&ui_cfg_data);//ui_cfg_data参数可以在版籍配置
    }
}

Source: lcd_drive.c

该入口适合不加载 UI 资源的场景:应用只需在启动早期调用一次,lcd_hdl 句柄保证幂等(重复调用直接返回)。

示例 2:完整初始化(UI + LCD)

void user_ui_lcd_init(void)
{
#if TCFG_USE_SD_ADD_UI_FILE
    int storage_device_ready(void);
    while (!storage_device_ready()) {
        os_time_dly(1);
    }
#endif
    extern const struct ui_devices_cfg ui_cfg_data;
    int lcd_ui_init(void *arg);
    lcd_ui_init(&ui_cfg_data);
}

Source: lcd_drive.c

当 UI 资源存放于 SD 卡(TCFG_USE_SD_ADD_UI_FILE)时,必须等存储设备挂载完成才能加载资源,否则 lcd_ui_init 会读不到文件;这里用 os_time_dly(1) 自旋等待而非直接失败返回,体现"资源依赖必须先就绪"的启动约束。

示例 3:新增一块屏驱动(以 480x272 8bits RGB 屏为例)

    .row_addr_align    = 1,
	.LCD_Init          = lcd_480x272_8bits_init,
    .SetDrawArea       = lcd_480x272_8bit_SetRange,

Source: lcd_480x272_8bits.c

新增屏驱动的模式是:在 apps/common/ui/lcd_driver/ 下新建 lcd_xxx.c,定义 struct lcd_device lcd_xxx_dev 并填写 name、分辨率、row_addr_align、LCD_Init、SetDrawArea 等字段(字段值跟随屏走),然后通过链接器段加入设备链表。框架侧无需任何修改即可被 list_for_each_lcd_driver 遍历到——这是该框架最典型的扩展点。

示例 4:框架对外上屏接口

void ui_show_frame(u8 *buf, u32 len);//该接口主要用于显示UI数据  RGB数据接口
void lcd_show_frame(u8 *buf, u32 size, int width, int height);//数据为YUV数据
void play_gif_to_lcd(char *gif_path, char play_speed);//播放GIF动图 seepd = 1 = 10ms

Source: lcd_drive.h

三个接口覆盖三种显示需求:UI 静态/动态界面(RGB)、摄像头视频流(YUV,需指定宽高供框架换算帧大小)、GIF 动图(指定播放速度,play_speed = 1 表示每帧 10ms)。GIF 的实现位于 GUI_GIF.c / gif_api.c,解码后同样走 RGB 通路推屏。

配置选项

编译期配置(宏开关)

宏类型默认说明
LCD_DEBUG_ENABLEint0LCD 调试等级:0 只打印错误;1 打印错误+警告;2 全部打印(见 lcd_drive.h)
TCFG_LCD_ILI9341_ENABLEbool关启用 ILI9341 屏(240x320),并开启触摸识别分支
TCFG_LCD_ILI9481_ENABLEbool关启用 ILI9481 屏(320x480),并影响背光电平定义
TCFG_LCD_ILI9488_ENABLEbool关启用 ILI9488 屏(320x480),并影响背光电平定义
TCFG_LCD_TM9486X_ENABLEbool关启用客户屏 TM9486X(480x800),优先级最后
TCFG_LCD_480x272_8BITSbool关启用 480x272 RGB888 8bits 屏,切换帧格式计算(3 字节/像素)
TCFG_USE_SD_ADD_UI_FILEbool关UI 资源存放在 SD 卡,初始化前需等待存储设备就绪

屏识别与背光逻辑都依赖这些宏:例如 enum LCD_BL 中当 ILI9488/ILI9481 使能时 BL_ON=0、BL_OFF=1(背光低有效),否则相反(见 lcd_drive.h)——宏不仅控制功能,还直接参与电平语义定义。

板级运行期配置(ui_cfg_data)

struct ui_devices_cfg 的 private_data 指向 struct ui_lcd_platform_data,字段包括:

字段类型说明
rst_pinu32LCD 复位引脚
cs_pinu32片选引脚
rs_pinu32寄存器/数据选择引脚(DC)
bl_pinu32背光控制引脚
te_pinu32TE 帧同步引脚
check_pin_D6 / check_pin_D7u32屏型号识别 IO(上下拉电平区分屏)
touch_int_pin / touch_reset_pinu32触摸中断/复位引脚

板级配置缺失时 lcd_init() 通过 ASSERT(lcd_pdata, "Error! lcd io not config") 直接断言失败,属于强制校验。

图像合成颜色配置

宏值说明
FILTER_COLOR0x000000图像合成过滤纯黑色(RGB888 透明色)
FILTER_COLOR_RGB5650x0000图像合成过滤纯黑色(RGB565 透明色)
FILTER_COLOR_OUTER_FRAME0xffff画中画外框颜色

见 lcd_drive.h。

API 参考

void user_lcd_init(void)

只初始化屏幕(含背光、总线、屏控制器),不加载 UI 资源。以 lcd_hdl 是否为空保证幂等。

参数: 无 返回: 无 注意: 需要 ui_cfg_data 板级配置有效;内部可能因未匹配到屏驱动或接口未知而打印 err no lcd_dev / err unknow lcd port 并返回。

void user_ui_lcd_init(void)

完整初始化 UI 与 LCD。开启 TCFG_USE_SD_ADD_UI_FILE 时先自旋等待存储设备就绪,再调用 lcd_ui_init(&ui_cfg_data)。

参数: 无 返回: 无

void ui_show_frame(u8 *buf, u32 len)

UI 数据上屏接口(RGB 数据)。数据写入 ui_save_buf 并触发 ui_send_data_ready(),由合成线程接管。

参数:

  • buf (u8*):UI 帧数据指针
  • len (u32):数据长度(应与 lcd_data_size 匹配)

返回: 无

void lcd_show_frame(u8 *buf, u32 size, int width, int height)

摄像头/视频数据上屏接口(YUV 数据)。数据写入 camera_save_buf 并触发 camera_send_data_ready()。

参数:

  • buf (u8*):YUV 帧数据指针
  • size (u32):数据长度
  • width (int):图像宽度
  • height (int):图像高度

返回: 无

void play_gif_to_lcd(char *gif_path, char play_speed)

播放 GIF 动图到 LCD。

参数:

  • gif_path (char*):GIF 文件路径
  • play_speed (char):播放速度,1 表示每帧 10ms

返回: 无

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

屏驱动未匹配

list_for_each_lcd_driver 遍历结束后 find_lcd 仍为 false 时,打印 err no lcd_dev 并返回 -1(见 lcd_drive.c)。常见原因:屏驱动文件未加入编译/链接器段、name 字段与 lcd_choice 匹配条件不一致、屏 ID 宏配置遗漏。

接口初始化失败

lcd_interface_init() 返回非 0 时打印 err unknow lcd port 并返回 -1。lcd_pdata->lcd_if(enum LCD_INTERFACE:LCD_SPI/LCD_EMI/LCD_PAP/LCD_IMD)与板级总线配置不一致是典型诱因。

板级配置缺失

ASSERT(lcd_pdata, "Error! lcd io not config") 强制断言。设计意图:显示是产品必选功能,宁可启动即暴露配置错误,也不允许静默黑屏。

背光电平语义随屏变化

enum LCD_BL 的 BL_ON/BL_OFF 值受 TCFG_LCD_ILI9488_ENABLE || TCFG_LCD_ILI9481_ENABLE 影响而翻转。移植到新屏时若忽略该宏语义,会出现"背光点不亮"或"上电即亮"的边界问题。全局 lcd_bk_on/lcd_bk_off 在 lcd_init 中随屏型号赋值,正是为了把这种差异收敛到一处。

双线程推屏的并发约束

UI 通路(ui_send_data_ready)与摄像头通路(camera_send_data_ready)由不同线程触发,合成线程同时读取 ui_save_buf 与 camera_save_buf。设计上依赖"写完才发 ready 信号"的握手顺序保证可见性;dev_ioctl(lcd_hdl, IOCTL_SPI_WRITE_NON_BLOCK_FLUSH, 0) 保证上一帧 SPI 发包完成后才提交下一帧,避免总线乱序。若某一路数据长期不更新(如未开摄像头),合成结果只含另一路内容,符合预期。

资源未就绪的启动边界

TCFG_USE_SD_ADD_UI_FILE 开启时,user_ui_lcd_init 以 os_time_dly(1) 自旋等待 SD 卡挂载。若存储介质损坏导致永远不就绪,该函数会阻塞——这是"资源必须可用"的强约束设计,实际产品中由上层看门狗兜底。

性能与运维

  • 帧缓冲复用:ui_save_buf、camera_save_buf、show_buf 三个整帧缓冲由 malloc_lcd_buf() 一次性申请,避免运行时反复分配;YUV420 按 w*h*3/2、RGB565 按 w*h*2、RGB888 按 w*h*3 精确计算,不浪费内存。
  • 双线程推屏:UI 与摄像头两条数据通路并行,合成线程负责最终输出,UI 帧率不被摄像头帧率拖累——这是 lcd_data_driver.c 被设计为独立文件的核心原因。
  • 非阻塞刷帧:SPI/EMI 使用 NON_BLOCK_FLUSH 等待机制,刷帧不阻塞 UI 线程。
  • 调试分级:LCD_DEBUG_ENABLE 控制 lcd_d/lcd_w/lcd_e 三级日志,默认 0 仅保留 [LCD ERROR],量产固件无需改动即可关闭冗余打印。
  • 旋转支持:ROTATE_DEGREE_0/90/180/270 宏(lcd_drive.h)为横竖屏切换预留;具体屏驱动的 SetDrawArea/写入方向需与旋转配合。

扩展点

  1. 新增屏驱动:在 apps/common/ui/lcd_driver/ 新建 lcd_xxx.c,实现 struct lcd_device(含 LCD_Init、SetDrawArea 回调),放入链接器段即可,框架零改动。参考 lcd_480x272_8bits.c 的字段填充模式。
  2. 新增屏 ID 识别:在 lcd_init() 的识别链中追加分支(触摸识别 → check IO 识别 → 强制指定),并在 list_for_each_lcd_driver 匹配处增加 name 与 CONFIG_XXX_ID 的比较。
  3. 自定义接口总线:enum LCD_INTERFACE 已预留 LCD_SPI/LCD_EMI/LCD_PAP/LCD_IMD 四类;新增总线类型需同步扩展 lcd_interface_init() 与刷帧 dev_ioctl 分支。
  4. RGB 屏私有协议:通过 lcd_device.lcd_priv 携带私有配置(如 RGB 屏时序),无需改动框架数据结构。
  5. GIF 扩展:GUI_GIF.c/gif_api.c 提供动图能力,业务可直接调用 play_gif_to_lcd(),无需关心解码细节。

测试与示例代码

  • apps/common/example/ui/ui_touch_demo.c 是触摸 + UI 显示的一体化示例,展示了 user_ui_lcd_init 在真实应用中的调用方式与触摸事件配合。
  • 第三方 GUI 框架示例(apps/common/example/third_party/awtk、lvgl_v9、ugfx)展示了在本框架之上挂接完整 GUI 库的集成路径,其中 AWTK 还包含 remote_ui 远程 UI 客户端/服务端实现,可作为"框架驱动 + 第三方渲染"的参考。

相关链接

  • lcd_drive.h — LCD 驱动框架头文件:设备模型、枚举、宏与全局变量定义
  • lcd_drive.c — 屏识别、初始化与刷帧实现
  • lcd_data_driver.c — 图像合成双线程推屏
  • lcd_config.h — LCD 编译期配置
  • ui_api.h — UI 对外接口
  • lcd_480x272_8bits.c — 屏驱动实现范例
  • GUI_GIF.c / gif_api.c — GIF 动图播放
  • fontinit.c — 字体初始化
  • 第三方 GUI 集成:AWTK(apps/common/example/third_party/awtk)、LVGL v9(apps/common/example/third_party/lvgl_v9)、uGFX(apps/common/example/third_party/ugfx)
Prev
摄像头传感器驱动
Next
USB 主机与设备栈