Rdzleo 4b7b1949d4 perf(rtc-only): Phase 6 收尾 - 卡顿优化 + PowerSaveTimer 守卫 + 开机加速
代码改动:
- AudioLoop 加 vTaskDelay(1),让出 Core 1 idle task 防 WiFi/RTC 饥饿
- BackgroundTask 优先级 2 → 5,提升 Opus 解码实时性
- LVGL 刷新 5ms → 16ms (60Hz),CPU 占用降 60%
- GIF 定时器 20ms → 33ms (3 处),PSRAM 流量减半
- AI 字幕推送 100ms 节流,避免 LVGL 锁争抢
- EnterIdleHibernate 清空 audio_decode_queue_,防 standby_sound 残留误触发首帧
- PowerSaveTimer OnEnterSleepMode 加 device_state 守卫,拦截 dialog/connecting
  期间关功放(修复欢迎语期间被静音 bug)
- 取消开机 ADC 阻塞采样,开机播报响应从 6 秒缩到 < 3 秒

新增规划:
- Phase 7 占位文档:电量保护 + PowerSaveTimer 重构 + 唤醒杂音根治 + RTC 抖动缓解

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:38:48 +08:00

457 lines
17 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 "dzbj_gpio.h"
#include "esp_lvgl_port.h"
#include "esp_lcd_st77916.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lcd.h"
#if DZBJ_ENABLE_TOUCH
#include "esp_lcd_touch_cst816s.h"
#endif
#include <string.h>
#include "esp_heap_caps.h"
// ST77916 初始化命令(从 dzbj 项目已验证的驱动版本提取)
// 主项目 managed_component 新版默认命令与本硬件面板不匹配,需手动注入
static const st77916_lcd_init_cmd_t lcd_init_cmds[] = {
{0xF0, (uint8_t[]){0x28}, 1, 0},
{0xF2, (uint8_t[]){0x28}, 1, 0},
{0x73, (uint8_t[]){0xF0}, 1, 0},
{0x7C, (uint8_t[]){0xD1}, 1, 0},
{0x83, (uint8_t[]){0xE0}, 1, 0},
{0x84, (uint8_t[]){0x61}, 1, 0},
{0xF2, (uint8_t[]){0x82}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x01}, 1, 0},
{0xF1, (uint8_t[]){0x01}, 1, 0},
{0xB0, (uint8_t[]){0x5E}, 1, 0},
{0xB1, (uint8_t[]){0x55}, 1, 0},
{0xB2, (uint8_t[]){0x24}, 1, 0},
{0xB3, (uint8_t[]){0x01}, 1, 0},
{0xB4, (uint8_t[]){0x87}, 1, 0},
{0xB5, (uint8_t[]){0x44}, 1, 0},
{0xB6, (uint8_t[]){0x8B}, 1, 0},
{0xB7, (uint8_t[]){0x40}, 1, 0},
{0xB8, (uint8_t[]){0x86}, 1, 0},
{0xB9, (uint8_t[]){0x15}, 1, 0},
{0xBA, (uint8_t[]){0x00}, 1, 0},
{0xBB, (uint8_t[]){0x08}, 1, 0},
{0xBC, (uint8_t[]){0x08}, 1, 0},
{0xBD, (uint8_t[]){0x00}, 1, 0},
{0xBE, (uint8_t[]){0x00}, 1, 0},
{0xBF, (uint8_t[]){0x07}, 1, 0},
{0xC0, (uint8_t[]){0x80}, 1, 0},
{0xC1, (uint8_t[]){0x10}, 1, 0},
{0xC2, (uint8_t[]){0x37}, 1, 0},
{0xC3, (uint8_t[]){0x80}, 1, 0},
{0xC4, (uint8_t[]){0x10}, 1, 0},
{0xC5, (uint8_t[]){0x37}, 1, 0},
{0xC6, (uint8_t[]){0xA9}, 1, 0},
{0xC7, (uint8_t[]){0x41}, 1, 0},
{0xC8, (uint8_t[]){0x01}, 1, 0},
{0xC9, (uint8_t[]){0xA9}, 1, 0},
{0xCA, (uint8_t[]){0x41}, 1, 0},
{0xCB, (uint8_t[]){0x01}, 1, 0},
{0xCC, (uint8_t[]){0x7F}, 1, 0},
{0xCD, (uint8_t[]){0x7F}, 1, 0},
{0xCE, (uint8_t[]){0xFF}, 1, 0},
{0xD0, (uint8_t[]){0x91}, 1, 0},
{0xD1, (uint8_t[]){0x68}, 1, 0},
{0xD2, (uint8_t[]){0x68}, 1, 0},
{0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0},
{0xDD, (uint8_t[]){0x40}, 1, 0},
{0xDE, (uint8_t[]){0x40}, 1, 0},
{0xF1, (uint8_t[]){0x10}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x02}, 1, 0},
{0xE0, (uint8_t[]){0xF0, 0x10, 0x18, 0x0D, 0x0C, 0x38, 0x3E, 0x44, 0x51, 0x39, 0x15, 0x15, 0x30, 0x34}, 14, 0},
{0xE1, (uint8_t[]){0xF0, 0x0F, 0x17, 0x0D, 0x0B, 0x07, 0x3E, 0x33, 0x51, 0x39, 0x15, 0x15, 0x30, 0x34}, 14, 0},
{0xF0, (uint8_t[]){0x10}, 1, 0},
{0xF3, (uint8_t[]){0x10}, 1, 0},
{0xE0, (uint8_t[]){0x08}, 1, 0},
{0xE1, (uint8_t[]){0x00}, 1, 0},
{0xE2, (uint8_t[]){0x00}, 1, 0},
{0xE3, (uint8_t[]){0x00}, 1, 0},
{0xE4, (uint8_t[]){0xE0}, 1, 0},
{0xE5, (uint8_t[]){0x06}, 1, 0},
{0xE6, (uint8_t[]){0x21}, 1, 0},
{0xE7, (uint8_t[]){0x03}, 1, 0},
{0xE8, (uint8_t[]){0x05}, 1, 0},
{0xE9, (uint8_t[]){0x02}, 1, 0},
{0xEA, (uint8_t[]){0xE9}, 1, 0},
{0xEB, (uint8_t[]){0x00}, 1, 0},
{0xEC, (uint8_t[]){0x00}, 1, 0},
{0xED, (uint8_t[]){0x14}, 1, 0},
{0xEE, (uint8_t[]){0xFF}, 1, 0},
{0xEF, (uint8_t[]){0x00}, 1, 0},
{0xF8, (uint8_t[]){0xFF}, 1, 0},
{0xF9, (uint8_t[]){0x00}, 1, 0},
{0xFA, (uint8_t[]){0x00}, 1, 0},
{0xFB, (uint8_t[]){0x30}, 1, 0},
{0xFC, (uint8_t[]){0x00}, 1, 0},
{0xFD, (uint8_t[]){0x00}, 1, 0},
{0xFE, (uint8_t[]){0x00}, 1, 0},
{0xFF, (uint8_t[]){0x00}, 1, 0},
{0x60, (uint8_t[]){0x40}, 1, 0},
{0x61, (uint8_t[]){0x05}, 1, 0},
{0x62, (uint8_t[]){0x00}, 1, 0},
{0x63, (uint8_t[]){0x42}, 1, 0},
{0x64, (uint8_t[]){0xDA}, 1, 0},
{0x65, (uint8_t[]){0x00}, 1, 0},
{0x66, (uint8_t[]){0x00}, 1, 0},
{0x67, (uint8_t[]){0x00}, 1, 0},
{0x68, (uint8_t[]){0x00}, 1, 0},
{0x69, (uint8_t[]){0x00}, 1, 0},
{0x6A, (uint8_t[]){0x00}, 1, 0},
{0x6B, (uint8_t[]){0x00}, 1, 0},
{0x70, (uint8_t[]){0x40}, 1, 0},
{0x71, (uint8_t[]){0x04}, 1, 0},
{0x72, (uint8_t[]){0x00}, 1, 0},
{0x73, (uint8_t[]){0x42}, 1, 0},
{0x74, (uint8_t[]){0xD9}, 1, 0},
{0x75, (uint8_t[]){0x00}, 1, 0},
{0x76, (uint8_t[]){0x00}, 1, 0},
{0x77, (uint8_t[]){0x00}, 1, 0},
{0x78, (uint8_t[]){0x00}, 1, 0},
{0x79, (uint8_t[]){0x00}, 1, 0},
{0x7A, (uint8_t[]){0x00}, 1, 0},
{0x7B, (uint8_t[]){0x00}, 1, 0},
{0x80, (uint8_t[]){0x48}, 1, 0},
{0x81, (uint8_t[]){0x00}, 1, 0},
{0x82, (uint8_t[]){0x07}, 1, 0},
{0x83, (uint8_t[]){0x02}, 1, 0},
{0x84, (uint8_t[]){0xD7}, 1, 0},
{0x85, (uint8_t[]){0x04}, 1, 0},
{0x86, (uint8_t[]){0x00}, 1, 0},
{0x87, (uint8_t[]){0x00}, 1, 0},
{0x88, (uint8_t[]){0x48}, 1, 0},
{0x89, (uint8_t[]){0x00}, 1, 0},
{0x8A, (uint8_t[]){0x09}, 1, 0},
{0x8B, (uint8_t[]){0x02}, 1, 0},
{0x8C, (uint8_t[]){0xD9}, 1, 0},
{0x8D, (uint8_t[]){0x04}, 1, 0},
{0x8E, (uint8_t[]){0x00}, 1, 0},
{0x8F, (uint8_t[]){0x00}, 1, 0},
{0x90, (uint8_t[]){0x48}, 1, 0},
{0x91, (uint8_t[]){0x00}, 1, 0},
{0x92, (uint8_t[]){0x0B}, 1, 0},
{0x93, (uint8_t[]){0x02}, 1, 0},
{0x94, (uint8_t[]){0xDB}, 1, 0},
{0x95, (uint8_t[]){0x04}, 1, 0},
{0x96, (uint8_t[]){0x00}, 1, 0},
{0x97, (uint8_t[]){0x00}, 1, 0},
{0x98, (uint8_t[]){0x48}, 1, 0},
{0x99, (uint8_t[]){0x00}, 1, 0},
{0x9A, (uint8_t[]){0x0D}, 1, 0},
{0x9B, (uint8_t[]){0x02}, 1, 0},
{0x9C, (uint8_t[]){0xDD}, 1, 0},
{0x9D, (uint8_t[]){0x04}, 1, 0},
{0x9E, (uint8_t[]){0x00}, 1, 0},
{0x9F, (uint8_t[]){0x00}, 1, 0},
{0xA0, (uint8_t[]){0x48}, 1, 0},
{0xA1, (uint8_t[]){0x00}, 1, 0},
{0xA2, (uint8_t[]){0x06}, 1, 0},
{0xA3, (uint8_t[]){0x02}, 1, 0},
{0xA4, (uint8_t[]){0xD6}, 1, 0},
{0xA5, (uint8_t[]){0x04}, 1, 0},
{0xA6, (uint8_t[]){0x00}, 1, 0},
{0xA7, (uint8_t[]){0x00}, 1, 0},
{0xA8, (uint8_t[]){0x48}, 1, 0},
{0xA9, (uint8_t[]){0x00}, 1, 0},
{0xAA, (uint8_t[]){0x08}, 1, 0},
{0xAB, (uint8_t[]){0x02}, 1, 0},
{0xAC, (uint8_t[]){0xD8}, 1, 0},
{0xAD, (uint8_t[]){0x04}, 1, 0},
{0xAE, (uint8_t[]){0x00}, 1, 0},
{0xAF, (uint8_t[]){0x00}, 1, 0},
{0xB0, (uint8_t[]){0x48}, 1, 0},
{0xB1, (uint8_t[]){0x00}, 1, 0},
{0xB2, (uint8_t[]){0x0A}, 1, 0},
{0xB3, (uint8_t[]){0x02}, 1, 0},
{0xB4, (uint8_t[]){0xDA}, 1, 0},
{0xB5, (uint8_t[]){0x04}, 1, 0},
{0xB6, (uint8_t[]){0x00}, 1, 0},
{0xB7, (uint8_t[]){0x00}, 1, 0},
{0xB8, (uint8_t[]){0x48}, 1, 0},
{0xB9, (uint8_t[]){0x00}, 1, 0},
{0xBA, (uint8_t[]){0x0C}, 1, 0},
{0xBB, (uint8_t[]){0x02}, 1, 0},
{0xBC, (uint8_t[]){0xDC}, 1, 0},
{0xBD, (uint8_t[]){0x04}, 1, 0},
{0xBE, (uint8_t[]){0x00}, 1, 0},
{0xBF, (uint8_t[]){0x00}, 1, 0},
{0xC0, (uint8_t[]){0x10}, 1, 0},
{0xC1, (uint8_t[]){0x47}, 1, 0},
{0xC2, (uint8_t[]){0x56}, 1, 0},
{0xC3, (uint8_t[]){0x65}, 1, 0},
{0xC4, (uint8_t[]){0x74}, 1, 0},
{0xC5, (uint8_t[]){0x88}, 1, 0},
{0xC6, (uint8_t[]){0x99}, 1, 0},
{0xC7, (uint8_t[]){0x01}, 1, 0},
{0xC8, (uint8_t[]){0xBB}, 1, 0},
{0xC9, (uint8_t[]){0xAA}, 1, 0},
{0xD0, (uint8_t[]){0x10}, 1, 0},
{0xD1, (uint8_t[]){0x47}, 1, 0},
{0xD2, (uint8_t[]){0x56}, 1, 0},
{0xD3, (uint8_t[]){0x65}, 1, 0},
{0xD4, (uint8_t[]){0x74}, 1, 0},
{0xD5, (uint8_t[]){0x88}, 1, 0},
{0xD6, (uint8_t[]){0x99}, 1, 0},
{0xD7, (uint8_t[]){0x01}, 1, 0},
{0xD8, (uint8_t[]){0xBB}, 1, 0},
{0xD9, (uint8_t[]){0xAA}, 1, 0},
{0xF3, (uint8_t[]){0x01}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0x3A, (uint8_t[]){0x55}, 1, 0},
{0x21, (uint8_t[]){0x00}, 1, 0},
{0x11, (uint8_t[]){0x00}, 1, 120},
{0x29, (uint8_t[]){0x00}, 1, 0},
};
static lv_disp_t * disp_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL; // 暴露给 sprite_demo 等模块直接 DMA 写 LCD
static esp_lcd_panel_io_handle_t io_handle = NULL;
#if DZBJ_ENABLE_TOUCH
static esp_lcd_touch_handle_t touch_handle = NULL;
static esp_lcd_panel_io_handle_t tp_io_handle = NULL;
// 外部传入的 I2C 总线句柄(与主项目共享)
static i2c_master_bus_handle_t ext_i2c_bus = NULL;
#endif
void lcd_set_i2c_bus(i2c_master_bus_handle_t bus) {
#if DZBJ_ENABLE_TOUCH
ext_i2c_bus = bus;
#else
(void)bus;
#endif
}
void lcd_init(){
const spi_bus_config_t buscfg = ST77916_PANEL_BUS_QSPI_CONFIG(PIN_LCD_CLK,
PIN_LCD_D0,
PIN_LCD_D1,
PIN_LCD_D2,
PIN_LCD_D3,
LCD_HIGH * 80 * sizeof(uint16_t));
spi_bus_initialize(SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO);
// QSPI 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;
io_config.trans_queue_depth = 64; // 默认 10 太小sprite 分条传输需要更大队列
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 = {
.init_cmds = lcd_init_cmds,
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st77916_lcd_init_cmd_t),
.flags = {
.use_qspi_interface = 1,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_LCD_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
.vendor_config = &vendor_config,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(io_handle, &panel_config, &panel_handle));
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
// 启用 TE 内部同步信号TEON 0x35, 参数 0x00 = V-Blank only
// 即使 TE 引脚未接 MCU启用后可通过 TESLRD(0x45) 读取扫描行位置
uint8_t te_param = 0x00;
esp_lcd_panel_io_tx_param(io_handle, 0x35, &te_param, 1);
ESP_LOGI(LCD_TAG, "TEON enabled (V-Blank mode)");
// 清空LCD GRAM避免显示上次关机时的残留画面
size_t clear_buffer_size = LCD_WID * 40;
uint16_t *clear_buffer = heap_caps_malloc(clear_buffer_size * sizeof(uint16_t), MALLOC_CAP_DMA);
if (clear_buffer) {
memset(clear_buffer, 0, clear_buffer_size * sizeof(uint16_t));
for (int y = 0; y < LCD_HIGH; y += 40) {
int lines = (y + 40 > LCD_HIGH) ? (LCD_HIGH - y) : 40;
esp_lcd_panel_draw_bitmap(panel_handle, 0, y, LCD_WID, y + lines, clear_buffer);
}
heap_caps_free(clear_buffer);
ESP_LOGI(LCD_TAG, "LCD GRAM cleared (black filled)");
} else {
ESP_LOGE(LCD_TAG, "Failed to allocate clear buffer");
}
esp_lcd_panel_disp_on_off(panel_handle, true);
}
#if DZBJ_ENABLE_TOUCH
// 初始化触摸控制器(使用外部传入的 I2C 总线)
void touch_init(){
const esp_lcd_touch_config_t tp_cfg = {
.x_max = LCD_WID,
.y_max = LCD_HIGH,
.rst_gpio_num = PIN_TP_RST,
.int_gpio_num = PIN_TP_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
};
const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG();
// 使用外部传入的 I2C 总线句柄(与音频编解码器共享 I2C_NUM_1
i2c_master_bus_handle_t i2c_bus = ext_i2c_bus;
if (i2c_bus == NULL) {
ESP_LOGE(LCD_TAG, "I2C bus not set, call lcd_set_i2c_bus() first");
return;
}
// 直接传入 i2c_master_bus_handle_t_Generic 宏分派到 V2新驱动
// 不要强转为 esp_lcd_i2c_bus_handle_t(uint32_t),否则会触发 V1 legacy 驱动冲突
esp_err_t err = esp_lcd_new_panel_io_i2c(i2c_bus, &tp_io_config, &tp_io_handle);
if (err != ESP_OK) {
ESP_LOGE(LCD_TAG, "Failed to create I2C IO for touch: %s", esp_err_to_name(err));
return;
}
err = esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &touch_handle);
if (err != ESP_OK) {
ESP_LOGE(LCD_TAG, "Failed to create touch handle: %s", esp_err_to_name(err));
return;
}
ESP_LOGI(LCD_TAG, "Touch controller initialized successfully");
}
#endif // DZBJ_ENABLE_TOUCH
// 初始化LVGL显示
void lvgl_lcd_init(){
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4,
.task_stack = 8192,
.task_affinity = -1,
.task_max_sleep_ms = 500,
.timer_period_ms = 16 // 卡顿优化 4: 5ms→16ms (60Hz) 减少 LVGL CPU 占用 60%
};
lvgl_port_init(&lvgl_cfg);
// 使用内部 DMA 内存20 行单缓冲360×20×2 = 14400 字节)
// PSRAM 缓冲区与 SPI DMA 不兼容spi transmit queue 失败)
// 单缓冲 + 小行数,节省内部 SRAM 给 WiFi/BLE
#define LVGL_DRAW_BUF_LINES 20
size_t buffer_size = LCD_WID * LVGL_DRAW_BUF_LINES;
ESP_LOGI(LCD_TAG, "LVGL buffer: %d bytes (W:%d, Lines:%d, DMA, single)",
buffer_size * sizeof(uint16_t), LCD_WID, LVGL_DRAW_BUF_LINES);
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = io_handle,
.panel_handle = panel_handle,
.buffer_size = buffer_size,
.double_buffer = false,
.hres = LCD_WID,
.vres = LCD_HIGH,
.monochrome = false,
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.flags = {
.buff_dma = true,
.buff_spiram = false,
}
};
disp_handle = lvgl_port_add_disp(&disp_cfg);
#if DZBJ_ENABLE_TOUCH
if (touch_handle != NULL) {
lvgl_port_touch_cfg_t touch_cgf = {
.disp = disp_handle,
.handle = touch_handle,
};
lvgl_port_add_touch(&touch_cgf);
ESP_LOGI(LCD_TAG, "Touch controller added to LVGL");
}
#endif
}
#if DZBJ_ENABLE_TOUCH
void get_touch(uint16_t* touchx, uint16_t* touchy){
if (touch_handle == NULL) {
*touchx = 0;
*touchy = 0;
return;
}
*touchx = touch_handle->data.coords[0].x;
*touchy = touch_handle->data.coords[0].y;
}
#endif // DZBJ_ENABLE_TOUCH
// 直接 DMA 填充全屏纯色(绕过 LVGL使用调用者预分配的缓冲区
void lcd_fill_color_with_buf(uint16_t *buf, int strip_h) {
if (panel_handle == NULL || buf == NULL || strip_h <= 0) return;
for (int y = 0; y < LCD_HIGH; y += strip_h) {
int lines = (y + strip_h > LCD_HIGH) ? (LCD_HIGH - y) : strip_h;
esp_lcd_panel_draw_bitmap(panel_handle, 0, y, LCD_WID, y + lines, buf);
}
}
// 读取当前扫描行号TESLRD 0x45
// 返回 ESP_OK 表示读取成功scanline 存储扫描行号 N[11:0]
// 扫描行范围0 ~ (VSYNC+VBP+VACT+VFP-1),其中 0~359 为活跃显示区
esp_err_t lcd_read_scanline(uint16_t *scanline) {
if (!io_handle || !scanline) return ESP_ERR_INVALID_ARG;
uint8_t data[2] = {0};
esp_err_t ret = esp_lcd_panel_io_rx_param(io_handle, 0x45, data, 2);
if (ret == ESP_OK) {
// N[11:8] 在 data[0] 低4位N[7:0] 在 data[1]
*scanline = ((uint16_t)(data[0] & 0x0F) << 8) | data[1];
}
return ret;
}
// 等待 VBLANK 消隐期(带超时保护)
// 轮询扫描行号,当 scanline >= LCD_HIGH(360) 时表示进入消隐区
// timeout_us: 最大等待时间(微秒),建议 17000约1帧
// 返回 true = 成功等到 VBLANKfalse = 超时或读取失败
bool lcd_wait_vsync_timeout(uint32_t timeout_us) {
uint16_t scanline = 0;
int64_t start = esp_timer_get_time();
do {
if (lcd_read_scanline(&scanline) != ESP_OK) {
return false;
}
if (scanline >= LCD_HIGH) {
return true;
}
esp_rom_delay_us(50);
} while ((esp_timer_get_time() - start) < (int64_t)timeout_us);
return false;
}
// 清空LCD GRAM为黑色用于低功耗熄屏前避免残影
void lcd_clear_screen_black(void) {
if (panel_handle == NULL) {
ESP_LOGE(LCD_TAG, "Panel handle is NULL, cannot clear screen");
return;
}
size_t clear_buffer_size = LCD_WID * 40;
uint16_t *clear_buffer = heap_caps_malloc(clear_buffer_size * sizeof(uint16_t), MALLOC_CAP_DMA);
if (clear_buffer) {
memset(clear_buffer, 0, clear_buffer_size * sizeof(uint16_t));
for (int y = 0; y < LCD_HIGH; y += 40) {
int lines = (y + 40 > LCD_HIGH) ? (LCD_HIGH - y) : 40;
esp_lcd_panel_draw_bitmap(panel_handle, 0, y, LCD_WID, y + lines, clear_buffer);
}
heap_caps_free(clear_buffer);
ESP_LOGI(LCD_TAG, "LCD GRAM cleared to black (for low power mode)");
} else {
ESP_LOGE(LCD_TAG, "Failed to allocate clear buffer");
}
}