Dzbj_C3_Key/docs/touch-to-button-migration.md
Rdzleo f9dc7d4861 feat: 触屏版迁移到按键版,两键实现全部交互功能
1. 按键驱动重构:GPIO中断+手动去抖 → iot_button组件(单击/双击/长按)
2. 新增key_nav按键导航管理器:上下文状态机 + Set界面焦点蓝色边框高亮
3. 移除所有触摸手势/点击事件(ScreenHome/ScreenImg/ScreenSet)
4. 应援灯颜色切换优化:DISPOFF→直接写GRAM→DISPON,消除分band刷新
5. 亮度调节按键化:BOOT +10% / KEY -10% / KEY长按退出
6. 休眠管理适配:按键唤醒统一由key_nav处理
7. 新增迁移总结文档 docs/touch-to-button-migration.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 14:57:14 +08:00

13 KiB
Raw Blame History

触屏版 → 按键版迁移总结

适用于 ESP32-C3 / ESP32-S3 电子吧唧项目,从触摸屏交互迁移到两键物理按键交互。

一、迁移背景

取消触摸芯片以降低硬件成本,所有用户交互通过两个物理按键实现:

  • BOOT按键GPIO9确认/执行/返回
  • KEY按键GPIO8导航/切换

二、变更文件清单

文件 变更类型 说明
main/button/button.c 重构 GPIO中断+手动去抖 → iot_button 组件
main/button/include/button.h 重构 新增事件类型枚举,回调签名变更
main/key_nav/key_nav.c 新增 按键导航管理器(核心模块)
main/key_nav/include/key_nav.h 新增 导航上下文和焦点状态枚举
main/main.c 修改 移除 boot_btn_handler集成 key_nav_init
main/ui/screens/ui_ScreenHome.c 修改 移除手势事件,界面导航交由 key_nav
main/ui/screens/ui_ScreenHome.h 修改 移除 ui_event_ScreenHome 声明
main/ui/screens/ui_ScreenImg.c 修改 移除手势/点击事件,保留 SCREEN_LOADED 事件
main/ui/screens/ui_ScreenSet.c 修改 移除手势/滑块/节能/删除点击事件,新增按键版颜色切换
main/ui/screens/ui_ScreenSet.h 修改 新增 flashlight_switch_color/restart_blink 声明
main/lcd/lcd.c 修改 新增 lcd_fill_color() 直接写 GRAM
main/lcd/include/lcd.h 修改 新增 lcd_fill_color() 声明
main/sleep_mgr/sleep_mgr.c 修改 移除按键回调注册,交由 key_nav 统一处理
main/CMakeLists.txt 修改 添加 key_nav 源文件和头文件路径
main/idf_component.yml 修改 添加 button: ">=3.2.0" 依赖
sdkconfig 修改 新增 IoT Button 配置项

三、核心架构变更

3.1 按键驱动重构button模块

触屏版:自定义 GPIO 中断 + FreeRTOS 队列 + 手动去抖200ms时间戳判断

// 旧方案
gpio_isr_handler_add(PIN_BTN_BOOT, gpio_isr_handler, (void *)PIN_BTN_BOOT);
xTaskCreate(btn_task, "btn_task", 3072, NULL, 5, NULL);  // 队列消费任务

按键版ESP-IDF 官方 iot_button 组件,原生支持单击/双击/长按

// 新方案
button_config_t btn_cfg = {
    .long_press_time = 2000,    // 长按2秒
    .short_press_time = 0,      // 使用默认180ms双击检测窗口
};
button_gpio_config_t gpio_cfg = {
    .gpio_num = PIN_BTN_BOOT,
    .active_level = 0,          // 低电平有效
};
iot_button_new_gpio_device(&btn_cfg, &gpio_cfg, &boot_btn_handle);
iot_button_register_cb(boot_btn_handle, BUTTON_SINGLE_CLICK, NULL, boot_click_cb, NULL);
iot_button_register_cb(boot_btn_handle, BUTTON_DOUBLE_CLICK, NULL, boot_double_click_cb, NULL);
iot_button_register_cb(boot_btn_handle, BUTTON_LONG_PRESS_START, NULL, boot_long_press_cb, NULL);

回调签名变更

// 旧版(只有按下事件)
typedef void (*btn_event_cb_t)(int gpio_num, void *usr_data);

// 新版(区分单击/双击/长按)
typedef enum {
    BTN_EVT_CLICK,
    BTN_EVT_DOUBLE_CLICK,
    BTN_EVT_LONG_PRESS,
} btn_event_type_t;
typedef void (*btn_event_cb_t)(int gpio_num, btn_event_type_t event, void *usr_data);

注册接口变更

// 旧版
button_on_boot_press(cb, usr_data);
button_on_key2_press(cb, usr_data);

// 新版
button_on_boot_event(cb, usr_data);
button_on_key2_event(cb, usr_data);

idf_component.yml 依赖

dependencies:
  button: ">=3.2.0"

3.2 新增按键导航管理器key_nav模块

这是本次迁移的核心新增模块,集中管理所有按键行为和界面导航逻辑。

设计思想

  • 上下文状态机:根据当前界面/模式决定按键行为
  • 焦点管理系统Set界面的图标选中和蓝色边框高亮
  • 任务派发模式iot_button 回调在 esp_timer 上下文中执行(不能 vTaskDelay通过 xTaskCreate 派发到独立任务

上下文枚举

typedef enum {
    NAV_CTX_HOME,           // Home界面
    NAV_CTX_IMG,            // Img界面正常浏览
    NAV_CTX_IMG_DELETE,     // Img界面删除模式
    NAV_CTX_SET,            // Set界面焦点导航
    NAV_CTX_SET_BRIGHTNESS, // Set界面亮度调节模式
    NAV_CTX_FLASHLIGHT,     // 应援灯全屏模式
} nav_context_t;

Set界面焦点枚举

typedef enum {
    SET_FOCUS_NONE = -1,    // 无选中
    SET_FOCUS_LOW_POWER = 0,// 节能
    SET_FOCUS_FLASHLIGHT,   // 应援灯
    SET_FOCUS_DELETE,       // 删除
    SET_FOCUS_BRIGHTNESS,   // 亮度
    SET_FOCUS_COUNT,        // 焦点总数(用于循环)
} set_focus_item_t;

任务派发模式(关键模式,必须遵循):

// iot_button 回调在 esp_timer 上下文中,不能 vTaskDelay
// 必须派发到独立 FreeRTOS 任务执行需要延时的操作
static void dispatch_task(TaskFunction_t func, const char *name)
{
    xTaskCreate(func, name, 3072, NULL, 5, NULL);
}

static void boot_event_handler(int gpio_num, btn_event_type_t event, void *usr_data)
{
    if (event == BTN_EVT_CLICK) {
        dispatch_task(nav_task_home_boot_click, "h_boot");  // 派发到独立任务
    }
}

焦点高亮实现

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

static void set_focus_border(int index) {
    lv_obj_set_style_border_color(obj, lv_color_hex(FOCUS_BORDER_COLOR), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_width(obj, FOCUS_BORDER_WIDTH, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_opa(obj, LV_OPA_COVER, LV_PART_MAIN | LV_STATE_DEFAULT);
}

3.3 各界面触摸事件移除

ScreenHome

  • 移除 ui_event_ScreenHome() 函数下滑→Set、左滑/右滑→Img 手势)
  • 移除 lv_obj_add_event_cb(ui_ScreenHome, ui_event_ScreenHome, LV_EVENT_ALL, NULL)
  • 移除 #include "ui_ScreenSet.h" 依赖

ScreenImg

  • 移除 ui_event_ImageDel()(删除按钮点击)
  • 移除 ui_event_ImageReturn()(返回按钮点击)
  • 移除所有手势事件上滑→Home、下滑→Set、左滑→下一张、右滑→上一张
  • 保留 LV_EVENT_SCREEN_LOADED 事件(首次加载图片初始化)
  • 保留 should_show_container 标志逻辑从Set删除图标进入时显示
  • 移除 lv_obj_add_event_cb 对 ImageDel 和 ImageReturn 的注册

ScreenSet

  • 移除 ui_event_ScreenSet()(上滑返回手势)
  • 移除 ui_event_SliderBrightness()(滑块值变更事件)
  • 移除 ui_event_ImgLowPower()(节能图标点击事件)
  • 移除 ui_event_ImgDelete()(删除图标点击事件)
  • 保留 ui_event_ImgFlashlight()LV_EVENT_CLICKED(由 key_nav 通过 lv_event_send 触发)
  • 移除 lv_obj_add_event_cb 对节能、删除、滑块、手势的注册

四、新增功能和优化

4.1 按键导航系统

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

4.2 Set界面焦点蓝色边框高亮

  • 用 LVGL border 样式实现焦点指示Material Blue #2196F3
  • 焦点切换时自动清除旧边框、设置新边框
  • 离开界面或返回 Home 自动清除所有边框
  • 进入删除模式时ImageDel 控件也显示蓝色边框提示

4.3 应援灯颜色切换优化

触屏版直接修改 LVGL 样式LVGL 30行分band渲染导致从上到下的视觉刷新感。

按键版优化为 LCD 硬件级切换

void flashlight_switch_color(void) {
    lcd_disp_on_off(false);    // DISPOFF 0x28LCD停止输出
    lcd_fill_color(new_color); // 直接写GRAM绕过LVGL~35ms同步阻塞
    lcd_disp_on_off(true);     // DISPON 0x29LCD瞬间恢复GRAM已完整
}

新增 lcd_fill_color() 函数:

// 绕过 LVGL 分band渲染直接用 esp_lcd_panel_draw_bitmap 写 GRAM
// 40行为单位DMA 内存,同步阻塞写入
void lcd_fill_color(uint32_t color_rgb) {
    lv_color_t c = lv_color_hex(color_rgb);  // RGB888→RGB565含byte swap
    uint16_t *buf = heap_caps_malloc(LCD_WID * 40 * sizeof(uint16_t), MALLOC_CAP_DMA);
    for (int y = 0; y < LCD_HIGH; y += 40) {
        esp_lcd_panel_draw_bitmap(panel_handle, 0, y, LCD_WID, y + lines, buf);
    }
    heap_caps_free(buf);
}

4.4 亮度调节按键化

触屏版通过滑块拖动调节。按键版改为:

  • BOOT单击 +10%KEY单击 -10%
  • 范围 10%~100%
  • 同步更新滑块控件值和百分比标签
  • KEY长按2秒退出调节模式

4.5 休眠管理适配

  • 移除 sleep_mgr 中的按键回调注册(旧版在 sleep_mgr_init 中注册 KEY2 回调)
  • 按键唤醒逻辑统一由 key_nav 处理:
    if (sleep_mgr_is_screen_off()) {
        sleep_mgr_notify_activity();  // 唤醒屏幕
        return;  // 仅唤醒,不触发业务
    }
    sleep_mgr_notify_activity();  // 重置休眠计时器
    

4.6 main.c 简化

  • 移除 ~50 行的 boot_btn_handler() 函数(手电筒退出+界面切换+亮度恢复逻辑)
  • 所有按键处理逻辑集中到 key_nav 模块
  • 初始化顺序调整:button_init()sleep_mgr_init()key_nav_init()

五、关键技术要点(迁移到 S3 时注意)

5.1 iot_button 状态机特性

  • BUTTON_SINGLE_CLICKBUTTON_DOUBLE_CLICK 互斥,不会同时触发
  • 按键释放后等待 short_press_time默认180ms决定是单击还是双击
  • short_press_time 控制双击检测窗口,不是去抖时间(去抖由 CONFIG_BUTTON_DEBOUNCE_TICKS 控制默认10ms
  • 设为 0 表示使用默认值 180ms

5.2 iot_button 回调上下文

  • 回调在 esp_timer 任务中执行
  • 禁止在回调中调用 vTaskDelay(),否则阻塞 esp_timer 任务,导致 LVGL tick 停止
  • 解决方案:通过 xTaskCreate 派发到独立 FreeRTOS 任务

5.3 LVGL 操作线程安全

  • 修改 UI 必须加锁:lvgl_port_lock(timeout_ms) / lvgl_port_unlock()
  • 建议超时 50-100ms避免死锁
  • 永久等待用 lvgl_port_lock(-1)

5.4 手电筒退出时序

亮度→0 → flashlight_exit() → vTaskDelay(80ms) → 切换界面 → vTaskDelay(150ms) → 恢复亮度
  • 80ms 等待 overlay 删除LVGL 需要 15+ 刷新周期)
  • 150ms 等待新界面渲染完成
  • 先切换界面再恢复亮度,避免用户看到旧界面

5.5 GPIO 引脚适配

S3 项目需要根据实际硬件修改 button.h 中的 GPIO 定义:

#define PIN_BTN_BOOT    9   // 根据S3硬件原理图修改
#define PIN_BTN_KEY2    8   // 根据S3硬件原理图修改

5.6 sdkconfig 配置

新增 IoT Button 相关配置menuconfig 或直接修改 sdkconfig

CONFIG_BUTTON_PERIOD_TIME_MS=5
CONFIG_BUTTON_DEBOUNCE_TICKS=2
CONFIG_BUTTON_SHORT_PRESS_TIME_MS=180
CONFIG_BUTTON_LONG_PRESS_TIME_MS=1500

六、迁移步骤(适用于 ESP32-S3 项目)

  1. 添加依赖idf_component.yml 添加 button: ">=3.2.0"
  2. 重构 button 模块:替换 GPIO 中断为 iot_button修改回调签名
  3. 新建 key_nav 模块:复制 main/key_nav/ 目录,根据 S3 项目的界面结构调整上下文枚举和任务函数
  4. 移除触摸事件:各 Screen 文件中移除手势事件函数和 lv_obj_add_event_cb 注册
  5. 适配 main.c:移除旧的按键处理函数,添加 key_nav_init() 调用
  6. 适配 sleep_mgr:移除按键回调注册,由 key_nav 统一处理唤醒
  7. 添加 lcd_fill_color()(如需应援灯颜色切换优化)
  8. 更新 CMakeLists.txt:添加 key_nav 源文件和头文件路径
  9. 适配 GPIO 引脚:根据 S3 硬件原理图修改按键 GPIO 定义
  10. 编译测试idf.py build 验证,逐个功能测试

七、资源变化

指标 触屏版 按键版 变化
button 任务栈 3072Bbtn_task 队列消费) 0iot_button 内部管理) 节省 3KB
key_nav 任务栈 3072B每次按键临时创建执行完销毁 临时占用
触摸事件代码 ~200 行(手势+点击回调) 0 移除
key_nav 代码 0 ~530 行 新增
iot_button 组件 ~20KB Flash 新增依赖