新增手电筒功能 + 颜色切换性能优化

## 核心功能
- 实现全屏手电筒:支持红/绿/蓝三色点击切换
- 自动闪烁模式: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 <noreply@anthropic.com>
This commit is contained in:
Rdzleo 2026-02-11 13:54:22 +08:00
parent 7fe958e589
commit 4860efca6e
4 changed files with 236 additions and 5 deletions

View File

@ -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,7 +87,6 @@ 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;

View File

@ -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. 休眠管理器已初始化");

View File

@ -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-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) {
// 停止闪烁定时器
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);

View File

@ -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