diff --git a/Boards/LilygoTdongleS3/CMakeLists.txt b/Boards/LilygoTdongleS3/CMakeLists.txt new file mode 100644 index 00000000..9c11ba55 --- /dev/null +++ b/Boards/LilygoTdongleS3/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 ST7735 GT911 PwmBacklight driver +) diff --git a/Boards/LilygoTdongleS3/Source/Init.cpp b/Boards/LilygoTdongleS3/Source/Init.cpp new file mode 100644 index 00000000..c185e5d5 --- /dev/null +++ b/Boards/LilygoTdongleS3/Source/Init.cpp @@ -0,0 +1,15 @@ +#include "PwmBacklight.h" +#include "Tactility/service/gps/GpsService.h" + +#include + +#define TAG "T-Dongle" + +bool initBoot() { + if (!driver::pwmbacklight::init(GPIO_NUM_38, 12000)) { + TT_LOG_E(TAG, "Backlight init failed"); + return false; + } + + return true; +} diff --git a/Boards/LilygoTdongleS3/Source/LilygoTdongleS3.cpp b/Boards/LilygoTdongleS3/Source/LilygoTdongleS3.cpp new file mode 100644 index 00000000..649e5241 --- /dev/null +++ b/Boards/LilygoTdongleS3/Source/LilygoTdongleS3.cpp @@ -0,0 +1,92 @@ +#include +#include + +#include "devices/Display.h" +#include "devices/Sdcard.h" + +#define TDECK_SPI_TRANSFER_SIZE_LIMIT (80 * 160 * (LV_COLOR_DEPTH / 8)) + +bool initBoot(); + +using namespace tt::hal; + +static std::vector> createDevices() { + return { + createDisplay(), + createSdCard() + }; +} + +extern const Configuration lilygo_tdongle_s3 = { + .initBoot = initBoot, + .uiScale = UiScale::Smallest, + .createDevices = createDevices, + .i2c = { + i2c::Configuration { + .name = "STEMMA QT", + .port = I2C_NUM_0, + .initMode = i2c::InitMode::ByTactility, + .isMutable = false, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_44, + .scl_io_num = GPIO_NUM_43, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + } + }, + .spi { + spi::Configuration { + .device = SPI3_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_3, + .miso_io_num = GPIO_NUM_NC, + .sclk_io_num = GPIO_NUM_5, + .quadwp_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 = TDECK_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 { + uart::Configuration { + .name = "STEMMA QT", + .port = UART_NUM_1, + .rxPin = GPIO_NUM_44, + .txPin = GPIO_NUM_43, + .rtsPin = GPIO_NUM_NC, + .ctsPin = GPIO_NUM_NC, + .rxBufferSize = 1024, + .txBufferSize = 1024, + .config = { + .baud_rate = 38400, + .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/Boards/LilygoTdongleS3/Source/LilygoTdongleS3.h b/Boards/LilygoTdongleS3/Source/LilygoTdongleS3.h new file mode 100644 index 00000000..f3a3d867 --- /dev/null +++ b/Boards/LilygoTdongleS3/Source/LilygoTdongleS3.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration lilygo_tdongle_s3; diff --git a/Boards/LilygoTdongleS3/Source/devices/Display.cpp b/Boards/LilygoTdongleS3/Source/devices/Display.cpp new file mode 100644 index 00000000..35597a8d --- /dev/null +++ b/Boards/LilygoTdongleS3/Source/devices/Display.cpp @@ -0,0 +1,36 @@ +#include "Display.h" + +#include +#include + +#define LCD_SPI_HOST SPI3_HOST +#define LCD_PIN_CS GPIO_NUM_4 +#define LCD_PIN_DC GPIO_NUM_2 +#define LCD_PIN_RESET GPIO_NUM_1 +#define LCD_HORIZONTAL_RESOLUTION 80 +#define LCD_VERTICAL_RESOLUTION 160 +#define LCD_SPI_TRANSFER_HEIGHT LCD_VERTICAL_RESOLUTION / 4 + +std::shared_ptr createDisplay() { + auto configuration = std::make_unique( + LCD_SPI_HOST, + LCD_PIN_CS, + LCD_PIN_DC, + LCD_PIN_RESET, + 80, + 160, + nullptr, + false, + false, + false, + true, + 0, + 26, + 1 + ); + + configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty; + + auto display = std::make_shared(std::move(configuration)); + return std::reinterpret_pointer_cast(display); +} diff --git a/Boards/LilygoTdongleS3/Source/devices/Display.h b/Boards/LilygoTdongleS3/Source/devices/Display.h new file mode 100644 index 00000000..7a9b967d --- /dev/null +++ b/Boards/LilygoTdongleS3/Source/devices/Display.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +std::shared_ptr createDisplay(); diff --git a/Boards/LilygoTdongleS3/Source/devices/Sdcard.cpp b/Boards/LilygoTdongleS3/Source/devices/Sdcard.cpp new file mode 100644 index 00000000..6315691a --- /dev/null +++ b/Boards/LilygoTdongleS3/Source/devices/Sdcard.cpp @@ -0,0 +1,22 @@ +#include "Sdcard.h" + +#include +#include + +using tt::hal::sdcard::SdmmcDevice; + +std::shared_ptr createSdCard() { + auto configuration = std::make_unique( + GPIO_NUM_12, + GPIO_NUM_16, + GPIO_NUM_14, + GPIO_NUM_17, + GPIO_NUM_21, + GPIO_NUM_18, + SdCardDevice::MountBehaviour::AtBoot + ); + + return std::make_shared( + std::move(configuration) + ); +} diff --git a/Boards/LilygoTdongleS3/Source/devices/Sdcard.h b/Boards/LilygoTdongleS3/Source/devices/Sdcard.h new file mode 100644 index 00000000..5cb65a73 --- /dev/null +++ b/Boards/LilygoTdongleS3/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/Buildscripts/board.cmake b/Buildscripts/board.cmake index e2c340fb..377b550c 100644 --- a/Buildscripts/board.cmake +++ b/Buildscripts/board.cmake @@ -51,6 +51,8 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE) set(TACTILITY_BOARD_PROJECT ElecrowCrowpanelBasic50) elseif (board_id STREQUAL "lilygo-tdeck") set(TACTILITY_BOARD_PROJECT LilygoTdeck) + elseif (board_id STREQUAL "lilygo-tdongle-s3") + set(TACTILITY_BOARD_PROJECT LilygoTdongleS3) elseif (board_id STREQUAL "lilygo-tlora-pager") set(TACTILITY_BOARD_PROJECT LilygoTLoraPager) elseif (board_id STREQUAL "m5stack-cardputer") diff --git a/Drivers/ST7735/CMakeLists.txt b/Drivers/ST7735/CMakeLists.txt new file mode 100644 index 00000000..b99e92e8 --- /dev/null +++ b/Drivers/ST7735/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" + INCLUDE_DIRS "Source" + REQUIRES Tactility driver EspLcdCompat esp_lcd_st7735 +) diff --git a/Drivers/ST7735/README.md b/Drivers/ST7735/README.md new file mode 100644 index 00000000..eb316897 --- /dev/null +++ b/Drivers/ST7735/README.md @@ -0,0 +1,3 @@ +# ST7735 + +A basic ESP32 LVGL driver for ST7735 displays. diff --git a/Drivers/ST7735/Source/St7735Display.cpp b/Drivers/ST7735/Source/St7735Display.cpp new file mode 100644 index 00000000..32503b9e --- /dev/null +++ b/Drivers/ST7735/Source/St7735Display.cpp @@ -0,0 +1,170 @@ +#include "St7735Display.h" + +#include + +#include +#include +#include +#include + +constexpr auto TAG = "ST7735"; + +bool St7735Display::createIoHandle(esp_lcd_panel_io_handle_t& outHandle) { + TT_LOG_I(TAG, "Starting"); + + const esp_lcd_panel_io_spi_config_t panel_io_config = { + .cs_gpio_num = configuration->csPin, + .dc_gpio_num = configuration->dcPin, + .spi_mode = 0, + .pclk_hz = configuration->pixelClockFrequency, + .trans_queue_depth = configuration->transactionQueueDepth, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .lcd_cmd_bits = 8, + .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 = 0, + .sio_mode = 1, + .lsb_first = 0, + .cs_high_active = 0 + } + }; + + if (esp_lcd_new_panel_io_spi(configuration->spiHostDevice, &panel_io_config, &outHandle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to create panel"); + return false; + } + + return true; +} + +bool St7735Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) { + + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = configuration->resetPin, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = 16, + .flags = { + .reset_active_high = false + }, + .vendor_config = nullptr + }; + + if (esp_lcd_new_panel_st7735(ioHandle, &panel_config, &panelHandle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to create panel"); + return false; + } + + if (esp_lcd_panel_reset(panelHandle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to reset panel"); + return false; + } + + if (esp_lcd_panel_init(panelHandle) != ESP_OK) { + TT_LOG_E(TAG, "Failed to init panel"); + return false; + } + + if (esp_lcd_panel_invert_color(panelHandle, configuration->invertColor) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel to invert"); + return false; + } + + // Warning: it looks like LVGL rotation is broken when "gap" is set and the screen is moved to a non-default orientation + int gap_x = configuration->swapXY ? configuration->gapY : configuration->gapX; + int gap_y = configuration->swapXY ? configuration->gapX : configuration->gapY; + if (esp_lcd_panel_set_gap(panelHandle, gap_x, gap_y) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel gap"); + return false; + } + + if (esp_lcd_panel_swap_xy(panelHandle, configuration->swapXY) != ESP_OK) { + TT_LOG_E(TAG, "Failed to swap XY "); + return false; + } + + if (esp_lcd_panel_mirror(panelHandle, configuration->mirrorX, configuration->mirrorY) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel to mirror"); + return false; + } + + if (esp_lcd_panel_disp_on_off(panelHandle, true) != ESP_OK) { + TT_LOG_E(TAG, "Failed to turn display on"); + return false; + } + + return true; +} + +lvgl_port_display_cfg_t St7735Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) { + return lvgl_port_display_cfg_t { + .io_handle = ioHandle, + .panel_handle = panelHandle, + .control_handle = nullptr, + .buffer_size = configuration->bufferSize, + .double_buffer = false, + .trans_size = 0, + .hres = configuration->horizontalResolution, + .vres = configuration->verticalResolution, + .monochrome = false, + .rotation = { + .swap_xy = configuration->swapXY, + .mirror_x = configuration->mirrorX, + .mirror_y = configuration->mirrorY, + }, + .color_format = LV_COLOR_FORMAT_RGB565, + .flags = { + .buff_dma = true, + .buff_spiram = false, + .sw_rotate = false, + .swap_bytes = true, + .full_refresh = false, + .direct_mode = false + } + }; +} +/** + * Note: + * The datasheet implies this should work, but it doesn't: + * https://www.digikey.com/htmldatasheets/production/1640716/0/0/1/ILI9341-Datasheet.pdf + * + * This repo claims it only has 1 curve: + * https://github.com/brucemack/hello-ili9341 + * + * I'm leaving it in as I'm not sure if it's just my hardware that's problematic. + */ +void St7735Display::setGammaCurve(uint8_t index) { + uint8_t gamma_curve; + switch (index) { + case 0: + gamma_curve = 0x01; + break; + case 1: + gamma_curve = 0x04; + break; + case 2: + gamma_curve = 0x02; + break; + case 3: + gamma_curve = 0x08; + break; + default: + return; + } + const uint8_t param[] = { + gamma_curve + }; + + auto io_handle = getIoHandle(); + assert(io_handle != nullptr); + if (esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_GAMSET, param, 1) != ESP_OK) { + TT_LOG_E(TAG, "Failed to set gamma"); + } +} diff --git a/Drivers/ST7735/Source/St7735Display.h b/Drivers/ST7735/Source/St7735Display.h new file mode 100644 index 00000000..13f64af3 --- /dev/null +++ b/Drivers/ST7735/Source/St7735Display.h @@ -0,0 +1,116 @@ +#pragma once + +#include "Tactility/hal/spi/Spi.h" + +#include +#include + +#include +#include +#include +#include +#include + +class St7735Display final : public EspLcdDisplay { + + std::shared_ptr lock; + +public: + + class Configuration { + + public: + + Configuration( + spi_host_device_t spiHostDevice, + gpio_num_t csPin, + gpio_num_t dcPin, + gpio_num_t resetPin, + 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 + int gapX = 0, + int gapY = 0 + ) : spiHostDevice(spiHostDevice), + csPin(csPin), + dcPin(dcPin), + resetPin(resetPin), + horizontalResolution(horizontalResolution), + verticalResolution(verticalResolution), + gapX(gapX), + gapY(gapY), + swapXY(swapXY), + mirrorX(mirrorX), + mirrorY(mirrorY), + invertColor(invertColor), + bufferSize(bufferSize), + 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_NC; + unsigned int pixelClockFrequency = 27'000'000; // Hertz + size_t transactionQueueDepth = 10; + unsigned int horizontalResolution; + unsigned int verticalResolution; + int gapX; + int gapY; + 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 + std::shared_ptr touch; + std::function _Nullable backlightDutyFunction = nullptr; + }; + +private: + + std::unique_ptr configuration; + + bool createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) override; + + bool createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) override; + + lvgl_port_display_cfg_t getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) override; + +public: + + explicit St7735Display(std::unique_ptr inConfiguration) : + EspLcdDisplay(tt::hal::spi::getLock(inConfiguration->spiHostDevice)), + configuration(std::move(inConfiguration) + ) { + assert(configuration != nullptr); + assert(getLock() != nullptr); + } + + std::string getName() const override { return "ST7735"; } + + std::string getDescription() const override { return "ST7735 display"; } + + 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; } + + void setGammaCurve(uint8_t index) override; + uint8_t getGammaCurveCount() const override { return 4; }; +}; + +std::shared_ptr createDisplay(); diff --git a/Firmware/Kconfig b/Firmware/Kconfig index ad1f7ac0..5649acc0 100644 --- a/Firmware/Kconfig +++ b/Firmware/Kconfig @@ -41,6 +41,8 @@ menu "Tactility App" bool "Elecrow CrowPanel Basic 5.0" config TT_BOARD_LILYGO_TDECK bool "LilyGo T-Deck" + config TT_BOARD_LILYGO_TDONGLE_S3 + bool "LilyGo T-Dongle S3" config TT_BOARD_LILYGO_TLORA_PAGER bool "LilyGo T-Lora Pager" config TT_BOARD_M5STACK_CARDPUTER diff --git a/Firmware/Source/Boards.h b/Firmware/Source/Boards.h index c7f4ff89..2af902f4 100644 --- a/Firmware/Source/Boards.h +++ b/Firmware/Source/Boards.h @@ -8,6 +8,9 @@ #if defined(CONFIG_TT_BOARD_LILYGO_TDECK) #include "LilygoTdeck.h" #define TT_BOARD_HARDWARE &lilygo_tdeck +#elif defined(CONFIG_TT_BOARD_LILYGO_TDONGLE_S3) +#include "LilygoTdongleS3.h" +#define TT_BOARD_HARDWARE &lilygo_tdongle_s3 #elif defined(CONFIG_TT_BOARD_LILYGO_TLORA_PAGER) #include "LilygoTloraPager.h" #define TT_BOARD_HARDWARE &lilygo_tlora_pager diff --git a/Firmware/idf_component.yml b/Firmware/idf_component.yml index 1d33b876..98c1b02e 100644 --- a/Firmware/idf_component.yml +++ b/Firmware/idf_component.yml @@ -1,6 +1,7 @@ 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_touch: "1.1.2" atanisoft/esp_lcd_touch_xpt2046: "1.0.5" espressif/esp_lcd_touch_cst816s: "1.0.3" diff --git a/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h b/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h new file mode 100644 index 00000000..992d403f --- /dev/null +++ b/Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h @@ -0,0 +1,91 @@ +#ifdef ESP_PLATFORM + +#pragma once + +#include "SdCardDevice.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace tt::hal::sdcard { + +/** + * SD card interface for the SDMMC interface. + */ +class SdmmcDevice final : public SdCardDevice { + + std::shared_ptr mutex = std::make_shared(Mutex::Type::Recursive); + +public: + + struct Config { + Config( + gpio_num_t pinClock, + gpio_num_t pinCmd, + gpio_num_t pinD0, + gpio_num_t pinD1, + gpio_num_t pinD2, + gpio_num_t pinD3, + MountBehaviour mountBehaviourAtBoot + ) : + pinClock(pinClock), + pinCmd(pinCmd), + pinD0(pinD0), + pinD1(pinD1), + pinD2(pinD2), + pinD3(pinD3), + mountBehaviourAtBoot(mountBehaviourAtBoot) + {} + + int spiFrequencyKhz; + gpio_num_t pinClock; + gpio_num_t pinCmd; + gpio_num_t pinD0; + gpio_num_t pinD1; + gpio_num_t pinD2; + gpio_num_t pinD3; + MountBehaviour mountBehaviourAtBoot; + bool formatOnMountFailed = false; + uint16_t maxOpenFiles = 4; + uint16_t allocUnitSize = 16 * 1024; + bool statusCheckEnabled = false; + }; + +private: + + std::string mountPath; + sdmmc_card_t* card = nullptr; + std::shared_ptr config; + + bool applyGpioWorkAround(); + bool mountInternal(const std::string& mountPath); + +public: + + explicit SdmmcDevice(std::unique_ptr config) : SdCardDevice(config->mountBehaviourAtBoot), + config(std::move(config)) + {} + + std::string getName() const override { return "SDMMC"; } + std::string getDescription() const override { return "SD card via SDMMC interface"; } + + bool mount(const std::string& mountPath) override; + bool unmount() override; + std::string getMountPath() const override { return mountPath; } + + std::shared_ptr getLock() const override { return mutex; } + + State getState(TickType_t timeout) const override; + + sdmmc_card_t* _Nullable getCard() { return card; } +}; + +} + +#endif diff --git a/Tactility/Private/Tactility/TactilityPrivate.h b/Tactility/Private/Tactility/TactilityPrivate.h index 7d57fd8a..ebabce00 100644 --- a/Tactility/Private/Tactility/TactilityPrivate.h +++ b/Tactility/Private/Tactility/TactilityPrivate.h @@ -4,6 +4,6 @@ namespace tt { -void initFromBootApp(); +void registerApps(); } diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 30516433..d94ffa49 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -201,6 +201,13 @@ static void registerInstalledAppsFromSdCards() { } } +static void registerInstalledAppsFromData() { + auto app_path = "/data/app"; + if (file::isDirectory(app_path)) { + registerInstalledApps(app_path); + } +} + static void registerAndStartSecondaryServices() { TT_LOG_I(TAG, "Registering and starting system services"); addService(service::loader::manifest); @@ -222,13 +229,14 @@ static void registerAndStartPrimaryServices() { #endif } -void initFromBootApp() { +void registerApps() { registerInternalApps(); auto data_apps_path = std::format("{}/apps", file::MOUNT_POINT_DATA); if (file::isDirectory(data_apps_path)) { registerInstalledApps(data_apps_path); } registerInstalledAppsFromSdCards(); + registerInstalledAppsFromData(); } void run(const Configuration& config) { diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp index db5f3c9d..60bb1464 100644 --- a/Tactility/Source/app/boot/Boot.cpp +++ b/Tactility/Source/app/boot/Boot.cpp @@ -108,7 +108,7 @@ class BootApp : public App { if (!setupUsbBootMode()) { TT_LOG_I(TAG, "initFromBootApp"); - initFromBootApp(); + registerApps(); waitForMinimalSplashDuration(start_time); stop(manifest.appId); startNextApp(); diff --git a/Tactility/Source/hal/sdcard/SdmmcDevice.cpp b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp new file mode 100644 index 00000000..0ce79396 --- /dev/null +++ b/Tactility/Source/hal/sdcard/SdmmcDevice.cpp @@ -0,0 +1,113 @@ +#ifdef ESP_PLATFORM + +#include +#include + +#include +#include +#include + +namespace tt::hal::sdcard { + +constexpr auto* TAG = "SdmmcDevice"; + +bool SdmmcDevice::mountInternal(const std::string& newMountPath) { + TT_LOG_I(TAG, "Mounting %s", newMountPath.c_str()); + + esp_vfs_fat_sdmmc_mount_config_t mount_config = { + .format_if_mount_failed = config->formatOnMountFailed, + .max_files = config->maxOpenFiles, + .allocation_unit_size = config->allocUnitSize, + .disk_status_check_enable = config->statusCheckEnabled, + .use_one_fat = false + }; + + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + + sdmmc_slot_config_t slot_config = { + .clk = config->pinClock, + .cmd = config->pinCmd, + .d0 = config->pinD0, + .d1 = config->pinD1, + .d2 = config->pinD2, + .d3 = config->pinD3, + .d4 = static_cast(0), + .d5 = static_cast(0), + .d6 = static_cast(0), + .d7 = static_cast(0), + .cd = GPIO_NUM_NC, + .wp = GPIO_NUM_NC, + .width = 4, + .flags = 0 + }; + + esp_err_t result = esp_vfs_fat_sdmmc_mount(newMountPath.c_str(), &host, &slot_config, &mount_config, &card); + + if (result != ESP_OK || card == nullptr) { + if (result == ESP_FAIL) { + TT_LOG_E(TAG, "Mounting failed. Ensure the card is formatted with FAT."); + } else { + TT_LOG_E(TAG, "Mounting failed (%s)", esp_err_to_name(result)); + } + return false; + } + + mountPath = newMountPath; + + return true; +} + +bool SdmmcDevice::mount(const std::string& newMountPath) { + if (mountInternal(newMountPath)) { + TT_LOG_I(TAG, "Mounted at %s", newMountPath.c_str()); + sdmmc_card_print_info(stdout, card); + return true; + } else { + TT_LOG_E(TAG, "Mount failed for %s", newMountPath.c_str()); + return false; + } +} + +bool SdmmcDevice::unmount() { + if (card == nullptr) { + TT_LOG_E(TAG, "Can't unmount: not mounted"); + return false; + } + + if (esp_vfs_fat_sdcard_unmount(mountPath.c_str(), card) != ESP_OK) { + TT_LOG_E(TAG, "Unmount failed for %s", mountPath.c_str()); + return false; + } + + TT_LOG_I(TAG, "Unmounted %s", mountPath.c_str()); + mountPath = ""; + card = nullptr; + return true; +} + +SdmmcDevice::State SdmmcDevice::getState(TickType_t timeout) const { + if (card == nullptr) { + return State::Unmounted; + } + + /** + * 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. + */ + auto lock = getLock()->asScopedLock(); + bool locked = lock.lock(timeout); + if (!locked) { + return State::Timeout; + } + + if (sdmmc_get_status(card) != ESP_OK) { + return State::Error; + } + + return State::Mounted; +} + +} + +#endif \ No newline at end of file diff --git a/TactilityCore/Include/Tactility/Mutex.h b/TactilityCore/Include/Tactility/Mutex.h index cfdf32aa..8642fc46 100644 --- a/TactilityCore/Include/Tactility/Mutex.h +++ b/TactilityCore/Include/Tactility/Mutex.h @@ -20,7 +20,10 @@ namespace tt { class Mutex final : public Lock { public: - + /** + * A "Normal" mutex can only be locked once. Even from within the same task/thread. + * A "Recursive" mutex can be locked again from the same task/thread. + */ enum class Type { Normal, Recursive, diff --git a/sdkconfig.board.lilygo-tdongle-s3 b/sdkconfig.board.lilygo-tdongle-s3 new file mode 100644 index 00000000..0eb4843d --- /dev/null +++ b/sdkconfig.board.lilygo-tdongle-s3 @@ -0,0 +1,53 @@ +# 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_SMP=n +CONFIG_FREERTOS_UNICORE=n +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-16mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-16mb.csv" +CONFIG_TT_BOARD_LILYGO_TDONGLE_S3=y +CONFIG_TT_BOARD_NAME="LilyGo T-Dongle S3" +CONFIG_TT_BOARD_ID="lilygo-tdongle-s3" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_FLASHMODE_QIO=y +# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved) +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +# LVGL +CONFIG_LV_DPI_DEF=186 +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 +CONFIG_LV_THEME_DEFAULT_DARK=y +# USB +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard" \ No newline at end of file