diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7493cd9b4..2c38b3208 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 }, diff --git a/Devices/guition-jc3248w535c/CMakeLists.txt b/Devices/guition-jc3248w535c/CMakeLists.txt new file mode 100644 index 000000000..933587db6 --- /dev/null +++ b/Devices/guition-jc3248w535c/CMakeLists.txt @@ -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 +) diff --git a/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bDisplay.cpp b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bDisplay.cpp new file mode 100644 index 000000000..644dfa447 --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bDisplay.cpp @@ -0,0 +1,495 @@ +#include "Axs15231bDisplay.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 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 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( + panelHandle, + tt::lvgl::getSyncLock(), + configuration->horizontalResolution, + configuration->verticalResolution, + tt::hal::display::ColorFormat::RGB565Swapped + ); + } + return displayDriver; +} diff --git a/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bDisplay.h b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bDisplay.h new file mode 100644 index 000000000..4c9e39b3d --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bDisplay.h @@ -0,0 +1,140 @@ +#pragma once + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 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 touch; + std::function _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; + std::shared_ptr _Nullable displayDriver; + + bool createIoHandle(); + + bool createPanelHandle(); + + bool setupTeSync(); + + void teardownTeSync(); + + static void IRAM_ATTR teIsrHandler(void* arg); + +public: + + explicit Axs15231bDisplay(std::unique_ptr 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 _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 _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); +}; diff --git a/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bTouch.cpp b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bTouch.cpp new file mode 100644 index 000000000..b6bbb087b --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bTouch.cpp @@ -0,0 +1,38 @@ +#include "Axs15231bTouch.h" + +#include +#include + +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 + }; +} diff --git a/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bTouch.h b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bTouch.h new file mode 100644 index 000000000..41013d21e --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/Axs15231b/Axs15231bTouch.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +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; + + 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 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"; } +}; diff --git a/Devices/guition-jc3248w535c/Source/Configuration.cpp b/Devices/guition-jc3248w535c/Source/Configuration.cpp new file mode 100644 index 000000000..c7fab1764 --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/Configuration.cpp @@ -0,0 +1,104 @@ +#include "devices/Display.h" +#include "devices/SdCard.h" + +#include +#include +#include + +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, + } + } + } + } +}; diff --git a/Devices/guition-jc3248w535c/Source/Drivers.cpp b/Devices/guition-jc3248w535c/Source/Drivers.cpp new file mode 100644 index 000000000..c8a5c6658 --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/Drivers.cpp @@ -0,0 +1,7 @@ +extern "C" { + +extern void register_device_drivers() { + /* NO-OP */ +} + +} diff --git a/Devices/guition-jc3248w535c/Source/devices/Display.cpp b/Devices/guition-jc3248w535c/Source/devices/Display.cpp new file mode 100644 index 000000000..d35c9e561 --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/devices/Display.cpp @@ -0,0 +1,44 @@ +#include "Display.h" + +#include +#include +#include + +static constexpr size_t JC3248W535C_LCD_DRAW_BUFFER_SIZE = 320 * 480; + +static std::shared_ptr createTouch() { + auto configuration = std::make_unique( + I2C_NUM_0, + 320, // width + 480, // height + false, // mirror_x + false, // mirror_y + false // swap_xy + ); + + return std::make_shared(std::move(configuration)); +} + +std::shared_ptr createDisplay() { + auto touch = createTouch(); + + auto configuration = std::make_unique( + 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(std::move(configuration)); +} diff --git a/Devices/guition-jc3248w535c/Source/devices/Display.h b/Devices/guition-jc3248w535c/Source/devices/Display.h new file mode 100644 index 000000000..13a1fe144 --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/devices/Display.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +std::shared_ptr createDisplay(); diff --git a/Devices/guition-jc3248w535c/Source/devices/SdCard.cpp b/Devices/guition-jc3248w535c/Source/devices/SdCard.cpp new file mode 100644 index 000000000..8648798b0 --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/devices/SdCard.cpp @@ -0,0 +1,26 @@ +#include "SdCard.h" + +#include +#include + +constexpr auto SDCARD_SPI_HOST = SPI3_HOST; +constexpr auto SDCARD_PIN_CS = GPIO_NUM_10; + +using tt::hal::sdcard::SpiSdCardDevice; + +std::shared_ptr createSdCard() { + auto configuration = std::make_unique( + SDCARD_PIN_CS, + GPIO_NUM_NC, + GPIO_NUM_NC, + GPIO_NUM_NC, + SdCardDevice::MountBehaviour::AtBoot, + std::make_shared(), + std::vector(), + SDCARD_SPI_HOST + ); + + return std::make_shared( + std::move(configuration) + ); +} diff --git a/Devices/guition-jc3248w535c/Source/devices/SdCard.h b/Devices/guition-jc3248w535c/Source/devices/SdCard.h new file mode 100644 index 000000000..6f5443c78 --- /dev/null +++ b/Devices/guition-jc3248w535c/Source/devices/SdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +using tt::hal::sdcard::SdCardDevice; + +std::shared_ptr createSdCard(); diff --git a/Devices/guition-jc3248w535c/device.properties b/Devices/guition-jc3248w535c/device.properties new file mode 100644 index 000000000..3c48128c4 --- /dev/null +++ b/Devices/guition-jc3248w535c/device.properties @@ -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 diff --git a/Devices/guition-jc3248w535c/devicetree.yaml b/Devices/guition-jc3248w535c/devicetree.yaml new file mode 100644 index 000000000..2b696c93e --- /dev/null +++ b/Devices/guition-jc3248w535c/devicetree.yaml @@ -0,0 +1,3 @@ +dependencies: + - Platforms/PlatformEsp32 +dts: guition,jc3248w535c.dts diff --git a/Devices/guition-jc3248w535c/guition,jc3248w535c.dts b/Devices/guition-jc3248w535c/guition,jc3248w535c.dts new file mode 100644 index 000000000..3a557e14f --- /dev/null +++ b/Devices/guition-jc3248w535c/guition,jc3248w535c.dts @@ -0,0 +1,35 @@ +/dts-v1/; + +#include +#include +#include + +/ { + compatible = "root"; + model = "Guition JC3248W535C"; + + gpio0 { + compatible = "espressif,esp32-gpio"; + gpio-count = <49>; + }; + + i2c_internal { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <400000>; + pin-sda = <4>; + pin-scl = <8>; + pin-sda-pullup; + pin-scl-pullup; + }; + + i2c_external { + compatible = "espressif,esp32-i2c"; + port = ; + clock-frequency = <400000>; + pin-sda = <17>; + pin-scl = <18>; + pin-sda-pullup; + pin-scl-pullup; + }; +}; diff --git a/Firmware/Kconfig b/Firmware/Kconfig index 157648c77..c77f0570a 100644 --- a/Firmware/Kconfig +++ b/Firmware/Kconfig @@ -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 diff --git a/Firmware/idf_component.yml b/Firmware/idf_component.yml index 0d4e737fe..5fabc6245 100644 --- a/Firmware/idf_component.yml +++ b/Firmware/idf_component.yml @@ -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: diff --git a/Tactility/Private/Tactility/app/files/View.h b/Tactility/Private/Tactility/app/files/View.h index c6b969df5..00aa34331 100644 --- a/Tactility/Private/Tactility/app/files/View.h +++ b/Tactility/Private/Tactility/app/files/View.h @@ -44,6 +44,7 @@ class View final { void onNewFolderPressed(); void onDirEntryListScrollBegin(); void onResult(LaunchId launchId, Result result, std::unique_ptr bundle); + void deinit(const AppContext& appContext); }; } diff --git a/Tactility/Source/app/files/FilesApp.cpp b/Tactility/Source/app/files/FilesApp.cpp index 9b30b3d0e..586f8b703 100644 --- a/Tactility/Source/app/files/FilesApp.cpp +++ b/Tactility/Source/app/files/FilesApp.cpp @@ -30,6 +30,10 @@ class FilesApp final : public App { void onResult(AppContext& appContext, LaunchId launchId, Result result, std::unique_ptr bundle) override { view->onResult(launchId, result, std::move(bundle)); } + + void onHide(AppContext& appContext) override { + view->deinit(appContext); + } }; extern const AppManifest manifest = { diff --git a/Tactility/Source/app/files/View.cpp b/Tactility/Source/app/files/View.cpp index c0af852b9..49e418952 100644 --- a/Tactility/Source/app/files/View.cpp +++ b/Tactility/Source/app/files/View.cpp @@ -472,4 +472,8 @@ void View::onResult(LaunchId launchId, Result result, std::unique_ptr bu } } +void View::deinit(const AppContext& appContext) { + lv_obj_remove_event_cb(dir_entry_list, dirEntryListScrollBeginCallback); +} + } diff --git a/TactilityC/Include/tt_app_alertdialog.h b/TactilityC/Include/tt_app_alertdialog.h index 0f7fd9521..ff57c2eb3 100644 --- a/TactilityC/Include/tt_app_alertdialog.h +++ b/TactilityC/Include/tt_app_alertdialog.h @@ -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) diff --git a/TactilityC/Include/tt_app_selectiondialog.h b/TactilityC/Include/tt_app_selectiondialog.h index 66f71cdfc..795772cdb 100644 --- a/TactilityC/Include/tt_app_selectiondialog.h +++ b/TactilityC/Include/tt_app_selectiondialog.h @@ -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); diff --git a/TactilityC/Source/tt_app_alertdialog.cpp b/TactilityC/Source/tt_app_alertdialog.cpp index e9fed771e..dd8d86dec 100644 --- a/TactilityC/Source/tt_app_alertdialog.cpp +++ b/TactilityC/Source/tt_app_alertdialog.cpp @@ -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 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) { diff --git a/TactilityC/Source/tt_app_selectiondialog.cpp b/TactilityC/Source/tt_app_selectiondialog.cpp index 4f45d5124..a67df0867 100644 --- a/TactilityC/Source/tt_app_selectiondialog.cpp +++ b/TactilityC/Source/tt_app_selectiondialog.cpp @@ -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 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) { diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 9b02aac20..368eed5f9 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -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),