feat: 启用 BLE 5.0 2M PHY 图传加速 + BLE 断连内存泄漏修复 + 滑动跳过无效图片

1、ble.c 新增 BLE 5.0 2M PHY 请求(连接时自动协商,不支持则回退 1M);
2、ble.c 新增 PHY 更新事件日志(tx_phy/rx_phy: 1=1M, 2=2M, 3=Coded);
3、ble.c 断连时清理未完成的图片传输状态,释放 img_data/filepath/file_img 防止内存泄漏;
4、sdkconfig 启用 BLE 5.0 全部子特性 + 保留 BLE 4.2 兼容;
5、update_ui_ImgBle 返回类型 void → bool,滑动时自动跳过解码失败的无效图片;

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rdzleo 2026-03-24 18:23:15 +08:00
parent 6711a24a68
commit da64d3e930
5 changed files with 55 additions and 17 deletions

View File

@ -258,6 +258,13 @@ static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *par
param->update_conn_params.latency, param->update_conn_params.latency,
param->update_conn_params.timeout); param->update_conn_params.timeout);
break; break;
case ESP_GAP_BLE_PHY_UPDATE_COMPLETE_EVT:
ESP_LOGI(CONN_TAG, "PHY update, status %d, tx_phy %d, rx_phy %d",
param->phy_update.status,
param->phy_update.tx_phy,
param->phy_update.rx_phy);
// tx_phy/rx_phy: 1=1M, 2=2M, 3=Coded
break;
default: default:
break; break;
} }
@ -388,10 +395,26 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_
ESP_LOGI(CONN_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"", ESP_LOGI(CONN_TAG, "Connected, conn_id %u, remote "ESP_BD_ADDR_STR"",
param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda)); param->connect.conn_id, ESP_BD_ADDR_HEX(param->connect.remote_bda));
esp_ble_gap_update_conn_params(&conn_params); esp_ble_gap_update_conn_params(&conn_params);
// 请求 2M PHY 提升传输速度(对端不支持时自动回退 1M不影响兼容性
esp_ble_gap_set_preferred_phy(param->connect.remote_bda,
ESP_BLE_GAP_NO_PREFER_TRANSMIT_PHY | ESP_BLE_GAP_NO_PREFER_RECEIVE_PHY,
ESP_BLE_GAP_PHY_2M_PREF_MASK,
ESP_BLE_GAP_PHY_2M_PREF_MASK,
ESP_BLE_GAP_PHY_OPTIONS_NO_PREF);
break; break;
case ESP_GATTS_DISCONNECT_EVT: case ESP_GATTS_DISCONNECT_EVT:
ESP_LOGI(CONN_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x", ESP_LOGI(CONN_TAG, "Disconnected, remote "ESP_BD_ADDR_STR", reason 0x%02x",
ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason); ESP_BD_ADDR_HEX(param->disconnect.remote_bda), param->disconnect.reason);
// 清理未完成的传输,防止内存泄漏
if (SendStatus.isSend) {
ESP_LOGW(CONN_TAG, "传输中断,已接收 %d/%d 字节",
(int)SendStatus.port, (int)firstMeg.len);
SendStatus.isSend = false;
SendStatus.port = 0;
if (img_data) { free(img_data); img_data = NULL; }
if (filepath) { free(filepath); filepath = NULL; }
if (file_img) { fclose(file_img); file_img = NULL; }
}
esp_ble_gap_start_advertising(&adv_params); esp_ble_gap_start_advertising(&adv_params);
break; break;
default: default:

View File

@ -1124,15 +1124,15 @@ void pages_cleanup_gif(void) {
#endif // LV_USE_GIF #endif // LV_USE_GIF
// 更新ui_ImgBle控件的图片支持 JPEG // 更新ui_ImgBle控件的图片支持 JPEG
void update_ui_ImgBle(const char *img_name) { bool update_ui_ImgBle(const char *img_name) {
if(!img_name) { if(!img_name) {
ESP_LOGE("IMG_UI", "图片名为空"); ESP_LOGE("IMG_UI", "图片名为空");
return; return false;
} }
if(!ui_ImgBle) { if(!ui_ImgBle) {
ESP_LOGE("IMG_UI", "ui_ImgBle控件不存在"); ESP_LOGE("IMG_UI", "ui_ImgBle控件不存在");
return; return false;
} }
static uint8_t *ui_img_data = NULL; static uint8_t *ui_img_data = NULL;
@ -1146,7 +1146,7 @@ void update_ui_ImgBle(const char *img_name) {
struct stat file_stat; struct stat file_stat;
if(stat(img_path, &file_stat) != 0) { if(stat(img_path, &file_stat) != 0) {
ESP_LOGE("IMG_UI", "文件不存在: %s", img_path); ESP_LOGE("IMG_UI", "文件不存在: %s", img_path);
return; return false;
} }
ESP_LOGI("IMG_UI", "文件大小: %ld 字节", file_stat.st_size); ESP_LOGI("IMG_UI", "文件大小: %ld 字节", file_stat.st_size);
@ -1170,13 +1170,13 @@ void update_ui_ImgBle(const char *img_name) {
FILE *gif_file = fopen(img_path, "rb"); FILE *gif_file = fopen(img_path, "rb");
if (!gif_file) { if (!gif_file) {
ESP_LOGE("IMG_UI", "GIF文件打开失败: %s", img_path); ESP_LOGE("IMG_UI", "GIF文件打开失败: %s", img_path);
return; return false;
} }
gif_psram_buf = heap_caps_malloc(file_stat.st_size, MALLOC_CAP_SPIRAM); gif_psram_buf = heap_caps_malloc(file_stat.st_size, MALLOC_CAP_SPIRAM);
if (!gif_psram_buf) { if (!gif_psram_buf) {
ESP_LOGE("IMG_UI", "PSRAM分配失败: %ld 字节", file_stat.st_size); ESP_LOGE("IMG_UI", "PSRAM分配失败: %ld 字节", file_stat.st_size);
fclose(gif_file); fclose(gif_file);
return; return false;
} }
fread(gif_psram_buf, 1, file_stat.st_size, gif_file); fread(gif_psram_buf, 1, file_stat.st_size, gif_file);
fclose(gif_file); fclose(gif_file);
@ -1187,13 +1187,14 @@ void update_ui_ImgBle(const char *img_name) {
ESP_LOGE("IMG_UI", "gifdec 打开失败: %s", img_name); ESP_LOGE("IMG_UI", "gifdec 打开失败: %s", img_name);
free(gif_psram_buf); free(gif_psram_buf);
gif_psram_buf = NULL; gif_psram_buf = NULL;
return; return false;
} }
// 启动自定义 GIF 播放器Palette LUT + 双缓冲流水线) // 启动自定义 GIF 播放器Palette LUT + 双缓冲流水线)
gif_player_start(); gif_player_start();
ESP_LOGI("IMG_UI", "GIF显示启动(优化): %s", img_name); ESP_LOGI("IMG_UI", "GIF显示启动(优化): %s", img_name);
return true;
} else } else
#endif // LV_USE_GIF #endif // LV_USE_GIF
{ {
@ -1226,7 +1227,7 @@ void update_ui_ImgBle(const char *img_name) {
if(ui_img_data == NULL) { if(ui_img_data == NULL) {
ESP_LOGE("IMG_UI", "解码数据为空"); ESP_LOGE("IMG_UI", "解码数据为空");
return; return false;
} }
// 配置图片数据 // 配置图片数据
@ -1243,11 +1244,14 @@ void update_ui_ImgBle(const char *img_name) {
lvgl_port_unlock(); lvgl_port_unlock();
ESP_LOGI("IMG_UI", "JPEG图片更新成功: %s", img_name); ESP_LOGI("IMG_UI", "JPEG图片更新成功: %s", img_name);
return true;
} else { } else {
ESP_LOGE("IMG_UI", "图片解码失败,错误码: %d", ret); ESP_LOGE("IMG_UI", "图片解码失败,错误码: %d", ret);
ui_img_data = NULL; ui_img_data = NULL;
return false;
} }
} }
return false;
} }

View File

@ -9,7 +9,7 @@
#include "esp_log.h" // 用于日志输出 #include "esp_log.h" // 用于日志输出
extern void init_spiffs_image_list(void); extern void init_spiffs_image_list(void);
extern void update_ui_ImgBle(const char *img_name); extern bool update_ui_ImgBle(const char *img_name);
extern void free_spiffs_image_list(void); extern void free_spiffs_image_list(void);
extern const char* get_next_image(void); extern const char* get_next_image(void);
extern const char* get_prev_image(void); extern const char* get_prev_image(void);
@ -127,16 +127,22 @@ lv_indev_wait_release(lv_indev_get_act());
} }
if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_LEFT ) { if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_LEFT ) {
lv_indev_wait_release(lv_indev_get_act()); lv_indev_wait_release(lv_indev_get_act());
// 解码失败时自动跳过,最多尝试全部图片避免死循环
for(int try = 0; try < 10; try++) {
const char *next_img = get_next_image(); const char *next_img = get_next_image();
if(next_img) { if(!next_img) break;
update_ui_ImgBle(next_img); if(update_ui_ImgBle(next_img)) break;
ESP_LOGW("ScreenImg", "跳过无效图片,继续下一张");
} }
} }
if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_RIGHT ) { if ( event_code == LV_EVENT_GESTURE && lv_indev_get_gesture_dir(lv_indev_get_act()) == LV_DIR_RIGHT ) {
lv_indev_wait_release(lv_indev_get_act()); lv_indev_wait_release(lv_indev_get_act());
// 解码失败时自动跳过,最多尝试全部图片避免死循环
for(int try = 0; try < 10; try++) {
const char *prev_img = get_prev_image(); const char *prev_img = get_prev_image();
if(prev_img) { if(!prev_img) break;
update_ui_ImgBle(prev_img); if(update_ui_ImgBle(prev_img)) break;
ESP_LOGW("ScreenImg", "跳过无效图片,继续上一张");
} }
} }
} }

View File

@ -21,7 +21,7 @@ extern lv_obj_t *ui_ImageDel;
extern lv_obj_t *ui_ImageReturn; extern lv_obj_t *ui_ImageReturn;
extern void init_spiffs_image_list(void); extern void init_spiffs_image_list(void);
extern void update_ui_ImgBle(const char *img_name); extern bool update_ui_ImgBle(const char *img_name);
extern void free_spiffs_image_list(void); extern void free_spiffs_image_list(void);
extern const char* get_next_image(void); extern const char* get_next_image(void);
extern const char* get_prev_image(void); extern const char* get_prev_image(void);

View File

@ -835,7 +835,12 @@ CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=y
# CONFIG_BT_BLE_ACT_SCAN_REP_ADV_SCAN is not set # CONFIG_BT_BLE_ACT_SCAN_REP_ADV_SCAN is not set
CONFIG_BT_MAX_DEVICE_NAME_LEN=32 CONFIG_BT_MAX_DEVICE_NAME_LEN=32
CONFIG_BT_BLE_RPA_TIMEOUT=900 CONFIG_BT_BLE_RPA_TIMEOUT=900
# CONFIG_BT_BLE_50_FEATURES_SUPPORTED is not set CONFIG_BT_BLE_50_FEATURES_SUPPORTED=y
CONFIG_BT_BLE_50_EXTEND_ADV_EN=y
CONFIG_BT_BLE_50_PERIODIC_ADV_EN=y
CONFIG_BT_BLE_50_EXTEND_SCAN_EN=y
CONFIG_BT_BLE_50_EXTEND_SYNC_EN=y
CONFIG_BT_BLE_50_DTM_TEST_EN=y
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
CONFIG_BT_BLE_42_DTM_TEST_EN=y CONFIG_BT_BLE_42_DTM_TEST_EN=y
CONFIG_BT_BLE_42_ADV_EN=y CONFIG_BT_BLE_42_ADV_EN=y