搭建LVGL仿真环境

This commit is contained in:
2026-08-31 17:09:59 +08:00
parent 9aa86bd561
commit a347cd18cb
11 changed files with 1305 additions and 1 deletions
+8 -1
View File
@@ -60,7 +60,8 @@ Lib/lvgl/xmls/
Lib/lvgl/configs/
# LVGL - Build system files not needed for Keil
Lib/lvgl/CMakeLists.txt
# NOTE: Lib/lvgl/CMakeLists.txt is REQUIRED by the PC simulator (simulator/),
# so it is tracked despite being a build-system file. Do not re-ignore it.
Lib/lvgl/CMakePresets.json
Lib/lvgl/component.mk
Lib/lvgl/idf_component.yml
@@ -72,6 +73,12 @@ Lib/lvgl/lvgl.pc.in
Lib/lvgl/SConscript
Lib/lvgl/sconscript
# LVGL PC Simulator - build artifacts
simulator/build/
# LVGL PC Simulator - SDL2 SDK (downloaded, see docs/测试说明/LVGL模拟器部署与使用说明.md)
simulator/SDL2/
# Binary / large files
*.bin
*.pyc
+41
View File
@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.12.4)
project(lvgl LANGUAGES C)
set(LVGL_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR})
# 收集所有 LVGL 源文件
file(GLOB_RECURSE LVGL_SOURCES
"${CMAKE_CURRENT_LIST_DIR}/src/*.c"
)
# 创建静态库
add_library(lvgl STATIC ${LVGL_SOURCES})
# 基础包含路径
target_include_directories(lvgl PUBLIC
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/src
)
# 如果调用者定义了 LV_CONF_DIR,将其加入包含路径(供 lv_conf.h 查找)
if(DEFINED LV_CONF_DIR)
target_include_directories(lvgl PUBLIC ${LV_CONF_DIR})
endif()
# 如果调用者定义了 SDL2_INCLUDE_DIR,将其加入包含路径(供 SDL 驱动使用)
if(DEFINED SDL2_INCLUDE_DIR)
target_include_directories(lvgl PUBLIC ${SDL2_INCLUDE_DIR})
endif()
# 编译定义
target_compile_definitions(lvgl PRIVATE
LV_CONF_INCLUDE_SIMPLE
)
# 关闭一些警告
if(MSVC)
target_compile_options(lvgl PRIVATE /W0)
else()
target_compile_options(lvgl PRIVATE -w)
endif()
+290
View File
@@ -0,0 +1,290 @@
# STM32F4-Base
基于 STM32F407ZGTx 的工业级嵌入式平台固件,集成以太网通信、NAND Flash 存储、ADC 采集、RS-485 通信及 FTP 文件服务。
## 硬件平台
- **MCU**: STM32F407ZGTx @ 168MHz (HSE 8MHz → PLL M=4/N=168/P=2)
- **封装**: LQFP144
- **RTOS**: FreeRTOS V10.3.1 (CMSIS-RTOS V2)
### 外设接口
| 外设 | 接口 | 引脚 | 说明 |
|------|------|------|------|
| CH395F 以太网 | SPI2 | PB12 CS, PB13 SCK, PB14 MISO, PB15 MOSI | 10/100M 以太网控制器 |
| GD5F2GQ5UE NAND | SPI1 | PE0 CS, PB3 SCK, PB4 MISO, PB5 MOSI, PB8 WP, PE1 HOLD | 256MB SPI NAND Flash |
| TPAFE5160 ADC | 并行16位 | PG0-PG15 数据, PD3 RD, PD4 CONVST, PD7 BUSY | 高精度 ADC |
| USART1 | 串口 | PA9 TX, PA10 RX | 115200bps 调试串口 |
| 6x LED | GPIO | PC4, PC5, PB1, PB2, PF11, PF12 | 低电平点亮 |
| RS-485 | UART5 | PC12 TX, PD2 RX, PD0 DE | 半双工通信 |
## 项目架构
```
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (App/) │
│ app_main.c │ net_task.c │ ui_task.c │ time_sync.c │
├─────────────────────────────────────────────────────────────┤
│ 驱动层 (Drivers/BSP/) │
│ CH395F/ │ GD5F2GQ5UE/ │ TPAFE5160/ │ SD2506/ │ RS485/ │ UI/ │
├─────────────────────────────────────────────────────────────┤
│ 中间件 (Middlewares/ & Lib/) │
│ FreeRTOS │ FatFs │ dhara FTL │ LVGL │
├─────────────────────────────────────────────────────────────┤
│ HAL 层 (Drivers/STM32F4xx_HAL_Driver/) │
│ CMSIS (Drivers/CMSIS/) │
├─────────────────────────────────────────────────────────────┤
│ 硬件 (STM32F407ZGTx) │
└─────────────────────────────────────────────────────────────┘
```
### 启动流程
```
main.c
├─ HAL_Init()
├─ SystemClock_Config() // 168MHz 系统时钟
├─ MX_GPIO/DMA/UART/SPI/I2C_Init() // CubeMX 外设初始化
├─ app_main_init() // 应用层初始化(时间→RS485→NAND→ADC→RTC)
│ ├─ sd2506_init() + sys_clock_init() // 时间子系统优先
│ ├─ rs485_init()
│ ├─ gd5f2gq5ue_init()
│ └─ tpafe5160_init()
├─ osKernelInitialize()
├─ MX_FREERTOS_Init() // 创建任务和事件组
└─ osKernelStart() // 启动调度器
```
### FreeRTOS 任务
| 任务 | 栈大小 | 优先级 | 职责 |
|------|--------|--------|------|
| defaultTask | 2048 words | Normal | FTL + FatFS 挂载(一次性) |
| netTask | 4096 words | Normal | 网络初始化 + CH395F 轮询 |
| ftpTask | 4096 words | Normal | FTP 服务(等待网络+存储就绪) |
| testTask | 4096 words | BelowNormal | 硬件测试入口 |
| uiTask | 16384 words | BelowNormal | LVGL UI 界面 |
| timeSyncTask | 2048 words | Normal | 墙钟时间同步 |
### 任务启动顺序管理
使用 CMSIS-RTOS v2 Event Flags 实现就绪屏障(readiness barrier):
- `SYS_EVT_NET_READY` (bit 0): netTask 置位
- `SYS_EVT_STORAGE_READY` (bit 1): defaultTask 置位
- `SYS_EVT_FTP_READY` (bit 2): ftpTask 置位(预留)
- `SYS_EVT_TIMESYNC_READY` (bit 3): timeSyncTask 置位(预留)
消费者通过 `osEventFlagsWait()` 阻塞等待,零占用,取代魔法延时和全局变量轮询。
## 目录结构
```
STM32F4-Base/
├── Src/ # CubeMX 生成的源文件(USER CODE 区域可编辑)
│ ├── main.c # 程序入口
│ ├── freertos.c # FreeRTOS 任务创建
│ ├── gpio.c / dma.c / spi.c / i2c.c / usart.c
│ └── stm32f4xx_hal_msp.c / stm32f4xx_it.c
├── Inc/ # CubeMX 生成的头文件
│ ├── main.h / FreeRTOSConfig.h / stm32f4xx_hal_conf.h
│ └── gpio.h / dma.h / spi.h / i2c.h / usart.h
├── App/ # 应用层代码
│ ├── app_main.c/h # 应用初始化入口
│ ├── app_cfg.h # 功能模块编译开关
│ ├── sys_events.h # 系统就绪事件定义
│ ├── sys_clock.c/h # 墙钟时间管理
│ ├── time_sync.c/h # 时间同步任务
│ ├── task/ # FreeRTOS 任务实现
│ │ ├── net_task.c/h # 网络任务
│ │ ├── ui_task.c/h # UI 任务
│ │ ├── adc_task.c/h # ADC 任务
│ │ └── rs485_task.c/h # RS-485 任务
│ ├── protocol/ # 通信协议(预留)
│ └── util/ # 工具函数(CRC、环形缓冲等)
├── Drivers/
│ ├── BSP/ # 板级驱动(手工编写)
│ │ ├── CH395F/ # 以太网控制器驱动
│ │ ├── GD5F2GQ5UE/ # NAND Flash 驱动 + FTL
│ │ ├── TPAFE5160/ # ADC 驱动
│ │ ├── SD2506/ # RTC 驱动
│ │ ├── RS485/ # RS-485 通信驱动
│ │ ├── NET/ # BSD Socket API 网络抽象层
│ │ │ ├── net_socket.c/h # Socket API
│ │ │ ├── net_select.c/h # select/poll 多路复用
│ │ │ └── lftpd/ # 轻量 FTP 服务器
│ │ └── UI/ # LVGL UI 组件
│ ├── STM32F4xx_HAL_Driver/ # ST HAL 库
│ └── CMSIS/ # ARM CMSIS
├── Lib/ # 第三方库
│ ├── dhara/ # NAND FTL 算法
│ ├── FatFs/ # FAT 文件系统
│ └── lvgl/ # 图形库
├── Middlewares/
│ └── Third_Party/FreeRTOS/ # FreeRTOS 内核
├── MDK-ARM/ # Keil 工程
│ ├── STM32F407-Demo.uvprojx # 工程文件
│ ├── build.bat # 命令行编译脚本
│ ├── download.bat # 烧录脚本
│ └── startup_stm32f407xx.s # 启动文件
├── test/ # 测试代码与脚本
│ ├── run_boot_test.ps1 # 自动化启动测试
│ ├── verify_log.ps1 # 日志校验脚本
│ ├── serial_capture.ps1 # 串口捕获脚本
│ ├── ch395f_test_task.c/h # CH395F 硬件测试
│ ├── gd5f_test_task.c/h # NAND Flash 测试
│ ├── net_test_task.c/h # 网络层测试
│ ├── sd2506_test_task.c/h # RTC 测试
│ └── storage_test_task.c/h # 存储性能测试
├── docs/ # 项目文档
│ ├── 问题记录/ # 已知陷阱与解决方案
│ ├── 测试说明/ # 测试指南
│ └── 芯片手册/ # 硬件数据手册
├── tools/ # 开发工具
│ └── font_gen/ # 中文字体生成工具
├── STM32F407-Demo.ioc # CubeMX 工程文件
└── AGENTS.md # AI 开发规范
```
## 构建系统
### 环境要求
- **Keil MDK-ARM v5** (ARMCC 编译器)
- **Python 3**(测试脚本)
- **ST-Link** 或兼容调试器
### 编译
```bash
# 方式一:命令行编译(推荐)
MDK-ARM\build.bat
# 方式二:Keil IDE
# 打开 MDK-ARM\STM32F407-Demo.uvprojx → Build (F7)
```
退出码:0 = 成功 | 1 = 有警告 | 2+ = 错误
编译日志:`MDK-ARM/build_log.txt`
### 烧录
```bash
MDK-ARM\download.bat
```
## 驱动开发指南
### CH395F 以太网
- 单连接模式:Socket 0 复用监听和数据通道
- 中断统一处理:所有中断在 `net_poll()` 中通过 `GET_GLOB_INT_STATUS` 集中读取清除
- SPI 事务必须用 `ch395f_spi_begin()` / `ch395f_spi_end()` 包裹
- SPI 分频系数必须为 8(PCB 设计限制)
### GD5F2GQ5UE NAND Flash
- 三层软件栈:FatFS → dhara FTL → NAND 驱动
- FTL 提供 LBA 映射、磨损均衡、坏块管理、垃圾回收
- 物理分区:全部 2048 块用于 FTL + FatFS
### 网络层 (NET)
- BSD Socket API 抽象:`net_socket.h/c`
- select/poll I/O 多路复用:`net_select.h/c`
- 线程安全通过消息队列(`netMsgQueue`)实现
- 所有 CH395F 操作在 netTask 中串行处理
### 时间子系统
- SD2506 RTC 提供初始时间
- sys_clock 将 RTC 时间转为 Unix epoch 墙钟基准
- timeSyncTask 通过 TCP 同步墙钟时间
## 自动化测试
### 启动测试
```bash
# 一键测试:构建 → 串口捕获 → 烧录 → 日志校验
powershell -ExecutionPolicy Bypass -File test\run_boot_test.ps1
```
退出码:0 = PASS | 1 = FAIL | 2 = 错误
### 测试模块
| 模块 | 测试文件 | 说明 |
|------|----------|------|
| CH395F 硬件 | `ch395f_test_task.c` | SPI 通信、寄存器读写 |
| NAND Flash | `gd5f_test_task.c` | 页读写、块擦除、ECC |
| 网络层 | `net_test_task.c` | BSD Socket、TCP/UDP |
| RTC | `sd2506_test_task.c` | 时间读写、温度、电池 |
| 存储性能 | `storage_test_task.c` | FTL/FatFS 读写速度 |
### 串口调试
通过 `serial-mcp-server` 自主读取 USART1 串口打印:
- 端口配置:`.opencode/serial_config.json`
- 详见 `docs/测试说明/serial-mcp-server_串口调试.md`
## 开发规范
### 源码编辑安全
**铁律**:绝不用 `Get-Content` / `Set-Content` / `cmd` 重写含中文的源码文件,会导致不可逆乱码。
- **改代码只用 Edit 工具**(精确子串替换)
- 发现乱码先评估能否用 Edit 修复,不要急着 `git checkout`
### CubeMX 生成文件
- `Inc/` 和 `Src/` 中的文件带有 `USER CODE BEGIN`/`END` 标记
- 自定义代码必须写在这些区域之间
- `Drivers/BSP/` 下的驱动文件为纯手工代码,不受 CubeMX 保护
### 编译验证
**所有代码修改后必须编译通过(0 错误 0 警告)才能提交。**
### 新增文件
新文件需手动添加到 Keil MDK 工程才能编译。
## 关键文件索引
| 文件 | 说明 |
|------|------|
| `Src/main.c` | 程序入口,CubeMX 生成 + USER CODE 区域 |
| `Src/freertos.c` | FreeRTOS 任务创建 |
| `App/app_main.c` | 应用初始化流程编排 |
| `App/task/net_task.c` | 网络任务主循环 |
| `App/sys_events.h` | 系统就绪事件定义 |
| `App/app_cfg.h` | 功能模块编译开关 |
| `Drivers/BSP/CH395F/ch395f.c` | 以太网控制器驱动 |
| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c` | NAND Flash 驱动 |
| `Drivers/BSP/NET/net_socket.c` | BSD Socket API |
| `MDK-ARM/build.bat` | 编译脚本 |
| `test/run_boot_test.ps1` | 自动化测试脚本 |
## 已知问题
详见 `docs/问题记录/` 目录:
- `CH395F_Trap_Records.md` — 以太网控制器已知陷阱
- `GD5F2GQ5UE_Trap_Records.md` — NAND Flash 已知陷阱
- `SD2506_Trap_Records.md` — RTC 已知陷阱
## 文档
- `docs/项目启动顺序管理.md` — Event Flags 就绪屏障设计
- `docs/STM32F4-Base存储架构说明.md` — 三层存储软件栈详解
- `docs/嵌入式C语言代码规范(V1.0).md` — 代码规范
- `docs/测试说明/自动化测试流程.md` — 测试流程说明
- `docs/芯片手册/` — 硬件数据手册
## 许可证
本项目为私有项目,未经授权不得分发。
@@ -0,0 +1,262 @@
# LVGL PC 模拟器部署与使用说明
> 适用于:`simulator/` 目录(LVGL v9.3.0 SDL2 模拟器)
> 用途:在 PC 上预览/调试 160×160 1bpp 单色菜单 UI,无需烧录 MCU
## 一、方案概述
### 1.1 背景
STM32F4-Base 的 UI 是 **160×160 1bpp 单色**菜单(`lv_menu` 树形菜单),使用自定义中文字体 `ui_font_cn_12`。在 MCU 上开发 UI 需要反复烧录调试,效率低。本模拟器在 PC 上直接编译运行相同的 UI 代码(`ui_menu.c`),实现所见即所得。
### 1.2 架构
```
simulator/main.c ──→ lv_init → sim_port_init(SDL 窗口 + 键盘)→ ui_menu_init(共享 MCU 代码)
│
└── 主循环:SDL 事件 → lv_timer_handler → lv_refr_now
```
| 模块 | 文件 | 说明 |
|------|------|------|
| 入口 | `simulator/main.c` | SDL 初始化、主循环、键盘事件 |
| 端口 | `simulator/sim_port.c` | SDL 显示 + 键盘输入 + mono 主题初始化(替代 MCU 端 `ui_port.c`) |
| 配置 | `simulator/lv_conf.h` | **模拟器专用** LVGL 配置(1bpp + SDL 启用) |
| 共享 UI | `Drivers/BSP/UI/ui_menu.c` | 与 MCU 端共用,不做任何修改 |
| 共享字体 | `Drivers/BSP/UI/ui_font_cn_12.c` | 与 MCU 端共用 |
| LVGL 源码 | `Lib/lvgl/` | 项目自带 v9.3.0,新增最小 CMakeLists.txt |
| SDL2 SDK | `simulator/SDL2/SDL2-2.32.10/` | 本地预编译,无需安装 |
### 1.3 关键设计决策
- **SDL2**:LVGL 官方 `lv_port_pc_vscode` 同款显示方案
- **CMake + MSVC**:本机使用 Visual Studio 2019 BuildTools(`Visual Studio 16 2019` / `-A x64`)
- **仅 UI 预览**:模拟器只跑 UI,不包含网络(netTask/ftpTask)等 MCU 业务
- **共享代码不复制**:UI 源码直接引用 `Drivers/BSP/UI/`,MCU 与 PC 同步维护
## 二、环境要求
| 依赖 | 版本 | 说明 |
|------|------|------|
| CMake | ≥ 3.15 | 本机验证 4.3.0-rc1 |
| MSVC | VS2019 BuildTools | `Visual Studio 16 2019` 生成器,Hostx64/x64 |
| SDL2 | 2.32.10 | 已解压到 `simulator/SDL2/`,无需安装 |
| 操作系统 | Windows | 已验证 win32 |
> 说明:`simulator/SDL2/SDL2-2.32.10/` 是 `SDL2-devel-2.32.10-VC.zip` 解压后的目录,已包含 `include/`、`lib/x64/`。
## 三、首次部署(一次性)
### 3.1 下载 SDL2(如未包含)
若 `simulator/SDL2/` 为空,需手动下载解压:
1. 下载 `SDL2-devel-2.32.10-VC.zip`(官方 GitHub Releases 或官网)
2. 解压到 `simulator/SDL2/`,确保目录结构为:
```
simulator/SDL2/SDL2-2.32.10/
include/ ← SDL.h 及各头文件
lib/x64/ ← SDL2.lib, SDL2main.lib, SDL2.dll
```
> ⚠️ **重要**:LVGL SDL 驱动内部写死 `#include "SDL2/SDL.h"`。原版 SDL2 包的头文件直接平铺在 `include/` 下(没有 `SDL2/` 子目录),因此需要先把头文件复制一份到 `include/SDL2/` 子目录:
> ```powershell
> $inc = "simulator\SDL2\SDL2-2.32.10\include"
> New-Item -ItemType Directory -Path "$inc\SDL2" -Force
> Copy-Item "$inc\*.h" "$inc\SDL2\"
> ```
> 如未执行此步骤,编译报 `fatal error C1083: cannot open include file: "SDL2/SDL.h"`。
### 3.2 配置 CMake 并构建
```powershell
cd simulator
cmake -B build -G "Visual Studio 16 2019" -A x64
cmake --build build --config Debug
```
生成的可执行文件:`simulator/build/Debug/lvgl_sim.exe`(SDL2.dll 由 POST_BUILD 自动复制到同目录)。
### 3.3 运行
```powershell
cd simulator\build\Debug
.\lvgl_sim.exe
```
或使用 CMake 自定义目标:`cmake --build build --config Debug --target run`。
## 四、日常使用
### 4.1 构建 + 运行
```powershell
cd simulator
cmake --build build --config Debug # 增量编译
.\build\Debug\lvgl_sim.exe # 运行
```
> 改 CMakeLists.txt 后需重新 `cmake -B build`;仅改 .c/.h 时直接 `cmake --build` 即可。
> 遇到奇怪问题时先删 `build/` 全量重建:
> ```powershell
> Remove-Item -Recurse -Force build
> cmake -B build -G "Visual Studio 16 2019" -A x64
> cmake --build build --config Debug
> ```
### 4.2 键盘操作
| 按键 | 功能 |
|------|------|
| ↑ / ↓ | 菜单项上下移动 |
| ← / → | 展开/折叠 / 导航 |
| Enter | 确认/进入子页 |
| Escape | 返回上级 |
> 说明:模拟器**未创建鼠标**,纯键盘导航(与 MCU 端 keypad 一致)。
### 4.3 修改 UI 代码
- 修改 `Drivers/BSP/UI/ui_menu.c` 直接生效(重新编译模拟器即可预览)
- 修改 `Drivers/BSP/UI/ui_font_cn_12.c` 同样直接生效
- **不要**在 `simulator/` 下复制 UI 源码——保持共享,避免两份代码不同步
## 五、文件详解
### 5.1 `simulator/CMakeLists.txt`
| 配置项 | 作用 |
|--------|------|
| `SDL2_DIR` | SDL2 SDK 根目录 |
| `LVGL_DIR` | 指向 `../Lib/lvgl`(项目自带源码) |
| `UI_DIR` | 指向 `../Drivers/BSP/UI`(共享 UI 代码) |
| `SDL2_INCLUDE_DIR` | **必须在 `add_subdirectory(lvgl)` 之前定义**,供 LVGL SDL 驱动找到 `SDL2/SDL.h` |
| `LV_CONF_DIR` | 指向 `simulator/` 自身,供 LVGL 找到模拟器版 `lv_conf.h` |
| `add_subdirectory("${LVGL_DIR}" lvgl_build)` | 编译 LVGL 静态库 |
| UI 源文件 | 直接编译 `ui_menu.c` + `ui_font_cn_12.c` |
| POST_BUILD | 自动复制 SDL2.dll 到输出目录 |
### 5.2 `Lib/lvgl/CMakeLists.txt`(新增)
LVGL 官方源码**原本没有** CMakeLists.txt(只有 `src/`),本工程新增了一个最小化版本:
- `file(GLOB_RECURSE ...)` 收集 `src/**/*.c` 编译成静态库 `lvgl`
- 通过 `LV_CONF_DIR` / `SDL2_INCLUDE_DIR` 两个可选变量注入 include 路径
- 这些变量由上层 `simulator/CMakeLists.txt` 在 `add_subdirectory` 前设置
- MSVC 下关闭警告(`/W0`)
> ⚠️ 这个文件只被模拟器使用,**不影响 Keil 的 MCU 编译**(Keil 用自己的 `.uvprojx` 工程)。
### 5.3 `simulator/lv_conf.h`(模拟器专用)
与 MCU 端 `Lib/lv_conf.h` 的差异:
| 配置 | MCU 端 | 模拟器 |
|------|--------|--------|
| `LV_USE_SDL` | 0 | **1**(SDL 驱动) |
| `LV_SDL_RENDER_MODE` | - | `LV_DISPLAY_RENDER_MODE_PARTIAL`(**1bpp 必须**) |
| `LV_SDL_INCLUDE_PATH` | - | `"SDL2/SDL.h"` |
| `LV_USE_THEME_MONO` | 0 | **1**(否则 1bpp 下文字黑底黑字不可见) |
| `LV_FONT_DEFAULT` | `&ui_font_cn_12` | 同 |
| `LV_MEM_SIZE` | 40KB | 40KB(模拟器内存充足,可放宽) |
### 5.4 `simulator/sim_port.c`
MCU 端 `ui_port.c` 的 PC 替代实现:
- `lv_sdl_window_create(160,160)` → SDL 窗口
- `lv_theme_mono_init(disp, true, LV_FONT_DEFAULT)` → **必须**,否则黑屏
- `lv_sdl_keyboard_create()` → 键盘输入(关联默认 group)
- 无鼠标/触摸设备(纯键盘导航)
### 5.5 `simulator/dbg_log.h`
MCU 端 `Drivers/BSP/dbg_log.h` 会引用 `sys_clock.h`(RTC 时间戳),PC 上无法编译。`simulator/dbg_log.h` 是一个 stub:`DBG_INFO/ERROR/DEBUG` 直接映射到 `printf`,且 `simulator/` 在 include 路径最前会**优先生效**,覆盖 MCU 版本。
> 若 UI 代码新增 `DBG_*` 输出,会打印到模拟器控制台,方便调试。
## 六、注意事项与已知坑
### 6.1 构建相关
1. **`SDL2/SDL.h` 找不到**:需先执行 3.1 的 `include/SDL2/` 头文件复制步骤。
2. **`lv_conf.h` 找不到**:`LV_CONF_DIR` 必须在 `add_subdirectory(lvgl)` **之前**定义(CMake 变量顺序问题)。当前 CMakeLists 已正确处理,勿随意调整顺序。
3. **改 CMakeLists.txt 后不生效**:MSVC 生成器可能缓存旧配置,先删 `build/` 重建。
4. **中文字符串文件编码**:源码是 UTF-8,CMake 已加 `/utf-8` 编译选项消除 C4819 警告。**不要**用 PowerShell `Set-Content` 等重写含中文的文件(参照 AGENTS.md 编码铁律)。
### 6.2 渲染相关
1. **黑屏排查顺序**:
- `LV_USE_THEME_MONO` 是否 = 1 且在 `sim_port_init` 中调了 `lv_theme_mono_init`(1bpp 无主题 = 黑底黑字)
- 主循环是否调用 `lv_refr_now(NULL)` 强制首帧输出(PARTIAL 模式下保证)
- 是否重复设置 tick:SDL 驱动内部 `lv_tick_set_cb(SDL_GetTicks)`,**不要**再 `SDL_AddTimer` + `lv_tick_inc`
2. **中文字体缺字**:`ui_font_cn_12` 只覆盖 83 个汉字(ASCII + 指定符号集),完整菜单需要 186 个唯一汉字。**当前菜单部分文字会显示为空白**,需重新生成字体(见下文 6.3)。
3. **`LV_SDL_RENDER_MODE` 必须为 `PARTIAL`**:1bpp + SDL 若非 PARTIAL,`lv_sdl_window.c` 会 `#error` 编译失败。
### 6.3 字体生成(如需补全中文字符)
用 LVGL 官方 `lv_font_conv`(Node.js)重新生成:
```bash
# 生成包含全部菜单字符 + ASCII 的 12px 1bpp 字体
lv_font_conv --font simsun_regular.ttf --size 12 --bpp 1 \
-r 0x20-0x7E \
--symbols "<全部菜单用到的汉字,用引号包裹>" \
--format lvgl --lv-font-name ui_font_cn_12 \
-o Drivers/BSP/UI/ui_font_cn_12.c
```
> 菜单字符集合:`主界面 装置信息 实时数据 交流量 直流量 开入量 定值参数 定值 遥调 零压 电压 查看 遥测量 测量值 遥调系数 遥测系数 双字节 装置遥调 装置维护 时间设置 强制复归 手动录波 自动录波 通讯设置 通讯参数 网络配置 SNTP设置 记录查询 SOE记录 事故记录 操作记录 告警记录 变位告警 遥信记录 自检记录 定值记录 录波记录 厂家设置 元件设置 恢复默认 全部恢复 默认参数 默认定值 默认系数 元件参数 版本显示 测量遥调 直流遥调 开出遥调 装置记录 遥调记录 信号复归 版本信息`(去重后 76 字)
>
> 注意:字体符号集是**有损的**——`lv_font_conv` 对不在符号集内的字符不生成字形,运行时该字符渲染为空白。这是当前缺字的根因。
### 6.4 与 MCU 端的差异
| 项 | MCU(Keil) | 模拟器(CMake+MSVC) |
|----|------------|---------------------|
| LVGL 编译 | Keil 工程添加源文件 | 新增 `Lib/lvgl/CMakeLists.txt` |
| lv_conf.h | `Lib/lv_conf.h` | `simulator/lv_conf.h`(独立,互不影响) |
| 显示端口 | `ui_port.c`(离屏 framebuffer) | `sim_port.c`(SDL 窗口) |
| 输入 | 远端 keypad | SDL 键盘 |
| 业务任务 | netTask/ftpTask 等 | 无,纯 UI |
### 6.5 其他
1. **`run` 目标**:CMake 定义的 `--target run` 会执行 `build/Debug/lvgl_sim.exe`。
2. **窗口无法关闭**:点击窗口关闭按钮会触发 `SDL_QUIT` 正常退出;Escape 由 LVGL 键盘驱动处理(返回上级页)。
3. **多显示器/高 DPI**:SDL 窗口固定 160×160 物理像素,Windows 缩放可能放大显示,属正常现象。
## 七、常见问题(FAQ)
### Q1: 编译报 `cannot open include file "SDL2/SDL.h"`?
A: 未执行 `include/SDL2/` 头文件复制,见 3.1。
### Q2: 编译报 `cannot open include file "lv_conf.h"`?
A: `LV_CONF_DIR` 变量未在 `add_subdirectory(lvgl)` 前定义,检查 `simulator/CMakeLists.txt` 变量顺序。
### Q3: 运行后窗口全黑?
A: 按 6.2 黑屏排查顺序检查:mono 主题 → `lv_refr_now` → tick 不重复。
### Q4: 菜单某些中文显示空白?
A: 中文字体缺字(覆盖 83 字,需要 186 字),见 6.3 重新生成字体。
### Q5: 修改了 UI 代码但模拟器没变化?
A: 确认修改的是 `Drivers/BSP/UI/ui_menu.c` 而非 `simulator/` 内的副本(本工程无副本);然后重新 `cmake --build`。
### Q6: 能调试网络/存储等功能吗?
A: 不能。模拟器仅预览 UI,不含 CH395F/NAND/ADC 等硬件业务。
## 八、文件清单
| 路径 | 状态 | 说明 |
|------|------|------|
| `simulator/main.c` | 新增 | 入口 |
| `simulator/sim_port.c` / `.h` | 新增 | SDL 端口 |
| `simulator/lv_conf.h` | 新增 | 模拟器 LVGL 配置 |
| `simulator/dbg_log.h` | 新增 | printf 日志 stub |
| `simulator/CMakeLists.txt` | 新增 | 构建脚本 |
| `simulator/SDL2/SDL2-2.32.10/` | 新增 | SDL2 SDK(含 include/SDL2/ 子目录) |
| `Lib/lvgl/CMakeLists.txt` | 新增 | LVGL 最小 CMake(仅模拟器用) |
| `Drivers/BSP/UI/ui_menu.c` | 未改 | 共享 UI 代码 |
| `Drivers/BSP/UI/ui_font_cn_12.c` | 未改 | 共享字体(缺字待补) |
| `simulator/build/` | 生成物 | 构建输出,可随时删除重建 |
+141
View File
@@ -0,0 +1,141 @@
cmake_minimum_required(VERSION 3.15)
project(lvgl_sim C)
#=============================================================================
# 路径配置(相对于 simulator/ 目录)
#=============================================================================
set(PROJECT_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..")
set(LVGL_DIR "${PROJECT_ROOT}/Lib/lvgl")
set(UI_DIR "${PROJECT_ROOT}/Drivers/BSP/UI")
set(SDL2_DIR "${CMAKE_CURRENT_SOURCE_DIR}/SDL2/SDL2-2.32.10")
#=============================================================================
# 编译标准
#=============================================================================
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Windows 控制台输出(显示 printf)
if(WIN32)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()
# MSVC:源码为 UTF-8 编码(含中文注释),强制按 UTF-8 解析消除 C4819 警告
if(MSVC)
add_compile_options(/utf-8)
endif()
#=============================================================================
# SDL2 头文件路径(需先于 add_subdirectory 定义,供 LVGL SDL 驱动使用)
#=============================================================================
# LVGL SDL 驱动内部 #include "SDL2/SDL.h",include 目录下已有 SDL2/ 子目录
set(SDL2_INCLUDE_DIR "${SDL2_DIR}/include" CACHE PATH "SDL2 include directory for LVGL")
#=============================================================================
# LVGL 子目录(引用项目自带的 LVGL v9.3.0 源码)
#=============================================================================
# LVGL 内部会自动检测 lv_conf.h(通过 LV_CONF_INCLUDE_SIMPLE 宏)
# 需要将 simulator 目录加入 LVGL 的包含路径,使其能找到 lv_conf.h
set(LV_CONF_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE PATH "Directory containing lv_conf.h for LVGL")
add_subdirectory("${LVGL_DIR}" lvgl_build)
#=============================================================================
# 包含路径
#=============================================================================
include_directories(
${CMAKE_CURRENT_SOURCE_DIR} # sim_port.h, lvgl conf.h (LVGL 找 lv_conf.h), dbg_cfg.h
${LVGL_DIR} # lvgl.h
${LVGL_DIR}/src # lvgl 内部头文件
${UI_DIR} # ui_menu.h, ui_font_cn_12.h
${PROJECT_ROOT}/Drivers/BSP # dbg_log.h(使用 simulator 的 dbg_cfg.h 配置)
${SDL2_DIR}/include # SDL2 头文件
)
#=============================================================================
# SDL2 库(使用本地预编译版本)
#=============================================================================
set(SDL2_INCLUDE_PARENT "${SDL2_DIR}/include")
set(SDL2_LIB_X64 "${SDL2_DIR}/lib/x64")
#=============================================================================
# 源文件
#=============================================================================
set(SIM_SOURCES
main.c
sim_port.c
)
# UI 源文件(与 MCU 端共用,直接编译)
set(UI_SOURCES
${UI_DIR}/ui_menu.c
${UI_DIR}/ui_font_cn_12.c
)
#=============================================================================
# 构建可执行文件
#=============================================================================
add_executable(lvgl_sim WIN32 ${SIM_SOURCES} ${UI_SOURCES})
target_compile_definitions(lvgl_sim PRIVATE
LV_CONF_INCLUDE_SIMPLE # 让 lvgl.h 找到 lv_conf.h
LV_LVGL_H_INCLUDE_SIMPLE # ui_font_cn_12.c 内部 include 简化
)
target_include_directories(lvgl_sim PRIVATE
${SDL2_INCLUDE_PARENT} # SDL2/SDL.h 路径
)
target_link_directories(lvgl_sim PRIVATE
${SDL2_LIB_X64}
)
target_link_libraries(lvgl_sim
lvgl
SDL2
SDL2main
)
# 数学库(非 MSVC)
if(NOT MSVC)
target_link_libraries(lvgl_sim m pthread)
endif()
# Windows 子系统:控制台(显示 printf 输出)
if(WIN32)
if(MSVC)
target_link_options(lvgl_sim PRIVATE "/SUBSYSTEM:CONSOLE")
else()
target_link_options(lvgl_sim PRIVATE "-mconsole")
endif()
endif()
# 复制 SDL2.dll 到输出目录
add_custom_command(TARGET lvgl_sim POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${SDL2_LIB_X64}/SDL2.dll"
"$<TARGET_FILE_DIR:lvgl_sim>/SDL2.dll"
COMMENT "Copying SDL2.dll to output directory"
)
#=============================================================================
# 自定义目标:运行模拟器
#=============================================================================
add_custom_target(run COMMAND ${CMAKE_BINARY_DIR}/lvgl_sim DEPENDS lvgl_sim)
#=============================================================================
# 调试模式额外警告
#=============================================================================
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message(STATUS "Debug mode enabled for simulator")
if(MSVC)
target_compile_options(lvgl_sim PRIVATE /W4)
else()
target_compile_options(lvgl_sim PRIVATE -Wall -Wextra -Wpedantic)
endif()
endif()
message(STATUS "=== LVGL Simulator ===")
message(STATUS "Screen: 160x160, 1bpp monochrome")
message(STATUS "LVGL dir: ${LVGL_DIR}")
message(STATUS "UI dir: ${UI_DIR}")
message(STATUS "SDL2: ${SDL2_DIR}")
+38
View File
@@ -0,0 +1,38 @@
/*
* dbg_log.h — 模拟器专用调试日志 stub(覆盖 MCU 工程的 Drivers/BSP/dbg_log.h)
*
* 模拟器不使用 sys_clock.h / RTC,因此把 DBG_* 宏直接映射到 printf,
* 使 ui_menu.c 等共享代码的日志在 PC 控制台可见。
* DBG_TAG 仍需各 .c 文件在 include 前定义,这里保留该约定。
*/
#ifndef __DBG_LOG_H
#define __DBG_LOG_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#ifndef DBG_TAG
#define DBG_TAG ""
#endif
#define DBG_ERROR(fmt, ...) do { \
printf("[ERR]" DBG_TAG " " fmt "\n", ##__VA_ARGS__); \
} while (0)
#define DBG_INFO(fmt, ...) do { \
printf(DBG_TAG " " fmt "\n", ##__VA_ARGS__); \
} while (0)
#define DBG_DEBUG(fmt, ...) do { \
printf(DBG_TAG "(%s:%d) " fmt "\n", __func__, __LINE__, ##__VA_ARGS__); \
} while (0)
#ifdef __cplusplus
}
#endif
#endif /* __DBG_LOG_H */
+308
View File
@@ -0,0 +1,308 @@
/**
* @file lv_conf.h
* @brief LVGL v9.3.0 配置 — Windows SDL2 模拟器(160x160 1bpp 单色)
* @note 基于项目 Lib/lv_conf.h 适配,用于 PC 模拟器 UI 开发
*/
#ifndef LV_CONF_H
#define LV_CONF_H
/* clang-format off */
/*============================================================================
* MEMORY AND STANDARD LIBRARY
*============================================================================*/
#define LV_USE_STDLIB_MALLOC LV_STDLIB_BUILTIN
#define LV_USE_STDLIB_STRING LV_STDLIB_BUILTIN
#define LV_USE_STDLIB_SPRINTF LV_STDLIB_BUILTIN
#define LV_STDINT_INCLUDE <stdint.h>
#define LV_STDDEF_INCLUDE <stddef.h>
#define LV_STDBOOL_INCLUDE <stdbool.h>
#define LV_INTTYPES_INCLUDE <inttypes.h>
#define LV_LIMITS_INCLUDE <limits.h>
#define LV_STDARG_INCLUDE <stdarg.h>
#if LV_USE_STDLIB_MALLOC == LV_STDLIB_BUILTIN
#define LV_MEM_SIZE (40 * 1024U)
#define LV_MEM_ADR 0
#endif
/*============================================================================
* OS
*============================================================================*/
#define LV_USE_OS LV_OS_NONE
/*============================================================================
* RENDERING
*============================================================================*/
/** 1bpp 单色 */
#define LV_COLOR_DEPTH 1
#define LV_COLOR_MIX_ROUND_OFS 0
/** 刷新周期(ms) */
#define LV_DEF_REFR_PERIOD 20
/** DPI */
#define LV_DPI_DEF 130
/** 绘制缓冲对齐 */
#define LV_DRAW_BUF_STRIDE_ALIGN 1
#define LV_DRAW_BUF_ALIGN 4
/** 关闭复杂绘制,省代码 */
#define LV_DRAW_SW_COMPLEX 0
#define LV_DRAW_SW_COMPLEX_GRADIENTS 0
#define LV_DRAW_SW_SHADOW_CACHE_SIZE 0
#define LV_DRAW_SW_CIRCLE_CACHE_SIZE 0
#define LV_DRAW_LAYER_SIMPLE_BUF_SIZE 0
#define LV_DRAW_LAYER_MAX_MEMORY 0
/** SW 渲染器:启用 I1 支持 */
#define LV_USE_DRAW_SW 1
#define LV_DRAW_SW_SUPPORT_RGB565 0
#define LV_DRAW_SW_SUPPORT_RGB565_SWAPPED 0
#define LV_DRAW_SW_SUPPORT_RGB565A8 0
#define LV_DRAW_SW_SUPPORT_RGB888 0
#define LV_DRAW_SW_SUPPORT_XRGB8888 0
#define LV_DRAW_SW_SUPPORT_ARGB8888 0
#define LV_DRAW_SW_SUPPORT_ARGB8888_PREMULTIPLIED 0
#define LV_DRAW_SW_SUPPORT_L8 0
#define LV_DRAW_SW_SUPPORT_AL88 0
#define LV_DRAW_SW_SUPPORT_A8 0
#define LV_DRAW_SW_SUPPORT_I1 1
#define LV_DRAW_SW_I1_LUM_THRESHOLD 127
/** 关闭 GPU/矢量绘制 */
#define LV_USE_DRAW_VG_LITE 0
#define LV_USE_DRAW_DAVE2D 0
#define LV_USE_DRAW_NEMA_GFX 0
#define LV_USE_DRAW_PXP 0
#define LV_USE_DRAW_PPA 0
#define LV_USE_DRAW_DMA2D 0
#define LV_USE_DRAW_EVE 0
#define LV_USE_DRAW_G2D 0
#define LV_USE_DRAW_NANOVG 0
#define LV_USE_DRAW_OPENGLES 0
#define LV_USE_DRAW_SDL 0
#define LV_USE_THORVG 0
#define LV_USE_VECTOR_GRAPHIC 0
#define LV_USE_MATRIX 0
#define LV_USE_SNAPSHOT 0
/*============================================================================
* INPUT DEVICES
*============================================================================*/
#define LV_USE_GRIDNAV 0
#define LV_USE_GESTURE_RECOGNITION 0
/*============================================================================
* CORE
*============================================================================*/
#define LV_OBJ_STYLE_CACHE 1
#define LV_USE_OBJ_NAME 1
#define LV_USE_OBJ_ID 1
#define LV_OBJ_ID_AUTO_ASSIGN 1
#define LV_USE_OBJ_ID_BUILTIN 1
#define LV_USE_OBJ_PROPERTY 0
#define LV_USE_EXT_DATA 0
#define LV_USE_FLOAT 0
#define LV_USE_OBSERVER 0
#define LV_USE_TRANSLATION 0
#define LV_USE_COLOR_FILTER 0
/*============================================================================
* LOGGING
*============================================================================*/
#define LV_USE_LOG 0
/*============================================================================
* THEMES
*============================================================================*/
#define LV_USE_THEME_DEFAULT 0
#define LV_USE_THEME_SIMPLE 0
#define LV_USE_THEME_MONO 1
/*============================================================================
* LAYOUTS
*============================================================================*/
#define LV_USE_FLEX 1
#define LV_USE_GRID 0
/*============================================================================
* IMAGE SETTINGS
*============================================================================*/
#define LV_CACHE_DEF_SIZE 0
#define LV_IMAGE_HEADER_CACHE_DEF_CNT 0
#define LV_USE_RLE 0
#define LV_USE_LZ4 0
#define LV_BIN_DECODER_RAM_LOAD 0
#define LV_USE_LODEPNG 0
#define LV_USE_LIBPNG 0
#define LV_USE_BMP 0
#define LV_USE_TJPGD 0
#define LV_USE_LIBJPEG_TURBO 0
#define LV_USE_LIBWEBP 0
#define LV_USE_SVG 0
/*============================================================================
* TEXT & FONT
*============================================================================*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8
#define LV_TXT_BREAK_CHARS " ,.;:-_)]}"
#define LV_TXT_LINE_BREAK_LONG_LEN 0
#define LV_TXT_COLOR_CMD "#"
#define LV_USE_BIDI 0
#define LV_USE_ARABIC_PERSIAN_CHARS 0
/** 内置字体:仅启用 Montserrat 12 作为基础 */
#define LV_FONT_MONTSERRAT_8 0
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 0
#define LV_FONT_MONTSERRAT_16 0
#define LV_FONT_MONTSERRAT_18 0
#define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_22 0
#define LV_FONT_MONTSERRAT_24 0
#define LV_FONT_MONTSERRAT_26 0
#define LV_FONT_MONTSERRAT_28 0
#define LV_FONT_MONTSERRAT_30 0
#define LV_FONT_MONTSERRAT_32 0
#define LV_FONT_MONTSERRAT_34 0
#define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 0
#define LV_FONT_MONTSERRAT_40 0
#define LV_FONT_MONTSERRAT_42 0
#define LV_FONT_MONTSERRAT_44 0
#define LV_FONT_MONTSERRAT_46 0
#define LV_FONT_MONTSERRAT_48 0
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0
#define LV_FONT_SOURCE_HAN_SANS_SC_14_CJK 0
#define LV_FONT_SOURCE_HAN_SANS_SC_16_CJK 0
#define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0
/** 自定义字体:中文 12px 1bpp */
#define LV_FONT_USE_CUSTOM_INCLUDE 1
#define LV_FONT_CUSTOM_INCLUDE "ui_font_cn_12.h"
#define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(ui_font_cn_12)
#define LV_FONT_DEFAULT &ui_font_cn_12
#define LV_USE_CUSTOM_FONT_DEFAULT 0
#define LV_FONT_FMT_TXT_LARGE 0
#define LV_USE_FONT_COMPRESSED 0
#define LV_USE_FONT_PLACEHOLDER 1
#define LV_USE_FONT_MANAGER 0
#define LV_USE_IMGFONT 0
#define LV_USE_FREETYPE 0
#define LV_USE_TINY_TTF 0
/*============================================================================
* WIDGETS
*============================================================================*/
#define LV_WIDGETS_HAS_DEFAULT_VALUE 0
#define LV_USE_3DTEXTURE 0
#define LV_USE_ANIMIMG 0
#define LV_USE_ARC 0
#define LV_USE_ARCLABEL 0
#define LV_USE_BAR 0
#define LV_USE_BARCODE 0
#define LV_USE_BUTTON 1
#define LV_USE_BUTTONMATRIX 1
#define LV_USE_CALENDAR 0
#define LV_USE_CANVAS 0
#define LV_USE_CHART 0
#define LV_USE_CHECKBOX 0
#define LV_USE_DROPDOWN 0
#define LV_USE_FFMPEG 0
#define LV_USE_GIF 0
#define LV_USE_GLTF 0
#define LV_USE_GSTREAMER 0
#define LV_USE_IMAGE 1
#define LV_USE_IMAGEBUTTON 0
#define LV_USE_IME_PINYIN 0
#define LV_USE_KEYBOARD 1
#define LV_USE_LABEL 1
#define LV_LABEL_TEXT_SELECTION 0
#define LV_LABEL_LONG_TXT_HINT 0
#define LV_LABEL_WAIT_CHAR_COUNT 0
#define LV_USE_LED 0
#define LV_USE_LINE 0
#define LV_USE_LIST 0
#define LV_USE_LOTTIE 0
#define LV_USE_MENU 1
#define LV_USE_MSGBOX 0
#define LV_USE_QRCODE 0
#define LV_USE_RLOTTIE 0
#define LV_USE_ROLLER 0
#define LV_USE_SCALE 0
#define LV_USE_SLIDER 0
#define LV_USE_SPAN 0
#define LV_USE_SPINBOX 0
#define LV_USE_SPINNER 0
#define LV_USE_SWITCH 0
#define LV_USE_TEXTAREA 1
#define LV_USE_TABLE 0
#define LV_USE_TABVIEW 0
#define LV_USE_TILEVIEW 0
#define LV_USE_WIN 0
/*============================================================================
* DRIVERS — SDL2 (模拟器专用)
*============================================================================*/
#define LV_USE_LINUX_DRM 0
#define LV_USE_LINUX_FBDEV 0
#define LV_USE_FT81X 0
#define LV_USE_LOVYAN_GFX 0
#define LV_USE_GENERIC_MIPI 0
#define LV_USE_EVDEV 0
#define LV_USE_LIBINPUT 0
#define LV_USE_NUTTX 0
#define LV_USE_OPENGLES 0
#define LV_USE_GLFW 0
#define LV_USE_QNX 0
#define LV_USE_UEFI 0
#define LV_USE_WAYLAND 0
/** SDL2 驱动:启用 */
#define LV_USE_SDL 1
#if LV_USE_SDL
#define LV_SDL_INCLUDE_PATH "SDL2/SDL.h"
#define LV_SDL_AUTO_BACKEND 1
#define LV_SDL_BACKEND LV_SDL_BACKEND_SW
/** 1bpp 必须使用 PARTIAL 模式 */
#define LV_SDL_RENDER_MODE LV_DISPLAY_RENDER_MODE_PARTIAL
#define LV_SDL_BUF_COUNT 1
#define LV_SDL_ACCELERATED 0
#define LV_SDL_FULLSCREEN 0
#define LV_SDL_DIRECT_EXIT 1
#define LV_SDL_MOUSEWHEEL_MODE LV_SDL_MOUSEWHEEL_MODE_ENCODER
#endif
#define LV_USE_WINDOWS 0
/*============================================================================
* THEMES (补充)
*============================================================================*/
#define LV_USE_THEME_DEFAULT 0
#define LV_THEME_DEFAULT_DARK 0
#define LV_THEME_DEFAULT_GROW 0
#define LV_THEME_DEFAULT_TRANSITION_TIME 0
#define LV_USE_THEME_SIMPLE 0
/*============================================================================
* EXPORTS
*============================================================================*/
#define LV_USE_DEMO_NONE 1
#define LV_USE_DEMO_WIDGETS 0
#define LV_USE_DEMO_MUSIC 0
#define LV_USE_DEMO_BENCHMARK 0
#define LV_USE_DEMO_STRESS 0
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
#endif /* LV_CONF_H */
+107
View File
@@ -0,0 +1,107 @@
/**
* @file main.c
* @brief LVGL 模拟器入口 — 160x160 1bpp 单色菜单 UI
* @note 基于 lv_port_pc_vscode 适配,用于 PC 上 UI 开发与调试
*
* 键盘映射:
* ↑/↓/←/→ → 方向键导航
* Enter → 确认/进入
* Escape → 返回
*/
#include <stdlib.h>
#include <stdio.h>
#ifdef _MSC_VER
#include <Windows.h>
#else
#include <unistd.h>
#endif
#define SDL_MAIN_HANDLED
#include <SDL.h>
#include "lvgl.h"
#include "sim_port.h"
#include "ui_menu.h"
/* 屏幕尺寸(与 MCU 端一致) */
#define SIM_SCREEN_W 160
#define SIM_SCREEN_H 160
/**
* @brief 程序入口
*/
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
/* 初始化 SDL2(仅 timer + events,视频由 lv_sdl_window 内部初始化) */
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_EVENTS) != 0) {
fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
return 1;
}
/* 初始化 LVGL */
lv_init();
/* 初始化模拟器端口:创建 SDL 窗口 + 键盘输入 */
if (sim_port_init(SIM_SCREEN_W, SIM_SCREEN_H) != 0) {
fprintf(stderr, "sim_port_init failed\n");
SDL_Quit();
return 1;
}
/* 设置 SDL 窗口标题 */
lv_display_t *disp = lv_display_get_default();
if (disp != NULL) {
lv_sdl_window_set_title(disp, "STM32F4-Base UI Simulator (160x160 1bpp)");
}
/* 构建菜单 UI(与 MCU 端共用 ui_menu.c) */
if (ui_menu_init() != 0) {
fprintf(stderr, "ui_menu_init failed\n");
SDL_Quit();
return 1;
}
printf("=== LVGL Simulator Started ===\n");
printf("Screen: %dx%d 1bpp\n", SIM_SCREEN_W, SIM_SCREEN_H);
printf("Keyboard: ↑↓←→ navigate, Enter confirm, Escape back\n");
printf("Press Ctrl+C or close window to exit\n\n");
/* 主循环:处理 SDL 事件 + 驱动 LVGL 刷新 */
while (1) {
/* 处理 SDL 事件(窗口关闭等) */
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
printf("SDL_QUIT received, exiting...\n");
SDL_Quit();
return 0;
}
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
/* Escape 键:如果在子页面则返回上一级 */
/* LVGL keyboard 驱动已自动处理 LV_KEY_ESC */
}
}
/* 驱动 LVGL 定时器与刷新 */
uint32_t sleep_ms = lv_timer_handler();
if (sleep_ms == LV_NO_TIMER_READY) {
sleep_ms = LV_DEF_REFR_PERIOD;
}
/* 强制整屏刷新(1bpp SDL PARTIAL 模式下保证首帧输出) */
lv_refr_now(NULL);
#ifdef _MSC_VER
Sleep(sleep_ms);
#else
usleep(sleep_ms * 1000);
#endif
}
return 0;
}
+71
View File
@@ -0,0 +1,71 @@
/*
* 模块名称:Simulator Display Port (SDL2 模拟器端口)
* 模块功能:为 PC 模拟器提供 LVGL SDL2 显示与键盘输入端口
* 适用平台:Windows / Linux / macOS (SDL2)
* 说明:替代 MCU 端的 ui_port.c,用于 PC 上 UI 开发与调试
*/
#include "sim_port.h"
#include "lvgl.h"
/* SDL 驱动头文件(由 lv_conf.h 中 LV_SDL_INCLUDE_PATH 指定) */
#define SDL_MAIN_HANDLED
#include <SDL.h>
/* 键盘输入设备句柄(全局,供 main.c SDL 事件循环读取) */
static lv_indev_t *g_kbd_indev = NULL;
/*
* 函数功能:初始化 SDL2 显示窗口与键盘输入设备
* 入口参数:hor_res - 水平分辨率 int32_t
* ver_res - 垂直分辨率 int32_t
* 返回值:0 - 成功,-1 - 失败
* 限定条件:lv_init() 已调用
* 函数说明:1. 创建 SDL 窗口显示设备
* 2. 创建键盘输入设备及默认输入组
* 3. 不创建鼠标/触摸设备(模拟器仅用键盘导航)
*/
int sim_port_init(int32_t hor_res, int32_t ver_res)
{
/* 创建默认输入组 */
lv_group_t *g = lv_group_create();
if (g == NULL) {
return -1;
}
lv_group_set_default(g);
/* 创建 SDL 窗口显示设备 */
lv_display_t *disp = lv_sdl_window_create(hor_res, ver_res);
if (disp == NULL) {
return -1;
}
lv_display_set_default(disp);
/* 初始化 mono 主题(1bpp 单色屏:白字黑底),并应用默认字体 */
#if LV_USE_THEME_MONO
lv_theme_t *th = lv_theme_mono_init(disp, true, LV_FONT_DEFAULT);
if (th != NULL) {
lv_display_set_theme(disp, th);
}
#endif
/* 创建键盘输入设备(方向键/确认/返回 → LVGL keypad) */
g_kbd_indev = lv_sdl_keyboard_create();
if (g_kbd_indev == NULL) {
return -1;
}
lv_indev_set_group(g_kbd_indev, lv_group_get_default());
lv_indev_set_display(g_kbd_indev, disp);
return 0;
}
/*
* 函数功能:获取 SDL 显示设备句柄
* 入口参数:无
* 返回值:显示设备指针 lv_display_t * 可能为 NULL
* 限定条件:sim_port_init() 已成功调用
*/
void *sim_port_get_display(void)
{
return (void *)lv_display_get_default();
}
+38
View File
@@ -0,0 +1,38 @@
/*
* 模块名称:Simulator Display Port (SDL2 模拟器端口)
* 模块功能:为 PC 模拟器提供 LVGL SDL2 显示与键盘输入端口
* 适用平台:Windows / Linux / macOS (SDL2)
* 说明:替代 MCU 端的 ui_port.c,用于 PC 上 UI 开发与调试
*/
#ifndef __SIM_PORT_H
#define __SIM_PORT_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* 函数功能:初始化 SDL2 显示窗口与键盘输入设备
* 入口参数:hor_res - 水平分辨率 int32_t
* ver_res - 垂直分辨率 int32_t
* 返回值:0 - 成功,-1 - 失败
* 限定条件:lv_init() 已调用
* 函数说明:创建 SDL 窗口、键盘输入设备及默认输入组
*/
int sim_port_init(int32_t hor_res, int32_t ver_res);
/*
* 函数功能:获取 SDL 显示设备句柄
* 入口参数:无
* 返回值:显示设备指针 lv_display_t * 可能为 NULL
* 限定条件:sim_port_init() 已成功调用
*/
void *sim_port_get_display(void);
#ifdef __cplusplus
}
#endif
#endif /* __SIM_PORT_H */
+1
View File
@@ -0,0 +1 @@
* 严格的内存使用情况排查,现在所有任务的内存都是往大的给的,导致内存不够用,后面要仔细排查内存的预算情况。