Dzbj_ESP32-S3_Key/电子吧唧按键功能规划文档.md
Rdzleo 75586b3744 ESP32-S3按键版电子吧唧功能完整实现
1、按键驱动重构:从GPIO中断改为iot_button组件,支持BOOT/KEY2的单击/双击/长按6种事件;
2、新增按键导航管理器(key_nav):9种上下文状态机,统一分发按键事件到对应界面业务逻辑;
3、BLE模块改造:广播默认关闭由按键触发,新增设备间图片传输(GATT Client),支持扫描配对→MTU协商→分包发送;
4、新增6个UI界面:配对(Peiwang)、更新(Update)、发送等待(ImageShar)、接收等待(ImageReception)、发送中(Sharing)、接收中(Receiving);
5、新增电池指示器组件(battery_ui):支持多界面真实电量显示,3秒渐隐效果;
6、Home界面重构:移除手势事件,改为airhub背景图+电池指示器;
7、Img界面重构:移除触摸事件,新增删除二次确认边框机制;
8、禁用ScreenSet/ScreenChar界面(保留文件),禁用触摸初始化(保留代码可恢复);
9、sleep_mgr简化:移除ScreenSet亮度UI依赖,按键唤醒由key_nav统一处理;
10、新增9张图片资源(airhub背景、配对、传输状态、电池图标等);

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 17:35:05 +08:00

17 KiB
Raw Permalink Blame History

Dzbj_ESP32_S3 纯电子吧唧 — 两键实现三键功能规划

更新日期2026-03-25 目标项目Dzbj_ESP32_S3纯电子吧唧无 AI 对话模式) 参考实现Baji_Rtc_Toy_Key宝石角按键版的 key_nav 模块 硬件平台ESP32-S3-N16R8与 Baji_Rtc_Toy 开发板相同


一、硬件现状

1.1 物理按键

按键 GPIO 电气特性 可用于业务控制
BOOT GPIO0 低电平有效,内部上拉
KEY2 GPIO4 低电平有效,内部上拉
SW1 物理电源开关,未接 MCU GPIO

SW1 为纯硬件断电开关,无法被软件检测,不参与业务逻辑。

1.2 当前按键驱动(待重构)

当前使用 GPIO ISR + 手动去抖200ms 时间戳),只支持"按下"一种事件

// 当前驱动button.c
gpio_isr_handler()  xQueueSend  btn_task()  200ms去抖  回调

问题2 个按键 × 1 种事件 = 只有 2 种操作,无法覆盖宝石角的全部功能。


二、宝石角3 键)功能分析

宝石角按键版有 3 个按键:BOOTKEY1KEY2。通过 iot_button 组件支持单击/双击/长按,加上 key_nav 导航管理器,实现了完整的无触摸交互。

2.1 宝石角各界面按键行为

界面 BOOT 单击 KEY 单击 BOOT 双击 KEY 长按
Home → Set → Img
Img浏览 → Home 下一张图片 → Home
Img删除确认 确认删除 取消(退出删除模式) → Home
Set无焦点 → Home 选中第一个图标 → Home
Set有焦点 执行选中功能 切换到下一个焦点 → Home
Set亮度调节 亮度 +10% 亮度 -10% → Home 退出调节模式
应援灯 切换颜色 退出 → Set → Home

2.2 宝石角 Set 界面焦点系统

焦点通过蓝色边框高亮(#2196F33pxKEY 单击循环切换:

节能 → 应援灯 → 删除 → 亮度 → 节能 → ...

BOOT 单击执行当前焦点对应的功能(进入应援灯、进入亮度调节、进入删除模式等)。


三、两键方案设计

3.1 核心思路

iot_button 组件替换当前 GPIO ISR 驱动,为 2 个按键获得 6 种事件

按键 单击 双击 长按
BOOT (GPIO0)
KEY2 (GPIO4)

2 键 × 3 种事件 = 6 种操作,完全覆盖宝石角 3 键的功能需求。

3.2 按键角色定义

按键 角色 助记
BOOT 确认 / 执行 / 返回 右手拇指(主操作)
KEY2 导航 / 切换 / 浏览 左手拇指(辅助操作)

3.3 iot_button 时间参数

button_config_t btn_cfg = {
    .long_press_time = 2000,    // 长按阈值 2 秒
    .short_press_time = 0,      // 双击检测窗口 180ms默认值
};
事件 触发条件
单击 按下后 180ms 内无第二次按下
双击 180ms 内连续按下两次
长按 持续按住超过 2 秒

四、完整按键映射表

4.1 屏幕关闭(低功耗模式)

按键 任何操作 行为
BOOT 按下 唤醒屏幕,恢复亮度,不触发业务
KEY2 按下 唤醒屏幕,恢复亮度,不触发业务

唤醒后回到休眠前的界面。

4.2 Home 界面

按键 事件 行为 说明
BOOT 单击 Set 界面 进入设置
KEY2 单击 Img 界面 进入图片浏览
BOOT 长按 预留(可用于关机提示/重置等)
KEY2 长按 预留

4.3 Img 界面(图片浏览)

按键 事件 行为 说明
KEY2 单击 下一张图片 自动跳过解码失败的无效图片
KEY2 双击 上一张图片 自动跳过无效图片
BOOT 单击 Home 界面 返回主界面
BOOT 长按 进入 删除确认 模式 替代宝石角的 Set→删除→Img 路径

宝石角用左右两个按键分别切换上/下一张。本方案用 KEY2 单击=下一张、双击=上一张,一个按键两个方向。

4.4 Img 界面(删除确认模式)

BOOT 长按触发后,显示删除确认 UIContainerDle

按键 事件 行为 说明
BOOT 单击 确认删除当前图片 删除后自动显示下一张
KEY2 单击 取消,退出删除模式 隐藏 ContainerDle

4.5 Set 界面(焦点导航模式)

进入 Set 界面后默认无焦点。KEY2 单击开始焦点循环:

无焦点 → 节能 → 应援灯 → 亮度 → (无焦点) → 节能 → ...

注:相比宝石角去掉了"删除"焦点项。删除功能已移到 Img 界面的 BOOT 长按中,避免 Set→Img 的复杂跳转。

按键 事件 行为 说明
KEY2 单击 切换焦点到下一个图标 蓝色边框高亮
BOOT 单击(无焦点时) Home 界面 返回
BOOT 单击(有焦点时) 执行选中功能 进入节能/应援灯/亮度调节
BOOT 单击(在焦点项上) → 对应功能 详见下方

各焦点项的执行行为

焦点项 BOOT 单击执行
节能 切换节能模式开/关(亮度降至 10% + 10s 超时熄屏)
应援灯 进入应援灯全屏模式
亮度 进入亮度调节模式

4.6 Set 界面(亮度调节模式)

从焦点"亮度"按 BOOT 进入,滑块高亮显示:

按键 事件 行为 说明
BOOT 单击 亮度 +10% 上限 100%
KEY2 单击 亮度 -10% 下限 10%
KEY2 长按 退出亮度调节模式 回到焦点导航

4.7 应援灯全屏模式

从焦点"应援灯"按 BOOT 进入:

按键 事件 行为 说明
BOOT 单击 切换颜色(红→绿→蓝→循环) LCD 硬件级写 GRAM
KEY2 单击 退出 → Set 界面 恢复原亮度

4.8 ScreenChar 角色动画界面(如有)

按键 事件 行为 说明
BOOT 单击 切换动画播放/暂停 保持原有逻辑

五、与宝石角功能对照

5.1 功能覆盖对比

功能 宝石角3 键) 本方案2 键) 实现方式
界面导航Home/Img/Set BOOT=确认/返回KEY2=切换
下一张图片 KEY 单击 KEY2 单击 相同
上一张图片 另一个KEY KEY2 双击 双击替代第三键
删除图片 Set→删除→Img Img 中 BOOT 长按 更直接,少一步跳转
Set 焦点导航 相同
亮度调节 相同
应援灯 相同
节能开关 相同
低功耗唤醒 两键均可唤醒
跳过无效图片 自动跳过(已实现)

5.2 操作差异

操作 宝石角 本方案 差异说明
上一张图片 独立按键单击 KEY2 双击 需要快速双击,操作稍慢
删除图片入口 Set 界面焦点 Img 界面 BOOT 长按 更直接,浏览时即可删除
退出亮度调节 KEY 长按 KEY2 长按 相同

六、界面导航图

                    ┌──────────────────┐
                    │   Home主界面   │
                    │                  │
                    │  BOOT→Set        │
                    │  KEY2→Img        │
                    └───┬──────────┬───┘
                        │          │
              BOOT单击   │          │  KEY2单击
                        ▼          ▼
          ┌─────────────────┐  ┌──────────────────────┐
          │  Set设置界面  │  │  Img图片浏览界面   │
          │                 │  │                      │
          │  KEY2焦点切换  │  │  KEY2单击下一张      │
          │  BOOT执行/返回 │  │  KEY2双击上一张      │
          │                 │  │  BOOT单击返回Home    │
          │  ┌────────────┐ │  │  BOOT长按删除确认    │
          │  │焦点项:     │ │  │                      │
          │  │ 节能       │ │  │  ┌──────────────────┐ │
          │  │ 应援灯 ──┐ │ │  │  │  删除确认模式     │ │
          │  │ 亮度 ──┐ │ │ │  │  │  BOOT确认删除   │ │
          │  └───────┼─┼─┘ │  │  │  KEY2取消        │ │
          └──────────┼─┼───┘  │  └──────────────────┘ │
                     │ │      └──────────────────────┘
                     │ │
           ┌─────────┘ └──────────┐
           ▼                      ▼
  ┌─────────────────┐   ┌──────────────────┐
  │  亮度调节模式     │   │  应援灯全屏模式   │
  │                 │   │                  │
  │  BOOT亮度+10% │   │  BOOT切换颜色   │
  │  KEY2亮度-10%  │   │  KEY2退出→Set  │
  │  KEY2长按退出   │   │                  │
  └─────────────────┘   └──────────────────┘

七、技术实现要点

7.1 按键驱动重构GPIO ISR → iot_button

当前button.c

// GPIO 中断 + 手动 200ms 去抖,只支持"按下"
gpio_isr_handler_add(PIN_BTN_BOOT, gpio_isr_handler, ...);

目标(使用 iot_button 组件):

#include "iot_button.h"

button_config_t btn_cfg = {
    .long_press_time = 2000,
    .short_press_time = 0,  // 默认 180ms 双击窗口
};
button_gpio_config_t gpio_cfg = {
    .gpio_num = PIN_BTN_BOOT,
    .active_level = 0,  // 低电平有效
};

button_handle_t boot_handle;
iot_button_new_gpio_device(&btn_cfg, &gpio_cfg, &boot_handle);

// 注册三种事件
iot_button_register_cb(boot_handle, BUTTON_SINGLE_CLICK, NULL, boot_click_cb, NULL);
iot_button_register_cb(boot_handle, BUTTON_DOUBLE_CLICK, NULL, boot_dblclick_cb, NULL);
iot_button_register_cb(boot_handle, BUTTON_LONG_PRESS_START, NULL, boot_longpress_cb, NULL);

依赖添加idf_component.yml

dependencies:
  button: ">=3.2.0"

7.2 key_nav 导航管理器

从宝石角移植 key_nav 模块,核心结构:

// 导航上下文(根据当前界面和模式决定按键行为)
typedef enum {
    NAV_CTX_HOME,           // Home 界面
    NAV_CTX_IMG,            // Img 浏览
    NAV_CTX_IMG_DELETE,     // Img 删除确认
    NAV_CTX_SET,            // Set 焦点导航
    NAV_CTX_SET_BRIGHTNESS, // 亮度调节
    NAV_CTX_FLASHLIGHT,     // 应援灯
} nav_context_t;

// Set 焦点项(比宝石角少了"删除"项)
typedef enum {
    SET_FOCUS_NONE = -1,
    SET_FOCUS_LOW_POWER = 0,
    SET_FOCUS_FLASHLIGHT,
    SET_FOCUS_BRIGHTNESS,
    SET_FOCUS_COUNT,
} set_focus_item_t;

7.3 回调中禁止 vTaskDelay关键约束

iot_button 回调在 esp_timer 任务中执行,必须派发到独立任务:

static void boot_click_cb(void *arg, void *data) {
    // ❌ 禁止vTaskDelay(pdMS_TO_TICKS(100));
    // ✅ 正确:派发到独立任务
    xTaskCreate(nav_boot_click_task, "nav_boot", 3072, NULL, 5, NULL);
}

static void nav_boot_click_task(void *arg) {
    // 这里可以安全地 vTaskDelay、修改 LVGL 等
    if (sleep_mgr_is_screen_off()) {
        sleep_mgr_notify_activity();
    } else {
        // 根据当前 nav_context 执行对应操作
        handle_boot_click();
    }
    vTaskDelete(NULL);
}

7.4 焦点高亮样式

#define FOCUS_BORDER_COLOR  0x2196F3  // Material Blue
#define FOCUS_BORDER_WIDTH  3

static void set_focus_border(lv_obj_t *obj, bool active) {
    lvgl_port_lock(0);
    if (active) {
        lv_obj_set_style_border_color(obj, lv_color_hex(FOCUS_BORDER_COLOR), LV_PART_MAIN);
        lv_obj_set_style_border_width(obj, FOCUS_BORDER_WIDTH, LV_PART_MAIN);
        lv_obj_set_style_border_opa(obj, LV_OPA_COVER, LV_PART_MAIN);
    } else {
        lv_obj_set_style_border_opa(obj, LV_OPA_TRANSP, LV_PART_MAIN);
    }
    lvgl_port_unlock();
}

7.5 应援灯颜色切换优化

绕过 LVGL 分band渲染直接写 LCD GRAM瞬间切换无从上到下刷新感

void flashlight_switch_color(void) {
    lcd_disp_on_off(false);     // DISPOFFLCD 停止输出
    lcd_fill_color(new_color);  // 直接写 GRAM~35ms
    lcd_disp_on_off(true);      // DISPON瞬间恢复画面已完整
}

八、文件变更清单

8.1 需要重构的文件

文件 变更内容
main/button/button.c GPIO ISR → iot_button 组件,支持单击/双击/长按
main/button/include/button.h 新增事件类型枚举,回调签名变更
main/main.c 移除 boot_btn_handler()~65 行),集成 key_nav_init()
main/ui/screens/ui_ScreenHome.c 移除手势事件函数
main/ui/screens/ui_ScreenImg.c 移除手势/点击事件,保留 SCREEN_LOADED
main/ui/screens/ui_ScreenSet.c 移除手势/滑块/点击事件
main/sleep_mgr/sleep_mgr.c 移除按键回调注册,由 key_nav 统一处理
main/CMakeLists.txt 添加 key_nav 源文件和头文件路径
main/idf_component.yml 添加 button: ">=3.2.0" 依赖

8.2 需要新增的文件

文件 功能
main/key_nav/key_nav.c 按键导航管理器(从宝石角移植,适配两键方案)
main/key_nav/include/key_nav.h 导航上下文枚举 + 焦点状态

8.3 需要新增的函数

函数 文件 功能
lcd_fill_color(uint32_t color) main/lcd/lcd.c 直接写 GRAM 填充颜色(应援灯优化)
flashlight_switch_color() ui_ScreenSet.c 应援灯颜色切换(调用 lcd_fill_color

九、实施步骤

  1. 添加 iot_button 依赖idf_component.yml
  2. 重构 button 模块 → 替换 GPIO ISR 为 iot_button支持 6 种事件
  3. 新建 key_nav 模块 → 从宝石角移植调整为两键方案KEY2 双击=上一张BOOT 长按=删除)
  4. 移除触摸事件 → 各 Screen 文件中删除手势/点击回调和 lv_obj_add_event_cb 注册
  5. 适配 main.c → 删除旧的 boot_btn_handler(),添加 key_nav_init()
  6. 适配 sleep_mgr → 唤醒逻辑由 key_nav 统一处理
  7. 添加 lcd_fill_color() → 应援灯硬件级颜色切换
  8. 更新 CMakeLists.txt → 添加新源文件和头文件路径
  9. 编译测试idf.py build
  10. 逐功能验证 → 按第十章清单测试

十、测试验证清单

10.1 基础功能

  • BOOT 单击 / 双击 / 长按 事件均可正确触发
  • KEY2 单击 / 双击 / 长按 事件均可正确触发
  • 屏幕关闭时任意按键唤醒,不触发业务

10.2 Home 界面

  • BOOT 单击 → 进入 Set
  • KEY2 单击 → 进入 Img

10.3 Img 界面

  • KEY2 单击 → 下一张图片(自动跳过无效图片)
  • KEY2 双击 → 上一张图片(自动跳过无效图片)
  • BOOT 单击 → 返回 Home
  • BOOT 长按 → 显示删除确认
  • 删除确认中 BOOT 单击 → 删除成功
  • 删除确认中 KEY2 单击 → 取消

10.4 Set 界面

  • KEY2 单击 → 焦点循环切换(蓝色边框)
  • BOOT 单击(无焦点)→ 返回 Home
  • BOOT 单击(节能焦点)→ 切换节能模式
  • BOOT 单击(应援灯焦点)→ 进入应援灯
  • BOOT 单击(亮度焦点)→ 进入亮度调节

10.5 亮度调节

  • BOOT 单击 → 亮度 +10%(上限 100%
  • KEY2 单击 → 亮度 -10%(下限 10%
  • KEY2 长按 → 退出亮度调节模式

10.6 应援灯

  • BOOT 单击 → 颜色切换(红→绿→蓝→红)
  • KEY2 单击 → 退出回到 Set

10.7 低功耗

  • 10 秒无操作 → 屏幕熄灭
  • 任意按键 → 唤醒,显示熄屏前的界面
  • 唤醒时不闪烁(先渲染再恢复亮度)

10.8 稳定性

  • 快速连续按键不崩溃(防抖正常)
  • 连续切换 20+ 张图片无内存泄漏
  • 长时间运行 1 小时无崩溃