diff --git a/.gitignore b/.gitignore index 81cf354..f7a0070 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ sdkconfig.old 07-uniapp最新日志.txt /Dzbj_ESP32_S3 /esp-brookesia-master + +# 忽略文档PDF文件 +*.pdf diff --git a/main/dzbj/lcd.c b/main/dzbj/lcd.c index 7dc78a7..46c606f 100644 --- a/main/dzbj/lcd.c +++ b/main/dzbj/lcd.c @@ -255,6 +255,12 @@ void lcd_init(){ esp_lcd_panel_reset(panel_handle); esp_lcd_panel_init(panel_handle); + // 启用 TE 内部同步信号(TEON 0x35, 参数 0x00 = V-Blank only) + // 即使 TE 引脚未接 MCU,启用后可通过 TESLRD(0x45) 读取扫描行位置 + uint8_t te_param = 0x00; + esp_lcd_panel_io_tx_param(io_handle, 0x35, &te_param, 1); + ESP_LOGI(LCD_TAG, "TEON enabled (V-Blank mode)"); + // 清空LCD GRAM,避免显示上次关机时的残留画面 size_t clear_buffer_size = LCD_WID * 40; uint16_t *clear_buffer = heap_caps_malloc(clear_buffer_size * sizeof(uint16_t), MALLOC_CAP_DMA); @@ -380,6 +386,52 @@ void get_touch(uint16_t* touchx, uint16_t* touchy){ } #endif // DZBJ_ENABLE_TOUCH +// 直接 DMA 填充全屏纯色(绕过 LVGL,使用调用者预分配的缓冲区) +void lcd_fill_color_with_buf(uint16_t *buf, int strip_h) { + if (panel_handle == NULL || buf == NULL || strip_h <= 0) return; + + for (int y = 0; y < LCD_HIGH; y += strip_h) { + int lines = (y + strip_h > LCD_HIGH) ? (LCD_HIGH - y) : strip_h; + esp_lcd_panel_draw_bitmap(panel_handle, 0, y, LCD_WID, y + lines, buf); + } +} + +// 读取当前扫描行号(TESLRD 0x45) +// 返回 ESP_OK 表示读取成功,scanline 存储扫描行号 N[11:0] +// 扫描行范围:0 ~ (VSYNC+VBP+VACT+VFP-1),其中 0~359 为活跃显示区 +esp_err_t lcd_read_scanline(uint16_t *scanline) { + if (!io_handle || !scanline) return ESP_ERR_INVALID_ARG; + + uint8_t data[2] = {0}; + esp_err_t ret = esp_lcd_panel_io_rx_param(io_handle, 0x45, data, 2); + if (ret == ESP_OK) { + // N[11:8] 在 data[0] 低4位,N[7:0] 在 data[1] + *scanline = ((uint16_t)(data[0] & 0x0F) << 8) | data[1]; + } + return ret; +} + +// 等待 VBLANK 消隐期(带超时保护) +// 轮询扫描行号,当 scanline >= LCD_HIGH(360) 时表示进入消隐区 +// timeout_us: 最大等待时间(微秒),建议 17000(约1帧) +// 返回 true = 成功等到 VBLANK,false = 超时或读取失败 +bool lcd_wait_vsync_timeout(uint32_t timeout_us) { + uint16_t scanline = 0; + int64_t start = esp_timer_get_time(); + + do { + if (lcd_read_scanline(&scanline) != ESP_OK) { + return false; + } + if (scanline >= LCD_HIGH) { + return true; + } + esp_rom_delay_us(50); + } while ((esp_timer_get_time() - start) < (int64_t)timeout_us); + + return false; +} + // 清空LCD GRAM为黑色(用于低功耗熄屏前,避免残影) void lcd_clear_screen_black(void) { if (panel_handle == NULL) { diff --git a/main/dzbj/lcd.h b/main/dzbj/lcd.h index acef092..4d84b2d 100644 --- a/main/dzbj/lcd.h +++ b/main/dzbj/lcd.h @@ -10,6 +10,19 @@ void lcd_init(void); void lvgl_lcd_init(void); void lcd_clear_screen_black(void); +// 直接 DMA 填充全屏纯色(绕过 LVGL,约 26ms) +// buf: 调用者预分配的 DMA 缓冲区,大小 >= LCD_WID * strip_h * 2 字节 +// buf 中的数据必须已填充好目标颜色的 RGB565 值 +// strip_h: 每条带高度(推荐 40) +void lcd_fill_color_with_buf(uint16_t *buf, int strip_h); + +// VSYNC 同步(防撕裂) +// 读取当前 LCD 扫描行号,成功返回 ESP_OK +esp_err_t lcd_read_scanline(uint16_t *scanline); +// 等待进入 VBLANK 消隐期,timeout_us 建议 17000(1帧) +// 返回 true=成功等到 VBLANK,false=超时或读取失败 +bool lcd_wait_vsync_timeout(uint32_t timeout_us); + // I2C 总线共享:传入主项目的 I2C 总线句柄,供触摸控制器使用 void lcd_set_i2c_bus(i2c_master_bus_handle_t bus); diff --git a/main/ui/screens/ui_ScreenSet.c b/main/ui/screens/ui_ScreenSet.c index 1a21385..14679fc 100644 --- a/main/ui/screens/ui_ScreenSet.c +++ b/main/ui/screens/ui_ScreenSet.c @@ -8,6 +8,8 @@ #include "../../pages/include/pages.h" #include "../../sleep_mgr/include/sleep_mgr.h" #include "esp_lvgl_port.h" // LVGL锁机制 +#include "esp_timer.h" +#include "esp_log.h" #include "dzbj_button.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -24,193 +26,255 @@ 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; // 手电筒全屏遮罩层 +// ==================== 应援灯功能 ==================== +// 行为:进入时高亮常亮 → 点击屏幕切换闪烁/常亮 → 下滑切换颜色 +// 颜色切换使用直接 DMA 填充 GRAM(绕过 LVGL),约 27ms 瞬间完成 + +#include "lcd.h" +#include "esp_heap_caps.h" + +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; // 保存进入手电筒前的亮度 +static bool flashlight_blinking = false; // 是否处于闪烁状态 +static uint8_t saved_brightness = 50; // 进入前保存的亮度 -// 手电筒颜色数组(RGB三原色,可扩展) -// 使用24位RGB值,LVGL会自动转换为RGB565 +// DMA 缓冲区(应援灯生命周期内常驻,避免反复 malloc/free) +// 条带越大,条数越少,总传输时间越短,撕裂概率越低 +// 72行 × 5条 = 360行,~15ms 完成(< 1帧 16.67ms),基本消除撕裂 +#define FL_STRIP_H 72 // 每条带高度 +static uint16_t *fl_dma_buf = NULL; // DMA 缓冲区指针 +static size_t fl_dma_buf_size = 0; // 缓冲区像素数 + +// 颜色数组(RGB888,可扩展) static const uint32_t flashlight_color_values[] = { 0xFF0000, // 红色 0x00FF00, // 绿色 0x0000FF, // 蓝色 - // 可在此添加更多颜色,例如: - // 0xFFFF00, // 黄色 - // 0xFF00FF, // 品红 - // 0x00FFFF, // 青色 - // 0xFFFFFF, // 白色 + 0xFFFF00, // 黄色 + 0xFF00FF, // 品红 + 0x00FFFF, // 青色 + 0xFFFFFF, // 白色 }; #define FLASHLIGHT_COLOR_COUNT (sizeof(flashlight_color_values) / sizeof(flashlight_color_values[0])) -// 手电筒闪烁定时器回调(每500ms切换亮度,总周期1000ms) +// RGB888 转 RGB565(大端字节序,匹配 LCD GRAM 格式) +static uint16_t rgb888_to_rgb565(uint32_t rgb888) { + uint8_t r = (rgb888 >> 16) & 0xFF; + uint8_t g = (rgb888 >> 8) & 0xFF; + uint8_t b = rgb888 & 0xFF; + uint16_t rgb565 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + // ESP32 SPI LCD 使用大端字节序 + return (rgb565 >> 8) | (rgb565 << 8); +} + +// 用指定颜色填充 DMA 缓冲区 +static void fl_fill_buf_color(uint32_t rgb888) { + if (!fl_dma_buf) return; + uint16_t color565 = rgb888_to_rgb565(rgb888); + for (size_t i = 0; i < fl_dma_buf_size; i++) { + fl_dma_buf[i] = color565; + } +} + +// 分配 DMA 缓冲区(应援灯进入时调用) +static bool fl_alloc_dma_buf(void) { + if (fl_dma_buf) return true; // 已分配 + fl_dma_buf_size = LCD_WID * FL_STRIP_H; + fl_dma_buf = heap_caps_malloc(fl_dma_buf_size * sizeof(uint16_t), MALLOC_CAP_DMA); + if (!fl_dma_buf) { + fl_dma_buf_size = 0; + return false; + } + return true; +} + +// 释放 DMA 缓冲区(应援灯退出时调用) +static void fl_free_dma_buf(void) { + if (fl_dma_buf) { + heap_caps_free(fl_dma_buf); + fl_dma_buf = NULL; + fl_dma_buf_size = 0; + } +} + +// ---- LVGL flush 拦截:应援灯模式下阻止 LVGL 向 LCD 写入任何数据 ---- +// 仅暂停 refr timer 不够可靠(其他模块可能恢复它), +// 替换 flush 回调为空操作是最彻底的方案。 +static void (*fl_original_flush_cb)(struct _lv_disp_drv_t *, const lv_area_t *, lv_color_t *) = NULL; + +// 空 flush 回调:不写入 LCD,仅通知 LVGL 完成(防止 LVGL 挂起) +static void fl_dummy_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) { + lv_disp_flush_ready(drv); +} + +// 禁用 LVGL LCD 输出(进入应援灯时调用) +static void fl_disable_lvgl_flush(void) { + lv_disp_t *disp = lv_disp_get_default(); + if (disp && disp->driver) { + fl_original_flush_cb = disp->driver->flush_cb; + disp->driver->flush_cb = fl_dummy_flush_cb; + } +} + +// 恢复 LVGL LCD 输出 + 强制全屏重绘(退出应援灯时调用) +static void fl_enable_lvgl_flush(void) { + lv_disp_t *disp = lv_disp_get_default(); + if (disp && disp->driver && fl_original_flush_cb) { + disp->driver->flush_cb = fl_original_flush_cb; + fl_original_flush_cb = NULL; + // GRAM 已被 DMA 覆写,强制 LVGL 全屏重绘恢复正常界面 + lv_obj_invalidate(lv_scr_act()); + } +} + +// 直接 DMA 切换颜色(独占 GRAM,LVGL flush 已禁用,无竞争) +// QSPI 模式下 TESLRD 读取无效(始终返回0xFFFF),无法软件同步 VSYNC +// 采用 PWM 微降方案:DMA 期间亮度降至极低,人眼不可见撕裂,完成后恢复 +static void fl_switch_color_direct(uint32_t rgb888) { + fl_fill_buf_color(rgb888); + + // 持有 LVGL 锁,确保 DMA 期间不会与 indev 等其他 SPI 操作冲突 + if (lvgl_port_lock(100)) { + uint8_t restore_brightness = flashlight_bright ? 100 : 20; + + // 完全关闭背光(PWM=0),DMA期间屏幕全黑,撕裂不可见 + pwm_set_brightness(0); + // 等待1个PWM周期(200μs@5kHz)确保背光完全熄灭 + esp_rom_delay_us(200); + + lcd_fill_color_with_buf(fl_dma_buf, FL_STRIP_H); + + // DMA 完成后恢复亮度,显示新颜色 + pwm_set_brightness(restore_brightness); + + lvgl_port_unlock(); + } +} + +// 闪烁定时器回调(每 500ms 切换亮暗) 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_set_brightness(flashlight_bright ? 100 : 20); } -// PWM淡入淡出步进定时器(用于平滑过渡) -static lv_timer_t *fade_timer = NULL; -static uint8_t target_brightness = 100; -static int8_t fade_step = 0; // 正数=淡入,负数=淡出 +// 手动滑动检测(layer_top 上 LVGL 手势检测不可靠,改用坐标位移判定) +// 仅影响 overlay 对象,其他界面的 LV_EVENT_GESTURE 不受影响 +static lv_coord_t fl_press_y = 0; // 按下时的 Y 坐标 +static bool fl_press_valid = false; // 是否有有效的按下记录 +#define FL_SWIPE_THRESHOLD 50 // 下滑判定阈值(像素) -// 前向声明 -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; +// 切换颜色(下滑时调用) +// 无需 PWM=0 黑屏:纯色→纯色直接 DMA 覆写,~15ms 完成 +static void fl_do_switch_color(void) { + bool was_blinking = flashlight_blinking; + if (flashlight_timer) { + lv_timer_pause(flashlight_timer); } - 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; + fl_switch_color_direct(flashlight_color_values[flashlight_color_index]); + flashlight_bright = true; - 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); // 标记对象需要重绘 + if (was_blinking && flashlight_timer) { + lv_timer_resume(flashlight_timer); } - - 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; +// 切换闪烁状态(点击时调用) +static void fl_do_toggle_blink(void) { + flashlight_blinking = !flashlight_blinking; + if (flashlight_blinking) { + flashlight_bright = true; + pwm_set_brightness(100); + if (!flashlight_timer) { + flashlight_timer = lv_timer_create(flashlight_blink_timer_cb, 500, 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) { - // 优化退出时序,避免闪烁: - // 1. 先将亮度降到0(黑屏) - pwm_set_brightness(0); - - // 2. 在LVGL锁保护下删除所有对象和定时器 - if (lvgl_port_lock(100)) { - // 停止闪烁定时器 + } else { if (flashlight_timer) { lv_timer_del(flashlight_timer); flashlight_timer = NULL; } + flashlight_bright = true; + pwm_set_brightness(100); + } +} - // 停止淡入淡出定时器 - if (fade_timer) { - lv_timer_del(fade_timer); - fade_timer = NULL; +// 遮罩层事件处理:按下记录坐标,释放时判定点击 vs 下滑 +static void flashlight_overlay_event_cb(lv_event_t *e) { + lv_event_code_t code = lv_event_get_code(e); + + if (code == LV_EVENT_PRESSED) { + lv_indev_t *indev = lv_indev_get_act(); + lv_point_t point; + lv_indev_get_point(indev, &point); + fl_press_y = point.y; + fl_press_valid = true; + } else if (code == LV_EVENT_RELEASED) { + if (!fl_press_valid) return; + fl_press_valid = false; + + lv_indev_t *indev = lv_indev_get_act(); + lv_point_t point; + lv_indev_get_point(indev, &point); + lv_coord_t dy = point.y - fl_press_y; + + if (dy > FL_SWIPE_THRESHOLD) { + // 下滑:切换颜色 + fl_do_switch_color(); + } else if (dy > -FL_SWIPE_THRESHOLD) { + // 位移小:视为点击,切换闪烁/常亮 + fl_do_toggle_blink(); } + // 上滑(dy < -threshold):忽略 + } +} - // 删除遮罩层(此时屏幕已黑,看不到overlay删除的过程) +// 退出应援灯模式(外部调用,不恢复亮度) +void flashlight_exit(void) { + pwm_set_brightness(0); + + if (lvgl_port_lock(100)) { + if (flashlight_timer) { + lv_timer_del(flashlight_timer); + flashlight_timer = NULL; + } if (flashlight_overlay) { lv_obj_del(flashlight_overlay); flashlight_overlay = NULL; } - lvgl_port_unlock(); } - // 注意:不在这里恢复亮度,避免看到ScreenSet和Home界面混合显示 - // 亮度恢复由调用者(boot_btn_handler)在界面切换完成后执行 + flashlight_blinking = false; + fl_free_dma_buf(); + + // 恢复 LVGL 显示刷新(退出应援灯后 LVGL 重新接管屏幕渲染) + fl_enable_lvgl_flush(); } -// 查询手电筒是否激活 +// 查询应援灯是否激活 bool flashlight_is_active(void) { return (flashlight_overlay != NULL); } -// 获取手电筒模式前保存的亮度值 +// 获取进入应援灯前保存的亮度值 uint8_t flashlight_get_saved_brightness(void) { return saved_brightness; } -// 显示手电筒模式 +// 进入应援灯模式 static void show_flashlight(void) { - // 如果已经显示,则不重复创建 if (flashlight_overlay) return; - // 保存当前亮度 + // 分配 DMA 缓冲区 + if (!fl_alloc_dma_buf()) { + ESP_LOGE("Flashlight", "DMA缓冲区分配失败"); + return; + } + saved_brightness = pwm_get_brightness(); // 创建全屏遮罩层 @@ -219,8 +283,8 @@ static void show_flashlight(void) { 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); + // 启用滚动手势识别 + 点击 + // 只需 CLICKABLE 接收触摸事件,手动检测滑动不依赖 SCROLLABLE lv_obj_add_flag(flashlight_overlay, LV_OBJ_FLAG_CLICKABLE); // 设置初始颜色(红色) @@ -228,14 +292,20 @@ static void show_flashlight(void) { 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); + // 暂停 LVGL 显示刷新,应援灯模式由 DMA 独占 GRAM + // 必须在创建 overlay 后暂停,避免 LVGL 渲染 overlay 与 DMA 竞争 + fl_disable_lvgl_flush(); - // 立即设置为高亮度 + // DMA 填充初始颜色到 GRAM + fl_switch_color_direct(flashlight_color_values[0]); + + // 进入时:高亮常亮,不闪烁 + flashlight_blinking = false; + flashlight_bright = true; + flashlight_timer = NULL; pwm_set_brightness(100); } @@ -280,15 +350,50 @@ lv_indev_wait_release(lv_indev_get_act()); } } -void ui_event_SliderBrightness( lv_event_t * e) { - lv_event_code_t event_code = lv_event_get_code(e);lv_obj_t * target = lv_event_get_target(e); - -if ( event_code == LV_EVENT_VALUE_CHANGED) { - int32_t val = lv_slider_get_value(target); - if (val < 10) { val = 10; lv_slider_set_value(target, 10, LV_ANIM_OFF); } - pwm_set_brightness((uint8_t)val); - _ui_slider_set_text_value( ui_LabelBrightness, target, "", "%"); +// 滑块交互锁定:拖动期间禁用其他按钮,防止坐标跳变导致误触 +static void slider_lock_other_buttons(bool lock) +{ + if (lock) { + lv_obj_clear_flag(ui_ImgDelete, LV_OBJ_FLAG_CLICKABLE); + lv_obj_clear_flag(ui_ImgFlashlight, LV_OBJ_FLAG_CLICKABLE); + lv_obj_clear_flag(ui_ImgLowPower, LV_OBJ_FLAG_CLICKABLE); + } else { + lv_obj_add_flag(ui_ImgDelete, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_ADV_HITTEST); + lv_obj_add_flag(ui_ImgFlashlight, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_ADV_HITTEST); + lv_obj_add_flag(ui_ImgLowPower, LV_OBJ_FLAG_CLICKABLE); + } } + +// PWM节流:最小更新间隔50ms,避免频繁调用 +static int64_t last_brightness_update_us = 0; + +void ui_event_SliderBrightness( lv_event_t * e) { + lv_event_code_t event_code = lv_event_get_code(e); + + if (event_code == LV_EVENT_PRESSED) { + slider_lock_other_buttons(true); + } else if (event_code == LV_EVENT_RELEASED || event_code == LV_EVENT_PRESS_LOST) { + slider_lock_other_buttons(false); + // 松手时确保最终值写入 + lv_obj_t *target = lv_event_get_target(e); + int32_t val = lv_slider_get_value(target); + if (val < 10) { val = 10; lv_slider_set_value(target, 10, LV_ANIM_OFF); } + pwm_set_brightness((uint8_t)val); + _ui_slider_set_text_value(ui_LabelBrightness, target, "", "%"); + last_brightness_update_us = 0; + } else if (event_code == LV_EVENT_VALUE_CHANGED) { + lv_obj_t *target = lv_event_get_target(e); + int32_t val = lv_slider_get_value(target); + if (val < 10) { val = 10; lv_slider_set_value(target, 10, LV_ANIM_OFF); } + // 文本始终更新(轻量操作) + _ui_slider_set_text_value(ui_LabelBrightness, target, "", "%"); + // PWM节流:50ms内不重复调用 + int64_t now = esp_timer_get_time(); + if (now - last_brightness_update_us >= 50000) { + pwm_set_brightness((uint8_t)val); + last_brightness_update_us = now; + } + } } // ImgLowPower点击事件:切换休眠模式 @@ -447,9 +552,11 @@ lv_obj_set_style_text_color(ui_LabelPowerLevel, lv_color_hex(0xFFFFFF), LV_PART_ lv_obj_set_style_text_opa(ui_LabelPowerLevel, 255, LV_PART_MAIN| LV_STATE_DEFAULT); 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_ImgDelete, ui_event_ImgDelete, LV_EVENT_ALL, NULL); +// 按钮只注册需要的事件,减少无效回调调用 +lv_obj_add_event_cb(ui_ImgLowPower, ui_event_ImgLowPower, LV_EVENT_VALUE_CHANGED, NULL); +lv_obj_add_event_cb(ui_ImgFlashlight, ui_event_ImgFlashlight, LV_EVENT_CLICKED, NULL); +lv_obj_add_event_cb(ui_ImgDelete, ui_event_ImgDelete, LV_EVENT_CLICKED, NULL); +// 滑块需要 PRESSED/RELEASED/PRESS_LOST/VALUE_CHANGED,保留 ALL 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);