33 KiB
TXW82x FPV SDK 项目笔记
版本:v2.7.0.7-42229 芯片厂商:HugeIC(宏晶) 记录日期:2026-06-09
一、项目概述
这是一个 TXW82x FPV(第一人称视角)嵌入式 Wi-Fi 视频传输 SDK,主要用于无人机/航模/婴儿监视器/对讲机等实时无线视频传输设备的固件开发。
- 目标芯片:TXW82x(C-SKY CK804DF 内核,32位 RISC 处理器,192MHz,带硬件浮点和 DSP)
- 双核架构:CPU0 运行 WiFi MAC 层固件(
txw82xcore.bin),CPU1 运行应用固件,通过邮箱 RPC 通信 - 编程语言:几乎全是 C 语言(1584 个 .c 文件),少量汇编用于启动代码
- 支持的 RTOS:AliOS (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 开发环境
- IDE:C-SKY Development Kit (CDK) V2.24.19
- 工作区文件:
project/txw82xApp.cdkws - 工具链:
csky-elfabiv2-gcc(C-SKY 架构交叉编译器 V3.10.33) - 编译器标志:
-mcpu=e804df -mhard-float -melrw -Os -g3 -Wall -ffunction-sections -fdata-sections
6.2 编译流程
- PreBuild:
prebuild.sh清除旧目标文件 +precompile.exe生成代码 - Build:
make -f txw82xApp.mk -j 8(编译 500+ 源文件 + 链接 19 个预编译库) - PostBuild:
BuildBIN.sh- 拷贝
.elf、.map、.hex到工程根目录 crc.exeCRC 校验BinScript.exe合并核心固件 + 应用 hex →txw82xApp.binmakecode.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 片上外设
- WiFi(802.11 b/g/n,AP + 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 面板
ST7701S(MIPI), ST7789V, ST7735, ST7796, ILI9881C, JD9852 等
八、网络协议栈
- LwIP TCP/IP 协议栈(DHCP 客户端/服务端、DNS、NAT)
- RTSP/RTP 服务器(spook),支持 H264/MJPEG 实时流
- HTTP 客户端/服务端
- MQTT 客户端
- libcurl(完整 HTTP 客户端库)
- WiFi AP 模式默认 IP:192.168.169.1
- 多种 WiFi 速率控制算法(FPV/航拍/IPC/婴儿监视器场景)
九、预编译库说明
| 库文件 | 说明 |
|---|---|
libcore.a |
WiFi MAC(LMAC/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 命令解析 |
十、学习路径建议
- 先读配置:
project/project_config.h→project/sys_config.h→project/config.cfg - 跟主线:从
project/main.c的main()函数开始,按初始化流程往下读 - 读应用:根据
CUSTOMER_ID找到对应的应用文件(如 FPV 看app_fpv.c) - 理解 SDK 层:需要用到具体功能时再看
sdk/app/下的对应模块 - 底层驱动:需要硬件调试时再看
sdk/hal/和sdk/driver/ - 构建验证:安装 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步流程)
步骤1:在 project_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;
}
步骤3:在 user_app.h 声明接口
int sys_app_my_feature_init(void);
步骤4:在 main.c 的 sys_app_init() 中注册
在 sys_app_init() 函数内添加:
#ifdef MY_FEATURE_EN
sys_app_my_feature_init();
#endif
步骤5:处理系统事件(如需监听WiFi/网络事件)
有两种方式,根据复杂度选择:
方式A:轻量事件(直接在 events.c 的 sys_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.c 的 sys_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.c 的 static_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.h 的 WIFI_* 宏 |
参数配置 |
| WiFi初始化逻辑 | wifi.c |
ACS扫描、速率控制、FEM芯片初始化 |
| 网络IP/DHCP配置 | project_config.h 的 NET_*/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.h 的 DEV_SENSOR_* |
传感器选择 |
| 内存分配大小 | CONFIG_PSRAM_AVHEAP_SIZE / CONFIG_AVHEAP_SIZE |
PSRAM/SRAM音视频堆大小 |
| 编解码参数 | module_code/ 下的文件 |
H264/JPEG用户层配置 |
| LCD驱动选择 | project_config.h 的 LCD_* 宏 |
ST7701S/ST7789V等 |
11.4 关键注意事项
- 内存规划:新功能若需大块内存,需调整
CONFIG_PSRAM_AVHEAP_SIZE(PSRAM)或CONFIG_AVHEAP_SIZE(SRAM),系统需保留约 20KB 空闲 SRAM 防止 malloc 失败 - syscfg 兼容性:修改
struct sys_config(syscfg.h)时只能在末尾追加字段,不能修改已有字段的顺序或类型,否则会导致 Flash 中已存储的参数数据错乱(CRC校验失败后回退到默认值) - 事件处理线程安全:
sys_wifi_event_cb()→ WiFi协议栈Task上下文 → 禁止耗时操作sys_event_hdl()→ 独立系统事件Task上下文 → 可以执行耗时操作(DHCP、Flash读写等)- 耗时逻辑必须用
sys_event_post()从回调转发到事件Task
- 条件编译:用
#ifdef MY_FEATURE_EN包裹新代码,确保不影响其他CUSTOMER_ID分支 - CUSTOMER_ID 切换:修改
project_config.h顶部的CUSTOMER_ID值即可切换整个产品配置,每个ID分支独立配置互不影响 - 双核通信: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 |
网络带宽测试 |
串口参数:UART0,921600 波特率(见 atcmd.c 的 sys_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的步骤
步骤1:在 main_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, // ← 新增
};
步骤2:在 lvgl_ui.h 中声明函数
lv_obj_t *my_home_ui(lv_group_t *group, lv_obj_t *base_ui);
步骤3:在 main_ui.c 的 switch 中添加 case
case MY_SMART_CAMERA_UI:
my_home_ui(ui, group);
break;
步骤4:在 project_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.h(DEFINE_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() 的参数 |