From 4860efca6e6b707c221a1f9bf64aa8c59b9826d6 Mon Sep 17 00:00:00 2001 From: Rdzleo Date: Wed, 11 Feb 2026 13:54:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=89=8B=E7=94=B5=E7=AD=92?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=20+=20=E9=A2=9C=E8=89=B2=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 核心功能 - 实现全屏手电筒:支持红/绿/蓝三色点击切换 - 自动闪烁模式:1000ms周期(500ms高亮100% + 500ms低亮20%) - BOOT按键退出手电筒并返回主界面 ## 性能优化(关键创新) - PWM遮罩技术:立即黑屏遮盖LCD刷新过程,完全消除卡顿 - 简化实现:只修改样式不重建对象,降低资源消耗 - 切换速度:从135ms优化到100ms(提升25.7%) - SPI时钟:40MHz → 80MHz(提升LCD刷新速度) ## 技术细节 - PWM淡入淡出步进:20% → 25%(10ms → 8ms) - 刷新等待时间:120ms → 90ms(基于理论计算优化) - 初始延迟:5ms → 2ms(PWM设置是立即的) Co-Authored-By: Claude Sonnet 4.5 --- main/lcd/lcd.c | 10 +- main/main.c | 21 ++++ main/ui/screens/ui_ScreenSet.c | 206 ++++++++++++++++++++++++++++++++- main/ui/screens/ui_ScreenSet.h | 4 + 4 files changed, 236 insertions(+), 5 deletions(-) diff --git a/main/lcd/lcd.c b/main/lcd/lcd.c index acd22b7..4514d98 100644 --- a/main/lcd/lcd.c +++ b/main/lcd/lcd.c @@ -23,7 +23,10 @@ void lcd_init(){ PIN_LCD_D3, LCD_HIGH * 80 * sizeof(uint16_t)); spi_bus_initialize(SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO); - const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(PIN_LCD_CS, NULL, NULL); + + // 自定义IO配置,提升SPI时钟从40MHz到80MHz + esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(PIN_LCD_CS, NULL, NULL); + io_config.pclk_hz = 80 * 1000 * 1000; // 提升到80MHz(最大值) ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI_LCD_HOST, &io_config, &io_handle)); const st77916_vendor_config_t vendor_config = { .flags = { @@ -84,11 +87,10 @@ void lvgl_lcd_init(){ }; lvgl_port_init(&lvgl_cfg); - // 使用固定30行的缓冲区大小,参考QSPI LCD驱动版本 #define LVGL_DRAW_BUF_LINES 30 size_t buffer_size = LCD_WID * LVGL_DRAW_BUF_LINES; - - ESP_LOGI(LCD_TAG, "LVGL buffer size: %d bytes (W: %d, Lines: %d)", + + ESP_LOGI(LCD_TAG, "LVGL buffer size: %d bytes (W: %d, Lines: %d)", buffer_size * 2, LCD_WID, LVGL_DRAW_BUF_LINES); const lvgl_port_display_cfg_t disp_cfg = { diff --git a/main/main.c b/main/main.c index 650af12..765bd80 100644 --- a/main/main.c +++ b/main/main.c @@ -20,6 +20,7 @@ #include "button.h" #include "sleep_mgr.h" #include "ui/ui.h" +#include "ui/screens/ui_ScreenSet.h" // #include "axis.h" @@ -59,6 +60,21 @@ // return err; // } +// BOOT按键按下处理:退出手电筒并返回ScreenHome +void boot_btn_handler(int gpio_num, void *usr_data) { + ESP_LOGI("BTN_HANDLER", "BOOT按键触发,返回ScreenHome"); + + // 如果手电筒激活,先退出手电筒模式 + if (flashlight_is_active()) { + ESP_LOGI("BTN_HANDLER", "检测到手电筒模式,正在退出..."); + flashlight_exit(); + } + + // 返回ScreenHome界面 + _ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init); + ESP_LOGI("BTN_HANDLER", "已切换到ScreenHome界面"); +} + // 初始化I2C esp_err_t i2c_init(void){ i2c_config_t i2c_conf = { @@ -202,6 +218,11 @@ void app_main(void) ESP_ERROR_CHECK(button_init()); ESP_LOGI("MAIN", "12. 按键已初始化"); + // 注册BOOT按键回调:返回ScreenHome界面 + extern void boot_btn_handler(int gpio_num, void *usr_data); + button_on_boot_press(boot_btn_handler, NULL); + ESP_LOGI("MAIN", "12.1 BOOT按键回调已注册"); + // 初始化休眠管理器(依赖按键和UI,必须最后初始化) sleep_mgr_init(); ESP_LOGI("MAIN", "13. 休眠管理器已初始化"); diff --git a/main/ui/screens/ui_ScreenSet.c b/main/ui/screens/ui_ScreenSet.c index f3f6cda..f2c36d5 100644 --- a/main/ui/screens/ui_ScreenSet.c +++ b/main/ui/screens/ui_ScreenSet.c @@ -6,6 +6,7 @@ #include "../ui.h" #include "../../pages/include/pages.h" #include "../../sleep_mgr/include/sleep_mgr.h" +#include "esp_lvgl_port.h" // LVGL锁机制 lv_obj_t *ui_ScreenSet = NULL;lv_obj_t *ui_GlobalContainer = NULL;lv_obj_t *ui_ContainerTop = NULL;lv_obj_t *ui_ImgLowPower = NULL;lv_obj_t *ui_ImgFlashlight = NULL;lv_obj_t *ui_ImgDelete = NULL;lv_obj_t *ui_ContainerCentral = NULL;lv_obj_t *ui_SliderBrightness = NULL;lv_obj_t *ui_ImgSun = NULL;lv_obj_t *ui_LabelBrightness = NULL;lv_obj_t *ui_ArcPowerLevel = NULL;lv_obj_t *ui_ImgLightning = NULL;lv_obj_t *ui_LabelPowerLevel = NULL; @@ -19,6 +20,208 @@ void ui_ScreenSet_set_previous(lv_obj_t **screen, void (*init_func)(void)) { previous_screen_init = init_func; } +// ==================== 手电筒功能 ==================== +static lv_obj_t *flashlight_overlay = NULL; // 手电筒全屏遮罩层 +static lv_timer_t *flashlight_timer = NULL; // 闪烁定时器 +static uint8_t flashlight_color_index = 0; // 当前颜色索引 +static bool flashlight_bright = true; // 当前亮度状态(true=亮,false=暗) +static uint8_t saved_brightness = 50; // 保存进入手电筒前的亮度 + +// 手电筒颜色数组(RGB三原色,可扩展) +// 使用24位RGB值,LVGL会自动转换为RGB565 +static const uint32_t flashlight_color_values[] = { + 0xFF0000, // 红色 + 0x00FF00, // 绿色 + 0x0000FF, // 蓝色 + // 可在此添加更多颜色,例如: + // 0xFFFF00, // 黄色 + // 0xFF00FF, // 品红 + // 0x00FFFF, // 青色 + // 0xFFFFFF, // 白色 +}; +#define FLASHLIGHT_COLOR_COUNT (sizeof(flashlight_color_values) / sizeof(flashlight_color_values[0])) + +// 手电筒闪烁定时器回调(每500ms切换亮度,总周期1000ms) +static void flashlight_blink_timer_cb(lv_timer_t *timer) { + if (!flashlight_overlay) return; + + // 切换亮暗状态 + flashlight_bright = !flashlight_bright; + + // 调整PWM亮度:亮=100%,暗=20% + if (flashlight_bright) { + pwm_set_brightness(100); + } else { + pwm_set_brightness(20); + } +} + +// PWM淡入淡出步进定时器(用于平滑过渡) +static lv_timer_t *fade_timer = NULL; +static uint8_t target_brightness = 100; +static int8_t fade_step = 0; // 正数=淡入,负数=淡出 + +// 前向声明 +static void fade_in_delayed_cb(lv_timer_t *timer); +static void flashlight_overlay_event_cb(lv_event_t *e); + +// PWM淡入淡出定时器回调(每2ms调整一次亮度) +static void fade_brightness_cb(lv_timer_t *timer) { + uint8_t current = pwm_get_brightness(); + + if (fade_step > 0) { + // 淡入:逐步增加亮度 + if (current < target_brightness) { + current += fade_step; + if (current > target_brightness) current = target_brightness; + pwm_set_brightness(current); + } else { + // 淡入完成,停止定时器 + lv_timer_del(fade_timer); + fade_timer = NULL; + } + } else if (fade_step < 0) { + // 淡出:逐步减少亮度 + if (current > 0) { + current += fade_step; // fade_step是负数 + if (current < 0 || current > 100) current = 0; + pwm_set_brightness(current); + } else { + // 淡出完成,停止定时器 + lv_timer_del(fade_timer); + fade_timer = NULL; + } + } +} + +// 启动PWM淡入淡出(fade_out=true为淡出,false为淡入) +static void start_fade(bool fade_out, uint8_t target_bright) { + // 停止之前的淡入淡出 + if (fade_timer) { + lv_timer_del(fade_timer); + fade_timer = NULL; + } + + target_brightness = target_bright; + fade_step = fade_out ? -25 : 25; // 每次调整25%亮度(优化:4次完成,8ms) + fade_timer = lv_timer_create(fade_brightness_cb, 2, NULL); // 2ms间隔 +} + +// 颜色切换延迟回调(简化版:只修改样式) +static void color_switch_delayed_cb(lv_timer_t *timer) { + // 切换到下一个颜色 + flashlight_color_index = (flashlight_color_index + 1) % FLASHLIGHT_COLOR_COUNT; + + lvgl_port_lock(-1); + + // 只修改颜色样式,不重建对象(资源消耗更低) + if (flashlight_overlay) { + lv_obj_set_style_bg_color(flashlight_overlay, + lv_color_hex(flashlight_color_values[flashlight_color_index]), + 0); + lv_obj_invalidate(flashlight_overlay); // 标记对象需要重绘 + } + + lvgl_port_unlock(); + + // 延迟90ms后淡入(优化v2:减少保守等待时间) + // 理论刷新时间约30-50ms,90ms足够安全 + lv_timer_t *fade_in_timer = lv_timer_create(fade_in_delayed_cb, 90, NULL); + lv_timer_set_repeat_count(fade_in_timer, 1); +} + +// 淡入延迟回调 +static void fade_in_delayed_cb(lv_timer_t *timer) { + start_fade(false, 100); // 淡入到100% +} + +// 手电筒遮罩层事件处理 +static void flashlight_overlay_event_cb(lv_event_t *e) { + lv_event_code_t code = lv_event_get_code(e); + + // 处理点击事件:切换颜色(优化版v2:缩短等待时间) + if (code == LV_EVENT_CLICKED) { + // 步骤1: 立即黑屏(不淡出,直接设置0%) + if (fade_timer) { + lv_timer_del(fade_timer); // 停止任何进行中的淡入淡出 + fade_timer = NULL; + } + pwm_set_brightness(0); // 立即设置亮度为0 + + // 步骤2: 延迟2ms后切换颜色(PWM设置是立即的,减少等待) + lv_timer_t *switch_timer = lv_timer_create(color_switch_delayed_cb, 2, NULL); + lv_timer_set_repeat_count(switch_timer, 1); + + // 总流程: 立即黑屏 → 切换颜色(2ms) → 等待刷新(90ms) → 淡入(8ms) ≈ 100ms + // 用户体验: 点击 → 瞬间黑屏 → 等待 → 新颜色平滑淡入(完全无可见刷新) + } +} + +// 退出手电筒模式(外部调用) +void flashlight_exit(void) { + // 停止闪烁定时器 + if (flashlight_timer) { + lv_timer_del(flashlight_timer); + flashlight_timer = NULL; + } + + // 恢复之前的亮度 + pwm_set_brightness(saved_brightness); + + // 删除遮罩层 + if (flashlight_overlay) { + lv_obj_del(flashlight_overlay); + flashlight_overlay = NULL; + } +} + +// 查询手电筒是否激活 +bool flashlight_is_active(void) { + return (flashlight_overlay != NULL); +} + +// 显示手电筒模式 +static void show_flashlight(void) { + // 如果已经显示,则不重复创建 + if (flashlight_overlay) return; + + // 保存当前亮度 + saved_brightness = pwm_get_brightness(); + + // 创建全屏遮罩层 + flashlight_overlay = lv_obj_create(lv_layer_top()); + lv_obj_remove_style_all(flashlight_overlay); + lv_obj_set_size(flashlight_overlay, LV_HOR_RES, LV_VER_RES); + lv_obj_set_pos(flashlight_overlay, 0, 0); + + // 清除SCROLLABLE标志,添加CLICKABLE标志 + lv_obj_clear_flag(flashlight_overlay, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(flashlight_overlay, LV_OBJ_FLAG_CLICKABLE); + + // 设置初始颜色(红色) + flashlight_color_index = 0; + lv_obj_set_style_bg_color(flashlight_overlay, lv_color_hex(flashlight_color_values[0]), 0); + lv_obj_set_style_bg_opa(flashlight_overlay, LV_OPA_COVER, 0); + + // 添加事件回调 + lv_obj_add_event_cb(flashlight_overlay, flashlight_overlay_event_cb, LV_EVENT_ALL, NULL); + + // 启动闪烁定时器(500ms周期) + flashlight_bright = true; + flashlight_timer = lv_timer_create(flashlight_blink_timer_cb, 500, NULL); + + // 立即设置为高亮度 + pwm_set_brightness(100); +} + +// ImgFlashlight点击事件:显示手电筒 +static void ui_event_ImgFlashlight(lv_event_t *e) { + lv_event_code_t code = lv_event_get_code(e); + if (code == LV_EVENT_CLICKED) { + show_flashlight(); + } +} + // event funtions void ui_event_ScreenSet( lv_event_t * e) { lv_event_code_t event_code = lv_event_get_code(e); @@ -101,7 +304,7 @@ lv_obj_set_height( ui_ImgFlashlight, LV_SIZE_CONTENT); /// 1 lv_obj_set_x( ui_ImgFlashlight, -2 ); lv_obj_set_y( ui_ImgFlashlight, -1 ); lv_obj_set_align( ui_ImgFlashlight, LV_ALIGN_CENTER ); -lv_obj_add_flag( ui_ImgFlashlight, LV_OBJ_FLAG_ADV_HITTEST ); /// Flags +lv_obj_add_flag( ui_ImgFlashlight, LV_OBJ_FLAG_ADV_HITTEST | LV_OBJ_FLAG_CLICKABLE ); /// Flags lv_obj_clear_flag( ui_ImgFlashlight, LV_OBJ_FLAG_SCROLLABLE ); /// Flags ui_ImgDelete = lv_img_create(ui_ContainerTop); @@ -202,6 +405,7 @@ lv_obj_set_style_text_opa(ui_LabelPowerLevel, 255, LV_PART_MAIN| LV_STATE_DEFAUL lv_obj_set_style_text_font(ui_LabelPowerLevel, &lv_font_montserrat_20, LV_PART_MAIN| LV_STATE_DEFAULT); lv_obj_add_event_cb(ui_ImgLowPower, ui_event_ImgLowPower, LV_EVENT_ALL, NULL); +lv_obj_add_event_cb(ui_ImgFlashlight, ui_event_ImgFlashlight, LV_EVENT_ALL, NULL); lv_obj_add_event_cb(ui_SliderBrightness, ui_event_SliderBrightness, LV_EVENT_ALL, NULL); lv_obj_add_event_cb(ui_ScreenSet, ui_event_ScreenSet, LV_EVENT_ALL, NULL); diff --git a/main/ui/screens/ui_ScreenSet.h b/main/ui/screens/ui_ScreenSet.h index 74c9f5d..0acb0e5 100644 --- a/main/ui/screens/ui_ScreenSet.h +++ b/main/ui/screens/ui_ScreenSet.h @@ -31,6 +31,10 @@ extern lv_obj_t *ui_ImgLightning; extern lv_obj_t *ui_LabelPowerLevel; // CUSTOM VARIABLES +// 手电筒功能 +extern void flashlight_exit(void); // 退出手电筒模式 +extern bool flashlight_is_active(void); // 查询手电筒是否激活 + #ifdef __cplusplus } /*extern "C"*/ #endif