删除LVGL,屏幕驱动测试完成

This commit is contained in:
CSBOOK\44744
2026-06-25 23:10:18 +08:00
parent 5812fc500b
commit 3993bf962f
220 changed files with 1059 additions and 3455 deletions
+821
View File
@@ -0,0 +1,821 @@
/*
* Direct ST7789 LCD backend.
*
* This backend owns the low-level panel initialization, a serialized render
* loop, and a minimal presentation queue.
*/
#include "lcd_direct.h"
#include "lcd_direct_assets.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_check.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#define LCD_DIRECT_SPI_HOST SPI2_HOST
#define LCD_DIRECT_WIDTH 320U
#define LCD_DIRECT_HEIGHT 240U
#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_CMD_SWRESET 0x01
#define LCD_CMD_SLPOUT 0x11
#define LCD_CMD_COLMOD 0x3A
#define LCD_CMD_MADCTL 0x36
#define LCD_CMD_CASET 0x2A
#define LCD_CMD_RASET 0x2B
#define LCD_CMD_RAMWR 0x2C
#define LCD_CMD_INVON 0x21
#define LCD_CMD_INVOFF 0x20
#define LCD_CMD_DISPON 0x29
#define LCD_CMD_RAMCTRL 0xB0
#define LCD_RAMCTRL_LE_BIT (1U << 3)
static const char *TAG = "lcd_direct";
typedef struct {
platform_lcd_presentation_kind_t kind;
uint8_t priority;
bool interrupt_current;
bool return_to_idle;
char asset_dir[PLATFORM_LCD_PRESENTATION_ASSET_DIR_MAX];
uint16_t repeat_count;
uint32_t frame_ms;
} lcd_direct_request_t;
typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t databytes;
} lcd_direct_init_cmd_t;
static const lcd_direct_init_cmd_t s_st7789_init_cmds[] = {
{0x36, {(1 << 5) | (1 << 6)}, 1},
{0x3A, {0x55}, 1},
{0xB2, {0x0C, 0x0C, 0x00, 0x33, 0x33}, 5},
{0xB7, {0x45}, 1},
{0xBB, {0x2B}, 1},
{0xC0, {0x2C}, 1},
{0xC2, {0x01, 0xFF}, 2},
{0xC3, {0x11}, 1},
{0xC4, {0x20}, 1},
{0xC6, {0x0F}, 1},
{0xD0, {0xA4, 0xA1}, 2},
{0xE0, {0xD0, 0x00, 0x05, 0x0E, 0x15, 0x0D, 0x37, 0x43, 0x47, 0x09, 0x15, 0x12, 0x16, 0x19}, 14},
{0xE1, {0xD0, 0x00, 0x05, 0x0D, 0x0C, 0x06, 0x2D, 0x44, 0x40, 0x0E, 0x1C, 0x18, 0x16, 0x19}, 14},
{0, {0}, 0xFF},
};
static spi_device_handle_t s_spi;
static esp_lcd_panel_io_handle_t s_panel_io;
static esp_lcd_panel_handle_t s_panel;
static SemaphoreHandle_t s_lock;
static SemaphoreHandle_t s_completion_sem;
static TaskHandle_t s_render_task;
static bool s_initialized;
static bool s_render_loop_running;
static bool s_stop_requested;
static bool s_pending_valid;
static lcd_direct_request_t s_pending;
static uint32_t s_completion_count;
static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED;
static uint16_t s_line_buffer[LCD_DIRECT_PIXELS_PER_TRANSFER];
static uint16_t lcd_direct_swap_color(uint16_t color)
{
#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES
return (uint16_t)((color >> 8) | (color << 8));
#else
return color;
#endif
}
static void lcd_direct_delay_ms(uint32_t ms)
{
vTaskDelay(pdMS_TO_TICKS(ms));
}
static esp_err_t lcd_direct_spi_write(const void *data, uint32_t length)
{
spi_transaction_t transaction = {
.length = length * 8U,
.tx_buffer = data,
};
return spi_device_polling_transmit(s_spi, &transaction);
}
static esp_err_t lcd_direct_write(uint8_t dc_level, uint16_t value, uint8_t length)
{
uint8_t bytes[2] = {
(uint8_t)(value >> 8),
(uint8_t)value,
};
const uint8_t *data = length == 1U ? &bytes[1] : bytes;
gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, dc_level);
return lcd_direct_spi_write(data, length);
}
static esp_err_t lcd_direct_write_bytes(uint8_t dc_level, const uint8_t *data, uint32_t length)
{
gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, dc_level);
return lcd_direct_spi_write(data, length);
}
static esp_err_t lcd_direct_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_CASET, 1), TAG, "CASET failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(1, x0, 2), TAG, "CASET x0 failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(1, x1, 2), TAG, "CASET x1 failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_RASET, 1), TAG, "RASET failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(1, y0, 2), TAG, "RASET y0 failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(1, y1, 2), TAG, "RASET y1 failed");
return lcd_direct_write(0, LCD_CMD_RAMWR, 1);
}
static esp_err_t lcd_direct_write_pixels(const void *data, uint32_t length)
{
gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, 1);
return lcd_direct_spi_write(data, length);
}
static esp_err_t lcd_direct_draw_rows(uint16_t row_start, uint16_t row_count)
{
ESP_RETURN_ON_FALSE(s_panel != NULL, ESP_ERR_INVALID_STATE, TAG, "panel is not ready");
return esp_lcd_panel_draw_bitmap(s_panel,
0,
row_start,
LCD_DIRECT_WIDTH,
row_start + row_count,
s_line_buffer);
}
static esp_err_t lcd_direct_gpio_init(void)
{
uint64_t pin_mask = (1ULL << CONFIG_TQ_SCREEN_BACKLIGHT_GPIO) |
(1ULL << CONFIG_TQ_SCREEN_DC_GPIO);
if (CONFIG_TQ_SCREEN_RESET_PIN >= 0) {
pin_mask |= (1ULL << CONFIG_TQ_SCREEN_RESET_PIN);
}
gpio_config_t gpio_config_data = {
.pin_bit_mask = pin_mask,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_RETURN_ON_ERROR(gpio_config(&gpio_config_data), TAG, "gpio config failed");
gpio_set_level(CONFIG_TQ_SCREEN_BACKLIGHT_GPIO,
CONFIG_TQ_SCREEN_BACKLIGHT_ACTIVE_HIGH ? 1 : 0);
gpio_set_level(CONFIG_TQ_SCREEN_DC_GPIO, 0);
if (CONFIG_TQ_SCREEN_RESET_PIN >= 0) {
gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 1);
}
return ESP_OK;
}
static esp_err_t lcd_direct_spi_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,
.sclk_io_num = CONFIG_TQ_SCREEN_SPI_CLK_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_DIRECT_WIDTH * LCD_DIRECT_ROWS_PER_TRANSFER * sizeof(uint16_t),
};
esp_err_t err = spi_bus_initialize(LCD_DIRECT_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO);
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "spi bus init failed: %s", esp_err_to_name(err));
return err;
}
spi_device_interface_config_t device_config = {
.clock_speed_hz = CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ,
.mode = 0,
.spics_io_num = CONFIG_TQ_SCREEN_SPI_CS_GPIO,
.queue_size = 8,
};
ESP_RETURN_ON_ERROR(spi_bus_add_device(LCD_DIRECT_SPI_HOST, &device_config, &s_spi),
TAG,
"spi add device failed");
return ESP_OK;
}
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,
.sclk_io_num = CONFIG_TQ_SCREEN_SPI_CLK_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_DIRECT_PIXELS_PER_TRANSFER * sizeof(uint16_t),
};
ESP_RETURN_ON_ERROR(spi_bus_initialize(LCD_DIRECT_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO),
TAG,
"esp_lcd spi bus init failed");
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = CONFIG_TQ_SCREEN_DC_GPIO,
.cs_gpio_num = CONFIG_TQ_SCREEN_SPI_CS_GPIO,
.pclk_hz = CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.spi_mode = 0,
.trans_queue_depth = 10,
};
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_DIRECT_SPI_HOST,
&io_config,
&s_panel_io),
TAG,
"new panel io failed");
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = CONFIG_TQ_SCREEN_RESET_PIN,
#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
#else
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
#endif
#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
#else
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
#endif
.bits_per_pixel = 16,
};
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_st7789(s_panel_io, &panel_config, &s_panel),
TAG,
"new st7789 panel failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel), TAG, "panel reset failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_init(s_panel), TAG, "panel init failed");
for (size_t i = 0; s_st7789_init_cmds[i].databytes != 0xFFU; ++i) {
const lcd_direct_init_cmd_t *cmd = &s_st7789_init_cmds[i];
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io,
cmd->cmd,
cmd->databytes > 0U ? cmd->data : NULL,
cmd->databytes),
TAG,
"custom init cmd failed");
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_swap_xy(s_panel, CONFIG_TQ_SCREEN_SWAP_XY),
TAG,
"panel swap xy failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_mirror(s_panel, CONFIG_TQ_SCREEN_MIRROR_X, false),
TAG,
"panel mirror failed");
uint8_t madctl = 0;
if (CONFIG_TQ_SCREEN_MIRROR_X) {
madctl |= 0x40;
}
if (CONFIG_TQ_SCREEN_SWAP_XY) {
madctl |= 0x20;
}
#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR
madctl |= 0x08;
#endif
uint8_t ramctrl[] = {0x00, 0xF0};
#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES
ramctrl[1] |= LCD_RAMCTRL_LE_BIT;
#endif
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, LCD_CMD_MADCTL, &madctl, 1),
TAG,
"panel MADCTL failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(s_panel_io, LCD_CMD_RAMCTRL, ramctrl, sizeof(ramctrl)),
TAG,
"panel RAMCTRL failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_invert_color(s_panel, CONFIG_TQ_SCREEN_INVERT_COLOR),
TAG,
"panel invert failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_set_gap(s_panel,
CONFIG_TQ_SCREEN_X_GAP,
CONFIG_TQ_SCREEN_Y_GAP),
TAG,
"panel set gap failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_disp_on_off(s_panel, true),
TAG,
"panel display on failed");
return ESP_OK;
}
static void lcd_direct_reset(void)
{
if (CONFIG_TQ_SCREEN_RESET_PIN < 0) {
return;
}
gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 1);
lcd_direct_delay_ms(20);
gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 0);
lcd_direct_delay_ms(20);
gpio_set_level(CONFIG_TQ_SCREEN_RESET_PIN, 1);
lcd_direct_delay_ms(120);
}
static uint8_t lcd_direct_madctl_value(void)
{
uint8_t value = 0;
if (CONFIG_TQ_SCREEN_MIRROR_X) {
value |= 0x40;
}
if (CONFIG_TQ_SCREEN_SWAP_XY) {
value |= 0x20;
}
#if CONFIG_TQ_SCREEN_COLOR_ORDER_BGR
value |= 0x08;
#endif
return value;
}
static esp_err_t lcd_direct_controller_init(void)
{
ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_SWRESET, 1), TAG, "SWRESET failed");
lcd_direct_delay_ms(150);
for (size_t i = 0; s_st7789_init_cmds[i].databytes != 0xFFU; ++i) {
const lcd_direct_init_cmd_t *cmd = &s_st7789_init_cmds[i];
ESP_RETURN_ON_ERROR(lcd_direct_write(0, cmd->cmd, 1), TAG, "custom init cmd failed");
if (cmd->databytes > 0U) {
ESP_RETURN_ON_ERROR(lcd_direct_write_bytes(1, cmd->data, cmd->databytes),
TAG,
"custom init param failed");
}
}
ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_MADCTL, 1), TAG, "MADCTL failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(1, lcd_direct_madctl_value(), 1), TAG, "MADCTL param failed");
uint8_t ramctrl[] = {0x00, 0xF0};
#if CONFIG_TQ_SCREEN_SWAP_COLOR_BYTES
ramctrl[1] |= LCD_RAMCTRL_LE_BIT;
#endif
ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_RAMCTRL, 1), TAG, "RAMCTRL failed");
ESP_RETURN_ON_ERROR(lcd_direct_write_bytes(1, ramctrl, sizeof(ramctrl)), TAG, "RAMCTRL param failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(0,
CONFIG_TQ_SCREEN_INVERT_COLOR ? LCD_CMD_INVON : LCD_CMD_INVOFF,
1),
TAG,
"invert command failed");
ESP_RETURN_ON_ERROR(lcd_direct_write(0, LCD_CMD_DISPON, 1), TAG, "DISPON failed");
lcd_direct_delay_ms(120);
return ESP_OK;
}
static uint16_t lcd_direct_color_for_row(uint16_t row, const lcd_direct_request_t *request)
{
uint16_t band = row / 40U;
if (request != NULL && request->kind == PLATFORM_LCD_PRESENTATION_SLOT_REELS) {
static const uint16_t slot_colors[] = {0x001F, 0xF800, 0xFFE0, 0x07E0, 0xF81F, 0xFFFF};
return slot_colors[band % (sizeof(slot_colors) / sizeof(slot_colors[0]))];
}
if (request != NULL && request->kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) {
uint32_t hash = 0;
for (size_t i = 0; request->asset_dir[i] != '\0'; ++i) {
hash = (hash * 33U) + (uint8_t)request->asset_dir[i];
}
static const uint16_t asset_colors[] = {0x0000, 0x07FF, 0xF800, 0x07E0, 0x001F, 0xFFFF};
return asset_colors[(hash + band) % (sizeof(asset_colors) / sizeof(asset_colors[0]))];
}
static const uint16_t idle_colors[] = {0x0000, 0x39E7, 0x7BEF, 0xFFFF, 0x7BEF, 0x39E7};
return idle_colors[band % (sizeof(idle_colors) / sizeof(idle_colors[0]))];
}
static esp_err_t lcd_direct_draw_test_pattern(const lcd_direct_request_t *request)
{
for (uint16_t row_start = 0; row_start < LCD_DIRECT_HEIGHT; row_start += LCD_DIRECT_ROWS_PER_TRANSFER) {
uint16_t row_count = LCD_DIRECT_HEIGHT - row_start;
if (row_count > LCD_DIRECT_ROWS_PER_TRANSFER) {
row_count = LCD_DIRECT_ROWS_PER_TRANSFER;
}
for (uint16_t y = 0; y < row_count; ++y) {
uint16_t color = lcd_direct_swap_color(lcd_direct_color_for_row(row_start + y, request));
for (uint16_t x = 0; x < LCD_DIRECT_WIDTH; ++x) {
s_line_buffer[(uint32_t)y * LCD_DIRECT_WIDTH + x] = color;
}
}
ESP_RETURN_ON_ERROR(lcd_direct_draw_rows(row_start, row_count),
TAG,
"write pixels failed");
}
return ESP_OK;
}
static void lcd_direct_overlay_icons(uint16_t *pixel_buffer,
uint16_t row_start,
uint16_t row_count,
const lcd_direct_icon_draw_t *icons,
size_t icon_count)
{
if (pixel_buffer == NULL || icons == NULL || icon_count == 0U) {
return;
}
const uint16_t row_end = row_start + row_count;
for (size_t icon_index = 0; icon_index < icon_count; ++icon_index) {
const lcd_direct_icon_draw_t *draw = &icons[icon_index];
const lcd_direct_icon_t *icon = lcd_direct_icon(draw->icon_id);
if (icon == NULL || icon->runs == NULL || icon->width == 0U || icon->height == 0U) {
continue;
}
if (draw->x >= LCD_DIRECT_WIDTH || draw->y >= LCD_DIRECT_HEIGHT) {
continue;
}
uint16_t draw_width = icon->width;
uint16_t draw_height = icon->height;
if (draw_width > LCD_DIRECT_WIDTH - draw->x) {
draw_width = LCD_DIRECT_WIDTH - draw->x;
}
if (draw_height > LCD_DIRECT_HEIGHT - draw->y) {
draw_height = LCD_DIRECT_HEIGHT - draw->y;
}
const uint16_t overlay_y0 = draw->y;
const uint16_t overlay_y1 = draw->y + draw_height;
if (overlay_y1 <= row_start || overlay_y0 >= row_end) {
continue;
}
const uint16_t copy_y0 = overlay_y0 > row_start ? overlay_y0 : row_start;
const uint16_t copy_y1 = overlay_y1 < row_end ? overlay_y1 : row_end;
for (uint16_t y = copy_y0; y < copy_y1; ++y) {
const uint16_t local_y = y - row_start;
const uint16_t image_y = y - draw->y;
const uint32_t icon_pixel_start = (uint32_t)image_y * icon->width;
const uint32_t icon_pixel_end = icon_pixel_start + draw_width;
uint32_t icon_pixel_pos = 0;
for (uint32_t run_index = 0;
run_index < icon->run_count && icon_pixel_pos < icon_pixel_end;
++run_index) {
const uint32_t run_start = icon_pixel_pos;
const uint32_t run_end = run_start + icon->runs[run_index].count;
if (run_end > icon_pixel_start && run_start < icon_pixel_end) {
const uint32_t copy_start = run_start > icon_pixel_start ? run_start : icon_pixel_start;
const uint32_t copy_end = run_end < icon_pixel_end ? run_end : icon_pixel_end;
const uint16_t color = lcd_direct_swap_color(icon->runs[run_index].color);
for (uint32_t x = copy_start; x < copy_end; ++x) {
pixel_buffer[(uint32_t)local_y * LCD_DIRECT_WIDTH + draw->x + (x - icon_pixel_start)] = color;
}
}
icon_pixel_pos = run_end;
}
}
}
}
static void lcd_direct_overlay_boot_marker(uint16_t *pixel_buffer, uint16_t row_start, uint16_t row_count)
{
const uint16_t marker_color = lcd_direct_swap_color(0xFFFF);
const uint16_t row_end = row_start + row_count;
for (uint16_t y = row_start; y < row_end; ++y) {
uint16_t local_y = y - row_start;
for (uint16_t x = 0; x < LCD_DIRECT_WIDTH; ++x) {
const bool border = y < 2U || y >= (LCD_DIRECT_HEIGHT - 2U) ||
x < 2U || x >= (LCD_DIRECT_WIDTH - 2U);
const bool center_line = (x >= 158U && x <= 161U && y >= 100U && y <= 140U) ||
(y >= 118U && y <= 121U && x >= 140U && x <= 180U);
if (border || center_line) {
pixel_buffer[(uint32_t)local_y * LCD_DIRECT_WIDTH + x] = marker_color;
}
}
}
}
static esp_err_t lcd_direct_draw_rle_image(const lcd_direct_image_t *image,
const lcd_direct_icon_draw_t *icons,
size_t icon_count)
{
ESP_RETURN_ON_FALSE(image != NULL && image->runs != NULL,
ESP_ERR_INVALID_ARG,
TAG,
"invalid image");
uint32_t run_index = 0;
uint32_t run_remaining = 0;
uint16_t run_color = 0;
for (uint16_t row_start = 0; row_start < LCD_DIRECT_HEIGHT; row_start += LCD_DIRECT_ROWS_PER_TRANSFER) {
uint16_t row_count = LCD_DIRECT_HEIGHT - row_start;
if (row_count > LCD_DIRECT_ROWS_PER_TRANSFER) {
row_count = LCD_DIRECT_ROWS_PER_TRANSFER;
}
const uint32_t pixels_needed = (uint32_t)LCD_DIRECT_WIDTH * row_count;
uint32_t buffered_pixels = 0;
while (buffered_pixels < pixels_needed) {
if (run_remaining == 0U) {
if (run_index >= image->run_count) {
break;
}
run_color = lcd_direct_swap_color(image->runs[run_index].color);
run_remaining = image->runs[run_index].count;
run_index++;
}
uint32_t pixels_to_copy = run_remaining;
const uint32_t pixels_left = pixels_needed - buffered_pixels;
if (pixels_to_copy > pixels_left) {
pixels_to_copy = pixels_left;
}
for (uint32_t i = 0; i < pixels_to_copy; ++i) {
s_line_buffer[buffered_pixels + i] = run_color;
}
buffered_pixels += pixels_to_copy;
run_remaining -= pixels_to_copy;
}
while (buffered_pixels < pixels_needed) {
s_line_buffer[buffered_pixels++] = 0;
}
lcd_direct_overlay_icons(s_line_buffer, row_start, row_count, icons, icon_count);
lcd_direct_overlay_boot_marker(s_line_buffer, row_start, row_count);
ESP_RETURN_ON_ERROR(lcd_direct_draw_rows(row_start, row_count),
TAG,
"write image pixels failed");
}
return ESP_OK;
}
static esp_err_t lcd_direct_draw_default_image(void)
{
size_t icon_count = 0;
const lcd_direct_icon_draw_t *icons = lcd_direct_default_icon_sequence(&icon_count);
ESP_LOGI(TAG, "Drawing default LCD image icons=%u", (unsigned)icon_count);
return lcd_direct_draw_rle_image(lcd_direct_default_image(), icons, icon_count);
}
void lcd_direct_run_blocking_test(void)
{
if (!s_initialized) {
ESP_LOGW(TAG, "Direct LCD is not initialized, skip blocking test");
return;
}
ESP_LOGI(TAG, "Direct LCD blocking test started");
lcd_direct_request_t color_test = {
.kind = PLATFORM_LCD_PRESENTATION_SLOT_REELS,
};
esp_err_t color_err = lcd_direct_draw_test_pattern(&color_test);
ESP_LOGI(TAG, "Direct LCD color test draw result=%s", esp_err_to_name(color_err));
lcd_direct_delay_ms(1000);
esp_err_t image_err = lcd_direct_draw_default_image();
ESP_LOGI(TAG, "Direct LCD default image draw result=%s", esp_err_to_name(image_err));
while (true) {
lcd_direct_delay_ms(1000);
}
}
static void lcd_direct_signal_completion(void)
{
portENTER_CRITICAL(&s_state_lock);
s_completion_count++;
portEXIT_CRITICAL(&s_state_lock);
if (s_completion_sem != NULL) {
(void)xSemaphoreGive(s_completion_sem);
}
}
static bool lcd_direct_take_pending(lcd_direct_request_t *out)
{
bool has_pending = false;
portENTER_CRITICAL(&s_state_lock);
if (s_pending_valid) {
*out = s_pending;
s_pending_valid = false;
memset(&s_pending, 0, sizeof(s_pending));
has_pending = true;
}
portEXIT_CRITICAL(&s_state_lock);
return has_pending;
}
esp_err_t lcd_direct_init_device(void)
{
if (s_initialized) {
return ESP_OK;
}
s_lock = xSemaphoreCreateMutex();
ESP_RETURN_ON_FALSE(s_lock != NULL, ESP_ERR_NO_MEM, TAG, "lock create failed");
s_completion_sem = xSemaphoreCreateBinary();
ESP_RETURN_ON_FALSE(s_completion_sem != NULL, ESP_ERR_NO_MEM, TAG, "completion sem create failed");
ESP_RETURN_ON_ERROR(lcd_direct_gpio_init(), TAG, "gpio init failed");
ESP_RETURN_ON_ERROR(lcd_direct_panel_init(), TAG, "esp_lcd panel init failed");
s_initialized = true;
ESP_LOGI(TAG,
"Direct LCD initialized (MOSI=%d SCLK=%d CS=%d DC=%d RST=%d BL=%d clock=%d)",
CONFIG_TQ_SCREEN_SPI_MOSI_GPIO,
CONFIG_TQ_SCREEN_SPI_CLK_GPIO,
CONFIG_TQ_SCREEN_SPI_CS_GPIO,
CONFIG_TQ_SCREEN_DC_GPIO,
CONFIG_TQ_SCREEN_RESET_PIN,
CONFIG_TQ_SCREEN_BACKLIGHT_GPIO,
CONFIG_TQ_SCREEN_SPI_PIXEL_CLOCK_HZ);
return ESP_OK;
}
void lcd_direct_start_render_loop(void)
{
if (!s_initialized) {
ESP_LOGW(TAG, "Direct LCD is not initialized, skip render loop");
return;
}
portENTER_CRITICAL(&s_state_lock);
if (s_render_loop_running) {
portEXIT_CRITICAL(&s_state_lock);
ESP_LOGI(TAG, "Direct LCD render loop already running");
return;
}
s_render_loop_running = true;
s_stop_requested = false;
s_render_task = xTaskGetCurrentTaskHandle();
portEXIT_CRITICAL(&s_state_lock);
ESP_LOGI(TAG, "Direct LCD render loop started");
esp_err_t default_draw_err = lcd_direct_draw_default_image();
ESP_LOGI(TAG, "Direct LCD default image draw result=%s", esp_err_to_name(default_draw_err));
while (true) {
bool stop = false;
portENTER_CRITICAL(&s_state_lock);
stop = s_stop_requested;
portEXIT_CRITICAL(&s_state_lock);
if (stop) {
break;
}
lcd_direct_request_t request = {0};
if (lcd_direct_take_pending(&request)) {
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) {
esp_err_t draw_err = ESP_OK;
if (request.kind == PLATFORM_LCD_PRESENTATION_IDLE) {
draw_err = lcd_direct_draw_default_image();
} else {
draw_err = lcd_direct_draw_test_pattern(&request);
}
xSemaphoreGive(s_lock);
if (draw_err == ESP_OK && request.repeat_count > 0U) {
lcd_direct_signal_completion();
}
}
}
(void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20));
}
portENTER_CRITICAL(&s_state_lock);
s_render_task = NULL;
s_render_loop_running = false;
s_stop_requested = false;
portEXIT_CRITICAL(&s_state_lock);
ESP_LOGI(TAG, "Direct LCD render loop stopped");
}
esp_err_t lcd_direct_stop_render_loop(int timeout_ms)
{
if (timeout_ms < 0) {
return ESP_ERR_INVALID_ARG;
}
TaskHandle_t task = NULL;
portENTER_CRITICAL(&s_state_lock);
task = s_render_task;
if (task != NULL) {
s_stop_requested = true;
}
portEXIT_CRITICAL(&s_state_lock);
if (task == NULL || timeout_ms == 0) {
return ESP_OK;
}
xTaskNotifyGive(task);
TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
if (timeout_ticks == 0) {
timeout_ticks = 1;
}
TickType_t start = xTaskGetTickCount();
while ((xTaskGetTickCount() - start) < timeout_ticks) {
bool stopped = false;
portENTER_CRITICAL(&s_state_lock);
stopped = !s_render_loop_running && s_render_task == NULL;
portEXIT_CRITICAL(&s_state_lock);
if (stopped) {
return ESP_OK;
}
vTaskDelay(pdMS_TO_TICKS(10));
}
if (task != xTaskGetCurrentTaskHandle()) {
ESP_LOGW(TAG, "Direct LCD render loop stop timed out after %d ms; force deleting", timeout_ms);
vTaskDelete(task);
portENTER_CRITICAL(&s_state_lock);
if (s_render_task == task) {
s_render_task = NULL;
}
s_render_loop_running = false;
s_stop_requested = false;
portEXIT_CRITICAL(&s_state_lock);
}
return ESP_ERR_TIMEOUT;
}
esp_err_t lcd_direct_request_presentation(const platform_lcd_presentation_request_t *request)
{
if (!s_initialized || request == NULL) {
return ESP_ERR_INVALID_STATE;
}
lcd_direct_request_t copied = {
.kind = request->kind,
.priority = request->priority,
.interrupt_current = request->interrupt_current,
.return_to_idle = request->return_to_idle,
.repeat_count = request->repeat_count,
.frame_ms = request->frame_ms,
};
if (request->asset_dir != NULL) {
strlcpy(copied.asset_dir, request->asset_dir, sizeof(copied.asset_dir));
}
TaskHandle_t task = NULL;
portENTER_CRITICAL(&s_state_lock);
s_pending = copied;
s_pending_valid = true;
task = s_render_task;
portEXIT_CRITICAL(&s_state_lock);
if (task != NULL) {
xTaskNotifyGive(task);
}
ESP_LOGI(TAG, "Direct LCD presentation queued kind=%d asset=%s", copied.kind, copied.asset_dir);
return ESP_OK;
}
esp_err_t lcd_direct_preheat_presentation(const platform_lcd_presentation_request_t *request)
{
(void)request;
return s_initialized ? ESP_OK : ESP_ERR_INVALID_STATE;
}
uint32_t lcd_direct_presentation_completion_count(void)
{
uint32_t count;
portENTER_CRITICAL(&s_state_lock);
count = s_completion_count;
portEXIT_CRITICAL(&s_state_lock);
return count;
}
esp_err_t lcd_direct_wait_presentation_completion(uint32_t since_count, uint32_t timeout_ms)
{
if (!s_initialized) {
return ESP_ERR_INVALID_STATE;
}
TickType_t start_tick = xTaskGetTickCount();
TickType_t timeout_ticks = pdMS_TO_TICKS(timeout_ms);
for (;;) {
if (lcd_direct_presentation_completion_count() != since_count) {
return ESP_OK;
}
if (timeout_ms == 0 || (xTaskGetTickCount() - start_tick) >= timeout_ticks) {
return ESP_ERR_TIMEOUT;
}
TickType_t elapsed = xTaskGetTickCount() - start_tick;
TickType_t remaining = timeout_ticks > elapsed ? timeout_ticks - elapsed : 0;
if (s_completion_sem != NULL && remaining > 0) {
(void)xSemaphoreTake(s_completion_sem, remaining);
} else {
vTaskDelay(pdMS_TO_TICKS(LCD_DIRECT_PRESENTATION_WAIT_POLL_MS));
}
}
}