Files
TAIXIN/项目笔记.md

33 KiB
Raw Permalink Blame History

TXW82x FPV SDK 项目笔记

版本v2.7.0.7-42229 芯片厂商HugeIC宏晶 记录日期2026-06-09


一、项目概述

这是一个 TXW82x FPV第一人称视角嵌入式 Wi-Fi 视频传输 SDK,主要用于无人机/航模/婴儿监视器/对讲机等实时无线视频传输设备的固件开发。

  • 目标芯片TXW82xC-SKY CK804DF 内核32位 RISC 处理器192MHz带硬件浮点和 DSP
  • 双核架构CPU0 运行 WiFi MAC 层固件(txw82xcore.binCPU1 运行应用固件,通过邮箱 RPC 通信
  • 编程语言:几乎全是 C 语言1584 个 .c 文件),少量汇编用于启动代码
  • 支持的 RTOSAliOS (Rhino) 和 OpenHarmony LiteOS-M二选一

二、目录结构

目录 说明
project/ 主工程目录,包含应用代码、配置、构建脚本、入口文件
sdk/ 核心 SDK包含应用模块、HAL、驱动、库
csky/ C-SKY CPU 支持,包含 RTOS 内核AliOS Rhino、DSP/NN 库、minilibc
ohos/ OpenHarmony LiteOS-M 内核(可选的第二个 RTOS
libs/ 预编译静态库19 个 .a 文件WiFi、ISP、Video、Audio 等闭源模块)
tools/ 工具目录(此版本为空)
doc/ 文档目录(此版本为空)
no_WPA3/ 不含 WPA3 的核心固件备选

三、系统架构(从上到下)

┌─────────────────────────────────────────┐
│          用户应用层 (project/app/)        │
│   FPV │ Demo │ 婴儿监视器 │ 对讲机 等     │
├─────────────────────────────────────────┤
│         SDK 应用模块 (sdk/app/)          │
│  RTSP │ Video │ Audio │ UI/LVGL │ MQTT   │
├─────────────────────────────────────────┤
│         SDK 库 (sdk/lib/)               │
│  视频│音频│网络│蓝牙│加密│文件系统│多媒体   │
├─────────────────────────────────────────┤
│         HAL 硬件抽象层 (sdk/hal/)        │
│         40 个外设 HAL 模块               │
├─────────────────────────────────────────┤
│         驱动层 (sdk/driver/)             │
│         16 类外设驱动                    │
├─────────────────────────────────────────┤
│         OSAL 操作系统抽象层 (sdk/osal/)   │
│    AliOS (Rhino) │ OpenHarmony (LiteOS)  │
├─────────────────────────────────────────┤
│         芯片启动 (sdk/chip/txw82x/)      │
│         向量表 │ 中断 │ 系统初始化        │
├─────────────────────────────────────────┤
│         预编译核心库 (libs/*.a)           │
│  WiFi MAC │ ISP │ Video │ Audio │ Flash  │
├─────────────────────────────────────────┤
│         TXW82x 硬件 (CK804DF 双核)       │
│  WiFi│H264│JPEG│ISP│LCD│USB│SD│PSRAM     │
└─────────────────────────────────────────┘

四、关键源文件

4.1 入口和配置(最先看)

文件 行数 说明
project/project_config.h - 主配置,CUSTOMER_ID 选择产品类型
project/sys_config.h - 系统配置OS选择、内存布局
project/config.cfg - 引脚复用/GPIO 配置
project/main.c 286 主入口初始化看门狗→CPU RPC→堆→配置→事件→WiFi→网络→启动应用

4.2 系统初始化

文件 行数 说明
project/device.c 725 所有硬件外设初始化
project/events.c 180 WiFi/网络事件处理
project/wifi.c 171 WiFi 参数设置ACS、速率控制、发射功率
project/network.c - LwIP 网络栈初始化
project/syscfg.c/h - 系统配置持久化

4.3 应用层

文件 行数 说明
project/app/app_fpv.c 597 FPV 应用初始化,视频/音频流、RTSP、对讲
project/app/app_demo.c - 通用 Demo
project/app/app_tunning.c - ISP 调参
project/app/app_bbm_cam.c - 婴儿监视器-摄像头端
project/app/app_bbm_lcd.c - 婴儿监视器-LCD 端
project/app/app_walkie_talkie.c - 对讲机
project/app/fpv_mem.c - FPV 内存管理

4.4 SDK 层

目录 说明
sdk/app/spook/ RTSP 服务器RTP/H264/MJPEG 流媒体)
sdk/app/video_app/ 视频采集、编码
sdk/app/audio_msi/ 音频 ADC/DAC
sdk/app/recorder/ 视频录制MP4/AVI
sdk/app/intercom/ 双向对讲
sdk/app/ui/ LVGL UI 界面
sdk/app/http/ HTTP 客户端/服务端
sdk/app/mqtt/ MQTT 客户端
sdk/app/babyprotocol/ 婴儿监视器协议
sdk/app/walkie-talkie/ 对讲机应用
sdk/app/decode/ JPEG/H264 解码
sdk/app/scale_msi/ 图像缩放
sdk/app/mp4/ MP4 容器封装

五、支持的产品配置

通过 project_config.h 中的 CUSTOMER_ID 切换:

ID 产品 说明
1 FPV Demo FPV 实时视频传输
2 ISP Tunning Demo ISP 图像调参
3 720P Camera H264 主码流 + H264 子码流 + RTSP + MP4 录制
4 1080P Camera H264 1080P@25fps + MJPG 子码流 + RTSP + MP4 录制
5 720P→1080P 720P 上采样到 1080P
6 UVC Camera USB 视频类摄像头
7 Walkie-Talkie 对讲机
8 LCD Demo LCD 显示
9 Baby Monitor LCD 婴儿监视器 LCD 端
10 Baby Monitor Camera 婴儿监视器摄像头端
11 LCD MP4 Player LCD MP4 播放器

六、构建系统

6.1 开发环境

  • IDEC-SKY Development Kit (CDK) V2.24.19
  • 工作区文件project/txw82xApp.cdkws
  • 工具链csky-elfabiv2-gccC-SKY 架构交叉编译器 V3.10.33
  • 编译器标志-mcpu=e804df -mhard-float -melrw -Os -g3 -Wall -ffunction-sections -fdata-sections

6.2 编译流程

  1. PreBuildprebuild.sh 清除旧目标文件 + precompile.exe 生成代码
  2. Buildmake -f txw82xApp.mk -j 8(编译 500+ 源文件 + 链接 19 个预编译库)
  3. PostBuildBuildBIN.sh
    • 拷贝 .elf.map.hex 到工程根目录
    • crc.exe CRC 校验
    • BinScript.exe 合并核心固件 + 应用 hex → txw82xApp.bin
    • makecode.exe 生成 SPI Flash 镜像
    • merge.bat 创建带时间戳的最终 .bin

6.3 如何构建

在 CDK IDE 中打开 project/txw82xApp.cdkws,点击 Build或命令行在 project/ 目录下运行 make -f cdkws.mk(需工具链在 PATH 中)。


七、硬件资源

7.1 内存布局

区域 起始地址 大小 用途
ISRAM 0x04001000 168 KB 核心代码
SRAM 0x20006B44 256 KB 堆/数据
SRAM0 0x20001100 ~12 KB 部分 sleep 用
SRAM2 0x20068000 ~14 KB 最后 256 字节 CoreSetting
FLASH 0x10000000 2 MB 固件存储
PSRAM 0x28000000 8 MB 外部 PSRAM

7.2 片上外设

  • WiFi802.11 b/g/nAP + STA 模式HT MCS0-7
  • 蓝牙低功耗BLE
  • MIPI CSI摄像头接口支持双摄
  • DVP并口摄像头接口
  • H.264 硬件编码器
  • JPEG 硬件编码/解码器
  • ISP图像信号处理器AE/AWB/gamma/IR-cut
  • VPP视频后处理器
  • LCD 控制器MIPI DSI + MCU 接口)
  • Audio ADC/DAC、I2S、PDM
  • USB 2.0 OTG + USB 1.1 Host
  • SD 卡主机SDIO
  • SPI、I2C、UART、PWM、ADC、DMA、SHA、AES
  • PSRAM 接口(外部 8MB

7.3 支持的图像传感器

GC1084, GC2053, GC2083, GC2145, GC0308, GC0329, OV2640, OV7725, OV9734, SC1346, SC2331, SC2336P, BF3A03, BF30A2, BF2013, BF3703, BF3720, IMX219, H62, H63P, F37P, F38P, TP9950 等

7.4 支持的 LCD 面板

ST7701SMIPI, ST7789V, ST7735, ST7796, ILI9881C, JD9852 等


八、网络协议栈

  • LwIP TCP/IP 协议栈DHCP 客户端/服务端、DNS、NAT
  • RTSP/RTP 服务器spook支持 H264/MJPEG 实时流
  • HTTP 客户端/服务端
  • MQTT 客户端
  • libcurl(完整 HTTP 客户端库)
  • WiFi AP 模式默认 IP192.168.169.1
  • 多种 WiFi 速率控制算法FPV/航拍/IPC/婴儿监视器场景)

九、预编译库说明

库文件 说明
libcore.a WiFi MACLMAC/UMAC核心
libwifi.a WiFi 驱动
libvideo.a 视频编码管线
libisp.a 图像信号处理
libaudio_app.a 音频应用
libaudio_drver.a 音频驱动
libflash.a Flash 访问
libpsram.a PSRAM 驱动
libusb.a USB 协议栈
libsd.a SD 卡主机
libcommon.a 通用工具
libatcmd.a AT 命令解析

十、学习路径建议

  1. 先读配置project/project_config.hproject/sys_config.hproject/config.cfg
  2. 跟主线:从 project/main.cmain() 函数开始,按初始化流程往下读
  3. 读应用:根据 CUSTOMER_ID 找到对应的应用文件(如 FPV 看 app_fpv.c
  4. 理解 SDK 层:需要用到具体功能时再看 sdk/app/ 下的对应模块
  5. 底层驱动:需要硬件调试时再看 sdk/hal/sdk/driver/
  6. 构建验证:安装 CDK IDE打开 .cdkws 工作区,尝试编译和烧录

十一、如何添加/修改功能

11.1 项目文件职责速查

project/
├── project_config.h    ← 全局宏配置(功能开关、参数)
├── main.c              ← 程序入口,系统初始化流程
├── events.c            ← 系统事件分发WiFi连接/断开/DHCP等
├── wifi.c              ← WiFi初始化和参数配置
├── network.c           ← 网络初始化
├── syscfg.c/h          ← 系统参数持久化存储Flash读写
├── atcmd.c             ← AT命令注册表
├── device.c            ← 所有硬件外设初始化
├── app/
│   ├── app_fpv.c       ← FPV图传应用主逻辑当前激活CUSTOMER_ID=11
│   ├── app_demo.c      ← 通用Demo
│   ├── app_bbm_lcd.c   ← 婴儿监视器LCD端
│   ├── app_bbm_cam.c   ← 婴儿监视器摄像头端
│   ├── app_walkie_talkie.c ← 对讲机
│   ├── app_tunning.c   ← ISP调参
│   └── user_app.h      ← 应用层对外接口声明
├── module_code/        ← 硬件模块用户层代码
│   ├── gen422_user.c   ← H.264辅码流(gen420)用户层
│   ├── h264_user.c     ← H.264编码用户层
│   ├── jpg_user.c      ← JPEG编解码用户层
│   └── prc_user.c      ← PRC电源/复位用户层
├── config.cfg          ← 引脚配置脚本
└── pin_param.h         ← 生成的引脚映射

11.2 添加新功能模块6步流程

步骤1project_config.h 添加宏开关

在对应 CUSTOMER_ID 分支当前为ID=11中添加

#define MY_FEATURE_EN    1    // 我的新功能使能0=禁用1=启用

步骤2创建功能代码文件

project/app/ 下新建 app_my_feature.c

#include "sys_config.h"
#include "basic_include.h"

// 初始化函数,会被 main.c 的 sys_app_init() 调用
int sys_app_my_feature_init(void)
{
    // 在此初始化你的功能模块
    os_printf("my feature init ok\r\n");
    return 0;
}

步骤3user_app.h 声明接口

int sys_app_my_feature_init(void);

步骤4main.csys_app_init() 中注册

sys_app_init() 函数内添加:

#ifdef MY_FEATURE_EN
    sys_app_my_feature_init();
#endif

步骤5处理系统事件如需监听WiFi/网络事件)

有两种方式,根据复杂度选择:

方式A轻量事件(直接在 events.csys_event_hdl() 中添加):

#if MY_FEATURE_EN
    my_feature_event_handler(event_id, data, priv);
#endif

适合:只需响应几个简单事件的场景。

方式B复杂事件(用 sys_event_take() 注册独立处理函数):

main() 函数的正常模式分支中添加:

sys_event_take(0xffffffff, my_feature_event_hdl, 0);

适合:需要完整事件处理链的复杂模块。注意:每次调用消耗 16 字节 heap。

WiFi协议栈回调(在 events.csys_wifi_event_cb() 中添加):

#if MY_FEATURE_EN
    ret |= my_wifi_event_hdl(ifidx, evt, param1, param2);
#endif

重要sys_wifi_event_cb() 在WiFi协议栈Task中执行禁止耗时操作如DHCP、Flash读写、长循环。 需要耗时逻辑时,通过 sys_event_post() 将事件转发到 sys_event_hdl() 处理。

步骤6添加AT调试命令可选

atcmd.cstatic_atcmds[] 表中注册:

#ifdef MY_FEATURE_EN
    { "AT+MYCMD", my_atcmd_handler },
#endif

AT命令处理函数格式

int32 my_atcmd_handler(const char *cmd, char *argv[], uint32 argc)
{
    // 解析参数、执行操作、返回结果
    os_printf("my feature cmd received\r\n");
    return ATCMD_RESULT_DONE;
}

11.3 修改现有功能——按目标定位文件

修改目标 修改文件 说明
功能开关/使能 project_config.h 改宏值0↔1或添加新宏
WiFi功率/速率/信道 project_config.hWIFI_* 参数配置
WiFi初始化逻辑 wifi.c ACS扫描、速率控制、FEM芯片初始化
网络IP/DHCP配置 project_config.hNET_*/DHCPD_* IP地址池、子网等
网络初始化流程 network.c + syscfg.c LwIP初始化、DHCP服务端
FPV图传逻辑 app/app_fpv.c 视频/音频流、RTSP、对讲、录像
系统事件处理 events.c WiFi连接/断开/DHCP/LTE事件分发
程序启动流程 main.c 初始化顺序、应用入口注册
系统参数持久化 syscfg.h(结构体)+ syscfg.c(默认值/保存) Flash参数读写
GPIO引脚配置 config.cfg + pin_param.h 引脚复用映射
调试命令 atcmd.c AT命令注册表
传感器型号支持 project_config.hDEV_SENSOR_* 传感器选择
内存分配大小 CONFIG_PSRAM_AVHEAP_SIZE / CONFIG_AVHEAP_SIZE PSRAM/SRAM音视频堆大小
编解码参数 module_code/ 下的文件 H264/JPEG用户层配置
LCD驱动选择 project_config.hLCD_* ST7701S/ST7789V等

11.4 关键注意事项

  1. 内存规划:新功能若需大块内存,需调整 CONFIG_PSRAM_AVHEAP_SIZEPSRAMCONFIG_AVHEAP_SIZESRAM系统需保留约 20KB 空闲 SRAM 防止 malloc 失败
  2. syscfg 兼容性:修改 struct sys_configsyscfg.h)时只能在末尾追加字段,不能修改已有字段的顺序或类型,否则会导致 Flash 中已存储的参数数据错乱CRC校验失败后回退到默认值
  3. 事件处理线程安全
    • sys_wifi_event_cb() → WiFi协议栈Task上下文 → 禁止耗时操作
    • sys_event_hdl() → 独立系统事件Task上下文 → 可以执行耗时操作DHCP、Flash读写等
    • 耗时逻辑必须用 sys_event_post() 从回调转发到事件Task
  4. 条件编译:用 #ifdef MY_FEATURE_EN 包裹新代码,确保不影响其他 CUSTOMER_ID 分支
  5. CUSTOMER_ID 切换:修改 project_config.h 顶部的 CUSTOMER_ID 值即可切换整个产品配置每个ID分支独立配置互不影响
  6. 双核通信CPU0 运行WiFi MAC固件不可修改CPU1 运行应用固件,通过 cpu_rpc_init() 建立邮箱通信,应用层通过 RPC 接口调用CPU0的WiFi功能

11.5 系统事件ID一览

用于 sys_event_hdl()sys_event_post() 的事件:

事件宏 说明
SYS_EVENT(SYS_EVENT_WIFI, SYSEVT_WIFI_CONNECTTED) WiFi连接成功
SYS_EVENT(SYS_EVENT_WIFI, SYSEVT_WIFI_DISCONNECT) WiFi断开连接
SYS_EVENT(SYS_EVENT_NETWORK, SYSEVT_LWIP_DHCPC_DONE) DHCP客户端获取IP完成
SYS_EVENT(SYS_EVENT_LTE, SYSEVT_LTE_CONNECTED) LTE网卡连接
SYS_EVENT(SYS_EVENT_LTE, SYSEVT_LTE_DISCONNECTED) LTE网卡断开

WiFi协议栈回调事件sys_wifi_event_cb

事件宏 说明
IEEE80211_EVENT_CONNECTED WiFi关联成功
IEEE80211_EVENT_DISCONNECTED WiFi断开
IEEE80211_EVENT_CONNECT_FAIL WiFi连接失败
IEEE80211_EVENT_RSSI RSSI信号强度更新
IEEE80211_EVENT_EVM EVM信号质量更新
IEEE80211_EVENT_CHANNEL_CHANGE 信道切换
IEEE80211_EVENT_INTERFACE_ENABLE WiFi接口使能

11.6 常用AT命令速查

AT命令 说明
AT+RST 软件复位
AT+SYSDBG 系统调试开关
AT+SYSCFG 打印系统配置
AT+HEAP 打印堆内存信息
AT+SSID 设置WiFi SSID
AT+KEY 设置WiFi密码
AT+WIFIMODE 设置WiFi模式sta/ap
AT+CHANNEL 设置WiFi信道
AT+SCAN 扫描WiFi
AT+LOADDEF 恢复出厂默认配置
AT+FPV_HEAP 打印FPV内存状态
AT+PING Ping测试
AT+IPERF2 网络带宽测试

串口参数UART0921600 波特率(见 atcmd.csys_atcmd_init())。


十二、LCD UI 自由修改完全指南

本项目使用 LVGL v8 图形库所有UI代码都用C语言编写。

12.1 UI代码在哪里

sdk/app/ui/
├── main_ui.c           ← UI入口选择显示哪种UI风格
├── main_ui.h           ← 入口函数声明
├── lvgl_ui.h           ← 所有UI页面函数声明
├── preview_ui.c        ← 摄像头预览页面(参考这个学习)
├── mp4_player_ui.c     ← MP4播放页面
├── mp4_recorder_ui.c   ← MP4录像页面
├── photo_ui.c          ← 拍照页面
└── takephoto_ui.c      ← 拍照页面(拇指相机版)

sdk/app/interface_management/
└── interface_mgnt_msi.c  ← LVGL初始化入口第122-153行

sdk/app/app_lcd/
├── app_lcd.h           ← LCD显示接口
├── lvgl_osd_msi.c      ← LVGL与LCD显示桥接
└── lcd_video_msi.c     ← 视频流显示到LCD

12.2 UI启动调用链

app_fpv.c: hardware_init()
    ↓
interface_mgnt_msi.c: lvgl_init_msi(w, h, rotate)
    ↓  第122行
    lv_init()                          // LVGL库初始化
    lv_port_disp_init_msi()            // 显示屏初始化
    lv_style_init(&g_style)            // 全局黑色背景样式
    ↓
    main_ui(NULL)                      // ← UI从这里开始第148行
    ↓
    创建GUI线程开始刷新屏幕

main_ui() 函数sdk/app/ui/main_ui.c 第68行是所有UI的入口

lv_obj_t *main_ui(lv_obj_t *base_ui)
{
    lv_group_t *group = lv_group_create();
    lv_indev_set_group(indev_keypad, group);
    lv_obj_t *ui = lv_list_create(lv_scr_act());
    lv_obj_set_size(ui, LV_PCT(100), LV_PCT(100));
    
    switch(DEFINE_UI) {   // 根据 DEFINE_UI 宏选择UI
        case MINI_DV_UI:       main_Mini_DV_ui(ui, group); break;
        case POCKET_CAMERA_UI: main_pocket_camera_ui(ui, group); break;
        case BBM_UI:           lv_obj_add_style(ui, &g_style, 0); break;
        // 在这里添加自己的UI case
    }
    return ui;
}

12.3 添加自定义UI的步骤

步骤1main_ui.c 的枚举中添加UI类型

enum UI_MODE
{
    MINI_DV_UI,
    IPC_UI,
    BBM_UI,
    CHILDREN_UI,
    POCKET_CAMERA_UI,
    LLM_VISION_UI,
    MY_SMART_CAMERA_UI,    // ← 新增
};

步骤2lvgl_ui.h 中声明函数

lv_obj_t *my_home_ui(lv_group_t *group, lv_obj_t *base_ui);

步骤3main_ui.c 的 switch 中添加 case

case MY_SMART_CAMERA_UI:
    my_home_ui(ui, group);
    break;

步骤4project_config.h 对应 CUSTOMER_ID 分支中选择UI

#define DEFINE_UI    MY_SMART_CAMERA_UI

步骤5新建 sdk/app/ui/my_home_ui.c 编写UI代码

12.4 LVGL v8 核心控件速查

基本概念

lv_obj_t      // 所有UI控件的类型按钮、标签、容器等
lv_group_t    // 按键组,物理按键通过组来切换焦点
lv_style_t    // 样式(颜色、字体、边距等)
lv_scr_act()  // 获取当前屏幕对象

容器(用于布局)

lv_obj_t *container = lv_obj_create(lv_scr_act());
lv_obj_set_size(container, 320, 240);                    // 像素
lv_obj_set_size(container, LV_PCT(100), LV_PCT(50));     // 百分比
lv_obj_set_pos(container, 0, 0);                         // x, y 像素
lv_obj_align(container, LV_ALIGN_CENTER, 0, 0);          // 居中
lv_obj_align(container, LV_ALIGN_TOP_MID, 0, 10);        // 顶部居中偏移10px
lv_obj_add_flag(container, LV_OBJ_FLAG_HIDDEN);          // 隐藏
lv_obj_clear_flag(container, LV_OBJ_FLAG_HIDDEN);        // 显示

文字标签

lv_obj_t *label = lv_label_create(parent_obj);
lv_label_set_text(label, "Hello World");
lv_label_set_text_fmt(label, "WiFi: %s", ssid);
lv_obj_set_style_text_color(label, lv_color_white(), 0);
lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0);

按钮

lv_obj_t *btn = lv_btn_create(parent_obj);
lv_obj_set_size(btn, 80, 40);
lv_obj_t *btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "Click Me");
lv_obj_add_event_cb(btn, my_btn_callback, LV_EVENT_PRESSED, my_data);
lv_group_add_obj(group, btn);  // 加入按键组(物理按键可选它)

列表按钮(本项目大量使用)

lv_obj_t *btn = lv_list_add_btn(list_obj, NULL, "预览");  // 一步创建
lv_group_add_obj(group, btn);

12.5 UI页面编写模板核心套路

参考 preview_ui.c每个UI页面都遵循这个模式

// 1. 定义页面状态结构体(保存控件引用)
struct my_page_s {
    lv_group_t *last_group;    // 上一个按键组(用于返回时恢复)
    lv_obj_t   *base_ui;       // 主界面容器
    lv_obj_t   *now_ui;        // 当前页面容器(退出时删除)
    lv_group_t *now_group;     // 当前页面按键组
    lv_obj_t   *label_status;  // 页面内控件
};

// 2. 退出页面的回调
static void exit_my_page(lv_event_t *e)
{
    struct my_page_s *s = lv_event_get_user_data(e);
    lv_indev_set_group(indev_keypad, s->last_group);  // 恢复按键组
    lv_obj_clear_flag(s->base_ui, LV_OBJ_FLAG_HIDDEN); // 显示主界面
    if (s->now_group) lv_group_del(s->now_group);
    if (s->now_ui) lv_obj_del(s->now_ui);
    lv_free(s);
}

// 3. 进入页面的回调
static void enter_my_page(lv_event_t *e)
{
    struct my_page_s *s = lv_event_get_user_data(e);
    lv_obj_add_flag(s->base_ui, LV_OBJ_FLAG_HIDDEN); // 隐藏主界面

    lv_obj_t *page = lv_obj_create(lv_scr_act());
    s->now_ui = page;
    lv_obj_add_style(page, &g_style, 0);
    lv_obj_set_size(page, LV_PCT(100), LV_PCT(100));

    // 在这里添加UI控件...

    lv_group_t *group = lv_group_create();
    lv_indev_set_group(indev_keypad, group);
    // lv_group_add_obj(group, btn);  // 把按钮加入按键组
    s->now_group = group;
}

// 4. 对外接口(在主界面列表中创建入口按钮)
lv_obj_t *my_page_ui(lv_group_t *group, lv_obj_t *base_ui)
{
    struct my_page_s *s = lv_malloc(sizeof(struct my_page_s));
    s->last_group = group;
    s->base_ui    = base_ui;

    lv_obj_t *btn = lv_list_add_btn(base_ui, NULL, "我的页面");
    lv_group_add_obj(group, btn);
    lv_obj_add_event_cb(btn, enter_my_page, LV_EVENT_PRESSED, s);
    return btn;
}

12.6 视频流绑定到LCD

摄像头画面不是LVGL控件画的而是通过 MSI流 直接写到LCD的Video层

// 开启摄像头 → LCD显示
struct msi *s = scale3_normal_msi2(S_PREVIEW_SCALE3, FSTYPE_YUV_P0, w, h);
msi_do_cmd(s, MSI_CMD_SCALE3_NORMAL, MSI_SCALE3_START, 1);
msi_add_output(s, NULL, R_VIDEO_P0);
msi_add_output(s, NULL, R_VIDEO_P1);
msi_cmd(R_VIDEO_P0, MSI_CMD_LCD_VIDEO, MSI_VIDEO_ENABLE, 1);
msi_cmd(R_VIDEO_P1, MSI_CMD_LCD_VIDEO, MSI_VIDEO_ENABLE, 1);
s->enable = 1;

// 关闭视频显示(退出预览时)
msi_cmd(R_VIDEO_P0, MSI_CMD_LCD_VIDEO, MSI_VIDEO_ENABLE, 0);
msi_cmd(R_VIDEO_P1, MSI_CMD_LCD_VIDEO, MSI_VIDEO_ENABLE, 0);
msi_del_output(s, NULL, R_VIDEO_P0);
msi_del_output(s, NULL, R_VIDEO_P1);
s->enable = 0;
msi_do_cmd(s, MSI_CMD_SCALE3_NORMAL, MSI_SCALE3_START, 0);
msi_destroy(s);

LCD显示分两层

  • OSD层LVGL绘制的UI按钮、文字—— 在上层
  • Video层:摄像头实时视频 —— 在下层

两层叠加所以可以在视频画面上叠加半透明UI按钮。

12.7 布局技巧

Flex弹性布局推荐

lv_obj_t *row = lv_obj_create(parent);
lv_obj_set_size(row, LV_PCT(100), 60);
lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW);             // 横向排列
lv_obj_set_flex_align(row, LV_FLEX_ALIGN_CENTER,
                               LV_FLEX_ALIGN_CENTER,
                               LV_FLEX_ALIGN_CENTER);
lv_obj_set_style_pad_column(row, 10, 0);                  // 子元素间距10px

绝对定位

lv_obj_set_size(obj, 320, 30);   // 宽320 高30
lv_obj_set_pos(obj, 0, 0);       // 左上角

12.8 颜色和样式

lv_obj_set_style_bg_color(obj, lv_color_make(0xFF, 0x00, 0x00), 0);  // 红色背景
lv_obj_set_style_bg_opa(obj, LV_OPA_50, 0);   // 50%透明度(半透明叠加视频)
lv_obj_set_style_text_color(label, lv_color_white(), 0);              // 白色文字
lv_obj_set_style_border_width(obj, 0, 0);     // 去掉边框
lv_obj_set_style_radius(obj, 0, 0);           // 去掉圆角
lv_obj_set_style_pad_all(obj, 5, 0);          // 四边内边距5px

12.9 按键事件处理

本项目用物理按键LVGL通过 lv_group_t 管理焦点:

// 物理按键上下 → group自动切换焦点
// 短按 → LV_EVENT_PRESSED确认
// 长按 → LV_EVENT_LONG_PRESSED长按

lv_obj_add_event_cb(obj, callback, LV_EVENT_PRESSED, user_data);

static void my_callback(lv_event_t *e) {
    void *my_data = lv_event_get_user_data(e);     // 用户数据
    lv_obj_t *target = lv_event_get_target(e);      // 触发事件的控件
}

12.10 定时刷新UI实时时间/状态)

// 创建(页面进入时)
lv_timer_t *timer = lv_timer_create(timer_cb, 1000, my_data);  // 每1000ms

static void timer_cb(lv_timer_t *t) {
    struct my_page_s *s = t->user_data;
    lv_label_set_text_fmt(s->label_time, "Time: %d", os_get_tick_count());
}

// 删除(退出页面时)
lv_timer_del(s->timer);

12.11 完整示例:自定义"预览+按钮列表"UI

// sdk/app/ui/my_home_ui.c
#include "lvgl/lvgl.h"
#include "lvgl_ui.h"
#include "basic_include.h"
#include "lib/multimedia/msi.h"
#include "scale3_normal_msi.h"

extern lv_indev_t *indev_keypad;
extern lv_style_t  g_style;

struct my_home_s {
    lv_group_t *last_group;
    lv_obj_t   *base_ui;
    lv_obj_t   *now_ui;
    lv_group_t *now_group;
    lv_obj_t   *label_status;
    lv_timer_t *timer;
    struct msi *video_msi;
    uint16_t    w, h;
};

static void exit_home(lv_event_t *e)
{
    struct my_home_s *s = lv_event_get_user_data(e);
    if (s->video_msi) {
        msi_cmd(R_VIDEO_P0, MSI_CMD_LCD_VIDEO, MSI_VIDEO_ENABLE, 0);
        msi_del_output(s->video_msi, NULL, R_VIDEO_P0);
        s->video_msi->enable = 0;
        msi_do_cmd(s->video_msi, MSI_CMD_SCALE3_NORMAL, MSI_SCALE3_START, 0);
        msi_destroy(s->video_msi);
    }
    if (s->timer) lv_timer_del(s->timer);
    lv_indev_set_group(indev_keypad, s->last_group);
    lv_obj_clear_flag(s->base_ui, LV_OBJ_FLAG_HIDDEN);
    if (s->now_group) lv_group_del(s->now_group);
    if (s->now_ui) lv_obj_del(s->now_ui);
    lv_free(s);
}

static void home_timer_cb(lv_timer_t *t)
{
    struct my_home_s *s = t->user_data;
    lv_label_set_text_fmt(s->label_status, "WiFi:%s | RTSP://554",
        sys_status.wifi_connected ? "OK" : "--");
}

static void enter_home(lv_event_t *e)
{
    struct my_home_s *s = lv_event_get_user_data(e);
    lv_obj_add_flag(s->base_ui, LV_OBJ_FLAG_HIDDEN);

    lv_obj_t *page = lv_obj_create(lv_scr_act());
    s->now_ui = page;
    lv_obj_add_style(page, &g_style, 0);
    lv_obj_set_size(page, LV_PCT(100), LV_PCT(100));

    // 顶部状态栏
    lv_obj_t *top = lv_obj_create(page);
    lv_obj_set_size(top, LV_PCT(100), 30);
    lv_obj_set_pos(top, 0, 0);
    lv_obj_set_style_bg_opa(top, LV_OPA_70, 0);
    s->label_status = lv_label_create(top);
    lv_label_set_text(s->label_status, "WiFi:-- | RTSP://554");
    lv_obj_set_style_text_color(s->label_status, lv_color_white(), 0);

    // 启动视频流到LCD
    s->video_msi = scale3_normal_msi2(S_PREVIEW_SCALE3, FSTYPE_YUV_P0, s->w, s->h);
    if (s->video_msi) {
        msi_do_cmd(s->video_msi, MSI_CMD_SCALE3_NORMAL, MSI_SCALE3_START, 1);
        msi_add_output(s->video_msi, NULL, R_VIDEO_P0);
        msi_cmd(R_VIDEO_P0, MSI_CMD_LCD_VIDEO, MSI_VIDEO_ENABLE, 1);
        s->video_msi->enable = 1;
    }

    // 底部按钮栏
    lv_obj_t *bottom = lv_obj_create(page);
    lv_obj_set_size(bottom, LV_PCT(100), 50);
    lv_obj_align(bottom, LV_ALIGN_BOTTOM_MID, 0, 0);
    lv_obj_set_flex_flow(bottom, LV_FLEX_FLOW_ROW);
    lv_obj_set_flex_align(bottom, LV_FLEX_ALIGN_SPACE_EVENLY,
                                    LV_FLEX_ALIGN_CENTER,
                                    LV_FLEX_ALIGN_CENTER);

    lv_obj_t *btn_back = lv_btn_create(bottom);
    lv_label_create(btn_back)->lv_label_set_text("返回");
    lv_obj_add_event_cb(btn_back, exit_home, LV_EVENT_PRESSED, s);

    lv_obj_t *btn_rec = lv_btn_create(bottom);
    lv_label_create(btn_rec)->lv_label_set_text("录像");

    lv_obj_t *btn_photo = lv_btn_create(bottom);
    lv_label_create(btn_photo)->lv_label_set_text("拍照");

    lv_group_t *group = lv_group_create();
    lv_indev_set_group(indev_keypad, group);
    lv_group_add_obj(group, btn_back);
    lv_group_add_obj(group, btn_rec);
    lv_group_add_obj(group, btn_photo);
    s->now_group = group;
    s->timer = lv_timer_create(home_timer_cb, 1000, s);
}

lv_obj_t *my_home_ui(lv_group_t *group, lv_obj_t *base_ui)
{
    struct my_home_s *s = lv_malloc(sizeof(struct my_home_s));
    s->last_group = group;
    s->base_ui    = base_ui;
    s->video_msi  = NULL;
    s->timer      = NULL;
    s->w          = 320;
    s->h          = 240;

    lv_obj_t *btn = lv_list_add_btn(base_ui, NULL, "Smart Camera");
    lv_group_add_obj(group, btn);
    lv_obj_add_event_cb(btn, enter_home, LV_EVENT_PRESSED, s);
    return btn;
}

12.12 远程WiFi看摄像头STA模式配网

SDK已内置RTSP服务器sdk/app/spook/app_fpv.c 中的 spook_init() 自动启动。

RTSP流地址

地址 编码 说明
rtsp://设备IP:554/h264 H.264 视频流(带宽小画质好,推荐)
rtsp://设备IP:554/webcam MJPEG 视频流(兼容性好)
rtsp://设备IP:554/custom MJPEG 自定义MJPEG流

AT命令配网步骤串口921600波特率

AT+SSID=你家WiFi名称     ← 设置WiFi名称
AT+KEY=你家WiFi密码      ← 设置WiFi密码
AT+WIFIMODE=sta          ← 切换为STA模式连接路由器
AT+SAVE                  ← 保存配置到Flash
AT+RST                   ← 重启生效

重启后设备自动连接路由器串口会打印获取到的IP地址。手机装VLC播放器同一WiFi下打开 rtsp://设备IP:554/h264 即可看到画面。

12.13 调试技巧

技巧 方法
查看UI效果 改代码后编译烧录,屏幕直接看
调整预览分辨率 scale3_normal_msi2() 的第3、4个参数
LCD屏幕分辨率 project/config.cfg 中配置
按键焦点高亮 LVGL默认在焦点控件周围显示高亮边框
RTSP视频流 手机VLC → 打开网络流 → rtsp://设备IP:554/h264
CPU占用 串口发 AT+SYSDBG=top,1
恢复默认 串口发 AT+LOADDEF

12.14 UI修改文件速查

要改什么 改哪个文件
添加新UI页面 新建 sdk/app/ui/xxx_ui.c
声明新UI函数 sdk/app/ui/lvgl_ui.h
注册UI入口 sdk/app/ui/main_ui.c(枚举+switch
选择UI风格 project/project_config.hDEFINE_UI 宏)
修改全局样式 sdk/app/interface_management/interface_mgnt_msi.c 第134-140行
LVGL字体/配置 sdk/lib/lvgl/lv_conf.h
LCD分辨率/旋转 project/config.cfg + pin_param.h
视频流分辨率 UI代码中 scale3_normal_msi2() 的参数