feat: 应援灯防撕裂优化 - DMA直接填充GRAM + LVGL flush拦截 + PWM黑屏遮蔽

应援灯颜色切换从 LVGL 渲染改为直接 DMA 填充 GRAM,彻底消除 LVGL 刷新竞争:

lcd.c/lcd.h:
- 新增 lcd_fill_color_with_buf() 直接 DMA 分条填充全屏纯色
- 新增 TEON(0x35) 启用 TE 内部同步信号
- 新增 lcd_read_scanline()/lcd_wait_vsync_timeout() VSYNC 读取接口
  (实测 QSPI 模式下 TESLRD 始终返回 0xFFFF,软件 VSYNC 不可用)

ui_ScreenSet.c:
- LVGL flush 回调拦截:进入应援灯时替换为空操作,退出时恢复
  解决 LVGL 周期刷新覆盖 DMA 颜色导致红色方块残留的问题
- DMA 缓冲区生命周期管理:进入时分配,退出时释放
- 颜色切换 PWM=0 黑屏遮蔽:DMA 期间完全熄灭背光,撕裂不可见
- 滑块交互优化:拖动期间锁定其他按钮 + PWM 50ms 节流
- 手动滑动检测替代 LVGL 手势(layer_top 上手势不可靠)

.gitignore: 排除 docs/*.pdf 文档文件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rdzleo 2026-03-30 15:18:41 +08:00
parent 99d7e910f1
commit 0bdf7be875
4 changed files with 328 additions and 153 deletions

3
.gitignore vendored
View File

@ -22,3 +22,6 @@ sdkconfig.old
07-uniapp最新日志.txt
/Dzbj_ESP32_S3
/esp-brookesia-master
# 忽略文档PDF文件
*.pdf

View File

@ -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 = 成功等到 VBLANKfalse = 超时或读取失败
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) {

View File

@ -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 建议 170001帧
// 返回 true=成功等到 VBLANKfalse=超时或读取失败
bool lcd_wait_vsync_timeout(uint32_t timeout_us);
// I2C 总线共享:传入主项目的 I2C 总线句柄,供触摸控制器使用
void lcd_set_i2c_bus(i2c_master_bus_handle_t bus);

View File

@ -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
static void flashlight_blink_timer_cb(lv_timer_t *timer) {
if (!flashlight_overlay) return;
// 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);
}
// 切换亮暗状态
flashlight_bright = !flashlight_bright;
// 调整PWM亮度亮=100%,暗=20%
if (flashlight_bright) {
pwm_set_brightness(100);
} else {
pwm_set_brightness(20);
// 用指定颜色填充 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;
}
}
// 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;
}
// 分配 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;
}
// 启动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;
// 释放 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;
}
}
target_brightness = target_bright;
fade_step = fade_out ? -25 : 25; // 每次调整25%亮度优化4次完成8ms
fade_timer = lv_timer_create(fade_brightness_cb, 2, NULL); // 2ms间隔
// ---- 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);
}
// 颜色切换延迟回调(简化版:只修改样式)
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 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 切换颜色(独占 GRAMLVGL 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=0DMA期间屏幕全黑撕裂不可见
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();
// 延迟90ms后淡入优化v2减少保守等待时间
// 理论刷新时间约30-50ms90ms足够安全
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) {
// 优化退出时序,避免闪烁:
// 1. 先将亮度降到0黑屏
pwm_set_brightness(0);
// 闪烁定时器回调(每 500ms 切换亮暗)
static void flashlight_blink_timer_cb(lv_timer_t *timer) {
if (!flashlight_overlay) return;
flashlight_bright = !flashlight_bright;
pwm_set_brightness(flashlight_bright ? 100 : 20);
}
// 2. 在LVGL锁保护下删除所有对象和定时器
if (lvgl_port_lock(100)) {
// 停止闪烁定时器
// 手动滑动检测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 // 下滑判定阈值(像素)
// 切换颜色(下滑时调用)
// 无需 PWM=0 黑屏:纯色→纯色直接 DMA 覆写,~15ms 完成
static void fl_do_switch_color(void) {
bool was_blinking = flashlight_blinking;
if (flashlight_timer) {
lv_timer_pause(flashlight_timer);
}
flashlight_color_index = (flashlight_color_index + 1) % FLASHLIGHT_COLOR_COUNT;
fl_switch_color_direct(flashlight_color_values[flashlight_color_index]);
flashlight_bright = true;
if (was_blinking && flashlight_timer) {
lv_timer_resume(flashlight_timer);
}
}
// 切换闪烁状态(点击时调用)
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);
}
} else {
if (flashlight_timer) {
lv_timer_del(flashlight_timer);
flashlight_timer = NULL;
}
// 停止淡入淡出定时器
if (fade_timer) {
lv_timer_del(fade_timer);
fade_timer = NULL;
flashlight_bright = true;
pwm_set_brightness(100);
}
}
// 删除遮罩层此时屏幕已黑看不到overlay删除的过程
// 遮罩层事件处理:按下记录坐标,释放时判定点击 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忽略
}
}
// 退出应援灯模式(外部调用,不恢复亮度)
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,14 +350,49 @@ 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);
// 滑块交互锁定:拖动期间禁用其他按钮,防止坐标跳变导致误触
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);
}
}
if ( event_code == LV_EVENT_VALUE_CHANGED) {
// 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;
}
}
}
@ -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);