diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index c859fd141..4d44621fd 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -42,7 +42,8 @@ jobs: { id: waveshare-s3-touch-lcd-43, arch: esp32s3 }, { id: waveshare-s3-touch-lcd-147, arch: esp32s3 }, { id: waveshare-s3-touch-lcd-128, arch: esp32s3 }, - { id: waveshare-s3-lcd-13, arch: esp32s3 } + { id: waveshare-s3-lcd-13, arch: esp32s3 }, + { id: waveshare-esp32-c6-lcd-147, arch: esp32c6 } ] runs-on: ubuntu-latest steps: diff --git a/Boards/waveshare-esp32-c6-lcd-147/CMakeLists.txt b/Boards/waveshare-esp32-c6-lcd-147/CMakeLists.txt new file mode 100644 index 000000000..49dc3e316 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "Source" + REQUIRES Tactility esp_lvgl_port ST7789 PwmBacklight driver +) diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp new file mode 100644 index 000000000..9a87d2c6c --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp @@ -0,0 +1,53 @@ +#include "devices/Display.h" +#include "devices/SdCard.h" + +#include +#include +#include +#include + +using namespace tt::hal; + +static DeviceVector createDevices() { + return { + createDisplay(), + createSdCard() + // TODO: Add RGB LED device (GPIO8, RMT-based WS2812) + }; +} + +extern bool initBoot(); + +extern const Configuration hardwareConfiguration = { + .initBoot = initBoot, + .uiScale = UiScale::Smallest, + .createDevices = createDevices, + .i2c = {}, + .spi { + // Display and SD card (shared SPI bus) + spi::Configuration { + .device = LCD_SPI_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = LCD_PIN_MOSI, + .miso_io_num = LCD_PIN_MISO, + .sclk_io_num = LCD_PIN_SCLK, + .quadwp_io_num = GPIO_NUM_NC, + .quadhd_io_num = GPIO_NUM_NC, + .data4_io_num = GPIO_NUM_NC, + .data5_io_num = GPIO_NUM_NC, + .data6_io_num = GPIO_NUM_NC, + .data7_io_num = GPIO_NUM_NC, + .data_io_default_level = false, + .max_transfer_sz = LCD_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() // esp_lvgl_port owns the lock for the display + } + }, + .uart {} +}; diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp new file mode 100644 index 000000000..e1e86a248 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp @@ -0,0 +1,8 @@ +#include "devices/Display.h" + +#include + +bool initBoot() { + // Initialize backlight with 5 kHz frequency (as per demo code) + return driver::pwmbacklight::init(LCD_PIN_BACKLIGHT, 5000); +} diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.cpp new file mode 100644 index 000000000..848b26f0d --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.cpp @@ -0,0 +1,38 @@ +#include "Display.h" + +#include +#include + +std::shared_ptr createDisplay() { + // Configuration based on demo code: + // - Resolution: 172x320 + // - X offset: 34 pixels (gapX parameter) + // - Y offset: 0 pixels + // - Mirror X-axis disabled (to fix inverted text) + // - 12MHz SPI clock + + St7789Display::Configuration panel_configuration = { + .horizontalResolution = LCD_HORIZONTAL_RESOLUTION, + .verticalResolution = LCD_VERTICAL_RESOLUTION, + .gapX = LCD_GAP_X, + .gapY = LCD_GAP_Y, + .swapXY = false, + .mirrorX = false, // disabled to fix inverted text + .mirrorY = false, + .invertColor = true, + .bufferSize = 0, // default = 1/10 of screen + .touch = nullptr, + .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, + .resetPin = LCD_PIN_RESET + }; + + auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { + .spiHostDevice = LCD_SPI_HOST, + .csPin = LCD_PIN_CS, + .dcPin = LCD_PIN_DC, + .pixelClockFrequency = LCD_PIXEL_CLOCK_HZ, + .transactionQueueDepth = 10 + }); + + return std::make_shared(panel_configuration, spi_configuration); +} diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h new file mode 100644 index 000000000..49d5ff37e --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Tactility/hal/display/DisplayDevice.h" +#include +#include +#include + +// Display SPI configuration +constexpr auto LCD_SPI_HOST = SPI2_HOST; +constexpr auto LCD_PIN_CS = GPIO_NUM_14; +constexpr auto LCD_PIN_DC = GPIO_NUM_15; +constexpr auto LCD_PIN_RESET = GPIO_NUM_21; +constexpr auto LCD_PIXEL_CLOCK_HZ = 12'000'000; // 12 MHz as in demo + +// Display panel configuration +constexpr auto LCD_HORIZONTAL_RESOLUTION = 172; +constexpr auto LCD_VERTICAL_RESOLUTION = 320; +constexpr auto LCD_GAP_X = 34; // X offset for 1.47" display +constexpr auto LCD_GAP_Y = 0; + +// Display backlight +constexpr auto LCD_PIN_BACKLIGHT = GPIO_NUM_22; + +// SPI bus configuration (shared with SD card) +constexpr auto LCD_PIN_MOSI = GPIO_NUM_6; +constexpr auto LCD_PIN_MISO = GPIO_NUM_5; +constexpr auto LCD_PIN_SCLK = GPIO_NUM_7; +constexpr auto LCD_BUFFER_HEIGHT = LCD_VERTICAL_RESOLUTION / 10; +constexpr auto LCD_BUFFER_SIZE = LCD_HORIZONTAL_RESOLUTION * LCD_BUFFER_HEIGHT; +constexpr auto LCD_SPI_TRANSFER_SIZE_LIMIT = LCD_BUFFER_SIZE * LV_COLOR_DEPTH / 8; + +std::shared_ptr createDisplay(); diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.cpp new file mode 100644 index 000000000..d8f59c4e4 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.cpp @@ -0,0 +1,26 @@ +#include "SdCard.h" +#include "Display.h" + +#include +#include + +using tt::hal::sdcard::SpiSdCardDevice; + +std::shared_ptr createSdCard() { + // SD card shares SPI bus with display (SPI2_HOST) + // CS pin is GPIO4, need to protect display CS during SD operations + auto configuration = std::make_unique( + SD_PIN_CS, // CS pin for SD card + GPIO_NUM_NC, // CD (card detect) pin - not used + GPIO_NUM_NC, // WP (write protect) pin - not used + GPIO_NUM_NC, // Power pin - not used + SdCardDevice::MountBehaviour::AtBoot, + tt::hal::spi::getLock(LCD_SPI_HOST), // Use same lock as display + std::vector { LCD_PIN_CS }, // Assert display CS high during SD operations + LCD_SPI_HOST + ); + + return std::make_shared( + std::move(configuration) + ); +} diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h new file mode 100644 index 000000000..f8af7b0d7 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Tactility/hal/sdcard/SdCardDevice.h" +#include +#include +#include + +using tt::hal::sdcard::SdCardDevice; + +// SD card configuration (shares SPI bus with display) +constexpr auto SD_PIN_CS = GPIO_NUM_4; + +std::shared_ptr createSdCard(); diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b2e4d8ec..6d8001a45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,7 +48,11 @@ if (DEFINED ENV{ESP_IDF_VERSION}) set(EXCLUDE_COMPONENTS "Simulator") - idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) + # Panic handler wrapping is only available on Xtensa architecture + if(CONFIG_IDF_TARGET_ARCH_XTENSA) + idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) + endif() + idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_button_create" APPEND) idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_dropdown_create" APPEND) idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=lv_list_create" APPEND) diff --git a/Firmware/Kconfig b/Firmware/Kconfig index d41b47a53..ce8d378a9 100644 --- a/Firmware/Kconfig +++ b/Firmware/Kconfig @@ -71,6 +71,8 @@ menu "Tactility App" bool "Waveshare ESP32 S3 Touch LCD 1.28" config TT_BOARD_WAVESHARE_S3_LCD_13 bool "Waveshare ESP32 S3 LCD 1.3" + config TT_BOARD_WAVESHARE_ESP32_C6_LCD_147 + bool "Waveshare ESP32 C6 LCD 1.47" help Select a board/hardware configuration. Use TT_BOARD_CUSTOM if you will manually configure the board in your project. diff --git a/Firmware/idf_component.yml b/Firmware/idf_component.yml index 98c1b02e0..cb204ce0d 100644 --- a/Firmware/idf_component.yml +++ b/Firmware/idf_component.yml @@ -1,9 +1,21 @@ dependencies: - espressif/esp_lcd_ili9341: "2.0.1" - atanisoft/esp_lcd_ili9488: "1.0.10" - teriyakigod/esp_lcd_st7735: "0.0.1" + espressif/esp_lcd_ili9341: + version: "2.0.1" + rules: + - if: "target in [esp32, esp32s3]" + atanisoft/esp_lcd_ili9488: + version: "1.0.10" + rules: + - if: "target in [esp32, esp32s3]" + teriyakigod/esp_lcd_st7735: + version: "0.0.1" + rules: + - if: "target in [esp32, esp32s3]" espressif/esp_lcd_touch: "1.1.2" - atanisoft/esp_lcd_touch_xpt2046: "1.0.5" + atanisoft/esp_lcd_touch_xpt2046: + version: "1.0.5" + rules: + - if: "target in [esp32, esp32s3]" espressif/esp_lcd_touch_cst816s: "1.0.3" espressif/esp_lcd_touch_gt911: "1.1.3" espressif/esp_lcd_touch_ft5x06: "1.0.6~1" @@ -15,6 +27,8 @@ dependencies: - if: "target in [esp32s3, esp32p4]" espressif/esp_lcd_st7796: version: "1.3.4" + rules: + - if: "target in [esp32, esp32s3]" espressif/esp_lcd_gc9a01: "2.0.3" espressif/esp_lcd_panel_io_additions: "1.0.1" espressif/esp_tinyusb: diff --git a/Tactility/Source/app/crashdiagnostics/QrUrl.cpp b/Tactility/Source/app/crashdiagnostics/QrUrl.cpp index 06a1ef768..10fe9c9b4 100644 --- a/Tactility/Source/app/crashdiagnostics/QrUrl.cpp +++ b/Tactility/Source/app/crashdiagnostics/QrUrl.cpp @@ -5,16 +5,26 @@ #include #include +#include +#include + +#if CONFIG_IDF_TARGET_ARCH_XTENSA #include +#endif #include std::string getUrlFromCrashData() { auto crash_data = getRtcCrashData(); - auto* stack_buffer = (uint32_t*) malloc(crash_data.callstackLength * 2 * sizeof(uint32_t)); + std::vector stack_buffer(crash_data.callstackLength * 2); + for (int i = 0; i < crash_data.callstackLength; ++i) { const CallstackFrame&frame = crash_data.callstack[i]; +#if CONFIG_IDF_TARGET_ARCH_XTENSA uint32_t pc = esp_cpu_process_stack_pc(frame.pc); +#else + uint32_t pc = frame.pc; // No processing needed on RISC-V +#endif #if CRASH_DATA_INCLUDES_SP uint32_t sp = frame.sp; #endif @@ -41,8 +51,6 @@ std::string getUrlFromCrashData() { #endif } - free(stack_buffer); - return stream.str(); } diff --git a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp index 8bbb198ae..4b5c20eff 100644 --- a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp +++ b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp @@ -1,4 +1,4 @@ -#ifdef ESP_PLATFORM +#if defined(ESP_PLATFORM) && defined(SOC_SDMMC_HOST_SUPPORTED) #include #include @@ -97,9 +97,9 @@ SdmmcDevice::State SdmmcDevice::getState(TickType_t timeout) const { } /** - * The SD card and the screen are on the same SPI bus. - * Writing and reading to the bus from 2 devices at the same time causes crashes. - * This work-around ensures that this check is only happening when LVGL isn't rendering. + * Use locking to prevent concurrent access to the SD card during display rendering. + * This ensures that SD card status checks only happen when LVGL isn't actively rendering, + * preventing potential timing issues or resource conflicts. */ auto lock = getLock()->asScopedLock(); bool locked = lock.lock(timeout); diff --git a/TactilityCore/Source/CpuAffinity.cpp b/TactilityCore/Source/CpuAffinity.cpp index 290c1df61..36cd2dc22 100644 --- a/TactilityCore/Source/CpuAffinity.cpp +++ b/TactilityCore/Source/CpuAffinity.cpp @@ -6,11 +6,14 @@ namespace tt { #ifdef ESP_PLATFORM +#if CONFIG_FREERTOS_NUMBER_OF_CORES == 2 static CpuAffinity getEspWifiAffinity() { #ifdef CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0 return 0; #elif defined(CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1) return 1; +#else + return 0; // Default to core 0 if not specified #endif } @@ -20,8 +23,11 @@ static CpuAffinity getEspMainSchedulerAffinity() { return 0; #elif defined(CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1) return 1; +#else + return 0; // Default to core 0 if not specified #endif } +#endif // CONFIG_FREERTOS_NUMBER_OF_CORES == 2 static CpuAffinity getFreeRtosTimerAffinity() { #if defined(CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY) diff --git a/TactilityCore/Source/kernel/PanicHandler.cpp b/TactilityCore/Source/kernel/PanicHandler.cpp index 160c9598d..2e7619c30 100644 --- a/TactilityCore/Source/kernel/PanicHandler.cpp +++ b/TactilityCore/Source/kernel/PanicHandler.cpp @@ -1,11 +1,13 @@ -#ifdef ESP_PLATFORM +#if defined(ESP_PLATFORM) && defined(CONFIG_IDF_TARGET_ARCH_XTENSA) #include "Tactility/kernel/PanicHandler.h" #include #include #include +#include #include +#include extern "C" { @@ -35,10 +37,15 @@ void __wrap_esp_panic_handler(void* info) { #endif crashData.callstackLength++; - crashData.callstackCorrupted = !(esp_stack_ptr_is_sane(frame.sp) && - (esp_ptr_executable((void *)esp_cpu_process_stack_pc(frame.pc)) || - /* Ignore the first corrupted PC in case of InstrFetchProhibited */ - (frame.exc_frame && ((XtExcFrame *)frame.exc_frame)->exccause == EXCCAUSE_INSTR_PROHIBITED))); + uint32_t processed_pc = esp_cpu_process_stack_pc(frame.pc); + bool pc_is_valid = esp_ptr_executable((void *)processed_pc); + + /* Ignore the first corrupted PC in case of InstrFetchProhibited on Xtensa */ + if (frame.exc_frame && ((XtExcFrame *)frame.exc_frame)->exccause == EXCCAUSE_INSTR_PROHIBITED) { + pc_is_valid = true; + } + + crashData.callstackCorrupted = !(esp_stack_ptr_is_sane(frame.sp) && pc_is_valid); while ( frame.next_pc != 0 && @@ -46,6 +53,15 @@ void __wrap_esp_panic_handler(void* info) { && crashData.callstackLength < CRASH_DATA_CALLSTACK_LIMIT ) { if (esp_backtrace_get_next_frame(&frame)) { + // Validate the current frame + uint32_t processed_frame_pc = esp_cpu_process_stack_pc(frame.pc); + bool frame_pc_is_valid = esp_ptr_executable((void *)processed_frame_pc); + + if (!esp_stack_ptr_is_sane(frame.sp) || !frame_pc_is_valid) { + crashData.callstackCorrupted = true; + break; + } + crashData.callstack[crashData.callstackLength].pc = frame.pc; #if CRASH_DATA_INCLUDES_SP crashData.callstack[crashData.callstackLength].sp = frame.sp; @@ -66,4 +82,15 @@ void __wrap_esp_panic_handler(void* info) { const CrashData& getRtcCrashData() { return crashData; } +#elif defined(ESP_PLATFORM) + +// Stub implementation for RISC-V and other architectures +// TODO: Implement crash data collection for RISC-V using frame pointer or EH frame + +#include "Tactility/kernel/PanicHandler.h" + +static CrashData emptyCrashData = {}; + +const CrashData& getRtcCrashData() { return emptyCrashData; } + #endif \ No newline at end of file diff --git a/sdkconfig.board.waveshare-esp32-c6-lcd-147 b/sdkconfig.board.waveshare-esp32-c6-lcd-147 new file mode 100644 index 000000000..178ceecd8 --- /dev/null +++ b/sdkconfig.board.waveshare-esp32-c6-lcd-147 @@ -0,0 +1,55 @@ +# Software defaults +# Increase stack size for WiFi (fixes crash after scan) +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072 +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_18=y +CONFIG_LV_USE_USER_DATA=y +CONFIG_LV_USE_FS_STDIO=y +CONFIG_LV_FS_STDIO_LETTER=65 +CONFIG_LV_FS_STDIO_PATH="" +CONFIG_LV_FS_STDIO_CACHE_SIZE=4096 +CONFIG_LV_USE_LODEPNG=y +CONFIG_LV_USE_BUILTIN_MALLOC=n +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_MSGBOX=n +CONFIG_LV_USE_SPINNER=n +CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=5120 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 + +# Hardware: Main +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-4mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-4mb.csv" +CONFIG_TT_BOARD_WAVESHARE_ESP32_C6_LCD_147=y +CONFIG_TT_BOARD_NAME="Waveshare ESP32 C6 LCD 1.47" +CONFIG_TT_BOARD_ID="waveshare-esp32-c6-lcd-147" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32c6" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_FLASHMODE_QIO=y + +# Hardware: SPI RAM (ESP32-C6 with Octo PSRAM) +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y + +# SPI Flash +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y + +# LVGL - Display is 172x320, approximately 600 DPI for 1.47" screen +CONFIG_LV_DPI_DEF=600 +CONFIG_LV_DISP_DEF_REFR_PERIOD=10