Files
TAIXIN/项目笔记.md

934 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.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 编译流程
1. **PreBuild**`prebuild.sh` 清除旧目标文件 + `precompile.exe` 生成代码
2. **Build**`make -f txw82xApp.mk -j 8`(编译 500+ 源文件 + 链接 19 个预编译库)
3. **PostBuild**`BuildBIN.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.h``project/sys_config.h``project/config.cfg`
2. **跟主线**:从 `project/main.c``main()` 函数开始,按初始化流程往下读
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步流程
#### 步骤1在 `project_config.h` 添加宏开关
在对应 `CUSTOMER_ID` 分支当前为ID=11中添加
```c
#define MY_FEATURE_EN 1 // 我的新功能使能0=禁用1=启用
```
#### 步骤2创建功能代码文件
`project/app/` 下新建 `app_my_feature.c`
```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` 声明接口
```c
int sys_app_my_feature_init(void);
```
#### 步骤4在 `main.c` 的 `sys_app_init()` 中注册
`sys_app_init()` 函数内添加:
```c
#ifdef MY_FEATURE_EN
sys_app_my_feature_init();
#endif
```
#### 步骤5处理系统事件如需监听WiFi/网络事件)
有两种方式,根据复杂度选择:
**方式A轻量事件**(直接在 `events.c``sys_event_hdl()` 中添加):
```c
#if MY_FEATURE_EN
my_feature_event_handler(event_id, data, priv);
#endif
```
适合:只需响应几个简单事件的场景。
**方式B复杂事件**(用 `sys_event_take()` 注册独立处理函数):
`main()` 函数的正常模式分支中添加:
```c
sys_event_take(0xffffffff, my_feature_event_hdl, 0);
```
适合:需要完整事件处理链的复杂模块。注意:每次调用消耗 16 字节 heap。
**WiFi协议栈回调**(在 `events.c``sys_wifi_event_cb()` 中添加):
```c
#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[]` 表中注册:
```c
#ifdef MY_FEATURE_EN
{ "AT+MYCMD", my_atcmd_handler },
#endif
```
AT命令处理函数格式
```c
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 关键注意事项
1. **内存规划**:新功能若需大块内存,需调整 `CONFIG_PSRAM_AVHEAP_SIZE`PSRAM`CONFIG_AVHEAP_SIZE`SRAM系统需保留约 20KB 空闲 SRAM 防止 malloc 失败
2. **syscfg 兼容性**:修改 `struct sys_config``syscfg.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.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的入口
```c
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类型
```c
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` 中声明函数
```c
lv_obj_t *my_home_ui(lv_group_t *group, lv_obj_t *base_ui);
```
#### 步骤3在 `main_ui.c` 的 switch 中添加 case
```c
case MY_SMART_CAMERA_UI:
my_home_ui(ui, group);
break;
```
#### 步骤4在 `project_config.h` 对应 CUSTOMER_ID 分支中选择UI
```c
#define DEFINE_UI MY_SMART_CAMERA_UI
```
#### 步骤5新建 `sdk/app/ui/my_home_ui.c` 编写UI代码
### 12.4 LVGL v8 核心控件速查
#### 基本概念
```c
lv_obj_t // 所有UI控件的类型按钮、标签、容器等
lv_group_t // 按键组,物理按键通过组来切换焦点
lv_style_t // 样式(颜色、字体、边距等)
lv_scr_act() // 获取当前屏幕对象
```
#### 容器(用于布局)
```c
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); // 显示
```
#### 文字标签
```c
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);
```
#### 按钮
```c
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); // 加入按键组(物理按键可选它)
```
#### 列表按钮(本项目大量使用)
```c
lv_obj_t *btn = lv_list_add_btn(list_obj, NULL, "预览"); // 一步创建
lv_group_add_obj(group, btn);
```
### 12.5 UI页面编写模板核心套路
参考 `preview_ui.c`每个UI页面都遵循这个模式
```c
// 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层
```c
// 开启摄像头 → 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弹性布局推荐
```c
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
```
#### 绝对定位
```c
lv_obj_set_size(obj, 320, 30); // 宽320 高30
lv_obj_set_pos(obj, 0, 0); // 左上角
```
### 12.8 颜色和样式
```c
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` 管理焦点:
```c
// 物理按键上下 → 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实时时间/状态)
```c
// 创建(页面进入时)
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
```c
// 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()` 的参数 |