Guition JC3248W535C (#467)

* Guition JC3248W535C

Files app fix
alert dialog and selection dialog fixes
symbol export

* Update Axs15231bDisplay.cpp

* Update Axs15231bDisplay.cpp
This commit is contained in:
Shadowtrance 2026-01-31 09:01:12 +10:00 committed by GitHub
parent 87ca888bb4
commit d62314f41f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1028 additions and 6 deletions

View File

@ -49,6 +49,7 @@ jobs:
{ id: elecrow-crowpanel-basic-50, arch: esp32s3 },
{ id: guition-jc1060p470ciwy, arch: esp32p4 },
{ id: guition-jc2432w328c, arch: esp32 },
{ id: guition-jc3248w535c, arch: esp32s3 },
{ id: guition-jc8048w550c, arch: esp32s3 },
{ id: heltec-wifi-lora-32-v3, arch: esp32s3 },
{ id: lilygo-tdeck, arch: esp32s3 },

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat esp_lcd_axs15231b PwmBacklight driver vfs fatfs
)

View File

@ -0,0 +1,495 @@
#include "Axs15231bDisplay.h"
#include <Tactility/Logger.h>
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/lvgl/LvglSync.h>
#include <Axs15231b/Axs15231bTouch.h>
#include <EspLcdDisplayDriver.h>
#include <esp_lcd_axs15231b.h>
#include <esp_lcd_panel_commands.h>
#include <esp_lcd_panel_ops.h>
#include <esp_heap_caps.h>
static const auto LOGGER = tt::Logger("AXS15231B");
static const axs15231b_lcd_init_cmd_t lcd_init_cmds[] = {
{0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5}, 8, 0},
{0xA0, (uint8_t[]){0xC0, 0x10, 0x00, 0x02, 0x00, 0x00, 0x04, 0x3F, 0x20, 0x05, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00}, 17, 0},
{0xA2, (uint8_t[]){0x30, 0x3C, 0x24, 0x14, 0xD0, 0x20, 0xFF, 0xE0, 0x40, 0x19, 0x80, 0x80, 0x80, 0x20, 0xf9, 0x10, 0x02, 0xff, 0xff, 0xF0, 0x90, 0x01, 0x32, 0xA0, 0x91, 0xE0, 0x20, 0x7F, 0xFF, 0x00, 0x5A}, 31, 0},
{0xD0, (uint8_t[]){0xE0, 0x40, 0x51, 0x24, 0x08, 0x05, 0x10, 0x01, 0x20, 0x15, 0x42, 0xC2, 0x22, 0x22, 0xAA, 0x03, 0x10, 0x12, 0x60, 0x14, 0x1E, 0x51, 0x15, 0x00, 0x8A, 0x20, 0x00, 0x03, 0x3A, 0x12}, 30, 0},
{0xA3, (uint8_t[]){0xA0, 0x06, 0xAa, 0x00, 0x08, 0x02, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x55, 0x55}, 22, 0},
{0xC1, (uint8_t[]){0x31, 0x04, 0x02, 0x02, 0x71, 0x05, 0x24, 0x55, 0x02, 0x00, 0x41, 0x00, 0x53, 0xFF, 0xFF, 0xFF, 0x4F, 0x52, 0x00, 0x4F, 0x52, 0x00, 0x45, 0x3B, 0x0B, 0x02, 0x0d, 0x00, 0xFF, 0x40}, 30, 0},
{0xC3, (uint8_t[]){0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01}, 11, 0},
{0xC4, (uint8_t[]){0x00, 0x24, 0x33, 0x80, 0x00, 0xea, 0x64, 0x32, 0xC8, 0x64, 0xC8, 0x32, 0x90, 0x90, 0x11, 0x06, 0xDC, 0xFA, 0x00, 0x00, 0x80, 0xFE, 0x10, 0x10, 0x00, 0x0A, 0x0A, 0x44, 0x50}, 29, 0},
{0xC5, (uint8_t[]){0x18, 0x00, 0x00, 0x03, 0xFE, 0x3A, 0x4A, 0x20, 0x30, 0x10, 0x88, 0xDE, 0x0D, 0x08, 0x0F, 0x0F, 0x01, 0x3A, 0x4A, 0x20, 0x10, 0x10, 0x00}, 23, 0},
{0xC6, (uint8_t[]){0x05, 0x0A, 0x05, 0x0A, 0x00, 0xE0, 0x2E, 0x0B, 0x12, 0x22, 0x12, 0x22, 0x01, 0x03, 0x00, 0x3F, 0x6A, 0x18, 0xC8, 0x22}, 20, 0},
{0xC7, (uint8_t[]){0x50, 0x32, 0x28, 0x00, 0xa2, 0x80, 0x8f, 0x00, 0x80, 0xff, 0x07, 0x11, 0x9c, 0x67, 0xff, 0x24, 0x0c, 0x0d, 0x0e, 0x0f}, 20, 0},
{0xC9, (uint8_t[]){0x33, 0x44, 0x44, 0x01}, 4, 0},
{0xCF, (uint8_t[]){0x2C, 0x1E, 0x88, 0x58, 0x13, 0x18, 0x56, 0x18, 0x1E, 0x68, 0x88, 0x00, 0x65, 0x09, 0x22, 0xC4, 0x0C, 0x77, 0x22, 0x44, 0xAA, 0x55, 0x08, 0x08, 0x12, 0xA0, 0x08}, 27, 0},
{0xD5, (uint8_t[]){0x40, 0x8E, 0x8D, 0x01, 0x35, 0x04, 0x92, 0x74, 0x04, 0x92, 0x74, 0x04, 0x08, 0x6A, 0x04, 0x46, 0x03, 0x03, 0x03, 0x03, 0x82, 0x01, 0x03, 0x00, 0xE0, 0x51, 0xA1, 0x00, 0x00, 0x00}, 30, 0},
{0xD6, (uint8_t[]){0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE, 0x93, 0x00, 0x01, 0x83, 0x07, 0x07, 0x00, 0x07, 0x07, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x84, 0x00, 0x20, 0x01, 0x00}, 30, 0},
{0xD7, (uint8_t[]){0x03, 0x01, 0x0b, 0x09, 0x0f, 0x0d, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19, 0x40, 0x8E, 0x04, 0x00, 0x20, 0xA0, 0x1F}, 19, 0},
{0xD8, (uint8_t[]){0x02, 0x00, 0x0a, 0x08, 0x0e, 0x0c, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19}, 12, 0},
{0xD9, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0},
{0xDD, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0},
{0xDF, (uint8_t[]){0x44, 0x73, 0x4B, 0x69, 0x00, 0x0A, 0x02, 0x90}, 8, 0},
{0xE0, (uint8_t[]){0x3B, 0x28, 0x10, 0x16, 0x0c, 0x06, 0x11, 0x28, 0x5c, 0x21, 0x0D, 0x35, 0x13, 0x2C, 0x33, 0x28, 0x0D}, 17, 0},
{0xE1, (uint8_t[]){0x37, 0x28, 0x10, 0x16, 0x0b, 0x06, 0x11, 0x28, 0x5C, 0x21, 0x0D, 0x35, 0x14, 0x2C, 0x33, 0x28, 0x0F}, 17, 0},
{0xE2, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0},
{0xE3, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x32, 0x2F, 0x0F}, 17, 0},
{0xE4, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0},
{0xE5, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0F}, 17, 0},
{0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x82, 0xAF, 0xAA, 0xAA, 0x80, 0x10, 0x30, 0x40, 0x40, 0x20, 0xFF, 0x60, 0x30}, 16, 0},
{0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x85}, 4, 0},
{0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8, 0},
{0x13, (uint8_t[]){0x00}, 0, 0},
{0x35, (uint8_t[]){0x00}, 1, 0}, // Enable Tearing Effect output (V-blank sync)
{0x11, (uint8_t[]){0x00}, 0, 120},
{0x2C, (uint8_t[]){0x00, 0x00, 0x00, 0x00}, 4, 0}
};
void Axs15231bDisplay::lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) {
Axs15231bDisplay* self = (Axs15231bDisplay*)lv_display_get_user_data(drv);
assert(self != nullptr);
assert(drv != nullptr);
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_driver_data(drv);
assert(panel_handle != nullptr);
// Swap RGB565 byte order for SPI transmission (big-endian wire format)
lv_draw_sw_rgb565_swap(color_map, lv_area_get_size(area));
lv_display_rotation_t rotation = lv_display_get_rotation(drv);
int logical_width = lv_display_get_horizontal_resolution(drv);
int logical_height = lv_display_get_vertical_resolution(drv);
uint16_t* draw_buf = (uint16_t*)color_map;
if (rotation != LV_DISPLAY_ROTATION_0) {
// Lazy-allocate tempBuf on first rotated frame
static bool allocationErrorLogged = false;
if (self->tempBuf == nullptr) {
self->tempBuf = (uint16_t *)heap_caps_malloc(
self->configuration->horizontalResolution * self->configuration->verticalResolution * sizeof(uint16_t),
MALLOC_CAP_SPIRAM);
if (self->tempBuf == nullptr) {
if (!allocationErrorLogged) {
LOGGER.error("Failed to allocate rotation buffer, drawing unrotated");
allocationErrorLogged = true;
}
if (esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, self->configuration->horizontalResolution, self->configuration->verticalResolution, draw_buf) != ESP_OK) {
lv_display_flush_ready(drv);
}
return;
}
}
// Rotate to tempBuf using tile-based approach for cache efficiency.
// SPIRAM random access is very slow due to cache thrashing. By processing
// in small tiles (TILE x TILE pixels), the working set stays within the
// ESP32-S3's 32KB PSRAM cache, dramatically reducing cache misses.
constexpr int TILE = 32;
uint16_t* src = (uint16_t*)color_map;
uint16_t* dst = self->tempBuf;
const int hw = self->configuration->horizontalResolution;
switch (rotation) {
case LV_DISPLAY_ROTATION_90:
for (int ty = 0; ty < logical_height; ty += TILE) {
for (int tx = 0; tx < logical_width; tx += TILE) {
int y_end = (ty + TILE < logical_height) ? ty + TILE : logical_height;
int x_end = (tx + TILE < logical_width) ? tx + TILE : logical_width;
for (int y = ty; y < y_end; y++) {
for (int x = tx; x < x_end; x++) {
dst[(logical_width - 1 - x) * hw + y] = src[y * logical_width + x];
}
}
}
}
break;
case LV_DISPLAY_ROTATION_180: {
// 180° is a simple reverse - sequential reads and writes
const int total = logical_width * logical_height;
for (int i = 0; i < total; i++) {
dst[total - 1 - i] = src[i];
}
break;
}
case LV_DISPLAY_ROTATION_270:
for (int ty = 0; ty < logical_height; ty += TILE) {
for (int tx = 0; tx < logical_width; tx += TILE) {
int y_end = (ty + TILE < logical_height) ? ty + TILE : logical_height;
int x_end = (tx + TILE < logical_width) ? tx + TILE : logical_width;
for (int y = ty; y < y_end; y++) {
for (int x = tx; x < x_end; x++) {
dst[x * hw + (logical_height - 1 - y)] = src[y * logical_width + x];
}
}
}
}
break;
default:
break;
}
draw_buf = self->tempBuf;
}
// Wait for TE (tearing effect) signal before starting SPI transfer.
// This synchronizes frame output with the display's V-blank to reduce tearing.
if (self->teSyncSemaphore != nullptr) {
xSemaphoreTake(self->teSyncSemaphore, 0); // Drain any pending signal
xSemaphoreTake(self->teSyncSemaphore, pdMS_TO_TICKS(20)); // Wait for next V-blank
}
esp_err_t ret = esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, self->configuration->horizontalResolution, self->configuration->verticalResolution, draw_buf);
if (ret != ESP_OK) {
// If SPI transfer failed, on_color_trans_done won't fire.
// Manually signal flush ready to prevent LVGL from hanging.
LOGGER.error("draw_bitmap failed: {}", esp_err_to_name(ret));
lv_display_flush_ready(drv);
}
}
bool Axs15231bDisplay::onColorTransDone(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) {
lv_display_t *disp = (lv_display_t *)user_ctx;
if (disp != nullptr) {
lv_display_flush_ready(disp);
}
return false;
}
void IRAM_ATTR Axs15231bDisplay::teIsrHandler(void* arg) {
SemaphoreHandle_t sem = (SemaphoreHandle_t)arg;
BaseType_t needYield = pdFALSE;
xSemaphoreGiveFromISR(sem, &needYield);
if (needYield == pdTRUE) {
portYIELD_FROM_ISR();
}
}
bool Axs15231bDisplay::setupTeSync() {
if (configuration->tePin == GPIO_NUM_NC) {
LOGGER.info("TE pin not configured, skipping TE sync");
return true;
}
teSyncSemaphore = xSemaphoreCreateBinary();
if (teSyncSemaphore == nullptr) {
LOGGER.error("Failed to create TE sync semaphore");
return false;
}
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_POSEDGE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pin_bit_mask = (1ULL << configuration->tePin);
if (gpio_config(&io_conf) != ESP_OK) {
LOGGER.error("Failed to configure TE GPIO");
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
return false;
}
esp_err_t err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
if (err == ESP_OK) {
isrServiceInstalledByUs = true;
} else if (err != ESP_ERR_INVALID_STATE) {
LOGGER.error("Failed to install GPIO ISR service");
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
return false;
}
if (gpio_isr_handler_add(configuration->tePin, teIsrHandler, (void*)teSyncSemaphore) != ESP_OK) {
LOGGER.error("Failed to add TE ISR handler");
gpio_intr_disable(configuration->tePin);
if (isrServiceInstalledByUs) {
gpio_uninstall_isr_service();
isrServiceInstalledByUs = false;
}
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
return false;
}
teIsrInstalled = true;
LOGGER.info("TE sync enabled on GPIO {}", (int)configuration->tePin);
return true;
}
void Axs15231bDisplay::teardownTeSync() {
if (teIsrInstalled && configuration->tePin != GPIO_NUM_NC) {
gpio_isr_handler_remove(configuration->tePin);
gpio_intr_disable(configuration->tePin);
teIsrInstalled = false;
}
if (isrServiceInstalledByUs) {
gpio_uninstall_isr_service();
isrServiceInstalledByUs = false;
}
if (teSyncSemaphore != nullptr) {
vSemaphoreDelete(teSyncSemaphore);
teSyncSemaphore = nullptr;
}
}
bool Axs15231bDisplay::createIoHandle() {
const esp_lcd_panel_io_spi_config_t panel_io_config = {
.cs_gpio_num = configuration->csPin,
.dc_gpio_num = configuration->dcPin,
.spi_mode = 3,
.pclk_hz = configuration->pixelClockFrequency,
.trans_queue_depth = configuration->transactionQueueDepth,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 32,
.lcd_param_bits = 8,
.cs_ena_pretrans = 0,
.cs_ena_posttrans = 0,
.flags = {
.dc_high_on_cmd = 0,
.dc_low_on_data = 0,
.dc_low_on_param = 0,
.octal_mode = 0,
.quad_mode = 1,
.sio_mode = 0,
.lsb_first = 0,
.cs_high_active = 0
}
};
return esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &ioHandle) == ESP_OK;
}
bool Axs15231bDisplay::createPanelHandle() {
const axs15231b_vendor_config_t vendor_config = {
.init_cmds = lcd_init_cmds,
.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
.flags = {
.use_qspi_interface = 1,
},
};
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = configuration->resetPin,
.rgb_ele_order = configuration->rgbElementOrder,
.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE,
.bits_per_pixel = 16,
.flags = {
.reset_active_high = false
},
.vendor_config = (void *)&vendor_config
};
if (esp_lcd_new_panel_axs15231b(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
LOGGER.error("Failed to create axs15231b");
return false;
}
if (esp_lcd_panel_reset(panelHandle) != ESP_OK) {
LOGGER.error("Failed to reset panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_init(panelHandle) != ESP_OK) {
LOGGER.error("Failed to init panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
//SWAPXY Doesn't work with the JC3248W535... Left in for future compatibility.
if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) {
LOGGER.error("Failed to swap XY");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) {
LOGGER.error("Failed to mirror panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) {
LOGGER.error("Failed to invert color");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
if (esp_lcd_panel_disp_on_off(panelHandle, false) != ESP_OK) {
LOGGER.error("Failed to turn off panel");
esp_lcd_panel_del(panelHandle);
panelHandle = nullptr;
return false;
}
return true;
}
Axs15231bDisplay::Axs15231bDisplay(std::unique_ptr<Configuration> inConfiguration) :
configuration(std::move(inConfiguration))
{
assert(configuration != nullptr);
}
bool Axs15231bDisplay::start() {
if (!createIoHandle()) {
LOGGER.error("Failed to create IO handle");
return false;
}
if (!createPanelHandle()) {
LOGGER.error("Failed to create panel handle");
esp_lcd_panel_io_del(ioHandle);
ioHandle = nullptr;
return false;
}
if (!setupTeSync()) {
LOGGER.warn("TE sync setup failed, continuing without TE synchronization");
}
return true;
}
bool Axs15231bDisplay::stop() {
if (lvglDisplay != nullptr) {
stopLvgl();
}
teardownTeSync();
// Invalidate cached DisplayDriver - it holds a raw panelHandle that is about to be deleted
displayDriver.reset();
if (panelHandle != nullptr && esp_lcd_panel_del(panelHandle) != ESP_OK) {
LOGGER.error("Failed to delete panel");
}
panelHandle = nullptr;
if (ioHandle != nullptr && esp_lcd_panel_io_del(ioHandle) != ESP_OK) {
LOGGER.error("Failed to delete IO");
}
ioHandle = nullptr;
return true;
}
bool Axs15231bDisplay::startLvgl() {
if (lvglDisplay != nullptr) {
LOGGER.error("LVGL already started");
return false;
}
lvglDisplay = lv_display_create(configuration->horizontalResolution, configuration->verticalResolution);
lv_display_set_user_data(lvglDisplay, this);
lv_display_set_color_format(lvglDisplay, LV_COLOR_FORMAT_RGB565);
uint32_t buffer_pixels = configuration->bufferSize;
uint32_t bufferSize = buffer_pixels * sizeof(uint16_t);
buffer1 = (uint16_t*)heap_caps_malloc(bufferSize, MALLOC_CAP_SPIRAM);
buffer2 = (uint16_t*)heap_caps_malloc(bufferSize, MALLOC_CAP_SPIRAM);
if (buffer1 == nullptr || buffer2 == nullptr) {
LOGGER.error("Failed to allocate buffers");
heap_caps_free(buffer1);
heap_caps_free(buffer2);
buffer1 = nullptr;
buffer2 = nullptr;
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
return false;
}
lv_display_set_buffers(lvglDisplay, buffer1, buffer2, bufferSize, LV_DISPLAY_RENDER_MODE_FULL);
lv_display_set_flush_cb(lvglDisplay, lvgl_port_flush_callback);
lv_display_set_driver_data(lvglDisplay, panelHandle);
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = onColorTransDone,
};
if (esp_lcd_panel_io_register_event_callbacks(ioHandle, &cbs, lvglDisplay) != ESP_OK) {
LOGGER.error("Failed to register panel IO callbacks");
heap_caps_free(buffer1);
heap_caps_free(buffer2);
buffer1 = nullptr;
buffer2 = nullptr;
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
return false;
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr && touch_device->supportsLvgl()) {
touch_device->startLvgl(lvglDisplay);
}
return true;
}
bool Axs15231bDisplay::stopLvgl() {
if (lvglDisplay == nullptr) {
return false;
}
auto touch_device = getTouchDevice();
if (touch_device != nullptr) {
touch_device->stopLvgl();
}
esp_lcd_panel_disp_on_off(panelHandle, false);
// Unregister IO callbacks before deleting the display to prevent use-after-free.
// The on_color_trans_done callback captures lvglDisplay as user_ctx; if a DMA
// transfer completes after lv_display_delete(), it would call
// lv_display_flush_ready() on a freed pointer.
const esp_lcd_panel_io_callbacks_t nullCbs = {
.on_color_trans_done = nullptr,
};
esp_lcd_panel_io_register_event_callbacks(ioHandle, &nullCbs, nullptr);
lv_display_delete(lvglDisplay);
lvglDisplay = nullptr;
heap_caps_free(buffer1);
heap_caps_free(buffer2);
buffer1 = nullptr;
buffer2 = nullptr;
heap_caps_free(tempBuf);
tempBuf = nullptr;
return true;
}
std::shared_ptr<tt::hal::display::DisplayDriver> Axs15231bDisplay::getDisplayDriver() {
if (lvglDisplay != nullptr) {
LOGGER.error("Cannot get DisplayDriver while LVGL is active - call stopLvgl() first");
return nullptr;
}
if (panelHandle == nullptr) {
LOGGER.error("Cannot get DisplayDriver - display is not started");
return nullptr;
}
if (displayDriver == nullptr) {
displayDriver = std::make_shared<EspLcdDisplayDriver>(
panelHandle,
tt::lvgl::getSyncLock(),
configuration->horizontalResolution,
configuration->verticalResolution,
tt::hal::display::ColorFormat::RGB565Swapped
);
}
return displayDriver;
}

View File

@ -0,0 +1,140 @@
#pragma once
#include <Tactility/hal/spi/Spi.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <Tactility/hal/display/DisplayDriver.h>
#include <driver/gpio.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_types.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <functional>
#include <lvgl.h>
class Axs15231bDisplay final : public tt::hal::display::DisplayDevice {
public:
class Configuration {
public:
Configuration(
spi_host_device_t spiHostDevice,
gpio_num_t csPin,
gpio_num_t dcPin,
gpio_num_t resetPin,
gpio_num_t tePin,
unsigned int horizontalResolution,
unsigned int verticalResolution,
std::shared_ptr<tt::hal::touch::TouchDevice> touch,
bool swapXY = false,
bool mirrorX = false,
bool mirrorY = false,
bool invertColor = false,
uint32_t bufferSize = 0, // Size in pixel count. 0 means default, which is 1/10 of the screen size,
lcd_rgb_element_order_t rgbElementOrder = LCD_RGB_ELEMENT_ORDER_RGB
) : spiHostDevice(spiHostDevice),
csPin(csPin),
dcPin(dcPin),
resetPin(resetPin),
tePin(tePin),
horizontalResolution(horizontalResolution),
verticalResolution(verticalResolution),
swapXY(swapXY),
mirrorX(mirrorX),
mirrorY(mirrorY),
invertColor(invertColor),
bufferSize(bufferSize),
rgbElementOrder(rgbElementOrder),
touch(std::move(touch))
{
if (this->bufferSize == 0) {
this->bufferSize = horizontalResolution * verticalResolution / 10;
}
}
spi_host_device_t spiHostDevice;
gpio_num_t csPin;
gpio_num_t dcPin;
gpio_num_t resetPin;
gpio_num_t tePin;
unsigned int pixelClockFrequency = 40'000'000; // Hertz
size_t transactionQueueDepth = 10;
unsigned int horizontalResolution;
unsigned int verticalResolution;
bool swapXY;
bool mirrorX;
bool mirrorY;
bool invertColor;
uint32_t bufferSize; // Size in pixel count. 0 means default, which is 1/10 of the screen size
lcd_rgb_element_order_t rgbElementOrder;
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
std::function<void(uint8_t)> _Nullable backlightDutyFunction = nullptr;
};
private:
esp_lcd_panel_io_handle_t _Nullable ioHandle = nullptr;
esp_lcd_panel_handle_t _Nullable panelHandle = nullptr;
lv_display_t* _Nullable lvglDisplay = nullptr;
uint16_t* _Nullable buffer1 = nullptr;
uint16_t* _Nullable buffer2 = nullptr;
uint16_t* _Nullable tempBuf = nullptr;
SemaphoreHandle_t _Nullable teSyncSemaphore = nullptr;
bool teIsrInstalled = false;
bool isrServiceInstalledByUs = false;
std::unique_ptr<Configuration> configuration;
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable displayDriver;
bool createIoHandle();
bool createPanelHandle();
bool setupTeSync();
void teardownTeSync();
static void IRAM_ATTR teIsrHandler(void* arg);
public:
explicit Axs15231bDisplay(std::unique_ptr<Configuration> inConfiguration);
std::string getName() const override { return "AXS15231B Display"; }
std::string getDescription() const override { return "AXS15231B display"; }
bool start() override;
bool stop() override;
bool supportsLvgl() const override { return true; }
bool startLvgl() override;
bool stopLvgl() override;
lv_display_t* _Nullable getLvglDisplay() const override { return lvglDisplay; }
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override { return configuration->touch; }
void setBacklightDuty(uint8_t backlightDuty) override {
if (configuration->backlightDutyFunction != nullptr) {
configuration->backlightDutyFunction(backlightDuty);
}
}
bool supportsBacklightDuty() const override { return configuration->backlightDutyFunction != nullptr; }
bool supportsDisplayDriver() const override { return true; }
std::shared_ptr<tt::hal::display::DisplayDriver> _Nullable getDisplayDriver() override;
static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map);
static bool onColorTransDone(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
};

View File

@ -0,0 +1,38 @@
#include "Axs15231bTouch.h"
#include <esp_lcd_axs15231b.h>
#include <esp_err.h>
bool Axs15231bTouch::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) {
if (configuration == nullptr) {
return false;
}
esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_AXS15231B_CONFIG();
return esp_lcd_new_panel_io_i2c(configuration->port, &io_config, &outHandle) == ESP_OK;
}
bool Axs15231bTouch::createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) {
return esp_lcd_touch_new_i2c_axs15231b(ioHandle, &configuration, &panelHandle) == ESP_OK;
}
esp_lcd_touch_config_t Axs15231bTouch::createEspLcdTouchConfig() {
return {
.x_max = configuration->xMax,
.y_max = configuration->yMax,
.rst_gpio_num = configuration->pinReset,
.int_gpio_num = configuration->pinInterrupt,
.levels = {
.reset = configuration->pinResetLevel,
.interrupt = configuration->pinInterruptLevel,
},
.flags = {
.swap_xy = configuration->swapXy,
.mirror_x = configuration->mirrorX,
.mirror_y = configuration->mirrorY,
},
.process_coordinates = nullptr,
.interrupt_callback = nullptr,
.user_data = nullptr,
.driver_data = nullptr
};
}

View File

@ -0,0 +1,72 @@
#pragma once
#include <Tactility/hal/touch/TouchDevice.h>
#include <Tactility/TactilityCore.h>
#include <driver/i2c.h>
#include <memory>
#include <string>
#include <EspLcdTouch.h>
class Axs15231bTouch final : public EspLcdTouch {
public:
class Configuration {
public:
Configuration(
i2c_port_t port,
uint16_t xMax,
uint16_t yMax,
bool swapXy = false,
bool mirrorX = false,
bool mirrorY = false,
gpio_num_t pinReset = GPIO_NUM_NC,
gpio_num_t pinInterrupt = GPIO_NUM_NC,
unsigned int pinResetLevel = 0,
unsigned int pinInterruptLevel = 0
) : port(port),
xMax(xMax),
yMax(yMax),
swapXy(swapXy),
mirrorX(mirrorX),
mirrorY(mirrorY),
pinReset(pinReset),
pinInterrupt(pinInterrupt),
pinResetLevel(pinResetLevel),
pinInterruptLevel(pinInterruptLevel)
{}
i2c_port_t port;
uint16_t xMax;
uint16_t yMax;
bool swapXy;
bool mirrorX;
bool mirrorY;
gpio_num_t pinReset;
gpio_num_t pinInterrupt;
unsigned int pinResetLevel;
unsigned int pinInterruptLevel;
};
private:
std::unique_ptr<Configuration> configuration;
bool createIoHandle(esp_lcd_panel_io_handle_t& outHandle) override;
bool createTouchHandle(esp_lcd_panel_io_handle_t ioHandle, const esp_lcd_touch_config_t& configuration, esp_lcd_touch_handle_t& panelHandle) override;
esp_lcd_touch_config_t createEspLcdTouchConfig() override;
public:
explicit Axs15231bTouch(std::unique_ptr<Configuration> inConfiguration) : configuration(std::move(inConfiguration)) {
assert(configuration != nullptr);
}
std::string getName() const override { return "AXS15231B Touch"; }
std::string getDescription() const override { return "AXS15231B I2C touch driver"; }
};

View File

@ -0,0 +1,104 @@
#include "devices/Display.h"
#include "devices/SdCard.h"
#include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h>
#include <PwmBacklight.h>
using namespace tt::hal;
static constexpr int SPI_TRANSFER_SIZE_LIMIT = 320 * 10;
static DeviceVector createDevices() {
return {
createDisplay(),
createSdCard()
};
}
static bool initBoot() {
return driver::pwmbacklight::init(GPIO_NUM_1);
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.createDevices = createDevices,
//I2C Internal - Touch
//I2C External - P3 (JST SH 1.0)/ P4 (JST SH 1.25) headers - GND 3.3V IO17 IO18
.spi {
//Display
spi::Configuration {
.device = SPI2_HOST,
.dma = SPI_DMA_CH_AUTO,
.config = {
.data0_io_num = GPIO_NUM_21,
.data1_io_num = GPIO_NUM_48,
.sclk_io_num = GPIO_NUM_47,
.data2_io_num = GPIO_NUM_40,
.data3_io_num = GPIO_NUM_39,
.data4_io_num = -1,
.data5_io_num = -1,
.data6_io_num = -1,
.data7_io_num = -1,
.data_io_default_level = false,
.max_transfer_sz = SPI_TRANSFER_SIZE_LIMIT,
.flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0
},
.initMode = spi::InitMode::ByTactility,
.isMutable = false,
.lock = tt::lvgl::getSyncLock()
},
//SD Card
spi::Configuration {
.device = SPI3_HOST,
.dma = SPI_DMA_CH_AUTO,
.config = {
.mosi_io_num = GPIO_NUM_11,
.miso_io_num = GPIO_NUM_13,
.sclk_io_num = GPIO_NUM_12,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.data4_io_num = -1,
.data5_io_num = -1,
.data6_io_num = -1,
.data7_io_num = -1,
.data_io_default_level = false,
.max_transfer_sz = 8192,
.flags = 0,
.isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO,
.intr_flags = 0
},
.initMode = spi::InitMode::ByTactility,
.isMutable = false,
.lock = nullptr
}
},
.uart {
//P1 header, JST SH 1.25, 5V / TXD (43) / RXD (44) / GND
uart::Configuration {
.name = "UART0",
.port = UART_NUM_0,
.rxPin = GPIO_NUM_44,
.txPin = GPIO_NUM_43,
.rtsPin = GPIO_NUM_NC,
.ctsPin = GPIO_NUM_NC,
.rxBufferSize = 1024,
.txBufferSize = 1024,
.config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.rx_flow_ctrl_thresh = 0,
.source_clk = UART_SCLK_DEFAULT,
.flags = {
.allow_pd = 0,
.backup_before_sleep = 0,
}
}
}
}
};

View File

@ -0,0 +1,7 @@
extern "C" {
extern void register_device_drivers() {
/* NO-OP */
}
}

View File

@ -0,0 +1,44 @@
#include "Display.h"
#include <PwmBacklight.h>
#include <Axs15231b/Axs15231bDisplay.h>
#include <Axs15231b/Axs15231bTouch.h>
static constexpr size_t JC3248W535C_LCD_DRAW_BUFFER_SIZE = 320 * 480;
static std::shared_ptr<tt::hal::touch::TouchDevice> createTouch() {
auto configuration = std::make_unique<Axs15231bTouch::Configuration>(
I2C_NUM_0,
320, // width
480, // height
false, // mirror_x
false, // mirror_y
false // swap_xy
);
return std::make_shared<Axs15231bTouch>(std::move(configuration));
}
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto touch = createTouch();
auto configuration = std::make_unique<Axs15231bDisplay::Configuration>(
SPI2_HOST,
GPIO_NUM_45, //CS
GPIO_NUM_NC, //DC
GPIO_NUM_NC, //RST
GPIO_NUM_38, //TE
320, // width
480, // height
touch,
false, // swap_xy
false, // mirror_x
false, // mirror_y
false, // invert color
JC3248W535C_LCD_DRAW_BUFFER_SIZE
);
configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty;
return std::make_shared<Axs15231bDisplay>(std::move(configuration));
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <memory>
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -0,0 +1,26 @@
#include "SdCard.h"
#include <Tactility/hal/sdcard/SpiSdCardDevice.h>
#include <Tactility/RecursiveMutex.h>
constexpr auto SDCARD_SPI_HOST = SPI3_HOST;
constexpr auto SDCARD_PIN_CS = GPIO_NUM_10;
using tt::hal::sdcard::SpiSdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard() {
auto configuration = std::make_unique<SpiSdCardDevice::Config>(
SDCARD_PIN_CS,
GPIO_NUM_NC,
GPIO_NUM_NC,
GPIO_NUM_NC,
SdCardDevice::MountBehaviour::AtBoot,
std::make_shared<tt::RecursiveMutex>(),
std::vector<gpio_num_t>(),
SDCARD_SPI_HOST
);
return std::make_shared<SpiSdCardDevice>(
std::move(configuration)
);
}

View File

@ -0,0 +1,7 @@
#pragma once
#include <Tactility/hal/sdcard/SdCardDevice.h>
using tt::hal::sdcard::SdCardDevice;
std::shared_ptr<SdCardDevice> createSdCard();

View File

@ -0,0 +1,20 @@
[general]
vendor=Guition
name=JC3248W535C
[hardware]
target=ESP32S3
flashSize=16MB
spiRam=true
spiRamMode=OCT
spiRamSpeed=120M
tinyUsb=true
esptoolFlashFreq=120M
[display]
size=3.5"
shape=rectangle
dpi=165
[lvgl]
colorDepth=16

View File

@ -0,0 +1,3 @@
dependencies:
- Platforms/PlatformEsp32
dts: guition,jc3248w535c.dts

View File

@ -0,0 +1,35 @@
/dts-v1/;
#include <tactility/bindings/root.h>
#include <tactility/bindings/esp32_gpio.h>
#include <tactility/bindings/esp32_i2c.h>
/ {
compatible = "root";
model = "Guition JC3248W535C";
gpio0 {
compatible = "espressif,esp32-gpio";
gpio-count = <49>;
};
i2c_internal {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_0>;
clock-frequency = <400000>;
pin-sda = <4>;
pin-scl = <8>;
pin-sda-pullup;
pin-scl-pullup;
};
i2c_external {
compatible = "espressif,esp32-i2c";
port = <I2C_NUM_1>;
clock-frequency = <400000>;
pin-sda = <17>;
pin-scl = <18>;
pin-sda-pullup;
pin-scl-pullup;
};
};

View File

@ -45,6 +45,8 @@ menu "Tactility App"
bool "Guition JC1060P470CIWY"
config TT_DEVICE_GUITION_JC2432W328C
bool "Guition JC2432W328C"
config TT_DEVICE_GUITION_JC3248W535C
bool "Guition JC3248W535C"
config TT_DEVICE_GUITION_JC8048W550C
bool "Guition JC8048W550C"
config TT_DEVICE_HELTEC_V3

View File

@ -29,6 +29,7 @@ dependencies:
espressif/esp_lcd_touch_ft5x06: "1.0.6~1"
espressif/esp_io_expander: "1.0.1"
espressif/esp_io_expander_tca95xx_16bit: "1.0.1"
espressif/esp_lcd_axs15231b: "2.0.2"
espressif/esp_lcd_st7701:
version: "1.1.3"
rules:

View File

@ -44,6 +44,7 @@ public:
void onNewFolderPressed();
void onDirEntryListScrollBegin();
void onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle);
void deinit(const AppContext& appContext);
};
}

View File

@ -30,6 +30,10 @@ public:
void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle) override {
view->onResult(launchId, result, std::move(bundle));
}
void onHide(AppContext& appContext) override {
view->deinit(appContext);
}
};
extern const AppManifest manifest = {

View File

@ -472,4 +472,8 @@ void View::onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bu
}
}
void View::deinit(const AppContext& appContext) {
lv_obj_remove_event_cb(dir_entry_list, dirEntryListScrollBeginCallback);
}
}

View File

@ -1,5 +1,6 @@
#pragma once
#include "tt_app.h"
#include "tt_bundle.h"
#ifdef __cplusplus
@ -14,8 +15,9 @@ extern "C" {
* @param[in] message the message to display
* @param[in] buttonLabels the buttons to show, or null when there are none to show
* @param[in] buttonLabelCount the amount of buttons (0 or more)
* @return the launch ID of the dialog, which can be compared in onResult to identify the source
*/
void tt_app_alertdialog_start(const char* title, const char* message, const char* buttonLabels[], uint32_t buttonLabelCount);
AppLaunchId tt_app_alertdialog_start(const char* title, const char* message, const char* buttonLabels[], uint32_t buttonLabelCount);
/**
* @return the index of the button that was clicked (the index in the array when start() was called)

View File

@ -1,5 +1,6 @@
#pragma once
#include "tt_app.h"
#include "tt_bundle.h"
#ifdef __cplusplus
@ -11,8 +12,9 @@ extern "C" {
* @param[in] title the title to show in the toolbar
* @param[in] argc the amount of items that the list contains
* @param[in] argv the labels of the items in the list
* @return the launch ID of the dialog, which can be compared in onResult to identify the source
*/
void tt_app_selectiondialog_start(const char* title, int argc, const char* argv[]);
AppLaunchId tt_app_selectiondialog_start(const char* title, int argc, const char* argv[]);
/** @return the index of the item that was clicked by the user, or -1 when the user didn't select anything */
int32_t tt_app_selectiondialog_get_result_index(BundleHandle handle);

View File

@ -3,12 +3,12 @@
extern "C" {
void tt_app_alertdialog_start(const char* title, const char* message, const char* buttonLabels[], uint32_t buttonLabelCount) {
AppLaunchId tt_app_alertdialog_start(const char* title, const char* message, const char* buttonLabels[], uint32_t buttonLabelCount) {
std::vector<std::string> list;
for (int i = 0; i < buttonLabelCount; i++) {
list.emplace_back(buttonLabels[i]);
}
tt::app::alertdialog::start(title, message, list);
return tt::app::alertdialog::start(title, message, list);
}
int32_t tt_app_alertdialog_get_result_index(BundleHandle handle) {

View File

@ -3,12 +3,12 @@
extern "C" {
void tt_app_selectiondialog_start(const char* title, int argc, const char* argv[]) {
AppLaunchId tt_app_selectiondialog_start(const char* title, int argc, const char* argv[]) {
std::vector<std::string> list;
for (int i = 0; i < argc; i++) {
list.emplace_back(argv[i]);
}
tt::app::selectiondialog::start(title, list);
return tt::app::selectiondialog::start(title, list);
}
int32_t tt_app_selectiondialog_get_result_index(BundleHandle handle) {

View File

@ -564,6 +564,7 @@ const esp_elfsym main_symbols[] {
ESP_ELFSYM_EXPORT(lv_group_get_default),
ESP_ELFSYM_EXPORT(lv_group_add_obj),
ESP_ELFSYM_EXPORT(lv_group_set_default),
ESP_ELFSYM_EXPORT(lv_group_set_editing),
// lv_mem
ESP_ELFSYM_EXPORT(lv_free),
ESP_ELFSYM_EXPORT(lv_malloc),