From 79d2202ab1dffac185071bd85ce262ed3f2f229b Mon Sep 17 00:00:00 2001 From: Marcio Date: Mon, 27 Oct 2025 18:08:10 -0300 Subject: [PATCH 1/3] Add support for Waveshare ESP32-C6-LCD-1.47 board MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds full support for the Waveshare ESP32-C6-LCD-1.47 development board, featuring a 172x320 ST7789 display with 1.47" diagonal and SD card support. This is the first ESP32-C6 (RISC-V) board supported by the project. Key changes: - New board implementation with ST7789 display (172x320, 12MHz SPI) - SD card support via shared SPI bus with proper CS coordination - PWM backlight control using LEDC peripheral - Build system integration (GitHub Actions, CMake, Kconfig) - Architecture compatibility fixes for ESP32-C6/RISC-V: - Conditional panic handler wrapping (Xtensa-only feature) - Updated CPU affinity handling for single-core chips - Fixed stack PC processing for RISC-V - SDMMC host check for unsupported peripherals - Component dependency rules for multi-architecture support - Development environment setup with .envrc for ESP-IDF Hardware specifications: - MCU: ESP32-C6 (RISC-V, single-core, 160MHz) - Display: 1.47" ST7789 LCD (172x320, ~600 DPI) - RAM: 4MB Octal PSRAM (80MHz) - Flash: 4MB QSPI (80MHz) - Storage: microSD card slot (shared SPI with display) - Backlight: PWM-controlled via GPIO22 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/build-firmware.yml | 3 +- .../waveshare-esp32-c6-lcd-147/CMakeLists.txt | 7 ++ .../Source/Init.cpp | 82 +++++++++++++++++++ .../Source/WaveshareEsp32C6Lcd147.cpp | 53 ++++++++++++ .../Source/WaveshareEsp32C6Lcd147.h | 5 ++ .../Source/devices/Display.cpp | 38 +++++++++ .../Source/devices/Display.h | 5 ++ .../Source/devices/SdCard.cpp | 26 ++++++ .../Source/devices/SdCard.h | 7 ++ CMakeLists.txt | 6 +- Firmware/Kconfig | 2 + Firmware/idf_component.yml | 22 ++++- .../Source/app/crashdiagnostics/QrUrl.cpp | 8 ++ Tactility/Source/hal/sdcard/SdmmcDevice.cpp | 2 +- TactilityCore/Source/CpuAffinity.cpp | 6 ++ TactilityCore/Source/kernel/PanicHandler.cpp | 28 +++++-- sdkconfig.board.waveshare-esp32-c6-lcd-147 | 55 +++++++++++++ 17 files changed, 343 insertions(+), 12 deletions(-) create mode 100644 Boards/waveshare-esp32-c6-lcd-147/CMakeLists.txt create mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp create mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.cpp create mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.h create mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.cpp create mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h create mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.cpp create mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h create mode 100644 sdkconfig.board.waveshare-esp32-c6-lcd-147 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/Init.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp new file mode 100644 index 000000000..1b613e850 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp @@ -0,0 +1,82 @@ +#include +#include +#include + +// Backlight configuration based on demo code +#define LCD_BL_PIN GPIO_NUM_22 +#define LCD_BL_LEDC_MODE LEDC_LOW_SPEED_MODE +#define LCD_BL_LEDC_TIMER LEDC_TIMER_0 +#define LCD_BL_LEDC_CHANNEL LEDC_CHANNEL_0 +#define LCD_BL_LEDC_RESOLUTION LEDC_TIMER_13_BIT // 13-bit resolution (8192 levels) +#define LCD_BL_LEDC_DUTY ((1 << LCD_BL_LEDC_RESOLUTION) - 1) // Max duty = 8191 +#define LCD_BL_FREQUENCY 5000 // 5 kHz + +namespace driver::pwmbacklight { + + bool init(gpio_num_t pin, uint32_t maxDuty) { + // Configure LEDC timer + ledc_timer_config_t timer_config = { + .speed_mode = LCD_BL_LEDC_MODE, + .duty_resolution = LCD_BL_LEDC_RESOLUTION, + .timer_num = LCD_BL_LEDC_TIMER, + .freq_hz = LCD_BL_FREQUENCY, + .clk_cfg = LEDC_AUTO_CLK + }; + esp_err_t err = ledc_timer_config(&timer_config); + if (err != ESP_OK) { + return false; + } + + // Configure LEDC channel + ledc_channel_config_t channel_config = { + .gpio_num = LCD_BL_PIN, + .speed_mode = LCD_BL_LEDC_MODE, + .channel = LCD_BL_LEDC_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LCD_BL_LEDC_TIMER, + .duty = 0, // Start with backlight off + .hpoint = 0, + .flags = { + .output_invert = 0 + } + }; + err = ledc_channel_config(&channel_config); + if (err != ESP_OK) { + return false; + } + + return true; + } + + void setBacklightDuty(uint8_t level) { + // Convert 0-255 level to duty cycle with inverted mapping as in demo code + // The demo uses: duty = max - (81 * (100 - percentage)) + // We'll adapt this for 0-255 range + if (level > 255) level = 255; + + // Map 0-255 to 0-100 percentage + uint8_t percentage = (level * 100) / 255; + + // Calculate duty with inverted logic (higher level = higher duty) + uint32_t duty; + if (percentage == 0) { + duty = 0; + } else { + // Map to full range: 0% -> 0, 100% -> LCD_BL_LEDC_DUTY + duty = LCD_BL_LEDC_DUTY - ((81 * (100 - percentage))); + } + + // Ensure duty doesn't exceed max + if (duty > LCD_BL_LEDC_DUTY) { + duty = LCD_BL_LEDC_DUTY; + } + + ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty); + ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL); + } + +} // namespace driver::pwmbacklight + +bool initBoot() { + return driver::pwmbacklight::init(LCD_BL_PIN, 256); +} diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.cpp new file mode 100644 index 000000000..2b7c7582e --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.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 = SPI2_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_6, + .miso_io_num = GPIO_NUM_5, + .sclk_io_num = GPIO_NUM_7, + .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 = ((172 * (320 / 10)) * LV_COLOR_DEPTH / 8), + .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/WaveshareEsp32C6Lcd147.h b/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.h new file mode 100644 index 000000000..974a8538f --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration waveshare_esp32_c6_lcd_147; 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..30cf1a458 --- /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 = 172, + .verticalResolution = 320, + .gapX = 34, // X offset for 1.47" display + .gapY = 0, + .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 = GPIO_NUM_21 + }; + + auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { + .spiHostDevice = SPI2_HOST, + .csPin = GPIO_NUM_14, + .dcPin = GPIO_NUM_15, + .pixelClockFrequency = 12'000'000, // 12 MHz as in demo + .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..5a0d81b38 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h @@ -0,0 +1,5 @@ +#pragma once + +#include "Tactility/hal/display/DisplayDevice.h" + +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..8baa9a132 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.cpp @@ -0,0 +1,26 @@ +#include "SdCard.h" + +#include +#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( + GPIO_NUM_4, // 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(SPI2_HOST), // Use same lock as display + std::vector { GPIO_NUM_14 }, // Assert display CS high during SD operations + SPI2_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..5cb65a735 --- /dev/null +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h @@ -0,0 +1,7 @@ +#pragma once + +#include "Tactility/hal/sdcard/SdCardDevice.h" + +using tt::hal::sdcard::SdCardDevice; + +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..f9720cf41 100644 --- a/Tactility/Source/app/crashdiagnostics/QrUrl.cpp +++ b/Tactility/Source/app/crashdiagnostics/QrUrl.cpp @@ -5,7 +5,11 @@ #include #include +#include + +#if CONFIG_IDF_TARGET_ARCH_XTENSA #include +#endif #include @@ -14,7 +18,11 @@ std::string getUrlFromCrashData() { auto* stack_buffer = (uint32_t*) malloc(crash_data.callstackLength * 2 * sizeof(uint32_t)); 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 diff --git a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp index 8bbb198ae..37bf97af0 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 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..909010f16 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 && @@ -66,4 +73,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 From 648129221f33934b884d4dae44757849ece8379d Mon Sep 17 00:00:00 2001 From: Marcio Date: Tue, 28 Oct 2025 14:17:00 -0300 Subject: [PATCH 2/3] Refactor board files to match standard naming convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename WaveshareEsp32C6Lcd147.cpp to Configuration.cpp - Remove unused WaveshareEsp32C6Lcd147.h header file This aligns the board implementation with other boards in the codebase, which use Configuration.cpp as the standard name for the board configuration file. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Source/{WaveshareEsp32C6Lcd147.cpp => Configuration.cpp} | 0 .../Source/WaveshareEsp32C6Lcd147.h | 5 ----- 2 files changed, 5 deletions(-) rename Boards/waveshare-esp32-c6-lcd-147/Source/{WaveshareEsp32C6Lcd147.cpp => Configuration.cpp} (100%) delete mode 100644 Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.h diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp similarity index 100% rename from Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.cpp rename to Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.h b/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.h deleted file mode 100644 index 974a8538f..000000000 --- a/Boards/waveshare-esp32-c6-lcd-147/Source/WaveshareEsp32C6Lcd147.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -extern const tt::hal::Configuration waveshare_esp32_c6_lcd_147; From 8c5b3792815008d2690a2e06f45e8f160208b172 Mon Sep 17 00:00:00 2001 From: Marcio Date: Tue, 28 Oct 2025 14:41:38 -0300 Subject: [PATCH 3/3] Address PR #394 review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Board-specific improvements (waveshare-esp32-c6-lcd-147): - Replace custom backlight implementation with existing PwmBacklight driver (Init.cpp) - Add constexpr constants for all pins and configuration values (Display.h) - Use constexpr constants instead of magic numbers throughout board code - Clean up includes: remove unused LvglSync.h, add explicit driver/gpio.h - Simplify board initialization code (78 lines → 4 lines in Init.cpp) Framework-level improvements: - Fix misleading comment in SdmmcDevice.cpp about locking rationale - Replace malloc/free with std::vector in QrUrl.cpp for safer memory management - Add stack frame validation for all frames in PanicHandler.cpp (not just first) Build verified: waveshare-esp32-c6-lcd-147 builds successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../Source/Configuration.cpp | 10 +-- .../Source/Init.cpp | 82 +------------------ .../Source/devices/Display.cpp | 18 ++-- .../Source/devices/Display.h | 27 ++++++ .../Source/devices/SdCard.cpp | 10 +-- .../Source/devices/SdCard.h | 6 ++ .../Source/app/crashdiagnostics/QrUrl.cpp | 6 +- Tactility/Source/hal/sdcard/SdmmcDevice.cpp | 6 +- TactilityCore/Source/kernel/PanicHandler.cpp | 9 ++ 9 files changed, 71 insertions(+), 103 deletions(-) diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp index 2b7c7582e..9a87d2c6c 100644 --- a/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/Configuration.cpp @@ -26,12 +26,12 @@ extern const Configuration hardwareConfiguration = { .spi { // Display and SD card (shared SPI bus) spi::Configuration { - .device = SPI2_HOST, + .device = LCD_SPI_HOST, .dma = SPI_DMA_CH_AUTO, .config = { - .mosi_io_num = GPIO_NUM_6, - .miso_io_num = GPIO_NUM_5, - .sclk_io_num = GPIO_NUM_7, + .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, @@ -39,7 +39,7 @@ extern const Configuration hardwareConfiguration = { .data6_io_num = GPIO_NUM_NC, .data7_io_num = GPIO_NUM_NC, .data_io_default_level = false, - .max_transfer_sz = ((172 * (320 / 10)) * LV_COLOR_DEPTH / 8), + .max_transfer_sz = LCD_SPI_TRANSFER_SIZE_LIMIT, .flags = 0, .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, .intr_flags = 0 diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp b/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp index 1b613e850..e1e86a248 100644 --- a/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/Init.cpp @@ -1,82 +1,8 @@ -#include -#include -#include +#include "devices/Display.h" -// Backlight configuration based on demo code -#define LCD_BL_PIN GPIO_NUM_22 -#define LCD_BL_LEDC_MODE LEDC_LOW_SPEED_MODE -#define LCD_BL_LEDC_TIMER LEDC_TIMER_0 -#define LCD_BL_LEDC_CHANNEL LEDC_CHANNEL_0 -#define LCD_BL_LEDC_RESOLUTION LEDC_TIMER_13_BIT // 13-bit resolution (8192 levels) -#define LCD_BL_LEDC_DUTY ((1 << LCD_BL_LEDC_RESOLUTION) - 1) // Max duty = 8191 -#define LCD_BL_FREQUENCY 5000 // 5 kHz - -namespace driver::pwmbacklight { - - bool init(gpio_num_t pin, uint32_t maxDuty) { - // Configure LEDC timer - ledc_timer_config_t timer_config = { - .speed_mode = LCD_BL_LEDC_MODE, - .duty_resolution = LCD_BL_LEDC_RESOLUTION, - .timer_num = LCD_BL_LEDC_TIMER, - .freq_hz = LCD_BL_FREQUENCY, - .clk_cfg = LEDC_AUTO_CLK - }; - esp_err_t err = ledc_timer_config(&timer_config); - if (err != ESP_OK) { - return false; - } - - // Configure LEDC channel - ledc_channel_config_t channel_config = { - .gpio_num = LCD_BL_PIN, - .speed_mode = LCD_BL_LEDC_MODE, - .channel = LCD_BL_LEDC_CHANNEL, - .intr_type = LEDC_INTR_DISABLE, - .timer_sel = LCD_BL_LEDC_TIMER, - .duty = 0, // Start with backlight off - .hpoint = 0, - .flags = { - .output_invert = 0 - } - }; - err = ledc_channel_config(&channel_config); - if (err != ESP_OK) { - return false; - } - - return true; - } - - void setBacklightDuty(uint8_t level) { - // Convert 0-255 level to duty cycle with inverted mapping as in demo code - // The demo uses: duty = max - (81 * (100 - percentage)) - // We'll adapt this for 0-255 range - if (level > 255) level = 255; - - // Map 0-255 to 0-100 percentage - uint8_t percentage = (level * 100) / 255; - - // Calculate duty with inverted logic (higher level = higher duty) - uint32_t duty; - if (percentage == 0) { - duty = 0; - } else { - // Map to full range: 0% -> 0, 100% -> LCD_BL_LEDC_DUTY - duty = LCD_BL_LEDC_DUTY - ((81 * (100 - percentage))); - } - - // Ensure duty doesn't exceed max - if (duty > LCD_BL_LEDC_DUTY) { - duty = LCD_BL_LEDC_DUTY; - } - - ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty); - ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL); - } - -} // namespace driver::pwmbacklight +#include bool initBoot() { - return driver::pwmbacklight::init(LCD_BL_PIN, 256); + // 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 index 30cf1a458..848b26f0d 100644 --- a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.cpp +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.cpp @@ -12,10 +12,10 @@ std::shared_ptr createDisplay() { // - 12MHz SPI clock St7789Display::Configuration panel_configuration = { - .horizontalResolution = 172, - .verticalResolution = 320, - .gapX = 34, // X offset for 1.47" display - .gapY = 0, + .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, @@ -23,14 +23,14 @@ std::shared_ptr createDisplay() { .bufferSize = 0, // default = 1/10 of screen .touch = nullptr, .backlightDutyFunction = driver::pwmbacklight::setBacklightDuty, - .resetPin = GPIO_NUM_21 + .resetPin = LCD_PIN_RESET }; auto spi_configuration = std::make_shared(St7789Display::SpiConfiguration { - .spiHostDevice = SPI2_HOST, - .csPin = GPIO_NUM_14, - .dcPin = GPIO_NUM_15, - .pixelClockFrequency = 12'000'000, // 12 MHz as in demo + .spiHostDevice = LCD_SPI_HOST, + .csPin = LCD_PIN_CS, + .dcPin = LCD_PIN_DC, + .pixelClockFrequency = LCD_PIXEL_CLOCK_HZ, .transactionQueueDepth = 10 }); diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h index 5a0d81b38..49d5ff37e 100644 --- a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/Display.h @@ -1,5 +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 index 8baa9a132..d8f59c4e4 100644 --- a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.cpp +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.cpp @@ -1,6 +1,6 @@ #include "SdCard.h" +#include "Display.h" -#include #include #include @@ -10,14 +10,14 @@ 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( - GPIO_NUM_4, // CS pin for SD card + 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(SPI2_HOST), // Use same lock as display - std::vector { GPIO_NUM_14 }, // Assert display CS high during SD operations - SPI2_HOST + 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( diff --git a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h index 5cb65a735..f8af7b0d7 100644 --- a/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h +++ b/Boards/waveshare-esp32-c6-lcd-147/Source/devices/SdCard.h @@ -1,7 +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/Tactility/Source/app/crashdiagnostics/QrUrl.cpp b/Tactility/Source/app/crashdiagnostics/QrUrl.cpp index f9720cf41..10fe9c9b4 100644 --- a/Tactility/Source/app/crashdiagnostics/QrUrl.cpp +++ b/Tactility/Source/app/crashdiagnostics/QrUrl.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #if CONFIG_IDF_TARGET_ARCH_XTENSA @@ -15,7 +16,8 @@ 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 @@ -49,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 37bf97af0..4b5c20eff 100644 --- a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp +++ b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp @@ -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/kernel/PanicHandler.cpp b/TactilityCore/Source/kernel/PanicHandler.cpp index 909010f16..2e7619c30 100644 --- a/TactilityCore/Source/kernel/PanicHandler.cpp +++ b/TactilityCore/Source/kernel/PanicHandler.cpp @@ -53,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;