## 新增功能 - 图片删除:支持从 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>
880 lines
31 KiB
C
880 lines
31 KiB
C
#include "lvgl.h"
|
||
#include "fatfs.h"
|
||
#include "driver/ledc.h"
|
||
#include "gpio.h"
|
||
#include "wifi.h"
|
||
#include "temp.h"
|
||
#include "jpeg_decoder.h"
|
||
#include "../ui/screens/ui_ScreenImg.h"
|
||
#include <inttypes.h>
|
||
#include <dirent.h>
|
||
#include <sys/stat.h>
|
||
#include <string.h>
|
||
#include <strings.h>
|
||
#include <stdbool.h>
|
||
char img_path[40];
|
||
char *img_filename;
|
||
lv_obj_t *app_img;
|
||
lv_obj_t *act_mainscreen;
|
||
uint8_t *app_img_data = 0;
|
||
esp_jpeg_image_output_t outdata;
|
||
lv_img_dsc_t image;
|
||
|
||
#define MAX_IMAGE_FILES 10
|
||
#define MAX_FILENAME_LEN 32
|
||
static char spiffs_image_files[MAX_IMAGE_FILES][MAX_FILENAME_LEN];
|
||
static int spiffs_image_count = 0;
|
||
static int current_image_index = 0;
|
||
static bool image_list_initialized = false;
|
||
|
||
// 当前亮度值(用于休眠恢复)
|
||
static uint8_t current_brightness = 50;
|
||
|
||
// 获取当前亮度值
|
||
uint8_t pwm_get_brightness(void) {
|
||
return current_brightness;
|
||
}
|
||
|
||
// 设置屏幕亮度,percent范围0-100
|
||
// 0=完全关闭背光,10~100为正常亮度范围
|
||
// 显示10%~100%映射到实际亮度20%~100%,背光低电平有效需反转占空比
|
||
void pwm_set_brightness(uint8_t percent) {
|
||
if (percent == 0) {
|
||
// 完全关闭背光(低电平有效,占空比100%=全高=关闭)
|
||
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 8191);
|
||
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
|
||
return;
|
||
}
|
||
if (percent < 10) percent = 10;
|
||
if (percent > 100) percent = 100;
|
||
current_brightness = percent;
|
||
uint32_t actual = 20 + (uint32_t)(percent - 10) * 80 / 90;
|
||
uint32_t duty = 8191 - (8191 * actual) / 100;
|
||
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty);
|
||
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
|
||
}
|
||
|
||
// 初始化PWM
|
||
void pwm_init(){
|
||
ledc_timer_config_t ledc_timer = {
|
||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||
.timer_num = LEDC_TIMER_0,
|
||
.duty_resolution = LEDC_TIMER_13_BIT,
|
||
.freq_hz = 5000,
|
||
.clk_cfg = LEDC_AUTO_CLK
|
||
};
|
||
ledc_timer_config(&ledc_timer);// 配置PWM定时器
|
||
ledc_channel_config_t ledc_channel = {
|
||
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||
.channel = LEDC_CHANNEL_0,
|
||
.timer_sel = LEDC_TIMER_0,
|
||
.intr_type = LEDC_INTR_DISABLE,
|
||
.gpio_num = PIN_LCD_EN,
|
||
.duty = 0,
|
||
.hpoint = 0
|
||
};
|
||
ledc_channel_config(&ledc_channel);// 配置PWM通道
|
||
pwm_set_brightness(50);// 初始亮度50%
|
||
// ledc_timer_config_t motor_timer = {
|
||
// .speed_mode = LEDC_LOW_SPEED_MODE,
|
||
// .timer_num = LEDC_TIMER_1,
|
||
// .duty_resolution = LEDC_TIMER_13_BIT,
|
||
// .freq_hz = 5000,
|
||
// .clk_cfg = LEDC_AUTO_CLK
|
||
// };
|
||
// ledc_timer_config(&motor_timer);
|
||
// ledc_channel_config_t motor_channel = {
|
||
// .speed_mode = LEDC_LOW_SPEED_MODE,
|
||
// .channel = LEDC_CHANNEL_1,
|
||
// .timer_sel = LEDC_TIMER_0,
|
||
// .intr_type = LEDC_INTR_DISABLE,
|
||
// .gpio_num = PIN_MOTOR_EN,
|
||
// .duty = 4095,
|
||
// .hpoint = 0
|
||
// };
|
||
// ledc_channel_config(&motor_channel);
|
||
// ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1, 0);
|
||
// ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_1);
|
||
}
|
||
|
||
// 测试扫描WiFi列表
|
||
void wifi_scan_list_test(){
|
||
wifi_ap_record_t* wifi_list = NULL;// 定义WiFi列表指针
|
||
uint16_t num = 0;// 定义WiFi数量变量
|
||
esp_err_t err = wifi_scan_list(&num,&wifi_list);// 扫描WiFi列表
|
||
if(err == ESP_OK){
|
||
ESP_LOGI("WIFI","列表获取成功,数量:%d",num);// 打印WiFi数量
|
||
for (int i = 0; i < num; i++) {
|
||
ESP_LOGI("WIFI", "AP %d - SSID: %s, RSSI: %d", i + 1, wifi_list[i].ssid, wifi_list[i].rssi);// 打印每个WiFi的SSID和RSSI
|
||
}
|
||
}
|
||
}
|
||
// 测试连接WiFi
|
||
void wifi_connect_test(){
|
||
wifi_config_t wifi_config = {
|
||
.sta = {
|
||
.password = "12345678",
|
||
.ssid = "LDL的iPhone"
|
||
}
|
||
};
|
||
esp_wifi_set_config(ESP_IF_WIFI_STA,&wifi_config);// 设置WiFi配置
|
||
esp_err_t err = esp_wifi_connect();// 连接WiFi
|
||
if(err == ESP_OK){
|
||
ESP_LOGI("WIFI","WIFI连接成功");
|
||
}
|
||
}
|
||
// 测试断开WiFi连接
|
||
void wifi_disconnect_test(){
|
||
esp_err_t err = esp_wifi_disconnect();// 断开WiFi连接
|
||
if(err == ESP_OK){
|
||
ESP_LOGI("WIFI","已断开WiFi连接");
|
||
}
|
||
}
|
||
|
||
// 测试开始扫描WiFi
|
||
void wifi_scan_start_test(){
|
||
wifi_scan_start();// 开始扫描WiFi
|
||
}
|
||
|
||
// 测试获取可用堆内存
|
||
void free_heap_test(){
|
||
ESP_LOGI("HEAP","可用堆内存:%d",(int)heap_caps_get_total_size(MALLOC_CAP_SPIRAM));
|
||
}
|
||
|
||
lv_obj_t *act_wifiscreen;// 当前WiFi屏幕对象
|
||
lv_obj_t *act_testscreen;// 当前测试屏幕对象
|
||
lv_obj_t *act_mainscreen;// 当前主屏幕对象
|
||
|
||
void app_wifi_display(){
|
||
|
||
}
|
||
|
||
// 测试获取温度和湿度
|
||
void temp_test(){
|
||
float temp,humi;// 定义温度和湿度变量
|
||
get_temp(&temp);// 获取温度
|
||
get_humi(&humi);// 获取湿度
|
||
ESP_LOGI("TEMP","temp:%.2f humi:%.2f",temp,humi);// 打印温度和湿度
|
||
}
|
||
|
||
// 从NVS中读取图片路径
|
||
esp_err_t nvs_read_img(void) {
|
||
nvs_handle_t nvs_handle; // NVS 句柄
|
||
esp_err_t err; // NVS 错误码
|
||
err = nvs_open("config", NVS_READONLY, &nvs_handle);// 打开 NVS 句柄
|
||
if (err != ESP_OK) return err; // 如果打开失败,返回错误码
|
||
size_t imgname_len;
|
||
err = nvs_get_str(nvs_handle, "img_filename", NULL, &imgname_len);// 获取图片路径长度
|
||
if (err == ESP_OK) {
|
||
img_filename = malloc(imgname_len);// 分配内存
|
||
err = nvs_get_str(nvs_handle, "img_filename", img_filename, &imgname_len);// 获取图片路径
|
||
if (err != ESP_OK) {
|
||
nvs_close(nvs_handle);// 关闭 NVS 句柄
|
||
return err; // 如果获取失败,返回错误码
|
||
}
|
||
ESP_LOGI("NVS", "img_filename: %s", img_filename);// 打印图片路径
|
||
}
|
||
nvs_close(nvs_handle);// 关闭 NVS 句柄
|
||
return err;
|
||
}
|
||
|
||
// 测试改变NVS中的图片路径
|
||
esp_err_t nvs_change_img(char *imgname) {
|
||
nvs_handle_t nvs_handle;// NVS 句柄
|
||
esp_err_t err;
|
||
err = nvs_open("config", NVS_READWRITE, &nvs_handle);// 打开 NVS 句柄
|
||
if (err != ESP_OK) goto close_handle;
|
||
err = nvs_set_str(nvs_handle, "img_filename", imgname);// 设置图片路径
|
||
if (err != ESP_OK) goto close_handle;
|
||
err = nvs_commit(nvs_handle);// 提交更改
|
||
if (err != ESP_OK) goto close_handle;// 如果提交失败,关闭句柄并返回错误码
|
||
close_handle:
|
||
nvs_close(nvs_handle); // 关闭 NVS 句柄
|
||
return err;
|
||
}
|
||
|
||
// 仅更新现有图片,显示其他图片
|
||
// img_name: 图片文件名,为NULL时从NVS读取
|
||
void app_img_change(const char *img_name){
|
||
// 释放之前的图片数据
|
||
if(app_img_data){
|
||
free(app_img_data);
|
||
app_img_data = NULL;
|
||
ESP_LOGI("IMG", "释放之前显示的图片数据缓存");
|
||
}
|
||
|
||
const char *current_img_name = img_name;
|
||
|
||
// 如果没有指定图片名,从NVS读取
|
||
if(!current_img_name) {
|
||
esp_err_t ret_nvs = nvs_read_img();// 从NVS中读取图片路径
|
||
if(ret_nvs != ESP_OK){
|
||
ESP_LOGE("NVS","图片路径获取失败2");
|
||
return;
|
||
}
|
||
current_img_name = img_filename;
|
||
}
|
||
|
||
// 构建图片路径
|
||
snprintf(img_path, sizeof(img_path), "/spiflash/%s", current_img_name);// 格式化图片路径
|
||
ESP_LOGI("IMG", "准备显示图片: %s, 路径: %s", current_img_name, img_path);
|
||
|
||
// 检查文件是否存在
|
||
struct stat file_stat;
|
||
if(stat(img_path, &file_stat) != 0) {
|
||
ESP_LOGE("IMG", "文件不存在: %s", img_path);
|
||
return;
|
||
}
|
||
ESP_LOGI("IMG", "文件大小: %ld 字节", file_stat.st_size);
|
||
|
||
// 解码图片
|
||
esp_err_t ret = DecodeImg(img_path,&app_img_data,&outdata);// 解码图片
|
||
if(ret == ESP_OK){
|
||
ESP_LOGI("IMG", "图片解码成功,数据地址: %p, 宽度: %d, 高度: %d",
|
||
app_img_data, outdata.width, outdata.height);
|
||
|
||
// 检查解码后的数据
|
||
if(app_img_data == NULL) {
|
||
ESP_LOGE("IMG", "解码数据为空");
|
||
return;
|
||
}
|
||
|
||
// 配置图片数据
|
||
image.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||
image.header.always_zero = 0;
|
||
image.header.reserved = 0;
|
||
image.header.w = outdata.width;
|
||
image.header.h = outdata.height;
|
||
image.data_size = outdata.output_len;
|
||
image.data = app_img_data;
|
||
|
||
// 获取屏幕对象
|
||
act_mainscreen = lv_scr_act();
|
||
if(act_mainscreen == NULL) {
|
||
ESP_LOGE("IMG", "获取屏幕对象失败");
|
||
return;
|
||
}
|
||
|
||
// 如果图片对象不存在,创建它
|
||
if(app_img == NULL) {
|
||
app_img = lv_img_create(act_mainscreen);
|
||
if(app_img == NULL) {
|
||
ESP_LOGE("IMG", "创建图片对象失败");
|
||
return;
|
||
}
|
||
lv_obj_center(app_img);
|
||
ESP_LOGI("IMG", "创建图片对象成功");
|
||
}
|
||
|
||
// 更新图片显示
|
||
lvgl_port_lock(0);// 锁定LVGL端口
|
||
lv_img_set_src(app_img, &image);// 设置图片源
|
||
lv_scr_load(act_mainscreen);// 加载主屏幕
|
||
lvgl_port_unlock();// 解锁LVGL端口
|
||
|
||
ESP_LOGI("IMG", "图片显示成功: %s", current_img_name);
|
||
} else {
|
||
ESP_LOGE("IMG", "图片解码失败,错误码: %d", ret);
|
||
}
|
||
}
|
||
|
||
// 完整的图片显示初始化
|
||
void app_img_display(){
|
||
ESP_LOGI("IMG", "开始显示图片");
|
||
esp_err_t ret_nvs = nvs_read_img();// 从NVS中读取图片路径
|
||
if(ret_nvs != ESP_OK){
|
||
ESP_LOGE("NVS","图片路径获取失败1");
|
||
return;
|
||
}
|
||
ESP_LOGI("IMG", "图片路径: %s", img_filename);
|
||
snprintf(img_path, sizeof(img_path), "/spiflash/%s",img_filename);// 格式化图片路径
|
||
ESP_LOGI("IMG", "完整路径: %s", img_path);
|
||
|
||
// 检查文件是否存在
|
||
struct stat file_stat;
|
||
if(stat(img_path, &file_stat) != 0){
|
||
ESP_LOGE("IMG", "文件不存在");
|
||
return;
|
||
}
|
||
ESP_LOGI("IMG", "文件大小: %ld 字节", file_stat.st_size);
|
||
|
||
esp_err_t ret = DecodeImg(img_path,&app_img_data,&outdata);// 解码图片
|
||
if(ret == ESP_OK){
|
||
ESP_LOGI("IMG", "图片解码成功,数据地址: %p", app_img_data);
|
||
|
||
// 检查解码后的数据
|
||
if(app_img_data == NULL){
|
||
ESP_LOGE("IMG", "解码数据为空");
|
||
return;
|
||
}
|
||
|
||
image.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||
image.header.always_zero = 0;
|
||
image.header.reserved = 0;
|
||
image.header.w = outdata.width;
|
||
image.header.h = outdata.height;
|
||
image.data_size = outdata.output_len;
|
||
image.data = app_img_data;
|
||
|
||
ESP_LOGI("IMG", "LV_IMG_CF_RGB565 值: %d", LV_IMG_CF_RGB565);
|
||
ESP_LOGI("IMG", "设置图片数据: 宽度=%lu, 高度=%lu, 数据大小=%lu", (unsigned long)image.header.w, (unsigned long)image.header.h, (unsigned long)image.data_size);
|
||
|
||
act_mainscreen = lv_scr_act();// 获取当前主屏幕对象
|
||
if(act_mainscreen == NULL){
|
||
ESP_LOGE("IMG", "获取屏幕对象失败");
|
||
return;
|
||
}
|
||
ESP_LOGI("IMG", "获取屏幕对象成功");
|
||
|
||
app_img = lv_img_create(act_mainscreen);// 创建图片对象
|
||
if(app_img == NULL){
|
||
ESP_LOGE("IMG", "创建图片对象失败");
|
||
return;
|
||
}
|
||
ESP_LOGI("IMG", "创建图片对象成功");
|
||
|
||
lvgl_port_lock(0);// 锁定LVGL端口
|
||
ESP_LOGI("IMG", "设置图片源前");
|
||
lv_img_set_src(app_img, &image);// 设置图片源
|
||
ESP_LOGI("IMG", "设置图片源后");
|
||
lv_obj_center(app_img);// 居中显示图片
|
||
ESP_LOGI("IMG", "居中显示图片后");
|
||
lv_scr_load(act_mainscreen);// 加载主屏幕
|
||
ESP_LOGI("IMG", "加载主屏幕后");
|
||
lvgl_port_unlock();// 解锁LVGL端口
|
||
vTaskDelay(50);// 延时50ms
|
||
pwm_init();// 初始化PWM
|
||
ESP_LOGI("IMG", "图片显示完成");
|
||
} else {
|
||
ESP_LOGE("IMG", "图片解码失败,错误码: %d", ret);
|
||
}
|
||
}
|
||
|
||
// // 图片切换任务
|
||
// void img_switch_task(void *pvParameters) {
|
||
// char *image_files[] = {"default.jpg", "02.jpg"};
|
||
// int file_count = 2;
|
||
// int current_index = 0;
|
||
|
||
// while(1) {
|
||
// // 释放之前的图片数据
|
||
// if(app_img_data) {
|
||
// free(app_img_data);
|
||
// app_img_data = NULL;
|
||
// }
|
||
|
||
// // 获取当前要显示的图片文件名
|
||
// const char *current_image = image_files[current_index];
|
||
// ESP_LOGI("IMG_SWITCH", "切换到图片: %s", current_image);
|
||
|
||
// // 构建图片路径
|
||
// snprintf(img_path, sizeof(img_path), "/spiflash/%s", current_image);
|
||
// ESP_LOGI("IMG_SWITCH", "图片路径: %s", img_path);
|
||
|
||
// // 检查文件是否存在
|
||
// struct stat file_stat;
|
||
// if(stat(img_path, &file_stat) != 0) {
|
||
// ESP_LOGE("IMG_SWITCH", "文件不存在");
|
||
// vTaskDelay(pdMS_TO_TICKS(2000));
|
||
// continue;
|
||
// }
|
||
// ESP_LOGI("IMG_SWITCH", "文件大小: %ld 字节", file_stat.st_size);
|
||
|
||
// // 解码图片
|
||
// esp_err_t ret = DecodeImg(img_path, &app_img_data, &outdata);
|
||
// if(ret == ESP_OK) {
|
||
// ESP_LOGI("IMG_SWITCH", "图片解码成功,数据地址: %p", app_img_data);
|
||
|
||
// // 检查解码后的数据
|
||
// if(app_img_data == NULL) {
|
||
// ESP_LOGE("IMG_SWITCH", "解码数据为空");
|
||
// vTaskDelay(pdMS_TO_TICKS(2000));
|
||
// continue;
|
||
// }
|
||
|
||
// // 配置图片数据
|
||
// image.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||
// image.header.always_zero = 0;
|
||
// image.header.reserved = 0;
|
||
// image.header.w = outdata.width;
|
||
// image.header.h = outdata.height;
|
||
// image.data_size = outdata.output_len;
|
||
// image.data = app_img_data;
|
||
|
||
// // 获取屏幕对象
|
||
// act_mainscreen = lv_scr_act();
|
||
// if(act_mainscreen == NULL) {
|
||
// ESP_LOGE("IMG_SWITCH", "获取屏幕对象失败");
|
||
// vTaskDelay(pdMS_TO_TICKS(2000));
|
||
// continue;
|
||
// }
|
||
|
||
// // 如果图片对象不存在,创建它
|
||
// if(app_img == NULL) {
|
||
// app_img = lv_img_create(act_mainscreen);
|
||
// if(app_img == NULL) {
|
||
// ESP_LOGE("IMG_SWITCH", "创建图片对象失败");
|
||
// vTaskDelay(pdMS_TO_TICKS(2000));
|
||
// continue;
|
||
// }
|
||
// lv_obj_center(app_img);
|
||
// }
|
||
|
||
// // 更新图片显示
|
||
// lvgl_port_lock(0);
|
||
// lv_img_set_src(app_img, &image);
|
||
// lv_scr_load(act_mainscreen);
|
||
// lvgl_port_unlock();
|
||
|
||
// ESP_LOGI("IMG_SWITCH", "图片显示成功");
|
||
// } else {
|
||
// ESP_LOGE("IMG_SWITCH", "图片解码失败,错误码: %d", ret);
|
||
// }
|
||
|
||
// // 切换到下一张图片
|
||
// current_index = (current_index + 1) % file_count;
|
||
|
||
// // 等待2秒
|
||
// vTaskDelay(pdMS_TO_TICKS(2000));
|
||
// }
|
||
// }
|
||
|
||
// 新的显示测试屏幕函数
|
||
void app_test_display(){
|
||
lvgl_port_lock(0);// 锁定LVGL端口
|
||
|
||
// 获取或创建屏幕对象
|
||
lv_obj_t *screen = lv_scr_act();
|
||
if(screen == NULL) {
|
||
screen = lv_obj_create(NULL);// 创建屏幕对象
|
||
if(screen == NULL) {
|
||
ESP_LOGE("TEST", "Failed to create screen object");// 创建屏幕对象失败
|
||
lvgl_port_unlock();// 解锁LVGL端口
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 清空屏幕
|
||
lv_obj_clean(screen);
|
||
|
||
// 创建标签
|
||
lv_obj_t *label = lv_label_create(screen);
|
||
if(label) {
|
||
lv_label_set_text(label, "Test Screen\nLCD is working!");// 设置标签文本
|
||
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
|
||
|
||
// 创建基本样式
|
||
static lv_style_t style;// 基本样式
|
||
lv_style_init(&style);// 初始化基本样式
|
||
lv_style_set_text_font(&style, &lv_font_montserrat_14);// 设置字体
|
||
lv_style_set_text_color(&style, lv_color_hex(0xf9076a));// 设置文本颜色
|
||
lv_style_set_bg_color(&style, lv_color_hex(0x000000));// 设置背景颜色
|
||
|
||
lv_obj_add_style(label, &style, 0);// 添加样式到标签
|
||
}
|
||
lv_scr_load(screen);// 加载屏幕
|
||
lvgl_port_unlock();// 解锁LVGL端口
|
||
}
|
||
|
||
|
||
// 图片循环显示任务 - 显示spiffs中的所有图片
|
||
void img_loop_task(void *pvParameters) {
|
||
// 存储SPIFFS中的图片文件名
|
||
char *image_files[10]; // 最多支持10张图片
|
||
int file_count = 0;
|
||
int current_index = 0;
|
||
static bool backlight_initialized = false;
|
||
|
||
// 初始化背光(只执行一次)
|
||
if(!backlight_initialized) {
|
||
pwm_init();
|
||
backlight_initialized = true;// 初始化背光
|
||
ESP_LOGI("IMG_LOOP", "背光初始化完成");
|
||
}
|
||
|
||
while(1) {
|
||
// 重新扫描SPIFFS中的图片文件
|
||
ESP_LOGI("IMG_LOOP", "开始扫描SPIFFS中的图片文件");
|
||
|
||
// 打开SPIFFS目录
|
||
DIR *dir = opendir("/spiflash");
|
||
if(!dir) {
|
||
ESP_LOGE("IMG_LOOP", "无法打开SPIFFS目录");
|
||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||
continue;
|
||
}
|
||
|
||
// 重置文件计数
|
||
file_count = 0;
|
||
|
||
// 遍历目录
|
||
struct dirent *entry;
|
||
while((entry = readdir(dir)) != NULL && file_count < 10) {
|
||
// 检查是否是图片文件(.jpg, .jpeg, .png等)
|
||
const char *name = entry->d_name;
|
||
int len = strlen(name);
|
||
if(len > 4) {
|
||
const char *ext = name + len - 4;
|
||
if(strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0 ||
|
||
strcasecmp(ext, ".png") == 0 || strcasecmp(ext, ".bmp") == 0) {
|
||
// 存储图片文件名
|
||
image_files[file_count] = strdup(name);
|
||
ESP_LOGI("IMG_LOOP", "发现图片文件: %s", name);
|
||
file_count++;
|
||
}
|
||
}
|
||
}
|
||
|
||
closedir(dir);
|
||
|
||
// 检查是否找到图片
|
||
if(file_count == 0) {
|
||
ESP_LOGE("IMG_LOOP", "未找到图片文件");
|
||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||
continue;
|
||
}
|
||
|
||
ESP_LOGI("IMG_LOOP", "共发现 %d 张图片,开始循环显示", file_count);
|
||
|
||
// 循环显示所有图片
|
||
for(current_index = 0; current_index < file_count; current_index++) {
|
||
const char *current_image = image_files[current_index];
|
||
ESP_LOGI("IMG_LOOP", "显示图片 %d/%d: %s", current_index + 1, file_count, current_image);
|
||
|
||
// 使用修改后的app_img_change函数显示图片
|
||
app_img_change(current_image);
|
||
|
||
// 等待3秒
|
||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||
}
|
||
|
||
// 释放文件名内存
|
||
for(int i = 0; i < file_count; i++) {
|
||
free(image_files[i]);
|
||
}
|
||
|
||
// 再次扫描前短暂延时
|
||
vTaskDelay(pdMS_TO_TICKS(500));
|
||
}
|
||
}
|
||
|
||
// 图片切换任务 - 显示指定的两张图片
|
||
void img_switch_task(void *pvParameters) {
|
||
char *image_files[] = {"default.jpg", "02.jpg"};
|
||
int file_count = 2;
|
||
int current_index = 0;
|
||
|
||
while(1) {
|
||
// 使用修改后的app_img_change函数显示图片
|
||
const char *current_image = image_files[current_index];
|
||
ESP_LOGI("IMG_SWITCH", "切换到图片: %s", current_image);
|
||
app_img_change(current_image);
|
||
|
||
// 切换到下一张图片
|
||
current_index = (current_index + 1) % file_count;
|
||
|
||
// 等待2秒
|
||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||
}
|
||
}
|
||
|
||
// 初始化SPIFFS图片列表
|
||
void init_spiffs_image_list(void) {
|
||
if(image_list_initialized) {
|
||
ESP_LOGI("IMG_LIST", "图片列表已初始化,跳过");
|
||
return;
|
||
}
|
||
|
||
ESP_LOGI("IMG_LIST", "开始扫描SPIFFS中的图片文件");
|
||
|
||
// 打开SPIFFS目录
|
||
DIR *dir = opendir("/spiflash");
|
||
if(!dir) {
|
||
ESP_LOGE("IMG_LIST", "无法打开SPIFFS目录");
|
||
return;
|
||
}
|
||
|
||
// 重置文件计数
|
||
spiffs_image_count = 0;
|
||
|
||
// 遍历目录
|
||
struct dirent *entry;
|
||
while((entry = readdir(dir)) != NULL && spiffs_image_count < MAX_IMAGE_FILES) {
|
||
// 检查是否是图片文件(.jpg, .jpeg, .png等)
|
||
const char *name = entry->d_name;
|
||
int len = strlen(name);
|
||
if(len > 4 && len < MAX_FILENAME_LEN) {
|
||
const char *ext = name + len - 4;
|
||
if(strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0 ||
|
||
strcasecmp(ext, ".png") == 0 || strcasecmp(ext, ".bmp") == 0) {
|
||
// 存储图片文件名到静态缓冲区
|
||
strncpy(spiffs_image_files[spiffs_image_count], name, MAX_FILENAME_LEN - 1);
|
||
spiffs_image_files[spiffs_image_count][MAX_FILENAME_LEN - 1] = '\0';
|
||
ESP_LOGI("IMG_LIST", "发现图片文件: %s", name);
|
||
spiffs_image_count++;
|
||
}
|
||
}
|
||
}
|
||
|
||
closedir(dir);
|
||
|
||
// 检查是否找到图片
|
||
if(spiffs_image_count == 0) {
|
||
ESP_LOGE("IMG_LIST", "未找到图片文件");
|
||
return;
|
||
}
|
||
|
||
image_list_initialized = true;
|
||
ESP_LOGI("IMG_LIST", "图片列表初始化完成,共发现 %d 张图片", spiffs_image_count);
|
||
|
||
// 查找default.jpg并设置为当前索引
|
||
for(int i = 0; i < spiffs_image_count; i++) {
|
||
if(strcmp(spiffs_image_files[i], "default.jpg") == 0) {
|
||
current_image_index = i;
|
||
ESP_LOGI("IMG_LIST", "设置默认图片索引: %d", current_image_index);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取下一张图片
|
||
const char* get_next_image(void) {
|
||
if(!image_list_initialized || spiffs_image_count == 0) {
|
||
ESP_LOGE("IMG_LIST", "图片列表未初始化或为空");
|
||
return NULL;
|
||
}
|
||
|
||
current_image_index = (current_image_index + 1) % spiffs_image_count;
|
||
ESP_LOGI("IMG_LIST", "切换到下一张图片,索引: %d/%d", current_image_index + 1, spiffs_image_count);
|
||
return spiffs_image_files[current_image_index];
|
||
}
|
||
|
||
// 获取上一张图片
|
||
const char* get_prev_image(void) {
|
||
if(!image_list_initialized || spiffs_image_count == 0) {
|
||
ESP_LOGE("IMG_LIST", "图片列表未初始化或为空");
|
||
return NULL;
|
||
}
|
||
|
||
current_image_index = (current_image_index - 1 + spiffs_image_count) % spiffs_image_count;
|
||
ESP_LOGI("IMG_LIST", "切换到上一张图片,索引: %d/%d", current_image_index + 1, spiffs_image_count);
|
||
return spiffs_image_files[current_image_index];
|
||
}
|
||
|
||
// 重置图片列表
|
||
void free_spiffs_image_list(void) {
|
||
if(!image_list_initialized) {
|
||
return;
|
||
}
|
||
|
||
spiffs_image_count = 0;
|
||
current_image_index = 0;
|
||
image_list_initialized = false;
|
||
|
||
ESP_LOGI("IMG_LIST", "图片列表已重置");
|
||
}
|
||
|
||
// 获取当前图片文件名
|
||
const char* get_current_image(void) {
|
||
if(!image_list_initialized || spiffs_image_count == 0) {
|
||
ESP_LOGE("IMG_LIST", "图片列表未初始化或为空");
|
||
return NULL;
|
||
}
|
||
|
||
return spiffs_image_files[current_image_index];
|
||
}
|
||
|
||
// 删除当前图片并从列表中移除
|
||
bool delete_current_image(void) {
|
||
if(!image_list_initialized || spiffs_image_count == 0) {
|
||
ESP_LOGE("IMG_DEL", "图片列表未初始化或为空");
|
||
return false;
|
||
}
|
||
|
||
const char *current_img = spiffs_image_files[current_image_index];
|
||
|
||
// 构建完整路径
|
||
char full_path[64];
|
||
snprintf(full_path, sizeof(full_path), "/spiflash/%s", current_img);
|
||
|
||
ESP_LOGI("IMG_DEL", "准备删除图片: %s", full_path);
|
||
|
||
// 从SPIFFS文件系统中删除文件
|
||
if(unlink(full_path) != 0) {
|
||
ESP_LOGE("IMG_DEL", "删除文件失败: %s", full_path);
|
||
return false;
|
||
}
|
||
|
||
ESP_LOGI("IMG_DEL", "文件删除成功: %s", current_img);
|
||
|
||
// 从列表中移除该图片
|
||
for(int i = current_image_index; i < spiffs_image_count - 1; i++) {
|
||
strncpy(spiffs_image_files[i], spiffs_image_files[i + 1], MAX_FILENAME_LEN);
|
||
}
|
||
|
||
// 更新计数
|
||
spiffs_image_count--;
|
||
|
||
// 如果列表为空,重置状态
|
||
if(spiffs_image_count == 0) {
|
||
ESP_LOGI("IMG_DEL", "所有图片已删除");
|
||
image_list_initialized = false;
|
||
current_image_index = 0;
|
||
return true;
|
||
}
|
||
|
||
// 调整当前索引(如果删除的是最后一张,回到第一张)
|
||
if(current_image_index >= spiffs_image_count) {
|
||
current_image_index = 0;
|
||
}
|
||
|
||
ESP_LOGI("IMG_DEL", "图片列表已更新,剩余 %d 张图片,当前索引: %d",
|
||
spiffs_image_count, current_image_index);
|
||
|
||
return true;
|
||
}
|
||
|
||
// 更新ui_ImgBle控件的图片
|
||
void update_ui_ImgBle(const char *img_name) {
|
||
if(!img_name) {
|
||
ESP_LOGE("IMG_UI", "图片名为空");
|
||
return;
|
||
}
|
||
|
||
if(!ui_ImgBle) {
|
||
ESP_LOGE("IMG_UI", "ui_ImgBle控件不存在");
|
||
return;
|
||
}
|
||
|
||
static uint8_t *ui_img_data = NULL;
|
||
static lv_img_dsc_t ui_image;
|
||
|
||
// 释放之前的图片数据
|
||
if(ui_img_data) {
|
||
free(ui_img_data);
|
||
ui_img_data = NULL;
|
||
ESP_LOGI("IMG_UI", "释放之前的图片数据");
|
||
}
|
||
|
||
// 构建图片路径
|
||
snprintf(img_path, sizeof(img_path), "/spiflash/%s", img_name);
|
||
ESP_LOGI("IMG_UI", "准备显示图片: %s, 路径: %s", img_name, img_path);
|
||
|
||
// 检查文件是否存在
|
||
struct stat file_stat;
|
||
if(stat(img_path, &file_stat) != 0) {
|
||
ESP_LOGE("IMG_UI", "文件不存在: %s", img_path);
|
||
return;
|
||
}
|
||
ESP_LOGI("IMG_UI", "文件大小: %ld 字节", file_stat.st_size);
|
||
|
||
// 解码图片
|
||
esp_jpeg_image_output_t ui_outdata;
|
||
esp_err_t ret = DecodeImg(img_path, &ui_img_data, &ui_outdata);
|
||
if(ret == ESP_OK) {
|
||
ESP_LOGI("IMG_UI", "图片解码成功,宽度: %d, 高度: %d", ui_outdata.width, ui_outdata.height);
|
||
|
||
// 检查解码后的数据
|
||
if(ui_img_data == NULL) {
|
||
ESP_LOGE("IMG_UI", "解码数据为空");
|
||
return;
|
||
}
|
||
|
||
// 配置图片数据
|
||
ui_image.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||
ui_image.header.always_zero = 0;
|
||
ui_image.header.reserved = 0;
|
||
ui_image.header.w = ui_outdata.width;
|
||
ui_image.header.h = ui_outdata.height;
|
||
ui_image.data_size = ui_outdata.output_len;
|
||
ui_image.data = ui_img_data;
|
||
|
||
// 更新ui_ImgBle控件的图片
|
||
lvgl_port_lock(0);
|
||
lv_img_set_src(ui_ImgBle, &ui_image);
|
||
lvgl_port_unlock();
|
||
|
||
ESP_LOGI("IMG_UI", "ui_ImgBle图片更新成功: %s", img_name);
|
||
} else {
|
||
ESP_LOGE("IMG_UI", "图片解码失败,错误码: %d", ret);
|
||
}
|
||
}
|
||
|
||
|
||
// // 原本显示测试屏幕 代码
|
||
// void app_test_display(){
|
||
// lvgl_port_lock(0);// 锁定LVGL端口
|
||
// act_testscreen = lv_scr_act();// 获取当前测试屏幕对象
|
||
// // 创建列表样式
|
||
// static lv_style_t list_style;
|
||
// lv_style_init(&list_style);
|
||
// lv_style_set_bg_color(&list_style, lv_color_hex(0x1E1E1E));
|
||
// lv_style_set_border_width(&list_style, 0);
|
||
// lv_style_set_radius(&list_style, 0);
|
||
// lv_style_set_text_align(&list_style, LV_TEXT_ALIGN_CENTER);
|
||
|
||
// // 创建列表项样式
|
||
// static lv_style_t list_item_style;
|
||
// lv_style_init(&list_item_style);
|
||
// lv_style_set_bg_color(&list_item_style, lv_color_hex(0x2E2E2E));
|
||
// lv_style_set_bg_opa(&list_item_style, LV_OPA_100);
|
||
// lv_style_set_text_color(&list_item_style, lv_color_hex(0xFFFFFF));
|
||
// lv_style_set_pad_all(&list_item_style, 10);
|
||
// lv_style_set_min_height(&list_item_style, 80); // 设置最小高度为60px
|
||
|
||
// // 创建选中项样式
|
||
// static lv_style_t list_item_selected_style;
|
||
// lv_style_init(&list_item_selected_style);
|
||
// lv_style_set_bg_color(&list_item_selected_style, lv_color_hex(0x4A90E2));
|
||
// lv_style_set_text_color(&list_item_selected_style, lv_color_hex(0xFFFFFF));
|
||
|
||
|
||
// lv_obj_t* list = lv_list_create(act_testscreen);
|
||
// lv_obj_add_style(list, &list_style, 0);
|
||
// lv_obj_set_size(list, lv_pct(100), lv_pct(100));
|
||
// lv_obj_t* list_item1 = lv_list_add_btn(list, LV_SYMBOL_FILE, "FLASH TEST");
|
||
// lv_obj_t* list_item2 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI SCAN START TEST");
|
||
// lv_obj_t* list_item3 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI SCAN LIST TEST");
|
||
// lv_obj_t* list_item5 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI CONNECT TEST");
|
||
// lv_obj_t* list_item6 = lv_list_add_btn(list, LV_SYMBOL_FILE, "WIFI DISCONNECT TEST");
|
||
// lv_obj_t* list_item4 = lv_list_add_btn(list, LV_SYMBOL_FILE, "FREE HEAP");
|
||
// lv_obj_t* list_item7 = lv_list_add_btn(list, LV_SYMBOL_FILE, "TEMP TEST");
|
||
|
||
|
||
// lv_obj_add_event_cb(list_item1, fs_test, LV_EVENT_CLICKED, NULL);
|
||
// lv_obj_add_event_cb(list_item2, wifi_scan_start_test, LV_EVENT_CLICKED, NULL);
|
||
// lv_obj_add_event_cb(list_item3, wifi_scan_list_test, LV_EVENT_CLICKED, NULL);
|
||
// lv_obj_add_event_cb(list_item4, free_heap_test, LV_EVENT_CLICKED, NULL);
|
||
// lv_obj_add_event_cb(list_item5, wifi_connect_test, LV_EVENT_CLICKED, NULL);
|
||
// lv_obj_add_event_cb(list_item6, wifi_disconnect_test, LV_EVENT_CLICKED, NULL);
|
||
// lv_obj_add_event_cb(list_item7, temp_test, LV_EVENT_CLICKED, NULL);
|
||
|
||
|
||
// lv_obj_t *parent = lv_obj_create(act_mainscreen);
|
||
// lv_obj_set_size(parent, 150, 150);
|
||
// lv_obj_center(parent); // 居中显示
|
||
|
||
// static lv_style_t style_bg_black;
|
||
// lv_style_init(&style_bg_black);
|
||
// lv_style_set_bg_color(&style_bg_black, lv_color_hex(0x000000));
|
||
// lv_obj_add_style(act_mainscreen, &style_bg_black, 0);
|
||
|
||
// static lv_style_t style_img;
|
||
// lv_style_init(&style_img);
|
||
// lv_style_set_radius(&style_img,75);
|
||
// lv_style_set_clip_corner(&style_img, true);
|
||
// lv_style_set_border_width(&style_img,75);
|
||
// lv_obj_add_style(parent,&style_img,0);
|
||
|
||
|
||
// // lv_obj_t *img = lv_img_create(act_mainscreen);
|
||
// // lv_img_set_src(img,"C:spiflash/face_1759919044875.jpg");
|
||
|
||
// lv_scr_load(act_testscreen);
|
||
|
||
// lvgl_port_unlock();
|
||
// vTaskDelay(50);
|
||
// pwm_init();
|
||
|
||
// }
|