Files
HeavenlyHeroStar/docs/spec/settings-screen-landscape.md

28 KiB
Raw Permalink Blame History

Settings 横屏页面实现规格

项目:HeavenlyHeroStar
目标芯片:ESP32-S3
ESP-IDF6.0.1
LVGL9.2.2
逻辑分辨率:320×170(全局横屏)
状态:待实现(v1.2 修订稿)

1. 背景与目标

1.1 现状

当前 UI 层由 SquareLine Studio 生成,尚无 Settings 页面:

模块 路径 说明
物理分辨率 main/bsp/board_config.h BOARD_LCD_H_RES=170, BOARD_LCD_V_RES=320(竖屏面板)
LVGL Display main/bsp/lvgl_port.c 以 170×320 创建 display,无旋转
触摸 main/bsp/touch.c CST816Sswap_xy/mirror_x/mirror_y 均为 0
背光 main/bsp/display.c display_backlight_set_brightness(01023) 已实现
UI 屏幕 main/ui/screens/ui_Screen{1,2,3}.c Screen1 日历 170×320Screen3 为默认启动页
事件处理 main/app/ui_handlers.c HID 相关回调,与 SquareLine 生成的 ui_events.c 分离
NVS main/app/app_main.c 已初始化 nvs_flashSettings 尚未使用
WiFi main/services/wifi_manager.c BOARD_WIFI_ENABLED=0Network Tab 首版为占位

1.2 目标

实现 Settings 设置页,采用 Master-Detail(主从) 布局:

  • 左侧 Sidebar90px):System / Display / Audio / Network 导航 + Save 按钮
  • 右侧 Content230px):随 Tab 切换动态刷新的设置项
  • 全局横屏:启动后 LVGL 逻辑分辨率为 320×170,所有页面按横屏坐标设计

首版(MVP)交付:

  • BSP 横屏基础设施(display 旋转 + touch 校准)
  • Settings 屏幕骨架与 Tab 切换机制
  • System Tab 完整功能(恢复出厂)
  • Display Tab 亮度滑块(实时预览 + NVS 持久化)
  • Audio / Network Tab 占位 UI + NVS 存根
  • Save 按钮写入 NVSFactory Reset 恢复默认并重启

1.3 方案选型结论

方案 说明 结论
A SquareLine Studio 导出静态 Settings 屏 不利于 Tab 动态刷新,且易被重新导出覆盖
B 手写 C + lv_obj_clean 动态加载 采用:模块化、可扩展、与 SquareLine 解耦
C 每个 Tab 独立 Screenlv_scr_load 切换 内存占用高,Sidebar 需重复创建
横屏-硬件 修改 ST7789 scan direction 不采用:需改 panel 驱动,touch 仍须软件映射
横屏-软件 lv_display_set_rotation(90°) 采用:改动集中在 BSP,UI 直接用 320×170 坐标

2. 页面布局

2.1 整体结构(320×170

System Tab 选中时:

+---------------------------------------------------------------+
| Sidebar 90px          | Content 230px                         |
| [System] *            | System Settings              --:--   |
| [Display]             | ----------------------------------- |
| [Audio]               | Enable Notification    [switch OFF] |
| [Network]             |                                     |
| [Save]                | [Factory Reset]                     |
+---------------------------------------------------------------+
|<-- 90px -->|<-------------------- 230px -------------------->|

Display Tab 选中时(Content 区域示意):

| Display Settings             --:--   |
| ----------------------------------- |
| Brightness: [=======>     ]      70% |

2.2 区域参数表

区域名称 宽度 高度 位置 (x, y) LVGL 控件 说明
Screen 根 320 170 (0, 0) lv_obj_create(NULL) 禁用滚动
Sidebar 90 170 (0, 0) lv_list 导航容器;显式设 pad_all=2item_gap=2
Sidebar 导航按钮 ×4 86 28 相对 listy 递增 lv_list_add_button System/Display/Audio/Network
Save 按钮 86 28 (2, 138) lv_button 固定底部,不在 list 内滚动
Content 容器 230 170 (90, 0) lv_obj 动态刷新区域
StatusBar 230 25 (0, 0) 相对 content lv_obj + lv_label ×2 标题左、时间右
SettingsBody 230 145 (0, 25) 相对 content switch/slider/button 各 Tab 内容

2.3 紧凑布局约束

170px 高度下必须严格控制间距,避免纵向滚动:

约束项
Screen / Sidebar / Content pad_all ≤ 2px
导航按钮间距 2px
SettingsBody 行高 3236px
字体 Phase 1 使用 LV_FONT_DEFAULTPhase 3 可换 12px 定制字体
滚动 Screen、Sidebar、Content 均移除 LV_OBJ_FLAG_SCROLLABLE

2.4 System Tab 控件坐标(相对 SettingsBody

控件 x y 类型
Label "Enable Notification" 4 4 120 28 lv_label
Switch 通知 180 4 40 28 lv_switch
Button "Factory Reset" 4 112 100 28 lv_button

2.5 Display Tab 控件坐标(相对 SettingsBody

控件 x y 类型
Label "Brightness" 4 4 80 28 lv_label
Slider 亮度 88 8 100 12 lv_slider
Label "70%" 196 4 30 28 lv_label

Audio / Network Tab 坐标见 §5.2;布局模式一致(label 左 80px + 控件区 140px)。


3. 系统架构

3.1 模块划分

main/bsp/
├── board_config.h       # 新增 BOARD_LCD_LOGIC_H_RES/V_RES
├── lvgl_port.c          # rotation + flush_cb 软件旋转(真机验证)
├── touch.c              # 物理面板 touch 对齐(非 LVGL 旋转)
└── display.c            # 背光 API(已有)

main/ui/
├── screens/
│   └── ui_Settings.c/.h     # 屏幕骨架:sidebar + content + Save
├── settings/
│   ├── settings_types.h     # settings_data_t、Tab 枚举
│   ├── settings_store.c/.h  # NVS 读写、默认值、commit/reset
│   └── settings_tabs.c/.h     # load_xxx_settings() 动态构建控件
├── ui.c / ui.h              # 注册 Settings 屏幕
└── ui_events.c              # SquareLine 生成,不修改

main/app/
└── ui_handlers.c            # Settings 事件回调(Save/Tab/Slider 等)

3.2 架构图

flowchart TB
    subgraph bsp [BSP Layer]
        Display["display.c"]
        LVGLPort["lvgl_port.c"]
        Touch["touch.c"]
    end

    subgraph ui [UI Layer]
        SettingsScreen["ui_Settings.c"]
        SettingsTabs["settings_tabs.c"]
        SettingsStore["settings_store.c"]
    end

    subgraph app [App Layer]
        UIHandlers["ui_handlers.c"]
    end

    Display --> LVGLPort
    LVGLPort --> SettingsScreen
    Touch --> LVGLPort
    SettingsScreen --> SettingsTabs
    SettingsScreen --> SettingsStore
    SettingsTabs --> UIHandlers
    UIHandlers --> SettingsStore
    SettingsStore --> NVS["nvs_flash"]
    SettingsStore --> Display

3.3 Tab 切换数据流

sequenceDiagram
    participant User as 用户
    participant Sidebar as Sidebar
    participant Content as content_obj
    participant Tabs as settings_tabs
    participant Store as settings_store

    User->>Sidebar: 点击 Display(当前在 System
    Sidebar->>Tabs: settings_tabs_collect(SYSTEM, buf)
    Tabs->>Store: settings_store_update(buf)
    Sidebar->>Sidebar: 更新选中高亮
    Sidebar->>Content: lv_obj_clean(content_obj)
    Sidebar->>Tabs: load_display_settings(content, store)
    Tabs->>Content: 创建 slider/label
    Tabs->>Store: 读取 brightness_pct 填充控件

    User->>Sidebar: 点击 Save
    Sidebar->>Tabs: settings_tabs_collect(active_tab, buf)
    Tabs->>Store: settings_store_update(buf)
    Store->>Store: settings_store_commit()
    Store->>Store: settings_store_apply()

Tab 切换前必须先 collect 当前 Tab 到内存,否则未 Save 的控件改动会在 lv_obj_clean 后丢失。Save 仅 collect 当前 active Tab,但 settings_store_commit() 持久化 内存 struct 全部字段(含其它 Tab 切换前已 collect 的值)。


4. 全局横屏基础设施(BSP

4.1 逻辑分辨率宏

main/bsp/board_config.h 新增:

/* Physical panel pixels (portrait wiring) */
#define BOARD_LCD_H_RES          170
#define BOARD_LCD_V_RES          320

/* LVGL logical resolution after 90° rotation (landscape) */
#define BOARD_LCD_LOGIC_H_RES    320
#define BOARD_LCD_LOGIC_V_RES    170

UI 代码 统一使用 BOARD_LCD_LOGIC_H_RES / BOARD_LCD_LOGIC_V_RES 作为屏幕尺寸。

4.2 Display 旋转与 flush_cb

main/bsp/lvgl_port.clvgl_port_init() 中,lv_display_create 之后添加:

lv_display_t *display =
    lv_display_create(BOARD_LCD_H_RES, BOARD_LCD_V_RES);

lv_display_set_rotation(display, LV_DISPLAY_ROTATION_90);
/* 逻辑分辨率:320×170 */

说明(依据 LVGL 9.2 Display porting — Rotation):

  • lv_display_set_rotation() 交换逻辑分辨率320×170),并变换 触摸坐标不会自动旋转像素
  • 本项目无 ST7789 硬件 scan rotation,须走 软件旋转 路径
  • 当前为 LV_DISPLAY_RENDER_MODE_PARTIAL:LVGL 可能在内部按块旋转后再调用 flush_cb但必须在真机验证;若画面仍呈竖屏或区域错位,在 flush_cb 中补充:
lv_display_rotate_area(disp, (lv_area_t *)&rotated_area);
lv_draw_sw_rotate(px_map, rotated_buf, src_w, src_h, src_stride, dst_stride,
                  lv_display_get_rotation(disp), cf);
/* 再将 rotated_buf / rotated_area 传给 esp_lcd_panel_draw_bitmap */
  • 绘制 buffer 大小仍基于物理 BOARD_LCD_H_RES × BOARD_LVGL_DRAW_BUF_LINES;逻辑宽变为 320 后 partial 分块与软件旋转 CPU 开销上升,需关注刷新流畅度(见 §11

Phase 1 步骤 1.2 验收: 加载 Settings 页后,Sidebar 在左、Content 在右,文字可读,无镜像/错位。

4.3 Panel Gap 与 flush 坐标

物理面板为 240×320,有效显示区 170×320 居中,display.c 已通过 esp_lcd_panel_set_gap(BOARD_LCD_GAP_X, 0) 设置 35px 水平偏移BOARD_LCD_GAP_X = (240170)/2)。

旋转 90° 后 gap 方向可能变化;若 flush_cb 需手动 lv_draw_sw_rotate,须同步验证:

  1. 画面是否整体偏移或裁切
  2. lv_display_rotate_area 输出坐标加上 gap offset 是否正确
  3. 四角触控是否与像素对齐

Phase 1 步骤 1.2/1.3 一并验收,必要时在 flush 路径对 area 做 gap 补偿。

4.4 Touch 坐标映射

原则:触摸驱动只做「物理竖屏面板」对齐;LVGL 负责 rotation 后的逻辑坐标变换。

  • esp_lcd_touch_config_tx_max / y_max 保持 物理尺寸 170×320,不随逻辑横屏改变
  • lv_indev_set_display(indev, display) 关联 display 后,LVGL 9 会根据 lv_display_set_rotation() 在内部变换触摸点
  • 因此 swap_xy / mirror_x / mirror_y 仅用于 触摸 IC 原始坐标 → 物理面板(170×320、原点左上角)的对齐,不得 为了 90° 逻辑横屏在驱动层做 rotation 补偿,否则 双重变换

初始值(与当前 touch.c 一致,待真机验证):

.flags = {
    .swap_xy = 0,
    .mirror_x = 0,
    .mirror_y = 0,
},

校准步骤:

  1. lv_display_set_rotation(90°)保持 touch flags 全 0,加载 Settings 页
  2. 在四个逻辑角点击,观察触控是否落在对应 UI 区域
  3. 若整体镜像或轴互换,仅调整物理对齐 flagsswap × mirror 共 8 种),每次改一项并记录
  4. 确认 Sidebar 左侧按钮可准确触控后,写入 board_config.h
#define BOARD_TOUCH_SWAP_XY      0   /* 真机确认值 */
#define BOARD_TOUCH_MIRROR_X     0
#define BOARD_TOUCH_MIRROR_Y     0

注释须标明:物理面板对齐,非 LVGL 逻辑旋转

4.5 现有 Screen 兼容

屏幕 现状 横屏影响 处理阶段
Screen1 日历 170×320 居中 布局错位 Phase 3 重排或替换
Screen2 SquareLine 生成 需验证 Phase 3
Screen3 HID 控制 绝对坐标按钮 需重排 Phase 3
Settings 新建 按 320×170 设计 Phase 1

Phase 1 建议将 ui.clv_disp_load_scr() 默认屏改为 ui_Settings,便于验收横屏与 Settings 功能。


5. Settings UI 规格

5.1 屏幕骨架 API

main/ui/screens/ui_Settings.h

#pragma once
#include "lvgl.h"
#include "settings_types.h"

extern lv_obj_t *ui_Settings;
extern lv_obj_t *ui_Settings_sidebar;
extern lv_obj_t *ui_Settings_content;

void ui_Settings_screen_init(void);
void ui_Settings_screen_destroy(void);

/* 切换 Tab 时由 ui_handlers 调用 */
void ui_Settings_switch_tab(settings_tab_t tab);
settings_tab_t ui_Settings_get_active_tab(void);

初始化伪代码:

void ui_Settings_screen_init(void)
{
    ui_Settings = lv_obj_create(NULL);
    lv_obj_set_size(ui_Settings, BOARD_LCD_LOGIC_H_RES, BOARD_LCD_LOGIC_V_RES);
    lv_obj_remove_flag(ui_Settings, LV_OBJ_FLAG_SCROLLABLE);

    ui_Settings_sidebar = lv_list_create(ui_Settings);
    lv_obj_set_size(ui_Settings_sidebar, 90, 170);
    lv_obj_set_pos(ui_Settings_sidebar, 0, 0);
    lv_obj_remove_flag(ui_Settings_sidebar, LV_OBJ_FLAG_SCROLLABLE);

    /* 4 个导航 button + 事件 */
    /* Save button 固定底部 */

    ui_Settings_content = lv_obj_create(ui_Settings);
    lv_obj_set_size(ui_Settings_content, 230, 170);
    lv_obj_set_pos(ui_Settings_content, 90, 0);
    lv_obj_remove_flag(ui_Settings_content, LV_OBJ_FLAG_SCROLLABLE);

    settings_store_load();
    settings_store_apply();   /* 启动时恢复 NVS 背光等到硬件 */
    load_system_settings(ui_Settings_content, settings_store_get());
}

5.2 各 Tab MVP 内容

System Tab

控件 LVGL 类型 数据字段 行为
Enable Notification lv_switch notification_enabled VALUE_CHANGED 时更新内存;Tab 切换前 collectSave 时 commit
Factory Reset lv_button 确认后 settings_store_factory_reset() + esp_restart()

StatusBar 标题:"System Settings"

Display Tab

控件 LVGL 类型 数据字段 行为
Brightness lv_slider (0100) + % label brightness_pct VALUE_CHANGED 时:settings_store 更新内存 + settings_store_apply() 实时调背光;Save 时 commit

StatusBar 标题:"Display Settings"

Audio Tab

控件 LVGL 类型 数据字段 行为
Volume lv_slider (0100) volume_pct VALUE_CHANGED 更新内存;Phase 1 无 codec 输出
Mute lv_switch mute VALUE_CHANGED 更新内存;Phase 1 无 codec 输出

StatusBar 标题:"Audio Settings"

Network Tab

控件 LVGL 类型 行为
WiFi Status lv_label 显示 "WiFi: Disabled"BOARD_WIFI_ENABLED=0
Scan lv_button Phase 1 点击显示 "Not available" 日志;Phase 4 接 wifi_manager

StatusBar 标题:"Network Settings"

5.3 Tab 加载函数

main/ui/settings/settings_tabs.h

#pragma once
#include "lvgl.h"
#include "settings_types.h"

void load_system_settings(lv_obj_t *content, const settings_data_t *data);
void load_display_settings(lv_obj_t *content, const settings_data_t *data);
void load_audio_settings(lv_obj_t *content, const settings_data_t *data);
void load_network_settings(lv_obj_t *content, const settings_data_t *data);

/* Save / Tab 切换前:从指定 Tab 控件回读到 outout 应先拷贝 settings_store_get() */
void settings_tabs_collect(settings_tab_t tab, settings_data_t *out);

每次切换 Tab先 collect 再 clean):

void ui_Settings_switch_tab(settings_tab_t tab)
{
    settings_data_t buf = *settings_store_get();
    settings_tabs_collect(s_active_tab, &buf);
    settings_store_update(&buf);

    s_active_tab = tab;
    sidebar_update_highlight(tab);
    lv_obj_clean(ui_Settings_content);
    switch (tab) {
    case SETTINGS_TAB_SYSTEM:
        load_system_settings(ui_Settings_content, settings_store_get());
        break;
    case SETTINGS_TAB_DISPLAY:
        load_display_settings(ui_Settings_content, settings_store_get());
        break;
    case SETTINGS_TAB_AUDIO:
        load_audio_settings(ui_Settings_content, settings_store_get());
        break;
    case SETTINGS_TAB_NETWORK:
        load_network_settings(ui_Settings_content, settings_store_get());
        break;
    default:
        LV_ASSERT_MSG(false, "unexpected settings_tab_t");
        break;
    }
}

5.4 样式规范

ui_Settings.c 内使用 static lv_style_t(或 settings_style.c):

元素 属性
Sidebar 按钮(默认) 宽×高 86×28
Sidebar 按钮(选中) 背景色 lv_palette_main(LV_PALETTE_BLUE)
Content 背景 背景色 lv_palette_lighten(LV_PALETTE_GREY, 2)
StatusBar 分隔线 高度 1px bottom border 或 lv_obj 线
Slider 100120px
Factory Reset 按钮 文字色 lv_palette_main(LV_PALETTE_RED)

Style 生命周期: 使用文件级 static style,在 ui_Settings_screen_init() 初始化一次;lv_obj_clean(content) 仅删除子对象,不删除 static style,避免泄漏。

5.5 StatusBar 时间

阶段 行为
Phase 1 静态占位 "--:--"
Phase 4 SNTP 同步后 lv_label_set_text_fmt() 每分钟更新

6. 数据模型与 NVS

6.1 类型定义

main/ui/settings/settings_types.h

#pragma once
#include <stdint.h>
#include <stdbool.h>

typedef enum {
    SETTINGS_TAB_SYSTEM = 0,
    SETTINGS_TAB_DISPLAY,
    SETTINGS_TAB_AUDIO,
    SETTINGS_TAB_NETWORK,
} settings_tab_t;

typedef struct {
    bool     notification_enabled;
    uint8_t  brightness_pct;       /* 0100, default 70 */
    uint8_t  volume_pct;           /* 0100, default 50 */
    bool     mute;
} settings_data_t;

6.2 默认值

字段 默认值 说明
notification_enabled false 与 mockup 一致,默认关闭
brightness_pct 70 与 mockup 一致
volume_pct 50 居中
mute false
static const settings_data_t SETTINGS_DEFAULTS = {
    .notification_enabled = false,
    .brightness_pct = 70,
    .volume_pct = 50,
    .mute = false,
};

6.3 NVS Schema

属性
Namespace "settings"
存储格式 各 key 独立 u8/blob,便于单项迁移
Key 类型 字段 说明
"notif" uint8_t notification_enabled 0/1
"bright" uint8_t brightness_pct 0100
"vol" uint8_t volume_pct 0100
"mute" uint8_t mute 0/1

6.4 Store API

main/ui/settings/settings_store.h

#pragma once
#include "settings_types.h"

void settings_store_load(void);
const settings_data_t *settings_store_get(void);
void settings_store_update(const settings_data_t *data);
esp_err_t settings_store_commit(void);
esp_err_t settings_store_factory_reset(void);
void settings_store_apply(void);   /* 将内存值应用到硬件(背光等) */
函数 说明
settings_store_load 启动时从 NVS 读取;key 不存在则用 SETTINGS_DEFAULTS
settings_store_get 返回内存中当前 struct 指针(只读)
settings_store_update 用 collect 结果更新内存 structcollect 前应先 *out = *settings_store_get()
settings_store_commit 将全部字段写入 NVS
settings_store_factory_reset 擦除 "settings" namespace,恢复默认值,commit
settings_store_apply 调用 display_backlight_set_brightness() 等即时生效项

6.5 Save 流程

sequenceDiagram
    participant User
    participant SaveBtn
    participant Tabs as settings_tabs
    participant Store as settings_store
    participant NVS
    participant HW as display.c

    User->>SaveBtn: 点击 Save
    SaveBtn->>Tabs: settings_tabs_collect(active_tab, buf)
    Tabs->>Store: settings_store_update(buf)
    Store->>NVS: settings_store_commit()
    Store->>HW: settings_store_apply()
    SaveBtn->>User: 可选:短暂 toast "Saved"

注意:

  • Display Tab 亮度在 VALUE_CHANGED 时已通过 settings_store_apply() 实时调背光并更新内存;Save 负责写入 NVS
  • 跨 Tab 修改:切换 Tab 时 collect 当前 Tab 到内存;最终 Save 一次 commit 全部字段
  • 背光等硬件调用统一经 settings_store_apply()settings_tabs.c 直接调用 display.c(见 §7.1

6.6 Factory Reset 流程

  1. 用户点击 Factory Reset 按钮
  2. Phase 1 无确认对话框,直接执行(Phase 2 可选加 lv_msgbox 确认,见 §8
  3. 调用 settings_store_factory_reset():擦除 NVS namespace,恢复 SETTINGS_DEFAULTS
  4. 调用 settings_store_apply() 恢复背光
  5. esp_restart()

7. 事件与集成

7.1 事件归属

事件 注册位置 处理函数(ui_handlers.c
Sidebar Tab 点击 ui_Settings.c init settings_on_tab_clicked()
Save 点击 ui_Settings.c init settings_on_save_clicked()
Factory Reset settings_tabs.c 创建时 settings_on_factory_reset()
Brightness slider settings_tabs.c 创建时 settings_on_brightness_changed() → 更新 store + settings_store_apply()
其他 slider/switch 各 load 函数 settings_on_value_changed() → 更新 store 内存

原则: Settings 相关回调一律放在 main/app/ui_handlers.c不修改 SquareLine 生成的 ui_events.c。硬件侧效应(背光)经 settings_store_apply()UI 层不直接 #include "display.h"

7.2 CMakeLists.txt 变更

main/CMakeLists.txtSRCS 中添加:

"ui/screens/ui_Settings.c"
"ui/settings/settings_store.c"
"ui/settings/settings_tabs.c"

7.3 ui.h / ui.c 变更

/* ui.h */
#include "screens/ui_Settings.h"

/* ui_init() */
ui_Settings_screen_init();
/* Phase 1 默认屏(LVGL 9 亦可写 lv_screen_load(ui_Settings) */
lv_disp_load_scr(ui_Settings);

/* ui_destroy() */
ui_Settings_screen_destroy();

7.4 初始化顺序

app_main.c 中保持现有顺序,Settings 无额外要求:

nvs_flash_init → display_init → lvgl_port_init → touch_init → ui_init → ui_handlers_init

settings_store_load()settings_store_apply()ui_Settings_screen_init() 内调用(NVS 已就绪),确保重启后背光等与 NVS 一致。


8. 实现步骤

阶段 0:规格审核

步骤 内容 状态
0.1 审核本文档 v1.2 完成
0.2 确认横屏全局策略与 Tab MVP 范围 待确认

阶段 1BSP 横屏 + Settings 全量 MVP

步骤 内容
1.1 board_config.h 增加逻辑分辨率宏、touch 对齐宏
1.2 lvgl_port.clv_display_set_rotation(90°);真机验证 flush,必要时 lv_draw_sw_rotate + gap 补偿
1.3 touch.c:物理面板对齐 flags 真机验证(勿与 LVGL rotation 双重变换)
1.4 新建 settings_types.hsettings_store.c(含 apply
1.5 新建 ui_Settings.cSidebar + Content + Save
1.6 新建 settings_tabs.c:四个 load_xxx_settings() + settings_tabs_collect()
1.7 ui_handlers.cTab 切换(先 collect)、Save、Factory Reset、slider/switch 回调
1.8 更新 CMakeLists.txtui.cui.h
1.9 默认加载 Settings 页,真机验收 §10 全部 AC

Phase 1 验收: 与 §1.2 MVP 及 §10 验收标准 AC-1AC-8 一致(含四个 Tab、Display 亮度、跨 Tab Save、Factory Reset)。

阶段 2:体验增强(可选,非 MVP 阻塞)

步骤 内容
2.1 Factory Reset lv_msgbox 确认对话框
2.2 Save 成功 toast / 视觉反馈
2.3 Sidebar / Tab 切换过渡动画

阶段 3:现有 Screen 横屏适配

步骤 内容
3.1 Screen3 HID 控制页按 320×170 重排
3.2 Screen1/2 适配或废弃
3.3 多 Screen 导航入口(如 Settings 返回主屏)

阶段 4:增强功能(后续)

步骤 内容
4.1 SNTP 时间显示
4.2 通知功能真实实现
4.3 WiFi 扫描/连接 UIBOARD_WIFI_ENABLED=1
4.4 Audio codec 对接
4.5 Factory Reset 确认对话框(若未在 Phase 2 完成)

9. 与现有代码对应关系

现有 Settings 实施后
lv_display_create(170, 320) 不变物理尺寸 + set_rotation(90°)flush 可能需软件旋转
ui_Screen3 默认启动 Phase 1 改为 ui_SettingsPhase 3 增加导航
ui_events.c SquareLine 回调 不修改;Settings 事件放 ui_handlers.c
display_backlight_set_brightness() settings_store_apply() 调用,不在 UI Tab 层直接调用
nvs_flash 已初始化 Settings 使用独立 namespace "settings"
wifi_manager.c Network Tab Phase 4 对接
SquareLine Screen1/2/3 Phase 3 适配,Settings 不纳入 SquareLine

10. 验收标准

编号 验收项 标准
AC-1 逻辑分辨率 LVGL 屏幕 320×170Sidebar 90px + Content 230px
AC-2 触摸 四角及 Sidebar 按钮触控准确,无误触
AC-3 Tab 切换 4 个 Tab 可切换,右侧内容正确刷新,无滚动条
AC-4 System Tab Switch/Factory Reset 可交互
AC-5 Display Tab 亮度 slider 实时改变背光
AC-6 Save 跨 Tab 修改后 Save,重启后全部设置值从 NVS 恢复
AC-7 Factory Reset 恢复默认值、背光恢复 70%、设备重启
AC-8 内存 Tab 反复切换 50 次无 crash、无显著内存增长

11. 风险与约束

  1. flush 软件旋转lv_display_set_rotation 不保证自动旋转像素;须真机验证,必要时改 flush_cb(§4.2)。
  2. 触摸校准:区分物理对齐与 LVGL rotation;错误地在 touch 驱动层做 rotation 补偿会导致双重变换(§4.4)。
  3. Panel gap:240 宽面板 170 有效区居中,旋转后须验证 offset(§4.3)。
  4. Partial 模式性能:逻辑宽 320 + 软件旋转增加 CPU 负担,若卡顿可考虑增大 BOARD_LVGL_DRAW_BUF_LINES 或后续评估 DIRECT 模式。
  5. SquareLine 冲突Settings 手写 C,不纳入 SquareLine 工程;重新导出 UI 时不应覆盖 ui_Settings.cui_handlers.c 中 Settings 部分。
  6. WiFi 默认关闭Network Tab Phase 1 仅为占位,避免用户误解为已连网。
  7. 170px 高度:控件过多时需合并 Tab 或减小行高,禁止启用纵向滚动。
  8. Style 生命周期Tab 切换使用 lv_obj_clean 而非 lv_obj_del 整个 contentstatic style 不可绑定到会被删除的对象上。
  9. 跨 Tab 数据Tab 切换前必须 collectslider/switch 的 VALUE_CHANGED 应同步内存,避免 UI 与硬件不一致。
  10. Factory Reset 范围Phase 1 仅擦除 "settings" namespaceBLE bonding、快捷键等其它 NVS namespace(见 ble-hid-keyboard.md 规划)保留。
  11. Screen 兼容Phase 1 默认 Settings 页会导致 Screen1/2/3 暂不可直接验收,属预期行为。

12. 参考

  • 板级配置:main/bsp/board_config.h
  • LVGL Portmain/bsp/lvgl_port.c
  • 背光 APImain/bsp/display.h
  • 现有 UI 入口:main/ui/ui.c
  • 事件处理范例:main/app/ui_handlers.c
  • 姊妹规格:docs/spec/ble-hid-keyboard.md
  • LVGL 9.2 Display Rotationporting/display.html — Rotation
  • LVGL APIlv_display_set_rotation()lv_display_rotate_area()lv_draw_sw_rotate()

修订记录

版本 日期 说明
1.0 2026-07-05 初版:Settings 横屏 320×170 布局、BSP、NVS、分阶段实现规格
1.1 2026-07-05 移除语言选择(dropdown / NVS lang / settings_language_t
1.2 2026-07-05 技术修订:统一 Tab 线框与 MVP 范围;修正 LVGL 9 flush/touch 旋转说明;Tab 切换先 collect;启动 apply 背光;补 gap/分层/跨 Tab Save 约束