删除LVGL,屏幕驱动测试完成
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user