Files
TAIXIN/项目笔记.md

934 lines
33 KiB
Markdown
Raw Permalink Normal View 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.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()` 的参数 |