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:
parent
99d7e910f1
commit
0bdf7be875
3
.gitignore
vendored
3
.gitignore
vendored
@ -22,3 +22,6 @@ sdkconfig.old
|
|||||||
07-uniapp最新日志.txt
|
07-uniapp最新日志.txt
|
||||||
/Dzbj_ESP32_S3
|
/Dzbj_ESP32_S3
|
||||||
/esp-brookesia-master
|
/esp-brookesia-master
|
||||||
|
|
||||||
|
# 忽略文档PDF文件
|
||||||
|
*.pdf
|
||||||
|
|||||||
@ -255,6 +255,12 @@ void lcd_init(){
|
|||||||
esp_lcd_panel_reset(panel_handle);
|
esp_lcd_panel_reset(panel_handle);
|
||||||
esp_lcd_panel_init(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,避免显示上次关机时的残留画面
|
// 清空LCD GRAM,避免显示上次关机时的残留画面
|
||||||
size_t clear_buffer_size = LCD_WID * 40;
|
size_t clear_buffer_size = LCD_WID * 40;
|
||||||
uint16_t *clear_buffer = heap_caps_malloc(clear_buffer_size * sizeof(uint16_t), MALLOC_CAP_DMA);
|
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
|
#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为黑色(用于低功耗熄屏前,避免残影)
|
// 清空LCD GRAM为黑色(用于低功耗熄屏前,避免残影)
|
||||||
void lcd_clear_screen_black(void) {
|
void lcd_clear_screen_black(void) {
|
||||||
if (panel_handle == NULL) {
|
if (panel_handle == NULL) {
|
||||||
|
|||||||
@ -10,6 +10,19 @@ void lcd_init(void);
|
|||||||
void lvgl_lcd_init(void);
|
void lvgl_lcd_init(void);
|
||||||
void lcd_clear_screen_black(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 总线句柄,供触摸控制器使用
|
// I2C 总线共享:传入主项目的 I2C 总线句柄,供触摸控制器使用
|
||||||
void lcd_set_i2c_bus(i2c_master_bus_handle_t bus);
|
void lcd_set_i2c_bus(i2c_master_bus_handle_t bus);
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
#include "../../pages/include/pages.h"
|
#include "../../pages/include/pages.h"
|
||||||
#include "../../sleep_mgr/include/sleep_mgr.h"
|
#include "../../sleep_mgr/include/sleep_mgr.h"
|
||||||
#include "esp_lvgl_port.h" // LVGL锁机制
|
#include "esp_lvgl_port.h" // LVGL锁机制
|
||||||
|
#include "esp_timer.h"
|
||||||
|
#include "esp_log.h"
|
||||||
#include "dzbj_button.h"
|
#include "dzbj_button.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.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;
|
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 lv_timer_t *flashlight_timer = NULL; // 闪烁定时器
|
||||||
static uint8_t flashlight_color_index = 0; // 当前颜色索引
|
static uint8_t flashlight_color_index = 0; // 当前颜色索引
|
||||||
static bool flashlight_bright = true; // 当前亮度状态(true=亮,false=暗)
|
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三原色,可扩展)
|
// DMA 缓冲区(应援灯生命周期内常驻,避免反复 malloc/free)
|
||||||
// 使用24位RGB值,LVGL会自动转换为RGB565
|
// 条带越大,条数越少,总传输时间越短,撕裂概率越低
|
||||||
|
// 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[] = {
|
static const uint32_t flashlight_color_values[] = {
|
||||||
0xFF0000, // 红色
|
0xFF0000, // 红色
|
||||||
0x00FF00, // 绿色
|
0x00FF00, // 绿色
|
||||||
0x0000FF, // 蓝色
|
0x0000FF, // 蓝色
|
||||||
// 可在此添加更多颜色,例如:
|
0xFFFF00, // 黄色
|
||||||
// 0xFFFF00, // 黄色
|
0xFF00FF, // 品红
|
||||||
// 0xFF00FF, // 品红
|
0x00FFFF, // 青色
|
||||||
// 0x00FFFF, // 青色
|
0xFFFFFF, // 白色
|
||||||
// 0xFFFFFF, // 白色
|
|
||||||
};
|
};
|
||||||
#define FLASHLIGHT_COLOR_COUNT (sizeof(flashlight_color_values) / sizeof(flashlight_color_values[0]))
|
#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) {
|
static void flashlight_blink_timer_cb(lv_timer_t *timer) {
|
||||||
if (!flashlight_overlay) return;
|
if (!flashlight_overlay) return;
|
||||||
|
|
||||||
// 切换亮暗状态
|
|
||||||
flashlight_bright = !flashlight_bright;
|
flashlight_bright = !flashlight_bright;
|
||||||
|
pwm_set_brightness(flashlight_bright ? 100 : 20);
|
||||||
// 调整PWM亮度:亮=100%,暗=20%
|
|
||||||
if (flashlight_bright) {
|
|
||||||
pwm_set_brightness(100);
|
|
||||||
} else {
|
|
||||||
pwm_set_brightness(20);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PWM淡入淡出步进定时器(用于平滑过渡)
|
// 手动滑动检测(layer_top 上 LVGL 手势检测不可靠,改用坐标位移判定)
|
||||||
static lv_timer_t *fade_timer = NULL;
|
// 仅影响 overlay 对象,其他界面的 LV_EVENT_GESTURE 不受影响
|
||||||
static uint8_t target_brightness = 100;
|
static lv_coord_t fl_press_y = 0; // 按下时的 Y 坐标
|
||||||
static int8_t fade_step = 0; // 正数=淡入,负数=淡出
|
static bool fl_press_valid = false; // 是否有有效的按下记录
|
||||||
|
#define FL_SWIPE_THRESHOLD 50 // 下滑判定阈值(像素)
|
||||||
|
|
||||||
// 前向声明
|
// 切换颜色(下滑时调用)
|
||||||
static void fade_in_delayed_cb(lv_timer_t *timer);
|
// 无需 PWM=0 黑屏:纯色→纯色直接 DMA 覆写,~15ms 完成
|
||||||
static void flashlight_overlay_event_cb(lv_event_t *e);
|
static void fl_do_switch_color(void) {
|
||||||
|
bool was_blinking = flashlight_blinking;
|
||||||
// PWM淡入淡出定时器回调(每2ms调整一次亮度)
|
if (flashlight_timer) {
|
||||||
static void fade_brightness_cb(lv_timer_t *timer) {
|
lv_timer_pause(flashlight_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;
|
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 (was_blinking && flashlight_timer) {
|
||||||
|
lv_timer_resume(flashlight_timer);
|
||||||
// 只修改颜色样式,不重建对象(资源消耗更低)
|
|
||||||
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) {
|
static void fl_do_toggle_blink(void) {
|
||||||
start_fade(false, 100); // 淡入到100%
|
flashlight_blinking = !flashlight_blinking;
|
||||||
}
|
if (flashlight_blinking) {
|
||||||
|
flashlight_bright = true;
|
||||||
// 手电筒遮罩层事件处理
|
pwm_set_brightness(100);
|
||||||
static void flashlight_overlay_event_cb(lv_event_t *e) {
|
if (!flashlight_timer) {
|
||||||
lv_event_code_t code = lv_event_get_code(e);
|
flashlight_timer = lv_timer_create(flashlight_blink_timer_cb, 500, NULL);
|
||||||
|
|
||||||
// 处理点击事件:切换颜色(优化版v2:缩短等待时间)
|
|
||||||
if (code == LV_EVENT_CLICKED) {
|
|
||||||
// 步骤1: 立即黑屏(不淡出,直接设置0%)
|
|
||||||
if (fade_timer) {
|
|
||||||
lv_timer_del(fade_timer); // 停止任何进行中的淡入淡出
|
|
||||||
fade_timer = NULL;
|
|
||||||
}
|
}
|
||||||
pwm_set_brightness(0); // 立即设置亮度为0
|
} else {
|
||||||
|
|
||||||
// 步骤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)) {
|
|
||||||
// 停止闪烁定时器
|
|
||||||
if (flashlight_timer) {
|
if (flashlight_timer) {
|
||||||
lv_timer_del(flashlight_timer);
|
lv_timer_del(flashlight_timer);
|
||||||
flashlight_timer = NULL;
|
flashlight_timer = NULL;
|
||||||
}
|
}
|
||||||
|
flashlight_bright = true;
|
||||||
|
pwm_set_brightness(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 停止淡入淡出定时器
|
// 遮罩层事件处理:按下记录坐标,释放时判定点击 vs 下滑
|
||||||
if (fade_timer) {
|
static void flashlight_overlay_event_cb(lv_event_t *e) {
|
||||||
lv_timer_del(fade_timer);
|
lv_event_code_t code = lv_event_get_code(e);
|
||||||
fade_timer = NULL;
|
|
||||||
|
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) {
|
if (flashlight_overlay) {
|
||||||
lv_obj_del(flashlight_overlay);
|
lv_obj_del(flashlight_overlay);
|
||||||
flashlight_overlay = NULL;
|
flashlight_overlay = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
lvgl_port_unlock();
|
lvgl_port_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 注意:不在这里恢复亮度,避免看到ScreenSet和Home界面混合显示
|
flashlight_blinking = false;
|
||||||
// 亮度恢复由调用者(boot_btn_handler)在界面切换完成后执行
|
fl_free_dma_buf();
|
||||||
|
|
||||||
|
// 恢复 LVGL 显示刷新(退出应援灯后 LVGL 重新接管屏幕渲染)
|
||||||
|
fl_enable_lvgl_flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询手电筒是否激活
|
// 查询应援灯是否激活
|
||||||
bool flashlight_is_active(void) {
|
bool flashlight_is_active(void) {
|
||||||
return (flashlight_overlay != NULL);
|
return (flashlight_overlay != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取手电筒模式前保存的亮度值
|
// 获取进入应援灯前保存的亮度值
|
||||||
uint8_t flashlight_get_saved_brightness(void) {
|
uint8_t flashlight_get_saved_brightness(void) {
|
||||||
return saved_brightness;
|
return saved_brightness;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示手电筒模式
|
// 进入应援灯模式
|
||||||
static void show_flashlight(void) {
|
static void show_flashlight(void) {
|
||||||
// 如果已经显示,则不重复创建
|
|
||||||
if (flashlight_overlay) return;
|
if (flashlight_overlay) return;
|
||||||
|
|
||||||
// 保存当前亮度
|
// 分配 DMA 缓冲区
|
||||||
|
if (!fl_alloc_dma_buf()) {
|
||||||
|
ESP_LOGE("Flashlight", "DMA缓冲区分配失败");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
saved_brightness = pwm_get_brightness();
|
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_size(flashlight_overlay, LV_HOR_RES, LV_VER_RES);
|
||||||
lv_obj_set_pos(flashlight_overlay, 0, 0);
|
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);
|
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_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_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);
|
lv_obj_add_event_cb(flashlight_overlay, flashlight_overlay_event_cb, LV_EVENT_ALL, NULL);
|
||||||
|
|
||||||
// 启动闪烁定时器(500ms周期)
|
// 暂停 LVGL 显示刷新,应援灯模式由 DMA 独占 GRAM
|
||||||
flashlight_bright = true;
|
// 必须在创建 overlay 后暂停,避免 LVGL 渲染 overlay 与 DMA 竞争
|
||||||
flashlight_timer = lv_timer_create(flashlight_blink_timer_cb, 500, NULL);
|
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);
|
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);
|
static void slider_lock_other_buttons(bool lock)
|
||||||
|
{
|
||||||
if ( event_code == LV_EVENT_VALUE_CHANGED) {
|
if (lock) {
|
||||||
int32_t val = lv_slider_get_value(target);
|
lv_obj_clear_flag(ui_ImgDelete, LV_OBJ_FLAG_CLICKABLE);
|
||||||
if (val < 10) { val = 10; lv_slider_set_value(target, 10, LV_ANIM_OFF); }
|
lv_obj_clear_flag(ui_ImgFlashlight, LV_OBJ_FLAG_CLICKABLE);
|
||||||
pwm_set_brightness((uint8_t)val);
|
lv_obj_clear_flag(ui_ImgLowPower, LV_OBJ_FLAG_CLICKABLE);
|
||||||
_ui_slider_set_text_value( ui_LabelBrightness, target, "", "%");
|
} 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点击事件:切换休眠模式
|
// 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_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_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_ImgLowPower, ui_event_ImgLowPower, LV_EVENT_VALUE_CHANGED, NULL);
|
||||||
lv_obj_add_event_cb(ui_ImgDelete, ui_event_ImgDelete, LV_EVENT_ALL, 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_SliderBrightness, ui_event_SliderBrightness, LV_EVENT_ALL, NULL);
|
||||||
lv_obj_add_event_cb(ui_ScreenSet, ui_event_ScreenSet, LV_EVENT_ALL, NULL);
|
lv_obj_add_event_cb(ui_ScreenSet, ui_event_ScreenSet, LV_EVENT_ALL, NULL);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user