1055 lines
38 KiB
C
1055 lines
38 KiB
C
/*
|
|
* 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 <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.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_MADCTL 0x36
|
|
#define LCD_CMD_RAMCTRL 0xB0
|
|
|
|
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];
|
|
uint8_t frame_order[PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX];
|
|
size_t frame_order_count;
|
|
uint16_t repeat_count;
|
|
uint32_t frame_ms;
|
|
uint32_t duration_ms;
|
|
platform_lcd_presentation_step_t steps[PLATFORM_LCD_PRESENTATION_STEP_MAX];
|
|
size_t step_count;
|
|
} 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 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 lcd_direct_rle_run_t s_rle_file_chunk[128];
|
|
|
|
static void lcd_direct_signal_completion(void);
|
|
|
|
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 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_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
|
|
.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
|
|
.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};
|
|
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 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 lcd_direct_rle_run_t *lcd_direct_load_icon_runs(const lcd_direct_icon_t *icon)
|
|
{
|
|
if (icon == NULL || icon->path == NULL || icon->run_count == 0U) {
|
|
return NULL;
|
|
}
|
|
|
|
const size_t bytes_to_read = icon->run_count * sizeof(lcd_direct_rle_run_t);
|
|
lcd_direct_rle_run_t *runs = (lcd_direct_rle_run_t *)malloc(bytes_to_read);
|
|
if (runs == NULL) {
|
|
ESP_LOGW(TAG, "allocate LCD icon failed path=%s bytes=%u", icon->path, (unsigned)bytes_to_read);
|
|
return NULL;
|
|
}
|
|
|
|
errno = 0;
|
|
int fd = open(icon->path, O_RDONLY);
|
|
if (fd < 0) {
|
|
ESP_LOGW(TAG, "open LCD icon failed path=%s errno=%d", icon->path, errno);
|
|
free(runs);
|
|
return NULL;
|
|
}
|
|
|
|
size_t offset = 0;
|
|
while (offset < bytes_to_read) {
|
|
ssize_t bytes_read = read(fd, ((uint8_t *)runs) + offset, bytes_to_read - offset);
|
|
if (bytes_read <= 0) {
|
|
ESP_LOGW(TAG,
|
|
"read LCD icon failed path=%s requested=%u read=%d errno=%d",
|
|
icon->path,
|
|
(unsigned)bytes_to_read,
|
|
(int)bytes_read,
|
|
errno);
|
|
close(fd);
|
|
free(runs);
|
|
return NULL;
|
|
}
|
|
offset += (size_t)bytes_read;
|
|
}
|
|
|
|
close(fd);
|
|
return runs;
|
|
}
|
|
|
|
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->width == 0U || icon->height == 0U) {
|
|
continue;
|
|
}
|
|
if (draw->x >= LCD_DIRECT_WIDTH || draw->y >= LCD_DIRECT_HEIGHT) {
|
|
continue;
|
|
}
|
|
|
|
lcd_direct_rle_run_t *loaded_runs = NULL;
|
|
const lcd_direct_rle_run_t *runs = icon->runs;
|
|
if (runs == NULL) {
|
|
loaded_runs = lcd_direct_load_icon_runs(icon);
|
|
runs = loaded_runs;
|
|
}
|
|
if (runs == NULL) {
|
|
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) {
|
|
free(loaded_runs);
|
|
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 + 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(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;
|
|
}
|
|
}
|
|
|
|
free(loaded_runs);
|
|
}
|
|
}
|
|
|
|
static esp_err_t lcd_direct_draw_rle_memory_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);
|
|
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_rle_file_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->path != NULL,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"invalid file image");
|
|
|
|
errno = 0;
|
|
int fd = open(image->path, O_RDONLY);
|
|
if (fd < 0) {
|
|
ESP_LOGE(TAG, "open LCD asset failed path=%s errno=%d", image->path, errno);
|
|
return ESP_ERR_NOT_FOUND;
|
|
}
|
|
|
|
uint32_t run_index = 0;
|
|
uint32_t chunk_index = 0;
|
|
uint32_t chunk_count = 0;
|
|
uint32_t run_remaining = 0;
|
|
uint16_t run_color = 0;
|
|
esp_err_t result = ESP_OK;
|
|
|
|
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;
|
|
}
|
|
if (chunk_index >= chunk_count) {
|
|
const size_t runs_left = image->run_count - run_index;
|
|
const size_t runs_to_read = runs_left < (sizeof(s_rle_file_chunk) / sizeof(s_rle_file_chunk[0])) ?
|
|
runs_left :
|
|
(sizeof(s_rle_file_chunk) / sizeof(s_rle_file_chunk[0]));
|
|
const size_t bytes_to_read = runs_to_read * sizeof(s_rle_file_chunk[0]);
|
|
ssize_t bytes_read = read(fd, s_rle_file_chunk, bytes_to_read);
|
|
if (bytes_read != (ssize_t)bytes_to_read) {
|
|
ESP_LOGE(TAG,
|
|
"read LCD asset failed path=%s requested=%u read=%d errno=%d",
|
|
image->path,
|
|
(unsigned)bytes_to_read,
|
|
(int)bytes_read,
|
|
errno);
|
|
result = ESP_FAIL;
|
|
goto out;
|
|
}
|
|
chunk_index = 0;
|
|
chunk_count = runs_to_read;
|
|
}
|
|
|
|
run_color = lcd_direct_swap_color(s_rle_file_chunk[chunk_index].color);
|
|
run_remaining = s_rle_file_chunk[chunk_index].count;
|
|
chunk_index++;
|
|
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);
|
|
result = lcd_direct_draw_rows(row_start, row_count);
|
|
if (result != ESP_OK) {
|
|
ESP_LOGE(TAG, "write image pixels failed path=%s", image->path);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
close(fd);
|
|
return result;
|
|
}
|
|
|
|
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,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"invalid image");
|
|
if (image->runs != NULL) {
|
|
return lcd_direct_draw_rle_memory_image(image, icons, icon_count);
|
|
}
|
|
return lcd_direct_draw_rle_file_image(image, icons, icon_count);
|
|
}
|
|
|
|
static esp_err_t lcd_direct_draw_asset_sequence_once(const lcd_direct_request_t *request)
|
|
{
|
|
ESP_RETURN_ON_FALSE(request != NULL,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"invalid asset request");
|
|
|
|
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
|
|
for (size_t i = 0; i < frame_count; ++i) {
|
|
bool stop = false;
|
|
portENTER_CRITICAL(&s_state_lock);
|
|
stop = s_stop_requested || s_pending_valid;
|
|
portEXIT_CRITICAL(&s_state_lock);
|
|
if (stop) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
const uint8_t frame_index = request->frame_order_count > 0U ? request->frame_order[i] : (uint8_t)i;
|
|
const lcd_direct_image_t *frame = lcd_direct_asset_frame(request->asset_dir, frame_index);
|
|
ESP_RETURN_ON_FALSE(frame != NULL,
|
|
ESP_ERR_NOT_FOUND,
|
|
TAG,
|
|
"LCD asset frame not found asset=%s frame=%u",
|
|
request->asset_dir,
|
|
(unsigned)frame_index);
|
|
|
|
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, NULL, 0),
|
|
TAG,
|
|
"draw asset frame failed");
|
|
|
|
const uint32_t frame_ms = request->frame_ms > 0U ? request->frame_ms : 100U;
|
|
vTaskDelay(pdMS_TO_TICKS(frame_ms));
|
|
}
|
|
|
|
static TickType_t s_last_draw_success_tick;
|
|
TickType_t now = xTaskGetTickCount();
|
|
if (s_last_draw_success_tick == 0 ||
|
|
(now - s_last_draw_success_tick) >= pdMS_TO_TICKS(5000)) {
|
|
s_last_draw_success_tick = now;
|
|
ESP_LOGI(TAG,
|
|
"LCD asset sequence drawn asset=%s frames=%u",
|
|
request->asset_dir,
|
|
(unsigned)frame_count);
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t lcd_direct_draw_asset_sequence_for_duration(const lcd_direct_request_t *request,
|
|
TickType_t start_tick,
|
|
TickType_t duration_ticks)
|
|
{
|
|
ESP_RETURN_ON_FALSE(request != NULL,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"invalid timed asset request");
|
|
|
|
const size_t frame_count = request->frame_order_count > 0U ? request->frame_order_count : 1U;
|
|
while ((xTaskGetTickCount() - start_tick) < duration_ticks) {
|
|
for (size_t i = 0; i < frame_count; ++i) {
|
|
bool stop = false;
|
|
portENTER_CRITICAL(&s_state_lock);
|
|
stop = s_stop_requested || s_pending_valid;
|
|
portEXIT_CRITICAL(&s_state_lock);
|
|
if (stop) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
const uint8_t frame_index = request->frame_order_count > 0U ? request->frame_order[i] : (uint8_t)i;
|
|
const lcd_direct_image_t *frame = lcd_direct_asset_frame(request->asset_dir, frame_index);
|
|
ESP_RETURN_ON_FALSE(frame != NULL,
|
|
ESP_ERR_NOT_FOUND,
|
|
TAG,
|
|
"LCD asset frame not found asset=%s frame=%u",
|
|
request->asset_dir,
|
|
(unsigned)frame_index);
|
|
|
|
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, NULL, 0),
|
|
TAG,
|
|
"draw timed asset frame failed");
|
|
|
|
const TickType_t now = xTaskGetTickCount();
|
|
const TickType_t elapsed_ticks = now - start_tick;
|
|
if (elapsed_ticks >= duration_ticks) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
const uint32_t frame_ms = request->frame_ms > 0U ? request->frame_ms : 100U;
|
|
const TickType_t frame_ticks = pdMS_TO_TICKS(frame_ms);
|
|
const TickType_t remaining_ticks = duration_ticks - elapsed_ticks;
|
|
vTaskDelay(frame_ticks < remaining_ticks ? frame_ticks : remaining_ticks);
|
|
}
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
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,
|
|
"invalid asset sequence");
|
|
|
|
if (request->asset_dir[0] == '\0' ||
|
|
lcd_direct_asset_frame(request->asset_dir,
|
|
request->frame_order_count > 0U ? request->frame_order[0] : 0U) == NULL) {
|
|
ESP_LOGW(TAG, "LCD asset sequence unavailable, fallback to basic face asset=%s", request->asset_dir);
|
|
const lcd_direct_request_t fallback = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.asset_dir = "jichubiaoqing",
|
|
.frame_order = {0, 1, 2},
|
|
.frame_order_count = 3,
|
|
.repeat_count = 1,
|
|
.frame_ms = 100,
|
|
};
|
|
esp_err_t fallback_err = lcd_direct_draw_asset_sequence_once(&fallback);
|
|
if (fallback_err == ESP_OK && request->repeat_count > 0U) {
|
|
lcd_direct_signal_completion();
|
|
}
|
|
return fallback_err;
|
|
}
|
|
|
|
if (request->duration_ms > 0U) {
|
|
const TickType_t start_tick = xTaskGetTickCount();
|
|
const TickType_t duration_ticks = pdMS_TO_TICKS(request->duration_ms);
|
|
esp_err_t err = lcd_direct_draw_asset_sequence_for_duration(request, start_tick, duration_ticks);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
lcd_direct_signal_completion();
|
|
return ESP_OK;
|
|
}
|
|
|
|
uint16_t remaining = request->repeat_count;
|
|
do {
|
|
esp_err_t err = lcd_direct_draw_asset_sequence_once(request);
|
|
if (err != ESP_OK) {
|
|
return err;
|
|
}
|
|
if (request->repeat_count > 0U) {
|
|
remaining--;
|
|
}
|
|
} while (request->repeat_count == 0U || remaining > 0U);
|
|
|
|
lcd_direct_signal_completion();
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t lcd_direct_play_step_sequence(const lcd_direct_request_t *request)
|
|
{
|
|
ESP_RETURN_ON_FALSE(request != NULL,
|
|
ESP_ERR_INVALID_ARG,
|
|
TAG,
|
|
"invalid step sequence");
|
|
|
|
for (size_t i = 0; i < request->step_count; ++i) {
|
|
bool stop = false;
|
|
portENTER_CRITICAL(&s_state_lock);
|
|
stop = s_stop_requested || s_pending_valid;
|
|
portEXIT_CRITICAL(&s_state_lock);
|
|
if (stop) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
|
|
const platform_lcd_presentation_step_t *step = &request->steps[i];
|
|
const lcd_direct_image_t *frame = lcd_direct_asset_frame(step->asset_dir, step->frame_index);
|
|
ESP_RETURN_ON_FALSE(frame != NULL,
|
|
ESP_ERR_NOT_FOUND,
|
|
TAG,
|
|
"LCD step frame not found asset=%s frame=%u",
|
|
step->asset_dir != NULL ? step->asset_dir : "<null>",
|
|
(unsigned)step->frame_index);
|
|
|
|
lcd_direct_icon_draw_t icon = {0};
|
|
const lcd_direct_icon_draw_t *icons = NULL;
|
|
size_t icon_count = 0;
|
|
if (step->icon_id != PLATFORM_LCD_PRESENTATION_NO_ICON) {
|
|
icon = (lcd_direct_icon_draw_t) {
|
|
.icon_id = step->icon_id,
|
|
.x = step->icon_x,
|
|
.y = step->icon_y,
|
|
};
|
|
icons = &icon;
|
|
icon_count = 1;
|
|
}
|
|
|
|
ESP_RETURN_ON_ERROR(lcd_direct_draw_rle_image(frame, icons, icon_count),
|
|
TAG,
|
|
"draw step frame failed");
|
|
|
|
uint32_t delay_ms = step->delay_ms > 0U ? step->delay_ms : 100U;
|
|
vTaskDelay(pdMS_TO_TICKS(delay_ms));
|
|
}
|
|
|
|
lcd_direct_signal_completion();
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t lcd_direct_play_basic_face_until_pending(void)
|
|
{
|
|
const lcd_direct_request_t request = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE,
|
|
.asset_dir = "jichubiaoqing",
|
|
.frame_order = {0, 1, 2},
|
|
.frame_order_count = 3,
|
|
.repeat_count = 0,
|
|
.frame_ms = 100,
|
|
};
|
|
|
|
while (true) {
|
|
bool stop = false;
|
|
portENTER_CRITICAL(&s_state_lock);
|
|
stop = s_stop_requested || s_pending_valid;
|
|
portEXIT_CRITICAL(&s_state_lock);
|
|
if (stop) {
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t err = lcd_direct_draw_asset_sequence_once(&request);
|
|
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
|
|
return err;
|
|
}
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
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_play_basic_face_until_pending();
|
|
} else if (request.kind == PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE) {
|
|
draw_err = lcd_direct_play_asset_sequence(&request);
|
|
if (draw_err == ESP_OK && request.return_to_idle) {
|
|
lcd_direct_request_t idle = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_IDLE,
|
|
};
|
|
portENTER_CRITICAL(&s_state_lock);
|
|
s_pending = idle;
|
|
s_pending_valid = true;
|
|
portEXIT_CRITICAL(&s_state_lock);
|
|
}
|
|
} else if (request.kind == PLATFORM_LCD_PRESENTATION_STEP_SEQUENCE) {
|
|
draw_err = lcd_direct_play_step_sequence(&request);
|
|
if (draw_err == ESP_OK && request.return_to_idle) {
|
|
lcd_direct_request_t idle = {
|
|
.kind = PLATFORM_LCD_PRESENTATION_IDLE,
|
|
};
|
|
portENTER_CRITICAL(&s_state_lock);
|
|
s_pending = idle;
|
|
s_pending_valid = true;
|
|
portEXIT_CRITICAL(&s_state_lock);
|
|
}
|
|
} else {
|
|
draw_err = lcd_direct_draw_test_pattern(&request);
|
|
}
|
|
xSemaphoreGive(s_lock);
|
|
if (draw_err != ESP_OK && draw_err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGW(TAG, "LCD presentation draw failed: %s", esp_err_to_name(draw_err));
|
|
}
|
|
}
|
|
} else {
|
|
if (xSemaphoreTake(s_lock, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
|
esp_err_t idle_err = lcd_direct_play_basic_face_until_pending();
|
|
xSemaphoreGive(s_lock);
|
|
if (idle_err != ESP_OK && idle_err != ESP_ERR_INVALID_STATE) {
|
|
ESP_LOGW(TAG, "LCD idle draw failed: %s", esp_err_to_name(idle_err));
|
|
vTaskDelay(pdMS_TO_TICKS(100));
|
|
}
|
|
}
|
|
}
|
|
|
|
(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,
|
|
.duration_ms = request->duration_ms,
|
|
};
|
|
if (request->asset_dir != NULL) {
|
|
strlcpy(copied.asset_dir, request->asset_dir, sizeof(copied.asset_dir));
|
|
}
|
|
if (request->frame_order != NULL && request->frame_order_count > 0U) {
|
|
copied.frame_order_count = request->frame_order_count;
|
|
if (copied.frame_order_count > PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX) {
|
|
copied.frame_order_count = PLATFORM_LCD_PRESENTATION_FRAME_ORDER_MAX;
|
|
}
|
|
memcpy(copied.frame_order, request->frame_order, copied.frame_order_count);
|
|
}
|
|
if (request->steps != NULL && request->step_count > 0U) {
|
|
copied.step_count = request->step_count;
|
|
if (copied.step_count > PLATFORM_LCD_PRESENTATION_STEP_MAX) {
|
|
copied.step_count = PLATFORM_LCD_PRESENTATION_STEP_MAX;
|
|
}
|
|
memcpy(copied.steps, request->steps, copied.step_count * sizeof(copied.steps[0]));
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|