docs: 补充核心分层中文注释
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* Direct ST7789P3 LCD backend.
|
||||
* ST7789P3 LCD 直接驱动后端。
|
||||
*
|
||||
* This backend owns the low-level panel initialization, a serialized render
|
||||
* loop, and a minimal presentation queue.
|
||||
* 本模块独占面板底层初始化和 SPI 访问,并通过串行渲染循环处理最小化的
|
||||
* presentation 队列;调用方不得绕过该任务直接操作面板。
|
||||
*/
|
||||
|
||||
#include "lcd_direct.h"
|
||||
@@ -44,14 +44,23 @@
|
||||
#define LCD_DIRECT_WIDTH LCD_DIRECT_NATIVE_WIDTH
|
||||
#define LCD_DIRECT_HEIGHT LCD_DIRECT_NATIVE_HEIGHT
|
||||
#endif
|
||||
/*
|
||||
* 每次只合成并传输8行,用较小 DMA 缓冲换取更多 SPI 提交次数;100ms 是单批传输
|
||||
* 故障边界。预热允许5秒是因为首次 PNG 文件读取和解码远慢于缓存命中。
|
||||
*/
|
||||
#define LCD_DIRECT_ROWS_PER_TRANSFER 8U
|
||||
#define LCD_DIRECT_PIXELS_PER_TRANSFER (LCD_DIRECT_WIDTH * LCD_DIRECT_ROWS_PER_TRANSFER)
|
||||
#define LCD_DIRECT_PRESENTATION_WAIT_POLL_MS 20U
|
||||
#define LCD_DIRECT_TRANSFER_TIMEOUT_MS 100U
|
||||
#define LCD_DIRECT_PREHEAT_WAIT_TIMEOUT_MS 5000U
|
||||
/*
|
||||
* PNG 缓存固定6项并使用 LRU 淘汰,限制 PSRAM 峰值;边界测试恰好覆盖一次完整
|
||||
* 缓存命中周期。路径上限同时约束缓存键和 LittleFS 资源路径复制。
|
||||
*/
|
||||
#define LCD_DIRECT_FRAME_CACHE_MAX 6U
|
||||
#define LCD_DIRECT_BOUNDARY_TEST_CACHE_HIT_FRAMES LCD_DIRECT_FRAME_CACHE_MAX
|
||||
#define LCD_DIRECT_PATH_MAX 96U
|
||||
/* 状态图标坐标基于逻辑屏幕尺寸计算,旋转屏幕时无需复制另一套绝对坐标。 */
|
||||
#define LCD_DIRECT_CHARGING_ICON_WIDTH 50U
|
||||
#define LCD_DIRECT_CHARGING_ICON_X (LCD_DIRECT_WIDTH - LCD_DIRECT_CHARGING_ICON_WIDTH - 8U)
|
||||
#define LCD_DIRECT_CHARGING_ICON_Y 8U
|
||||
@@ -59,15 +68,21 @@
|
||||
#define LCD_DIRECT_LOW_BATTERY_ICON_HEIGHT 36U
|
||||
#define LCD_DIRECT_LOW_BATTERY_ICON_X ((LCD_DIRECT_WIDTH - LCD_DIRECT_LOW_BATTERY_ICON_WIDTH) / 2U)
|
||||
#define LCD_DIRECT_LOW_BATTERY_ICON_Y (LCD_DIRECT_HEIGHT - LCD_DIRECT_LOW_BATTERY_ICON_HEIGHT - 8U)
|
||||
/* 基础表情按100ms节奏播放,有限动画末帧额外停留200ms以避免视觉跳变过快。 */
|
||||
#define LCD_DIRECT_LAST_FRAME_EXTRA_HOLD_MS 200U
|
||||
#define LCD_DIRECT_BASIC_FACE_FRAME_HOLD_MS 100U
|
||||
#define LCD_DIRECT_BASIC_FACE_LAST_FRAME_HOLD_MS 100U
|
||||
/*
|
||||
* 20kHz 背光 PWM 高于常见可听频率;10位占空比分辨率提供平滑亮度变化。
|
||||
* 独立背光锁最多等待100ms,防止控制路径因并发更新无限阻塞。
|
||||
*/
|
||||
#define LCD_DIRECT_BACKLIGHT_PWM_FREQUENCY_HZ 20000U
|
||||
#define LCD_DIRECT_BACKLIGHT_PWM_DUTY_RESOLUTION LEDC_TIMER_10_BIT
|
||||
#define LCD_DIRECT_BACKLIGHT_PWM_DUTY_MAX ((1U << 10U) - 1U)
|
||||
#define LCD_DIRECT_BACKLIGHT_PWM_TIMER LEDC_TIMER_0
|
||||
#define LCD_DIRECT_BACKLIGHT_PWM_CHANNEL LEDC_CHANNEL_0
|
||||
#define LCD_DIRECT_BACKLIGHT_LOCK_TIMEOUT_MS 100U
|
||||
/* 5x7 点阵放大4倍,24像素步进为字形留出间隔;颜色为 RGB565 白字黑描边。 */
|
||||
#define LCD_DIRECT_CLOCK_X 12U
|
||||
#define LCD_DIRECT_CLOCK_Y 10U
|
||||
#define LCD_DIRECT_CLOCK_GLYPH_WIDTH 5U
|
||||
@@ -79,6 +94,7 @@
|
||||
#define LCD_DIRECT_CLOCK_COLOR 0xFFFFU
|
||||
#define LCD_DIRECT_CLOCK_OUTLINE_COLOR 0x0000U
|
||||
|
||||
/* ST7789P3 的方向控制(MADCTL)和 RAM 数据格式控制(RAMCTRL)命令号。 */
|
||||
#define LCD_CMD_MADCTL 0x36
|
||||
#define LCD_CMD_RAMCTRL 0xB0
|
||||
|
||||
@@ -92,6 +108,7 @@ unsigned lodepng_esp_decode32(unsigned char **out,
|
||||
const char *lodepng_esp_error_text(unsigned code);
|
||||
|
||||
typedef struct {
|
||||
/* 请求中的外部指针在入队前复制到此结构,调用返回后不再依赖调用方内存。 */
|
||||
platform_lcd_presentation_kind_t kind;
|
||||
uint8_t priority;
|
||||
bool interrupt_current;
|
||||
@@ -107,6 +124,7 @@ typedef struct {
|
||||
} lcd_direct_request_t;
|
||||
|
||||
typedef struct lcd_direct_decoded_image_t {
|
||||
/* 缓存项拥有 pixels/alpha;pinned 项不可被 LRU 淘汰。 */
|
||||
bool valid;
|
||||
bool alpha_valid;
|
||||
char path[LCD_DIRECT_PATH_MAX];
|
||||
@@ -119,6 +137,7 @@ typedef struct lcd_direct_decoded_image_t {
|
||||
} lcd_direct_decoded_image_t;
|
||||
|
||||
typedef struct {
|
||||
/* 图标优先引用缓存,缓存不足时 owns_transient 标记本次绘制临时所有权。 */
|
||||
uint32_t completed_frames;
|
||||
uint64_t total_draw_us;
|
||||
uint32_t min_draw_us;
|
||||
@@ -200,6 +219,7 @@ static uint16_t s_line_buffer[LCD_DIRECT_PIXELS_PER_TRANSFER];
|
||||
static lcd_direct_decoded_image_t s_frame_cache[LCD_DIRECT_FRAME_CACHE_MAX];
|
||||
static uint32_t s_cache_use_counter;
|
||||
|
||||
/* 渲染状态由 s_state_lock 保护,面板和缓存访问则由 s_lock 串行化。 */
|
||||
static void lcd_direct_signal_completion(void);
|
||||
static void lcd_direct_clock_text(char text[LCD_DIRECT_CLOCK_TEXT_LENGTH + 1U]);
|
||||
static void lcd_direct_overlay_clock(uint16_t *pixel_buffer,
|
||||
@@ -211,6 +231,7 @@ static bool IRAM_ATTR lcd_direct_on_color_trans_done(esp_lcd_panel_io_handle_t p
|
||||
esp_lcd_panel_io_event_data_t *event_data,
|
||||
void *user_ctx)
|
||||
{
|
||||
/* SPI 完成回调位于中断上下文,只释放信号量并按需触发任务切换。 */
|
||||
(void)panel_io;
|
||||
(void)event_data;
|
||||
(void)user_ctx;
|
||||
@@ -233,6 +254,7 @@ static uint16_t lcd_direct_swap_color(uint16_t color)
|
||||
|
||||
static esp_err_t lcd_direct_draw_rows(uint16_t row_start, uint16_t row_count)
|
||||
{
|
||||
/* 每批行发送后等待传输完成,确保静态行缓冲不会在 DMA 使用期间被覆盖。 */
|
||||
ESP_RETURN_ON_FALSE(s_panel != NULL, ESP_ERR_INVALID_STATE, TAG, "panel is not ready");
|
||||
ESP_RETURN_ON_FALSE(s_transfer_done_sem != NULL,
|
||||
ESP_ERR_INVALID_STATE,
|
||||
@@ -296,6 +318,7 @@ static uint16_t lcd_direct_blend_rgb565(uint16_t dst, uint16_t src, uint8_t alph
|
||||
|
||||
static esp_err_t lcd_direct_read_file_psram(const char *path, unsigned char **out_data, size_t *out_size)
|
||||
{
|
||||
/* 整个 PNG 文件优先读入 PSRAM;任何短读都视为失败并关闭句柄。 */
|
||||
ESP_RETURN_ON_FALSE(path != NULL && out_data != NULL && out_size != NULL,
|
||||
ESP_ERR_INVALID_ARG,
|
||||
TAG,
|
||||
@@ -356,6 +379,7 @@ static esp_err_t lcd_direct_decode_png(const char *path,
|
||||
bool keep_alpha,
|
||||
lcd_direct_decoded_image_t *out_decoded)
|
||||
{
|
||||
/* 解码后立即转换为面板原生 RGB565,仅在确有透明像素时保留 alpha 平面。 */
|
||||
ESP_RETURN_ON_FALSE(path != NULL && out_decoded != NULL,
|
||||
ESP_ERR_INVALID_ARG,
|
||||
TAG,
|
||||
@@ -443,6 +467,7 @@ static esp_err_t lcd_direct_decode_png(const char *path,
|
||||
|
||||
static lcd_direct_decoded_image_t *lcd_direct_find_cached_image(const char *path, bool require_alpha)
|
||||
{
|
||||
/* 命中时更新单调递增使用序号,供固定容量缓存执行 LRU 淘汰。 */
|
||||
if (path == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -459,6 +484,7 @@ static lcd_direct_decoded_image_t *lcd_direct_find_cached_image(const char *path
|
||||
|
||||
static lcd_direct_decoded_image_t *lcd_direct_find_cache_slot(void)
|
||||
{
|
||||
/* 优先空槽,否则淘汰最久未使用且未 pinned 的资源。 */
|
||||
lcd_direct_decoded_image_t *oldest = NULL;
|
||||
for (size_t i = 0; i < LCD_DIRECT_FRAME_CACHE_MAX; ++i) {
|
||||
if (!s_frame_cache[i].valid) {
|
||||
@@ -481,6 +507,7 @@ static esp_err_t lcd_direct_get_png_image(const char *path,
|
||||
lcd_direct_decoded_image_t **out_image,
|
||||
lcd_direct_decoded_image_t *transient)
|
||||
{
|
||||
/* 缓存已满且全部固定时,允许调用方提供 transient 承接一次性解码结果。 */
|
||||
ESP_RETURN_ON_FALSE(path != NULL && out_image != NULL,
|
||||
ESP_ERR_INVALID_ARG,
|
||||
TAG,
|
||||
@@ -579,6 +606,7 @@ static esp_err_t lcd_direct_gpio_init(void)
|
||||
|
||||
static esp_err_t lcd_direct_panel_init(void)
|
||||
{
|
||||
/* 面板命令、方向和颜色格式在显示开启前一次性配置完成。 */
|
||||
spi_bus_config_t bus_config = {
|
||||
.mosi_io_num = CONFIG_TQ_SCREEN_SPI_MOSI_GPIO,
|
||||
.miso_io_num = CONFIG_TQ_SCREEN_SPI_MISO_GPIO,
|
||||
@@ -735,6 +763,7 @@ static size_t lcd_direct_prepare_icons(const lcd_direct_icon_draw_t *icons,
|
||||
lcd_direct_prepared_icon_t *prepared,
|
||||
size_t prepared_capacity)
|
||||
{
|
||||
/* 绘制前集中解析全部图标,避免逐行合成时反复访问文件系统。 */
|
||||
if (icons == NULL || icon_count == 0U || prepared == NULL || prepared_capacity == 0U) {
|
||||
return 0;
|
||||
}
|
||||
@@ -836,6 +865,7 @@ static void lcd_direct_overlay_icons(uint16_t *pixel_buffer,
|
||||
const lcd_direct_prepared_icon_t *icons,
|
||||
size_t icon_count)
|
||||
{
|
||||
/* 只处理当前传输行范围,并依据 alpha 平面与背景执行 RGB565 混合。 */
|
||||
if (pixel_buffer == NULL || icons == NULL || icon_count == 0U) {
|
||||
return;
|
||||
}
|
||||
@@ -972,6 +1002,7 @@ static void lcd_direct_overlay_clock(uint16_t *pixel_buffer,
|
||||
uint16_t row_count,
|
||||
const char text[LCD_DIRECT_CLOCK_TEXT_LENGTH + 1U])
|
||||
{
|
||||
/* 时钟作为最终覆盖层绘制,保证 presentation 与状态图标不会遮住时间。 */
|
||||
if (pixel_buffer == NULL || text == NULL) {
|
||||
return;
|
||||
}
|
||||
@@ -996,6 +1027,7 @@ static esp_err_t lcd_direct_draw_png_image(const lcd_direct_image_t *image,
|
||||
const lcd_direct_icon_draw_t *icons,
|
||||
size_t icon_count)
|
||||
{
|
||||
/* 按固定行块合成主图、状态图标和时钟,限制单次 DMA 缓冲占用。 */
|
||||
ESP_RETURN_ON_FALSE(image != NULL,
|
||||
ESP_ERR_INVALID_ARG,
|
||||
TAG,
|
||||
@@ -1138,6 +1170,7 @@ static bool lcd_direct_preheat_is_pending(void)
|
||||
static bool lcd_direct_take_preheat(lcd_direct_request_t *out_request,
|
||||
uint32_t *out_generation)
|
||||
{
|
||||
/* 取出请求与标记 in-progress 在同一临界区完成,确保只有渲染任务消费。 */
|
||||
bool has_request = false;
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
if (s_preheat_pending_valid) {
|
||||
@@ -1165,7 +1198,7 @@ static void lcd_direct_complete_preheat(uint32_t generation, esp_err_t result)
|
||||
}
|
||||
}
|
||||
|
||||
/* Called only by the LCD render task while it owns s_lock. */
|
||||
/* 仅由持有 s_lock 的 LCD 渲染任务调用,预热不会与实际绘制并发访问缓存。 */
|
||||
static bool lcd_direct_service_preheat(void)
|
||||
{
|
||||
lcd_direct_request_t request = {0};
|
||||
@@ -1465,6 +1498,7 @@ static esp_err_t lcd_direct_run_boundary_test(const lcd_direct_request_t *reques
|
||||
|
||||
static esp_err_t lcd_direct_play_asset_sequence(const lcd_direct_request_t *request)
|
||||
{
|
||||
/* 每帧间隙检查更高优先级请求和停止标志,使长动画可以被及时抢占。 */
|
||||
ESP_RETURN_ON_FALSE(request != NULL,
|
||||
ESP_ERR_INVALID_ARG,
|
||||
TAG,
|
||||
@@ -1602,6 +1636,7 @@ static esp_err_t lcd_direct_play_basic_face_until_pending(void)
|
||||
|
||||
static void lcd_direct_signal_completion(void)
|
||||
{
|
||||
/* 完成计数是事实来源,二值信号量仅用于唤醒等待者,不承担事件计数。 */
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
s_completion_count++;
|
||||
portEXIT_CRITICAL(&s_state_lock);
|
||||
@@ -1612,6 +1647,7 @@ static void lcd_direct_signal_completion(void)
|
||||
|
||||
static bool lcd_direct_take_pending(lcd_direct_request_t *out)
|
||||
{
|
||||
/* pending 槽采用移动语义,取出后立即清零,渲染任务独占本地副本。 */
|
||||
bool has_pending = false;
|
||||
portENTER_CRITICAL(&s_state_lock);
|
||||
if (s_pending_valid) {
|
||||
@@ -1626,6 +1662,7 @@ static bool lcd_direct_take_pending(lcd_direct_request_t *out)
|
||||
|
||||
esp_err_t lcd_direct_init_device(void)
|
||||
{
|
||||
/* 同步原语先于 GPIO/SPI 创建,任意异步完成回调到达时所需对象均已存在。 */
|
||||
if (s_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -1713,6 +1750,7 @@ esp_err_t lcd_direct_set_backlight_brightness(uint8_t brightness_percent)
|
||||
|
||||
void lcd_direct_start_render_loop(void)
|
||||
{
|
||||
/* 当前调用任务本身成为渲染任务,因此此函数在收到停止请求前不会返回。 */
|
||||
if (!s_initialized) {
|
||||
ESP_LOGW(TAG, "Direct LCD is not initialized, skip render loop");
|
||||
return;
|
||||
@@ -1810,6 +1848,7 @@ void lcd_direct_start_render_loop(void)
|
||||
|
||||
esp_err_t lcd_direct_stop_render_loop(int timeout_ms)
|
||||
{
|
||||
/* 先协作停止;超过边界后删除已记录任务,并取消所有预热等待者。 */
|
||||
if (timeout_ms < 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
@@ -1887,6 +1926,7 @@ esp_err_t lcd_direct_set_status_overlay(const platform_lcd_status_overlay_t *ove
|
||||
|
||||
esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_request_t *request)
|
||||
{
|
||||
/* 校验并深拷贝可变数组后再发布 pending,发布完成即不再引用调用方数据。 */
|
||||
if (!s_initialized || request == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
@@ -1934,6 +1974,7 @@ esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_reques
|
||||
|
||||
esp_err_t lcd_direct_preheat_presentation(const platform_lcd_presentation_request_t *request)
|
||||
{
|
||||
/* 每次只允许一个同步预热等待者,以 generation 区分迟到的完成信号。 */
|
||||
if (!s_initialized || request == NULL) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
@@ -2022,6 +2063,7 @@ uint32_t lcd_direct_presentation_completion_count(void)
|
||||
|
||||
esp_err_t lcd_direct_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms)
|
||||
{
|
||||
/* 循环检查单调计数而非相信信号量次数,可正确处理合并唤醒和先完成后等待。 */
|
||||
if (!s_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user