Rdzleo 3dced23cfd feat: 新增图片删除功能 + 修复开机/唤醒闪烁问题
## 新增功能
- 图片删除:支持从 SPIFFS 彻底删除图片(物理删除 + 内存管理)
- ContainerDle 管理:新增半透明圆形删除容器(含删除和返回按钮)
- 状态管理:标志位模式管理 UI 状态,离开界面自动清理

## 解决的关键问题
1. 开机闪烁 (️)
   - 原因:LCD GRAM 保留旧数据
   - 方案:背光使能前清空 GRAM(DMA 分批填充黑色,34ms)

2. 低功耗唤醒闪烁 (️)
   - 原因:先恢复亮度后切换界面,看到旧界面
   - 方案:先切换界面(背光=0)→ 延时 100ms → 恢复亮度

3. ContainerDle 状态保留 (️)
   - 原因:LVGL 对象不销毁,状态被保留
   - 方案:离开界面时主动清理(手势/按键回调中调用隐藏函数)

4. 按键回调冲突 (️)
   - 原因:按键系统单回调限制
   - 方案:统一在 main.c 管理 BOOT 按键,其他模块通过接口调用

5. 开机加载图片闪烁 (️)
   - 原因:screen_init() 中 JPEG 解码触发渲染
   - 方案:延迟到 LV_EVENT_SCREEN_LOADED 事件加载

## 功能改动
- BOOT 按键增强:唤醒 + 退出手电筒 + 隐藏容器 + 返回 Home
- 图片界面优化:支持删除当前图片并自动显示下一张
- 休眠管理优化:移除 BOOT 注册,避免回调冲突

## 技术优化
- 资源节约:分批处理大数据(LCD GRAM 清除)
- 时序优化:根据屏幕状态智能调整唤醒时序
- 模块化设计:按键集中管理 + 接口清晰 + 状态标志模式

## 文件变更
- lcd/lcd.c: LCD GRAM 清除逻辑
- main.c: BOOT 按键统一管理
- pages/pages.c: 图片删除功能实现
- ui/screens/ui_ScreenImg.c: ContainerDle 管理 + 状态控制
- sleep_mgr/sleep_mgr.c: 按键回调优化
- ui/images: 新增删除和返回按钮图标 (s13.png, s14.png)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-11 16:51:51 +08:00

279 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "driver/i2c.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_flash.h"
#include "esp_check.h"
#include "nvs_flash.h"
#include "lv_demos.h"
#include "string.h"
#include "gpio.h"
#include "wifi.h"
#include "lcd.h"
#include "temp.h"
#include "fatfs.h"
#include "pages.h"
#include "ble.h"
#include "battery.h"
#include "button.h"
#include "sleep_mgr.h"
#include "ui/ui.h"
#include "ui/screens/ui_ScreenSet.h"
#include "ui/screens/ui_ScreenImg.h"
// #include "axis.h"
// #include "esp_bt.h"
// #include "esp_mac.h"
// #include "esp_gap_ble_api.h"
// #include "esp_gattc_api.h"
// #include "esp_gatt_defs.h"
// #include "esp_bt_main.h"
// #include "esp_log.h"
// #include "esp_system.h"
// #include "esp_bt_defs.h"
// #include "freertos/FreeRTOS.h"
// esp_err_t spi_init(void){
// spi_bus_config_t spi_conf = {
// .sclk_io_num = PIN_LCD_CLK,
// .data0_io_num = PIN_LCD_D0,
// .data1_io_num = PIN_LCD_D1,
// .data2_io_num = PIN_LCD_D2,
// .data3_io_num = PIN_LCD_D3,
// .max_transfer_sz = 4096,
// .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_QUAD,
// };
// esp_err_t err = spi_bus_initialize(SPI_LCD_HOST,&spi_conf, SPI_DMA_CH_AUTO);
// spi_device_interface_config_t devcfg = {
// .command_bits = 8,
// .address_bits = 24,
// .mode = 0,
// .clock_speed_hz = 10000000,
// .spics_io_num = PIN_LCD_CS,
// .queue_size = 1,
// .cs_ena_posttrans = 0,
// .flags = SPI_DEVICE_HALFDUPLEX,
// };
// err = spi_bus_add_device(SPI_LCD_HOST, &devcfg, &spi_lcd_handle);
// return err;
// }
// BOOT按键按下处理唤醒屏幕 + 退出手电筒 + 返回ScreenHome
void boot_btn_handler(int gpio_num, void *usr_data) {
ESP_LOGI("BTN_HANDLER", "BOOT按键触发返回ScreenHome");
// 检查当前是否在ScreenImg界面如果是则先隐藏ContainerDle
lv_obj_t *current_screen = lv_scr_act();
if (current_screen == ui_ScreenImg) {
ui_ScreenImg_hide_delete_container();
ESP_LOGI("BTN_HANDLER", "从ScreenImg离开已隐藏ContainerDle");
}
// 检查屏幕是否关闭(低功耗模式)
bool screen_was_off = sleep_mgr_is_screen_off();
if (screen_was_off) {
// 屏幕关闭状态先切换界面背光仍为0用户看不到再恢复亮度
ESP_LOGI("BTN_HANDLER", "屏幕已关闭,先切换界面再唤醒");
// 退出手电筒
if (flashlight_is_active()) {
flashlight_exit();
}
// 切换到ScreenHome此时背光为0LCD已渲染但不可见
_ui_screen_change(&ui_ScreenHome, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenHome_screen_init);
// 等待 LVGL 完成界面切换和渲染100ms足够LVGL完成2-3个tick
vTaskDelay(pdMS_TO_TICKS(100));
// 现在恢复亮度此时ScreenHome已渲染完成避免看到旧界面
sleep_mgr_notify_activity();
} else {
// 屏幕正常显示状态:先通知活动,再切换界面
sleep_mgr_notify_activity();
if (flashlight_is_active()) {
flashlight_exit();
}
_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 = {
.scl_io_num = PIN_NUM_SCL,
.sda_io_num = PIN_NUM_SDA,
.mode = I2C_MODE_MASTER,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};
i2c_param_config(I2C_MASTER_NUM,&i2c_conf);
return i2c_driver_install(I2C_MASTER_NUM, i2c_conf.mode,0,0, 0);
}
// esp_err_t i2c_master_write_slave(uint8_t slave_addr, uint8_t *data_w, size_t size_w) {
// i2c_cmd_handle_t cmd = i2c_cmd_link_create();
// i2c_master_start(cmd);
// i2c_master_write_byte(cmd, (slave_addr << 1) | I2C_MASTER_WRITE, true);
// i2c_master_write(cmd, data_w, size_w, true);
// i2c_master_stop(cmd);
// esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 100);
// i2c_cmd_link_delete(cmd);
// return ret;
// }
// esp_err_t i2c_master_read_slave(uint8_t slave_addr,uint8_t order, uint8_t *data_r, size_t size_r) {
// i2c_cmd_handle_t cmd = i2c_cmd_link_create();
// i2c_master_start(cmd);
// i2c_master_write_byte(cmd, (slave_addr << 1) | I2C_MASTER_WRITE, true);
// i2c_master_write_byte(cmd,order,true);
// i2c_master_start(cmd);
// i2c_master_write_byte(cmd, (slave_addr << 1) | I2C_MASTER_READ, true);
// i2c_master_read(cmd, data_r, size_r, true);
// i2c_master_stop(cmd);
// esp_err_t ret = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 100);
// i2c_cmd_link_delete(cmd);
// return ret;
// }
// 初始化NVS
void nvs_init(){
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());// 擦除NVS分区
ret = nvs_flash_init();// 初始化NVS
}
ESP_ERROR_CHECK(ret);// 检查NVS初始化是否成功
// 添加默认图片路径设置
// ============================================================================
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open("config", NVS_READWRITE, &nvs_handle);// 打开NVS配置文件
if (err == ESP_OK) {
size_t imgname_len;// 图片路径长度
err = nvs_get_str(nvs_handle, "img_filename", NULL, &imgname_len);// 获取当前图片路径长度
if (err != ESP_OK) {
// 设置默认图片路径(与放入 spiffs_image 的文件名一致)
const char* default_img = "default.jpg";// 默认图片文件名
err = nvs_set_str(nvs_handle, "img_filename", default_img);// 设置默认图片路径
if (err == ESP_OK) {
nvs_commit(nvs_handle);
ESP_LOGI("NVS", "Set default image: %s", default_img);// 设置默认图片路径
}
}
nvs_close(nvs_handle);// 关闭NVS配置文件
}
// ============================================================================
// nvs_handle_t nvs_handle;
// esp_err_t err;
// err = nvs_open("config", NVS_READWRITE, &nvs_handle);
// if (err != ESP_OK) printf("创建config失败\n");
// int32_t brightness = 80;
// const char* wifi_ssid = "MyWiFi";
// const char* img_filename = "face_1762059331906.jpg";
// err = nvs_set_i32(nvs_handle, "brightness", brightness);
// if (err != ESP_OK) goto close_handle;
// err = nvs_set_str(nvs_handle, "wifi_ssid", wifi_ssid);
// if (err != ESP_OK) goto close_handle;
// err = nvs_set_str(nvs_handle, "img_filename", img_filename);
// if (err != ESP_OK) goto close_handle;
// err = nvs_commit(nvs_handle);
// close_handle:
// nvs_close(nvs_handle);
}
void app_main(void)
{
i2c_init();
ESP_LOGI("MAIN", "1. I2C已初始化");// I2C已初始化
nvs_init();
ESP_LOGI("MAIN", "2. NVS已初始化");// NVS已初始化
lcd_init();
ESP_LOGI("MAIN", "3. LCD已初始化");// LCD已初始化
touch_init();
ESP_LOGI("MAIN", "4. 触摸控制器已初始化");// 触摸控制器已初始化
lvgl_lcd_init();
ESP_LOGI("MAIN", "5. LVGL已初始化");// LVGL已初始化
// LVGL任务创建后下一个tick即运行利用这段时间初始化文件系统
fatfs_init();
ESP_LOGI("MAIN", "6. FATFS文件系统已初始化");// FATFS已初始化
fatfs_remove_nullData("/spiflash");// 移除空数据
fatfs_list_all_filenames("/spiflash",false);// 列出所有文件名
ESP_LOGI("MAIN", "7. SPIFFS处理完成");// SPIFFS处理完成
// =====================================================================
// 显示SquareLine Studio生成的UI界面
ui_init(); // 初始化UI含JPEG解码
ESP_LOGI("MAIN", "8. SquareLine UI已初始化");// SquareLine UI已初始化
// 等待LVGL完成屏幕切换和首帧渲染彻底避免开机闪烁
// ui_init()会创建所有屏幕并加载ScreenHome包括
// 1. JPEG解码约210ms
// 2. 屏幕对象创建和切换
// 3. QSPI传输首帧到LCD约100ms
// 增加到150ms确保所有渲染完成后再点亮背光
vTaskDelay(pdMS_TO_TICKS(150));
// UI渲染完成后再点亮背光避免开机闪烁旧画面
pwm_init();
ESP_LOGI("MAIN", "9. PWM背光已初始化");// PWM背光已初始化
// 初始化电池ADC检测并启动监控任务
ESP_ERROR_CHECK(battery_init());
ESP_LOGI("MAIN", "10. 电池ADC已初始化");
battery_monitor_start();
ESP_LOGI("MAIN", "11. 电池监控任务已启动");
// 初始化按键驱动
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. 休眠管理器已初始化");
ESP_LOGI("MAIN", "系统初始化完成成功!");// 系统初始化完成成功
// =====================================================================
// // 优先显示测试屏幕
// app_test_display();
// ESP_LOGI("MAIN", "Test screen displayed");
// //=====================================================================
// // // 启动图片循环显示任务
// xTaskCreate(img_loop_task, "img_loop_task", 8192, NULL, 5, NULL);
// ESP_LOGI("MAIN", "图片循环显示任务已启动");
// ESP_LOGI("MAIN", "任务将每3秒循环显示SPIFFS中的所有图片");
// //=====================================================================
// 注释掉静态图片显示,使用任务自动切换
// app_img_display();// 显示图片
// //=====================================================================
}