Rdzleo 9223fd5a7d feat: BLE传输优化 + GIF条件编译 + 自定义GIF播放器
一、BLE 蓝牙优化
- 设备名称改为动态名称 Airhub_MAC(基于BLE MAC地址)
- 广播数据拆分为 ADV + Scan Response 两包
- 图片接收完成后数据直通显示(跳过SPIFFS重读,减少200-500ms延迟)
- BLE耗时操作(NVS写入+导航显示)转移到独立FreeRTOS任务,避免BTC_TASK栈溢出
- 缩短BLE连接间隔(min=7.5ms, max=20ms),提升传输吞吐量
- 减少传输日志输出(每100包打印一次),提升传输速度

二、显示性能优化
- LVGL绘制缓冲区从DMA 30行改为PSRAM 120行大缓冲,减少flush次数
- CPU最大频率从160MHz提升到240MHz,提升解码性能

三、GIF动图支持(条件编译,当前默认关闭)
- 实现自定义GIF播放器:Palette LUT查表 + TRUE_COLOR无Alpha + 后台线程解码流水线
- 使用 #if LV_USE_GIF 条件编译包裹所有GIF代码,sdkconfig中CONFIG_LV_USE_GIF=n时零开销
- 启用GIF时需设置 CONFIG_LV_USE_GIF=y 即可

四、图片管理优化
- BLE接收新图片后直接追加到列表(避免重扫SPIFFS目录)
- SPIFFS图片扫描支持.gif扩展名(条件编译控制)

五、文档更新
- 设备运行日志:GIF性能瓶颈分析与优化方案

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 09:43:02 +08:00

1330 lines
47 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 "lvgl.h"
#if LV_USE_GIF
#include "extra/libs/gif/lv_gif.h"
#include "extra/libs/gif/gifdec.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#endif
#include "esp_heap_caps.h"
#include "fatfs.h"
#include "driver/ledc.h"
#include "gpio.h"
#include "wifi.h"
#include "jpeg_decoder.h"
#include "../ui/screens/ui_ScreenImg.h"
#include <inttypes.h>
// 前向声明界面切换函数
extern void _ui_screen_change(lv_obj_t **target, lv_scr_load_anim_t fademode, int spd, int delay, void (*target_init)(void));
#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;
#if LV_USE_GIF
// === 自定义 GIF 播放器(替代 lv_gif性能优化 ===
// 优化: 1.Palette LUT查表 2.TRUE_COLOR无Alpha 3.后台线程解码流水线
static gd_GIF *gif_decoder = NULL; // gifdec 解码句柄
static lv_obj_t *gif_img_obj = NULL; // 普通 lv_img 控件(替代 lv_gif
static uint16_t *gif_rgb565_buf[2] = {NULL, NULL}; // 双缓冲 RGB565 帧 (PSRAM)
static lv_img_dsc_t gif_frame_dsc; // LVGL 图片描述符TRUE_COLOR无Alpha
static uint16_t gif_palette_lut[256]; // 调色板 RGB565 查找表
static volatile uint8_t gif_front_idx = 0; // 当前显示的缓冲区索引
static lv_timer_t *gif_play_timer = NULL; // LVGL 播放定时器
static TaskHandle_t gif_decode_task_handle = NULL; // 后台解码任务句柄
static volatile bool gif_playing = false; // 播放状态标志
static volatile bool gif_new_frame_ready = false; // 新帧就绪标志
static uint32_t gif_last_frame_ms = 0; // 上一帧显示时间戳
static uint8_t *gif_psram_buf = NULL; // GIF 文件数据PSRAM
static bool current_is_gif = false; // 当前是否为 GIF 模式
// 前向声明
static bool is_gif_file(const char *filename);
static void gif_player_start(void);
static void gif_player_stop(void);
#endif // LV_USE_GIF
// 当前亮度值(用于休眠恢复)
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(){
}
// 从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
#if LV_USE_GIF
|| strcasecmp(ext, ".gif") == 0
#endif
) {
// 存储图片文件名到静态缓冲区
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", "图片列表已重置");
}
// 根据文件名设置当前图片索引
bool set_image_index_by_name(const char *name) {
if(!image_list_initialized || spiffs_image_count == 0 || !name) {
return false;
}
for(int i = 0; i < spiffs_image_count; i++) {
if(strcmp(spiffs_image_files[i], name) == 0) {
current_image_index = i;
ESP_LOGI("IMG_LIST", "设置图片索引为 %d: %s", i, name);
return true;
}
}
ESP_LOGW("IMG_LIST", "未找到图片: %s", name);
return false;
}
// BLE接收图片后导航到ScreenImg显示
void ble_image_navigate(const char *filename) {
// 将新文件直接追加到列表(避免重扫 SPIFFS 目录,节省 ~200ms
if (!image_list_initialized) {
init_spiffs_image_list();
}
// 检查文件是否已在列表中(避免重复)
bool found = false;
for (int i = 0; i < spiffs_image_count; i++) {
if (strcmp(spiffs_image_files[i], filename) == 0) {
current_image_index = i;
found = true;
break;
}
}
if (!found && spiffs_image_count < MAX_IMAGE_FILES) {
strncpy(spiffs_image_files[spiffs_image_count], filename, MAX_FILENAME_LEN - 1);
spiffs_image_files[spiffs_image_count][MAX_FILENAME_LEN - 1] = '\0';
current_image_index = spiffs_image_count;
spiffs_image_count++;
}
// 检查是否已在ScreenImg界面
lvgl_port_lock(0);
bool already_on_screen = (lv_scr_act() == ui_ScreenImg);
if (!already_on_screen) {
// 不在ScreenImg导航过去SCREEN_LOADED事件会触发update_ui_ImgBle
_ui_screen_change(&ui_ScreenImg, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenImg_screen_init);
}
lvgl_port_unlock();
// 已在ScreenImg时_ui_screen_change不会触发SCREEN_LOADED需手动更新图片
if (already_on_screen) {
update_ui_ImgBle(filename);
}
ESP_LOGI("IMG_LIST", "BLE导航到ScreenImg显示: %s", filename);
}
// BLE接收图片后导航显示携带预加载数据跳过 SPIFFS 重读)
void ble_image_navigate_with_data(const char *filename, uint8_t *data, size_t data_size) {
// 将新文件追加到列表
if (!image_list_initialized) {
init_spiffs_image_list();
}
bool found = false;
for (int i = 0; i < spiffs_image_count; i++) {
if (strcmp(spiffs_image_files[i], filename) == 0) {
current_image_index = i;
found = true;
break;
}
}
if (!found && spiffs_image_count < MAX_IMAGE_FILES) {
strncpy(spiffs_image_files[spiffs_image_count], filename, MAX_FILENAME_LEN - 1);
spiffs_image_files[spiffs_image_count][MAX_FILENAME_LEN - 1] = '\0';
current_image_index = spiffs_image_count;
spiffs_image_count++;
}
#if LV_USE_GIF
// 如果有预加载数据且是 GIF直接用内存数据显示跳过 SPIFFS 重读)
if (data && data_size > 0 && is_gif_file(filename)) {
// 停止旧 GIF 播放器
gif_player_stop();
if (gif_psram_buf) {
free(gif_psram_buf);
}
// BLE 数据直接作为 GIF 源(所有权转移)
gif_psram_buf = data;
// 打开 gifdec 解码器(从 PSRAM 内存源)
gif_decoder = gd_open_gif_data(gif_psram_buf);
if (!gif_decoder) {
ESP_LOGE("GIF", "gifdec 打开失败");
free(gif_psram_buf);
gif_psram_buf = NULL;
return;
}
// 确保在 ScreenImg 界面
lvgl_port_lock(0);
bool already_on_screen = (lv_scr_act() == ui_ScreenImg);
if (!already_on_screen) {
_ui_screen_change(&ui_ScreenImg, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenImg_screen_init);
}
lvgl_port_unlock();
// 启动自定义 GIF 播放器
gif_player_start();
ESP_LOGI("IMG_LIST", "BLE GIF直通显示(优化): %s", filename);
return;
}
#endif // LV_USE_GIF
// 非 GIF 或无预加载数据,释放 BLE 数据,走常规 SPIFFS 路径
if (data) {
free(data);
}
lvgl_port_lock(0);
bool already_on_screen = (lv_scr_act() == ui_ScreenImg);
if (!already_on_screen) {
_ui_screen_change(&ui_ScreenImg, LV_SCR_LOAD_ANIM_NONE, 0, 0, &ui_ScreenImg_screen_init);
}
lvgl_port_unlock();
if (already_on_screen) {
update_ui_ImgBle(filename);
}
ESP_LOGI("IMG_LIST", "BLE导航到ScreenImg显示: %s", filename);
}
// 获取当前图片文件名
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;
}
#if LV_USE_GIF
// 判断文件是否为 GIF 格式
static bool is_gif_file(const char *filename) {
int len = strlen(filename);
if (len < 4) return false;
return strcasecmp(filename + len - 4, ".gif") == 0;
}
// === GIF 播放器内部函数 ===
// 构建调色板 RGB565 查找表(每次帧解码前调用,处理局部调色板)
static void gif_build_palette_lut(gd_Palette *palette) {
for (int i = 0; i < palette->size; i++) {
uint8_t r = palette->colors[i * 3 + 0];
uint8_t g = palette->colors[i * 3 + 1];
uint8_t b = palette->colors[i * 3 + 2];
lv_color_t c = lv_color_make(r, g, b); // 自动处理 LV_COLOR_16_SWAP
gif_palette_lut[i] = c.full;
}
}
// 快速渲染帧到 canvaspalette LUT 替代逐像素 lv_color_make
static void gif_render_frame_fast(gd_GIF *gif) {
int i = gif->fy * gif->width + gif->fx;
for (int j = 0; j < gif->fh; j++) {
for (int k = 0; k < gif->fw; k++) {
uint8_t index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
if (!gif->gce.transparency || index != gif->gce.tindex) {
uint16_t c = gif_palette_lut[index];
gif->canvas[(i + k) * 3 + 0] = c & 0xff;
gif->canvas[(i + k) * 3 + 1] = (c >> 8) & 0xff;
gif->canvas[(i + k) * 3 + 2] = 0xff;
}
}
i += gif->width;
}
}
// canvas (RGB565+Alpha 3字节/像素) → RGB565 (2字节/像素) 快速拷贝
static void gif_canvas_to_rgb565(gd_GIF *gif, uint16_t *out) {
uint8_t *canvas = gif->canvas;
int total = gif->width * gif->height;
for (int i = 0; i < total; i++) {
out[i] = (uint16_t)canvas[i * 3] | ((uint16_t)canvas[i * 3 + 1] << 8);
}
}
// 后台解码任务FreeRTOS与 LVGL 显示并行)
static void gif_decode_task(void *pvParameters) {
while (1) {
// 等待解码请求
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (!gif_playing || !gif_decoder) break;
// LZW 解码(含 dispose 处理)
int ret = gd_get_frame(gif_decoder);
if (ret == 0) {
// GIF 循环播放
gd_rewind(gif_decoder);
ret = gd_get_frame(gif_decoder);
}
if (ret < 0 || !gif_playing) break;
// 更新调色板 LUT可能有局部调色板变化
gif_build_palette_lut(gif_decoder->palette);
// 快速渲染到 canvas + 拷贝到后台 RGB565 缓冲
gif_render_frame_fast(gif_decoder);
uint8_t back = 1 - gif_front_idx;
gif_canvas_to_rgb565(gif_decoder, gif_rgb565_buf[back]);
// 标记新帧就绪
gif_new_frame_ready = true;
}
gif_decode_task_handle = NULL;
vTaskDelete(NULL);
}
// LVGL 定时器回调(检查新帧并切换显示)
static void gif_play_timer_cb(lv_timer_t *t) {
if (!gif_playing || !gif_decoder || !gif_img_obj) return;
// 检查 GIF 帧延时
uint32_t delay_ms = gif_decoder->gce.delay * 10;
if (delay_ms < 20) delay_ms = 20;
uint32_t elapsed = lv_tick_elaps(gif_last_frame_ms);
if (elapsed < delay_ms) return;
if (!gif_new_frame_ready) return;
gif_last_frame_ms = lv_tick_get();
gif_new_frame_ready = false;
// 切换前后缓冲
uint8_t back = 1 - gif_front_idx;
gif_front_idx = back;
// 更新 LVGL 图片源TRUE_COLOR无 Alpha 混合)
gif_frame_dsc.data = (uint8_t *)gif_rgb565_buf[gif_front_idx];
lv_img_cache_invalidate_src(&gif_frame_dsc);
lv_obj_invalidate(gif_img_obj);
// 通知后台线程解码下一帧
if (gif_decode_task_handle) {
xTaskNotifyGive(gif_decode_task_handle);
}
}
// 启动自定义 GIF 播放器
static void gif_player_start(void) {
if (!gif_decoder || !gif_psram_buf) return;
uint16_t w = gif_decoder->width;
uint16_t h = gif_decoder->height;
size_t buf_size = w * h * sizeof(uint16_t);
// 分配双缓冲 RGB565 帧 (PSRAM)
for (int i = 0; i < 2; i++) {
if (!gif_rgb565_buf[i]) {
gif_rgb565_buf[i] = heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM);
if (!gif_rgb565_buf[i]) {
ESP_LOGE("GIF", "RGB565 缓冲分配失败: %d", i);
return;
}
}
memset(gif_rgb565_buf[i], 0, buf_size);
}
// 初始化调色板 LUT
gif_build_palette_lut(gif_decoder->palette);
// 同步解码第一帧(确保立即显示)
int ret = gd_get_frame(gif_decoder);
if (ret <= 0) {
ESP_LOGE("GIF", "首帧解码失败");
return;
}
gif_render_frame_fast(gif_decoder);
gif_canvas_to_rgb565(gif_decoder, gif_rgb565_buf[0]);
gif_front_idx = 0;
gif_new_frame_ready = false;
gif_last_frame_ms = lv_tick_get();
// 配置 LVGL 图片描述符TRUE_COLOR无 Alpha
gif_frame_dsc.header.cf = LV_IMG_CF_TRUE_COLOR;
gif_frame_dsc.header.always_zero = 0;
gif_frame_dsc.header.reserved = 0;
gif_frame_dsc.header.w = w;
gif_frame_dsc.header.h = h;
gif_frame_dsc.data_size = buf_size;
gif_frame_dsc.data = (uint8_t *)gif_rgb565_buf[0];
// 创建 lv_img 控件 + LVGL 播放定时器
lvgl_port_lock(0);
lv_obj_add_flag(ui_ImgBle, LV_OBJ_FLAG_HIDDEN);
gif_img_obj = lv_img_create(ui_ScreenImg);
lv_obj_set_size(gif_img_obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_align(gif_img_obj, LV_ALIGN_CENTER);
lv_obj_clear_flag(gif_img_obj, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_move_background(gif_img_obj);
lv_img_set_src(gif_img_obj, &gif_frame_dsc);
gif_play_timer = lv_timer_create(gif_play_timer_cb, 10, NULL);
lvgl_port_unlock();
// 启动后台解码任务
gif_playing = true;
current_is_gif = true;
xTaskCreatePinnedToCore(gif_decode_task, "gif_dec", 4096, NULL, 5, &gif_decode_task_handle, 1);
// 触发后台解码第2帧
xTaskNotifyGive(gif_decode_task_handle);
ESP_LOGI("GIF", "播放器启动: %dx%d, 双缓冲 %dKB×2", w, h, buf_size / 1024);
}
// 停止自定义 GIF 播放器
static void gif_player_stop(void) {
// 停止后台解码任务
gif_playing = false;
if (gif_decode_task_handle) {
xTaskNotifyGive(gif_decode_task_handle);
// 等待任务退出(最多 500ms
for (int i = 0; i < 50 && gif_decode_task_handle; i++) {
vTaskDelay(pdMS_TO_TICKS(10));
}
}
// 删除 LVGL 定时器和控件
lvgl_port_lock(0);
if (gif_play_timer) {
lv_timer_del(gif_play_timer);
gif_play_timer = NULL;
}
if (gif_img_obj) {
lv_obj_del(gif_img_obj);
gif_img_obj = NULL;
}
lvgl_port_unlock();
// 关闭 gifdec 解码器
if (gif_decoder) {
gd_close_gif(gif_decoder);
gif_decoder = NULL;
}
// 释放 RGB565 双缓冲
for (int i = 0; i < 2; i++) {
if (gif_rgb565_buf[i]) {
free(gif_rgb565_buf[i]);
gif_rgb565_buf[i] = NULL;
}
}
current_is_gif = false;
gif_new_frame_ready = false;
}
// 清理 GIF 资源(公开接口,供界面切换时调用)
void pages_cleanup_gif(void) {
gif_player_stop();
// 释放 GIF 源文件缓冲区
if (gif_psram_buf) {
free(gif_psram_buf);
gif_psram_buf = NULL;
}
}
#endif // LV_USE_GIF
// 更新ui_ImgBle控件的图片支持 JPEG
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;
// 构建图片路径
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);
#if LV_USE_GIF
if (is_gif_file(img_name)) {
// === GIF 显示路径自定义播放器Palette LUT + 无Alpha + 后台解码) ===
// 释放之前的 JPEG 数据
if(ui_img_data) {
free(ui_img_data);
ui_img_data = NULL;
}
// 停止旧 GIF 播放器 + 释放旧 GIF 数据
gif_player_stop();
if (gif_psram_buf) {
free(gif_psram_buf);
gif_psram_buf = NULL;
}
// 将 GIF 文件整体读入 PSRAM
FILE *gif_file = fopen(img_path, "rb");
if (!gif_file) {
ESP_LOGE("IMG_UI", "GIF文件打开失败: %s", img_path);
return;
}
gif_psram_buf = heap_caps_malloc(file_stat.st_size, MALLOC_CAP_SPIRAM);
if (!gif_psram_buf) {
ESP_LOGE("IMG_UI", "PSRAM分配失败: %ld 字节", file_stat.st_size);
fclose(gif_file);
return;
}
fread(gif_psram_buf, 1, file_stat.st_size, gif_file);
fclose(gif_file);
// 打开 gifdec 解码器(从 PSRAM 内存源)
gif_decoder = gd_open_gif_data(gif_psram_buf);
if (!gif_decoder) {
ESP_LOGE("IMG_UI", "gifdec 打开失败: %s", img_name);
free(gif_psram_buf);
gif_psram_buf = NULL;
return;
}
// 启动自定义 GIF 播放器Palette LUT + 双缓冲流水线)
gif_player_start();
ESP_LOGI("IMG_UI", "GIF显示启动(优化): %s", img_name);
} else
#endif // LV_USE_GIF
{
// === JPEG 显示路径 ===
#if LV_USE_GIF
// 停止 GIF 播放器并释放资源
gif_player_stop();
if (gif_psram_buf) {
free(gif_psram_buf);
gif_psram_buf = NULL;
}
// 恢复 JPEG 控件显示
lvgl_port_lock(0);
lv_obj_clear_flag(ui_ImgBle, LV_OBJ_FLAG_HIDDEN);
lvgl_port_unlock();
#endif
// 释放之前的图片数据
if(ui_img_data) {
free(ui_img_data);
ui_img_data = NULL;
ESP_LOGI("IMG_UI", "释放之前的图片数据");
}
// 解码图片
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;
lvgl_port_lock(0);
lv_img_set_src(ui_ImgBle, &ui_image);
lvgl_port_unlock();
ESP_LOGI("IMG_UI", "JPEG图片更新成功: %s", img_name);
} else {
ESP_LOGE("IMG_UI", "图片解码失败,错误码: %d", ret);
ui_img_data = NULL;
}
}
}
// // 原本显示测试屏幕 代码
// 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();
// }