From d8346998cec7ca0e1a61adc8471c5e1d4de87d25 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Tue, 14 Oct 2025 20:39:23 +0200 Subject: [PATCH] Merge develop into main (#368) New boards: - LilyGO T-Dongle S3 - M5Stack StickC Plus - M5Stack StickC Plus2 New drivers: - AXP192: power control via I2C - ButtonControl: GPIO button input as LVGL device Other changes: - Updated implementation of AXP192 driver for Core2 board - Fix launcher UX for vertical layout - Fix error when properties file had an empty line - Add `__floatsidf` to `tt_init.cpp` --- .github/workflows/build-firmware.yml | 27 +++ Boards/LilygoTdongleS3/CMakeLists.txt | 7 + Boards/LilygoTdongleS3/Source/Init.cpp | 15 ++ .../Source/LilygoTdongleS3.cpp | 92 ++++++++++ .../LilygoTdongleS3/Source/LilygoTdongleS3.h | 5 + .../Source/devices/Display.cpp | 36 ++++ .../LilygoTdongleS3/Source/devices/Display.h | 5 + .../LilygoTdongleS3/Source/devices/Sdcard.cpp | 22 +++ .../LilygoTdongleS3/Source/devices/Sdcard.h | 7 + Boards/M5stackCore2/CMakeLists.txt | 2 +- Boards/M5stackCore2/Source/InitBoot.cpp | 51 ------ Boards/M5stackCore2/Source/InitBoot.h | 3 - Boards/M5stackCore2/Source/M5stackCore2.cpp | 16 +- .../M5stackCore2/Source/devices/Core2Power.h | 26 --- Boards/M5stackCore2/Source/devices/Power.cpp | 35 ++++ Boards/M5stackCore2/Source/devices/Power.h | 9 + Boards/M5stackStickCPlus/CMakeLists.txt | 7 + Boards/M5stackStickCPlus/README.md | 5 + .../Source/M5StackStickCPlus.cpp | 123 +++++++++++++ .../Source/M5StackStickCPlus.h | 5 + .../Source/devices/Display.cpp | 63 +++++++ .../Source/devices/Display.h | 15 ++ .../Source/devices/Power.cpp | 38 ++++ .../M5stackStickCPlus/Source/devices/Power.h | 9 + Boards/M5stackStickCPlus2/CMakeLists.txt | 7 + Boards/M5stackStickCPlus2/README.md | 5 + .../Source/M5StackStickCPlus2.cpp | 125 +++++++++++++ .../Source/M5StackStickCPlus2.h | 5 + .../Source/devices/Display.cpp | 29 +++ .../Source/devices/Display.h | 15 ++ Buildscripts/board.cmake | 6 + Buildscripts/build-and-release-all.sh | 9 + Drivers/AXP192/CMakeLists.txt | 8 + Drivers/AXP192/Include/Axp192.h | 59 ++++++ .../AXP192/Include}/axp192/axp192.h | 0 .../AXP192/Private}/axp192/LICENSE | 0 .../AXP192/Private}/axp192/README.md | 0 .../AXP192/Private}/axp192/axp192_config.h | 0 Drivers/AXP192/README.md | 5 + .../AXP192/Source/Axp192.cpp | 45 +++-- .../AXP192}/Source/axp192/axp192.c | 4 +- Drivers/ButtonControl/CMakeLists.txt | 7 + Drivers/ButtonControl/README.md | 3 + .../ButtonControl/Source/ButtonControl.cpp | 162 +++++++++++++++++ Drivers/ButtonControl/Source/ButtonControl.h | 110 ++++++++++++ Drivers/ST7735/CMakeLists.txt | 5 + Drivers/ST7735/README.md | 3 + Drivers/ST7735/Source/St7735Display.cpp | 170 ++++++++++++++++++ Drivers/ST7735/Source/St7735Display.h | 116 ++++++++++++ Firmware/Kconfig | 6 + Firmware/Source/Boards.h | 9 + Firmware/idf_component.yml | 1 + .../Tactility/hal/sdcard/SdmmcDevice.h | 91 ++++++++++ .../Private/Tactility/TactilityPrivate.h | 2 +- Tactility/Source/Tactility.cpp | 14 +- Tactility/Source/app/ElfApp.cpp | 2 +- Tactility/Source/app/boot/Boot.cpp | 2 +- Tactility/Source/app/launcher/Launcher.cpp | 25 ++- Tactility/Source/app/notes/Notes.cpp | 10 +- Tactility/Source/file/PropertiesFile.cpp | 10 +- Tactility/Source/hal/sdcard/SdmmcDevice.cpp | 113 ++++++++++++ Tactility/Source/lvgl/LabelUtils.cpp | 5 +- TactilityC/Source/tt_init.cpp | 4 + TactilityCore/Include/Tactility/Mutex.h | 5 +- TactilityCore/Include/Tactility/file/File.h | 12 -- sdkconfig.board.lilygo-tdongle-s3 | 53 ++++++ sdkconfig.board.m5stack-stickc-plus | 54 ++++++ sdkconfig.board.m5stack-stickc-plus2 | 60 +++++++ 68 files changed, 1857 insertions(+), 142 deletions(-) create mode 100644 Boards/LilygoTdongleS3/CMakeLists.txt create mode 100644 Boards/LilygoTdongleS3/Source/Init.cpp create mode 100644 Boards/LilygoTdongleS3/Source/LilygoTdongleS3.cpp create mode 100644 Boards/LilygoTdongleS3/Source/LilygoTdongleS3.h create mode 100644 Boards/LilygoTdongleS3/Source/devices/Display.cpp create mode 100644 Boards/LilygoTdongleS3/Source/devices/Display.h create mode 100644 Boards/LilygoTdongleS3/Source/devices/Sdcard.cpp create mode 100644 Boards/LilygoTdongleS3/Source/devices/Sdcard.h delete mode 100644 Boards/M5stackCore2/Source/InitBoot.cpp delete mode 100644 Boards/M5stackCore2/Source/InitBoot.h delete mode 100644 Boards/M5stackCore2/Source/devices/Core2Power.h create mode 100644 Boards/M5stackCore2/Source/devices/Power.cpp create mode 100644 Boards/M5stackCore2/Source/devices/Power.h create mode 100644 Boards/M5stackStickCPlus/CMakeLists.txt create mode 100644 Boards/M5stackStickCPlus/README.md create mode 100644 Boards/M5stackStickCPlus/Source/M5StackStickCPlus.cpp create mode 100644 Boards/M5stackStickCPlus/Source/M5StackStickCPlus.h create mode 100644 Boards/M5stackStickCPlus/Source/devices/Display.cpp create mode 100644 Boards/M5stackStickCPlus/Source/devices/Display.h create mode 100644 Boards/M5stackStickCPlus/Source/devices/Power.cpp create mode 100644 Boards/M5stackStickCPlus/Source/devices/Power.h create mode 100644 Boards/M5stackStickCPlus2/CMakeLists.txt create mode 100644 Boards/M5stackStickCPlus2/README.md create mode 100644 Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.cpp create mode 100644 Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.h create mode 100644 Boards/M5stackStickCPlus2/Source/devices/Display.cpp create mode 100644 Boards/M5stackStickCPlus2/Source/devices/Display.h create mode 100644 Drivers/AXP192/CMakeLists.txt create mode 100644 Drivers/AXP192/Include/Axp192.h rename {Boards/M5stackCore2/Source => Drivers/AXP192/Include}/axp192/axp192.h (100%) rename {Boards/M5stackCore2/Source => Drivers/AXP192/Private}/axp192/LICENSE (100%) rename {Boards/M5stackCore2/Source => Drivers/AXP192/Private}/axp192/README.md (100%) rename {Boards/M5stackCore2/Source => Drivers/AXP192/Private}/axp192/axp192_config.h (100%) create mode 100644 Drivers/AXP192/README.md rename Boards/M5stackCore2/Source/devices/Core2Power.cpp => Drivers/AXP192/Source/Axp192.cpp (74%) rename {Boards/M5stackCore2 => Drivers/AXP192}/Source/axp192/axp192.c (99%) create mode 100644 Drivers/ButtonControl/CMakeLists.txt create mode 100644 Drivers/ButtonControl/README.md create mode 100644 Drivers/ButtonControl/Source/ButtonControl.cpp create mode 100644 Drivers/ButtonControl/Source/ButtonControl.h create mode 100644 Drivers/ST7735/CMakeLists.txt create mode 100644 Drivers/ST7735/README.md create mode 100644 Drivers/ST7735/Source/St7735Display.cpp create mode 100644 Drivers/ST7735/Source/St7735Display.h create mode 100644 Tactility/Include/Tactility/hal/sdcard/SdmmcDevice.h create mode 100644 Tactility/Source/hal/sdcard/SdmmcDevice.cpp create mode 100644 sdkconfig.board.lilygo-tdongle-s3 create mode 100644 sdkconfig.board.m5stack-stickc-plus create mode 100644 sdkconfig.board.m5stack-stickc-plus2 diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index 1ccf1c10..4151ec2a 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -144,6 +144,15 @@ jobs: with: board_id: lilygo-tdeck arch: esp32s3 + lilygo-tdongle-s3: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: lilygo-tdongle-s3 + arch: esp32s3 lilygo-tlora-pager: runs-on: ubuntu-latest steps: @@ -180,6 +189,24 @@ jobs: with: board_id: m5stack-cores3 arch: esp32s3 + m5stack-stickc-plus: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: m5stack-stickc-plus + arch: esp32 + m5stack-stickc-plus2: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: "Build" + uses: ./.github/actions/build-firmware + with: + board_id: m5stack-stickc-plus2 + arch: esp32 unphone: runs-on: ubuntu-latest steps: diff --git a/Boards/LilygoTdongleS3/CMakeLists.txt b/Boards/LilygoTdongleS3/CMakeLists.txt new file mode 100644 index 00000000..cebdb175 --- /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 ST7735 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/Boards/M5stackCore2/CMakeLists.txt b/Boards/M5stackCore2/CMakeLists.txt index a332ac41..8d28c585 100644 --- a/Boards/M5stackCore2/CMakeLists.txt +++ b/Boards/M5stackCore2/CMakeLists.txt @@ -3,5 +3,5 @@ file(GLOB_RECURSE SOURCE_FILES Source/*.c*) idf_component_register( SRCS ${SOURCE_FILES} INCLUDE_DIRS "Source" - REQUIRES Tactility esp_lvgl_port esp_lcd ILI934x FT6x36 driver vfs fatfs + REQUIRES Tactility esp_lvgl_port esp_lcd AXP192 ILI934x FT6x36 driver vfs fatfs ) diff --git a/Boards/M5stackCore2/Source/InitBoot.cpp b/Boards/M5stackCore2/Source/InitBoot.cpp deleted file mode 100644 index a4e7871c..00000000 --- a/Boards/M5stackCore2/Source/InitBoot.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "axp192/axp192.h" - -#include -#include -#include - -#include -#include - -constexpr auto* TAG = "Core2"; - -axp192_t axpDevice; - -static int32_t axpI2cRead(TT_UNUSED void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size) { - if (tt::hal::i2c::masterReadRegister(I2C_NUM_0, address, reg, buffer, size, 50 / portTICK_PERIOD_MS)) { - return AXP192_OK; - } else { - return 1; - } -} - -static int32_t axpI2cWrite(TT_UNUSED void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size) { - if (tt::hal::i2c::masterWriteRegister(I2C_NUM_0, address, reg, buffer, size, 50 / portTICK_PERIOD_MS)) { - return AXP192_OK; - } else { - return 1; - } -} - -void initAxp() { - axpDevice.read = axpI2cRead; - axpDevice.write = axpI2cWrite; - - axp192_ioctl(&axpDevice, AXP192_LDO2_SET_VOLTAGE, 3300); // LCD + SD - axp192_ioctl(&axpDevice, AXP192_LDO3_SET_VOLTAGE, 0); // VIB_MOTOR STOP - axp192_ioctl(&axpDevice, AXP192_DCDC3_SET_VOLTAGE, 3300); - - axp192_ioctl(&axpDevice, AXP192_LDO2_ENABLE); - axp192_ioctl(&axpDevice, AXP192_LDO3_DISABLE); - axp192_ioctl(&axpDevice, AXP192_DCDC3_ENABLE); - - axp192_write(&axpDevice, AXP192_PWM1_DUTY_CYCLE_2, 255); // PWM 255 (LED OFF) - axp192_write(&axpDevice, AXP192_GPIO1_CONTROL, 0x02); // GPIO1 PWM - // TODO: We could charge at 390mA according to the M5Unified code, but the AXP driver in M5Unified limits to 132mA, so it's unclear what the AXP supports. -} - -bool initBoot() { - TT_LOG_I(TAG, "initBoot"); - initAxp(); - return true; -} \ No newline at end of file diff --git a/Boards/M5stackCore2/Source/InitBoot.h b/Boards/M5stackCore2/Source/InitBoot.h deleted file mode 100644 index f3e5bf89..00000000 --- a/Boards/M5stackCore2/Source/InitBoot.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -bool initBoot(); diff --git a/Boards/M5stackCore2/Source/M5stackCore2.cpp b/Boards/M5stackCore2/Source/M5stackCore2.cpp index 0f187e77..86e1e20a 100644 --- a/Boards/M5stackCore2/Source/M5stackCore2.cpp +++ b/Boards/M5stackCore2/Source/M5stackCore2.cpp @@ -1,8 +1,7 @@ #include "M5stackCore2.h" -#include "InitBoot.h" #include "devices/Display.h" -#include "devices/Core2Power.h" #include "devices/SdCard.h" +#include "devices/Power.h" #include #include @@ -11,9 +10,16 @@ using namespace tt::hal; +constexpr auto* TAG = "Core2"; + +bool initBoot() { + TT_LOG_I(TAG, "initBoot"); + return initAxp(); +} + static DeviceVector createDevices() { return { - createPower(), + getAxp192(), createSdCard(), createDisplay() }; @@ -41,7 +47,7 @@ extern const Configuration m5stack_core2 = { } }, i2c::Configuration { - .name = "External", // (Grove) + .name = "Port A", // Grove .port = I2C_NUM_1, .initMode = i2c::InitMode::ByTactility, .isMutable = true, @@ -85,7 +91,7 @@ extern const Configuration m5stack_core2 = { }, .uart { uart::Configuration { - .name = "Grove", + .name = "Port A", // Grove .port = UART_NUM_1, .rxPin = GPIO_NUM_32, .txPin = GPIO_NUM_33, diff --git a/Boards/M5stackCore2/Source/devices/Core2Power.h b/Boards/M5stackCore2/Source/devices/Core2Power.h deleted file mode 100644 index c1e29b15..00000000 --- a/Boards/M5stackCore2/Source/devices/Core2Power.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "Tactility/hal/power/PowerDevice.h" -#include - -using tt::hal::power::PowerDevice; - -class Core2Power : public PowerDevice { - -public: - - Core2Power() = default; - ~Core2Power() override = default; - - std::string getName() const final { return "AXP192 Power"; } - std::string getDescription() const final { return "Power management via I2C"; } - - bool supportsMetric(MetricType type) const override; - bool getMetric(MetricType type, MetricData& data) override; - - bool supportsChargeControl() const override { return true; } - bool isAllowedToCharge() const override; - void setAllowedToCharge(bool canCharge) override; -}; - -std::shared_ptr createPower(); diff --git a/Boards/M5stackCore2/Source/devices/Power.cpp b/Boards/M5stackCore2/Source/devices/Power.cpp new file mode 100644 index 00000000..8140046f --- /dev/null +++ b/Boards/M5stackCore2/Source/devices/Power.cpp @@ -0,0 +1,35 @@ +#include + +static std::shared_ptr axp192 = nullptr; + +std::shared_ptr createAxp192() { + assert(axp192 == nullptr); + auto configuration = std::make_unique(I2C_NUM_0); + axp192 = std::make_shared(std::move(configuration)); + return axp192; +} + +std::shared_ptr getAxp192() { + assert(axp192 != nullptr); + return axp192; +} + +bool initAxp() { + const auto axp = createAxp192(); + return axp->init([](auto* driver) { + axp192_ioctl(driver, AXP192_LDO2_SET_VOLTAGE, 3300); // LCD + SD + axp192_ioctl(driver, AXP192_LDO3_SET_VOLTAGE, 0); // VIB_MOTOR STOP + axp192_ioctl(driver, AXP192_DCDC3_SET_VOLTAGE, 3300); + + axp192_ioctl(driver, AXP192_LDO2_ENABLE); + axp192_ioctl(driver, AXP192_LDO3_DISABLE); + axp192_ioctl(driver, AXP192_DCDC3_ENABLE); + + axp192_write(driver, AXP192_PWM1_DUTY_CYCLE_2, 255); // PWM 255 (LED OFF) + axp192_write(driver, AXP192_GPIO1_CONTROL, 0x02); // GPIO1 PWM + + // TODO: We could charge at 390mA according to the M5Unified code, but the AXP driver in M5Unified limits to 132mA, so it's unclear what the AXP supports. + return true; + }); + +} diff --git a/Boards/M5stackCore2/Source/devices/Power.h b/Boards/M5stackCore2/Source/devices/Power.h new file mode 100644 index 00000000..14a00889 --- /dev/null +++ b/Boards/M5stackCore2/Source/devices/Power.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +bool initAxp(); + +// Must call initAxp() first before this returns a non-nullptr response +std::shared_ptr getAxp192(); + diff --git a/Boards/M5stackStickCPlus/CMakeLists.txt b/Boards/M5stackStickCPlus/CMakeLists.txt new file mode 100644 index 00000000..b103142f --- /dev/null +++ b/Boards/M5stackStickCPlus/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 esp_lcd AXP192 ST7789 ButtonControl +) diff --git a/Boards/M5stackStickCPlus/README.md b/Boards/M5stackStickCPlus/README.md new file mode 100644 index 00000000..1d3e2e40 --- /dev/null +++ b/Boards/M5stackStickCPlus/README.md @@ -0,0 +1,5 @@ +# M5Stack StickC Plus + +Docs: + + - [M5Stack.com](https://docs.m5stack.com/en/core/m5stickc_plus) diff --git a/Boards/M5stackStickCPlus/Source/M5StackStickCPlus.cpp b/Boards/M5stackStickCPlus/Source/M5StackStickCPlus.cpp new file mode 100644 index 00000000..5f5ab6c4 --- /dev/null +++ b/Boards/M5stackStickCPlus/Source/M5StackStickCPlus.cpp @@ -0,0 +1,123 @@ +#include "M5StackStickCPlus.h" +#include "devices/Display.h" +#include "devices/Power.h" +#include + +#include + +#define SPI_TRANSFER_SIZE_LIMIT (LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8) + +using namespace tt::hal; + +constexpr auto* TAG = "StickCPlus"; + +bool initBoot() { + TT_LOG_I(TAG, "initBoot"); + + // CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity + // Setting output to high adds a bias of 3.3 V and suppresses over-voltage: + gpio::configure(0, gpio::Mode::Output, false, false); + gpio::setLevel(0, true); + + return initAxp(); +} + +static DeviceVector createDevices() { + return { + getAxp192(), + ButtonControl::createTwoButtonControl(37, 39), + createDisplay() + }; +} + +extern const Configuration m5stack_stickc_plus = { + .initBoot = initBoot, + .uiScale = UiScale::Smallest, + .createDevices = createDevices, + .i2c = { + i2c::Configuration { + .name = "Internal", + .port = I2C_NUM_0, + .initMode = i2c::InitMode::ByTactility, + .isMutable = false, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_21, + .scl_io_num = GPIO_NUM_22, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + }, + i2c::Configuration { + .name = "Grove", + .port = I2C_NUM_1, + .initMode = i2c::InitMode::ByTactility, + .isMutable = true, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_32, + .scl_io_num = GPIO_NUM_33, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + } + }, + .spi { + spi::Configuration { + .device = SPI2_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_15, + .miso_io_num = GPIO_NUM_NC, + .sclk_io_num = GPIO_NUM_13, + .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 = 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 = "Grove", + .port = UART_NUM_1, + .rxPin = GPIO_NUM_32, + .txPin = GPIO_NUM_33, + .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/Boards/M5stackStickCPlus/Source/M5StackStickCPlus.h b/Boards/M5stackStickCPlus/Source/M5StackStickCPlus.h new file mode 100644 index 00000000..6a4d767e --- /dev/null +++ b/Boards/M5stackStickCPlus/Source/M5StackStickCPlus.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration m5stack_stickc_plus; diff --git a/Boards/M5stackStickCPlus/Source/devices/Display.cpp b/Boards/M5stackStickCPlus/Source/devices/Display.cpp new file mode 100644 index 00000000..82c45907 --- /dev/null +++ b/Boards/M5stackStickCPlus/Source/devices/Display.cpp @@ -0,0 +1,63 @@ +#include "Display.h" + +#include "Power.h" + +#include +#include + +constexpr auto* TAG = "StickCPlus"; + +static void setBacklightOn(bool on) { + const auto axp = getAxp192(); + const auto* driver = axp->getAxp192(); + uint8_t state; + if (axp192_read(driver, AXP192_DCDC13_LDO23_CONTROL, &state) != AXP192_OK) { + TT_LOG_I(TAG, "Failed to read LCD brightness state"); + return; + } + std::bitset<8> new_state = state; + if (new_state[2] != on) { + new_state[2] = on; + const auto new_state_long = new_state.to_ulong(); + axp192_write(driver, AXP192_DCDC13_LDO23_CONTROL, static_cast(new_state_long)); // Display on/off + } +} + +static void setBrightness(uint8_t brightness) { + const auto axp = getAxp192(); + if (brightness) + { + brightness = (((brightness >> 1) + 8) / 13) + 5; + setBacklightOn(true); + axp192_write(axp->getAxp192(), AXP192_LDO23_VOLTAGE, brightness << 4); // Display brightness + } + else + { + setBacklightOn(false); + } +} + +std::shared_ptr createDisplay() { + auto configuration = std::make_unique( + LCD_SPI_HOST, + LCD_PIN_CS, + LCD_PIN_DC, + LCD_HORIZONTAL_RESOLUTION, + LCD_VERTICAL_RESOLUTION, + nullptr, + false, + false, + false, + true, + LCD_DRAW_BUFFER_SIZE, + 52, + 40 + ); + + configuration->pixelClockFrequency = 40'000'000; + configuration->resetPin = LCD_PIN_RESET; + + configuration->backlightDutyFunction = setBrightness; + const auto display = std::make_shared(std::move(configuration)); + return std::reinterpret_pointer_cast(display); +} diff --git a/Boards/M5stackStickCPlus/Source/devices/Display.h b/Boards/M5stackStickCPlus/Source/devices/Display.h new file mode 100644 index 00000000..2cacfee8 --- /dev/null +++ b/Boards/M5stackStickCPlus/Source/devices/Display.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Tactility/hal/display/DisplayDevice.h" +#include + +#define LCD_SPI_HOST SPI2_HOST +#define LCD_PIN_CS GPIO_NUM_5 +#define LCD_PIN_DC GPIO_NUM_23 +#define LCD_PIN_RESET GPIO_NUM_18 +#define LCD_HORIZONTAL_RESOLUTION 135 +#define LCD_VERTICAL_RESOLUTION 240 +#define LCD_DRAW_BUFFER_HEIGHT (LCD_VERTICAL_RESOLUTION / 3) +#define LCD_DRAW_BUFFER_SIZE (LCD_HORIZONTAL_RESOLUTION * LCD_DRAW_BUFFER_HEIGHT) + +std::shared_ptr createDisplay(); diff --git a/Boards/M5stackStickCPlus/Source/devices/Power.cpp b/Boards/M5stackStickCPlus/Source/devices/Power.cpp new file mode 100644 index 00000000..40dc1137 --- /dev/null +++ b/Boards/M5stackStickCPlus/Source/devices/Power.cpp @@ -0,0 +1,38 @@ +#include + +static std::shared_ptr axp192 = nullptr; + +std::shared_ptr createAxp192() { + assert(axp192 == nullptr); + auto configuration = std::make_unique(I2C_NUM_0); + axp192 = std::make_shared(std::move(configuration)); + return axp192; +} + +std::shared_ptr getAxp192() { + assert(axp192 != nullptr); + return axp192; +} + +bool initAxp() { + const auto axp = createAxp192(); + return axp->init([](auto* driver) { + // Reference: https://github.com/pr3y/Bruce/blob/main/lib/utility/AXP192.cpp + axp192_ioctl(driver, AXP192_LDO23_VOLTAGE, 3300); // LCD backlight + axp192_ioctl(driver, AXP192_ADC_ENABLE_1, 0xff); // Set all ADC enabled + axp192_ioctl(driver, AXP192_CHARGE_CONTROL_1, 0xc0); // Battery charging at 4.2 V and 100 mA + axp192_ioctl(driver, AXP192_ADC_ENABLE_1, 0xff); // Enable battery, AC in, Vbus, APS ADC + uint8_t buffer; + axp192_read(driver, AXP192_DCDC13_LDO23_CONTROL, &buffer); + axp192_ioctl(driver, AXP192_DCDC13_LDO23_CONTROL, buffer | 0x4D); // Enable Ext, LDO2, LDO3, DCDC1 + axp192_ioctl(driver, AXP192_PEK, 0x0C); // 128 ms power on, 4 s power off + axp192_ioctl(driver, AXP192_GPIO0_LDOIO0_VOLTAGE, 0xF0); // 3.3 V RTC voltage + axp192_ioctl(driver, AXP192_GPIO0_CONTROL, 0x02); // Set GPIO0 to LDO + axp192_ioctl(driver, AXP192_VBUS_IPSOUT_CHANNEL, 0x80); // Disable Vbus hold limit + axp192_ioctl(driver, AXP192_BATTERY_CHARGE_HIGH_TEMP, 0xFC); // Set temperature protection + axp192_ioctl(driver, AXP192_BATTERY_CHARGE_CONTROL, 0xA2); // Enable RTC BAT charge + axp192_ioctl(driver, AXP192_SHUTDOWN_BATTERY_CHGLED_CONTROL, 0x46); // Enable bat detection + return true; + }); + +} diff --git a/Boards/M5stackStickCPlus/Source/devices/Power.h b/Boards/M5stackStickCPlus/Source/devices/Power.h new file mode 100644 index 00000000..14a00889 --- /dev/null +++ b/Boards/M5stackStickCPlus/Source/devices/Power.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +bool initAxp(); + +// Must call initAxp() first before this returns a non-nullptr response +std::shared_ptr getAxp192(); + diff --git a/Boards/M5stackStickCPlus2/CMakeLists.txt b/Boards/M5stackStickCPlus2/CMakeLists.txt new file mode 100644 index 00000000..ac0756d3 --- /dev/null +++ b/Boards/M5stackStickCPlus2/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 esp_lcd ST7789 PwmBacklight ButtonControl +) diff --git a/Boards/M5stackStickCPlus2/README.md b/Boards/M5stackStickCPlus2/README.md new file mode 100644 index 00000000..bcf12f13 --- /dev/null +++ b/Boards/M5stackStickCPlus2/README.md @@ -0,0 +1,5 @@ +# M5Stack StickC Plus2 + +Docs: + +- [M5Stack.com](https://docs.m5stack.com/en/core/M5StickC%20PLUS2) diff --git a/Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.cpp b/Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.cpp new file mode 100644 index 00000000..aa8cb0e0 --- /dev/null +++ b/Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.cpp @@ -0,0 +1,125 @@ +#include "M5StackStickCPlus2.h" +#include "devices/Display.h" + +#include +#include +#include + +#define SPI_TRANSFER_SIZE_LIMIT (LCD_DRAW_BUFFER_SIZE * LV_COLOR_DEPTH / 8) + +using namespace tt::hal; + +constexpr auto* TAG = "StickCPlus2"; + +bool initBoot() { + TT_LOG_I(TAG, "initBoot"); + + // CH552 applies 4 V to GPIO 0, which reduces Wi-Fi sensitivity + // Setting output to high adds a bias of 3.3 V and suppresses over-voltage: + gpio::configure(0, gpio::Mode::Output, false, false); + gpio::setLevel(0, true); + + // "Hold power" pin: must be set to high to keep the device powered on: + gpio::configure(4, gpio::Mode::Output, false, false); + gpio::setLevel(4, true); + return driver::pwmbacklight::init(GPIO_NUM_27, 512); +} + +static DeviceVector createDevices() { + return { + ButtonControl::createTwoButtonControl(37, 39), + createDisplay() + }; +} + +extern const Configuration m5stack_stickc_plus2 = { + .initBoot = initBoot, + .uiScale = UiScale::Smallest, + .createDevices = createDevices, + .i2c = { + i2c::Configuration { + .name = "Internal", + .port = I2C_NUM_0, + .initMode = i2c::InitMode::ByTactility, + .isMutable = false, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_21, + .scl_io_num = GPIO_NUM_22, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + }, + i2c::Configuration { + .name = "Grove", + .port = I2C_NUM_1, + .initMode = i2c::InitMode::ByTactility, + .isMutable = true, + .config = (i2c_config_t) { + .mode = I2C_MODE_MASTER, + .sda_io_num = GPIO_NUM_32, + .scl_io_num = GPIO_NUM_33, + .sda_pullup_en = true, + .scl_pullup_en = true, + .master = { + .clk_speed = 400000 + }, + .clk_flags = 0 + } + } + }, + .spi { + spi::Configuration { + .device = SPI2_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_15, + .miso_io_num = GPIO_NUM_NC, + .sclk_io_num = GPIO_NUM_13, + .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 = 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 = "Grove", + .port = UART_NUM_1, + .rxPin = GPIO_NUM_32, + .txPin = GPIO_NUM_33, + .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/Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.h b/Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.h new file mode 100644 index 00000000..df4950d0 --- /dev/null +++ b/Boards/M5stackStickCPlus2/Source/M5StackStickCPlus2.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const tt::hal::Configuration m5stack_stickc_plus2; diff --git a/Boards/M5stackStickCPlus2/Source/devices/Display.cpp b/Boards/M5stackStickCPlus2/Source/devices/Display.cpp new file mode 100644 index 00000000..f97b88c8 --- /dev/null +++ b/Boards/M5stackStickCPlus2/Source/devices/Display.cpp @@ -0,0 +1,29 @@ +#include "Display.h" + +#include +#include + +std::shared_ptr createDisplay() { + auto configuration = std::make_unique( + LCD_SPI_HOST, + LCD_PIN_CS, + LCD_PIN_DC, + LCD_HORIZONTAL_RESOLUTION, + LCD_VERTICAL_RESOLUTION, + nullptr, + false, + false, + false, + true, + LCD_DRAW_BUFFER_SIZE, + 52, + 40 + ); + + configuration->pixelClockFrequency = 40'000'000; + configuration->resetPin = LCD_PIN_RESET; + configuration->backlightDutyFunction = driver::pwmbacklight::setBacklightDuty; + + const auto display = std::make_shared(std::move(configuration)); + return std::reinterpret_pointer_cast(display); +} diff --git a/Boards/M5stackStickCPlus2/Source/devices/Display.h b/Boards/M5stackStickCPlus2/Source/devices/Display.h new file mode 100644 index 00000000..87c666dc --- /dev/null +++ b/Boards/M5stackStickCPlus2/Source/devices/Display.h @@ -0,0 +1,15 @@ +#pragma once + +#include "Tactility/hal/display/DisplayDevice.h" +#include + +#define LCD_SPI_HOST SPI2_HOST +#define LCD_PIN_CS GPIO_NUM_5 +#define LCD_PIN_DC GPIO_NUM_14 +#define LCD_PIN_RESET GPIO_NUM_12 +#define LCD_HORIZONTAL_RESOLUTION 135 +#define LCD_VERTICAL_RESOLUTION 240 +#define LCD_DRAW_BUFFER_HEIGHT (LCD_VERTICAL_RESOLUTION / 3) +#define LCD_DRAW_BUFFER_SIZE (LCD_HORIZONTAL_RESOLUTION * LCD_DRAW_BUFFER_HEIGHT) + +std::shared_ptr createDisplay(); diff --git a/Buildscripts/board.cmake b/Buildscripts/board.cmake index e2c340fb..d9961b2e 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") @@ -59,6 +61,10 @@ function(INIT_TACTILITY_GLOBALS SDKCONFIG_FILE) set(TACTILITY_BOARD_PROJECT M5stackCore2) elseif (board_id STREQUAL "m5stack-cores3") set(TACTILITY_BOARD_PROJECT M5stackCoreS3) + elseif (board_id STREQUAL "m5stack-stickc-plus") + set(TACTILITY_BOARD_PROJECT M5stackStickCPlus) + elseif (board_id STREQUAL "m5stack-stickc-plus2") + set(TACTILITY_BOARD_PROJECT M5stackStickCPlus2) elseif (board_id STREQUAL "unphone") set(TACTILITY_BOARD_PROJECT UnPhone) elseif (board_id STREQUAL "waveshare-s3-touch-43") diff --git a/Buildscripts/build-and-release-all.sh b/Buildscripts/build-and-release-all.sh index b7609d63..37737cab 100755 --- a/Buildscripts/build-and-release-all.sh +++ b/Buildscripts/build-and-release-all.sh @@ -35,6 +35,9 @@ release elecrow-crowpanel-basic-50 build lilygo-tdeck release lilygo-tdeck +build lilygo-tdongle-s3 +release lilygo-tdongle-s3 + build lilygo-tlora-pager release lilygo-tlora-pager @@ -75,6 +78,12 @@ release m5stack-cardputer build m5stack-cores3 release m5stack-cores3 +build m5stack-stickc-plus +release m5stack-stickc-plus + +build m5stack-stickc-plus2 +release m5stack-stickc-plus2 + build waveshare-s3-touch-43 release waveshare-s3-touch-43 diff --git a/Drivers/AXP192/CMakeLists.txt b/Drivers/AXP192/CMakeLists.txt new file mode 100644 index 00000000..afcd06ed --- /dev/null +++ b/Drivers/AXP192/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "Include" + PRIV_INCLUDE_DIRS "Private" + REQUIRES Tactility +) diff --git a/Drivers/AXP192/Include/Axp192.h b/Drivers/AXP192/Include/Axp192.h new file mode 100644 index 00000000..c1c70b2b --- /dev/null +++ b/Drivers/AXP192/Include/Axp192.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include + +class Axp192 final : public tt::hal::power::PowerDevice { + + static int32_t i2cRead(void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size); + static int32_t i2cWrite(void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size); + +public: + + struct Configuration { + i2c_port_t port; + TickType_t readTimeout = 50 / portTICK_PERIOD_MS; + TickType_t writeTimeout = 50 / portTICK_PERIOD_MS; + }; + +private: + + std::unique_ptr configuration; + + axp192_t axpDevice = { + .read = i2cRead, + .write = i2cWrite, + .handle = this + }; + + bool isInitialized = false; + +public: + + explicit Axp192(std::unique_ptr configuration) : configuration(std::move(configuration)) {} + ~Axp192() override {} + + /** + * @warning Must call this function before device can operate! + * @param onInit + */ + bool init(const std::function& onInit) { + isInitialized = onInit(&axpDevice); + return isInitialized; + } + + axp192_t* getAxp192() { return &axpDevice; } + + std::string getName() const override { return "AXP192"; } + std::string getDescription() const override { return "AXP192 power management via I2C"; } + + bool supportsMetric(MetricType type) const override; + bool getMetric(MetricType type, MetricData& data) override; + + bool supportsChargeControl() const override { return true; } + bool isAllowedToCharge() const override; + void setAllowedToCharge(bool canCharge) override; +}; diff --git a/Boards/M5stackCore2/Source/axp192/axp192.h b/Drivers/AXP192/Include/axp192/axp192.h similarity index 100% rename from Boards/M5stackCore2/Source/axp192/axp192.h rename to Drivers/AXP192/Include/axp192/axp192.h diff --git a/Boards/M5stackCore2/Source/axp192/LICENSE b/Drivers/AXP192/Private/axp192/LICENSE similarity index 100% rename from Boards/M5stackCore2/Source/axp192/LICENSE rename to Drivers/AXP192/Private/axp192/LICENSE diff --git a/Boards/M5stackCore2/Source/axp192/README.md b/Drivers/AXP192/Private/axp192/README.md similarity index 100% rename from Boards/M5stackCore2/Source/axp192/README.md rename to Drivers/AXP192/Private/axp192/README.md diff --git a/Boards/M5stackCore2/Source/axp192/axp192_config.h b/Drivers/AXP192/Private/axp192/axp192_config.h similarity index 100% rename from Boards/M5stackCore2/Source/axp192/axp192_config.h rename to Drivers/AXP192/Private/axp192/axp192_config.h diff --git a/Drivers/AXP192/README.md b/Drivers/AXP192/README.md new file mode 100644 index 00000000..91aca586 --- /dev/null +++ b/Drivers/AXP192/README.md @@ -0,0 +1,5 @@ +# AXP192 + +Power management with I2C interface. Used on M5Stack Core2 and StickCPlus. + +It includes driver code from https://github.com/tuupola/axp192, Copyright (c) 2019-2021 Mika Tuupola, MIT License diff --git a/Boards/M5stackCore2/Source/devices/Core2Power.cpp b/Drivers/AXP192/Source/Axp192.cpp similarity index 74% rename from Boards/M5stackCore2/Source/devices/Core2Power.cpp rename to Drivers/AXP192/Source/Axp192.cpp index 3d5a5c3d..aa3a60b6 100644 --- a/Boards/M5stackCore2/Source/devices/Core2Power.cpp +++ b/Drivers/AXP192/Source/Axp192.cpp @@ -1,12 +1,30 @@ -#include "Core2Power.h" -#include -#include "axp192/axp192.h" +#include "Axp192.h" -constexpr auto TAG = "Core2Power"; +constexpr auto TAG = "Axp192Power"; -extern axp192_t axpDevice; +int32_t Axp192::i2cRead(void* handle, uint8_t address, uint8_t reg, uint8_t* buffer, uint16_t size) { + const auto* device = static_cast(handle); + if (tt::hal::i2c::masterReadRegister(device->configuration->port, address, reg, buffer, size, device->configuration->readTimeout)) { + return AXP192_OK; + } else { + return 1; + } +} + +int32_t Axp192::i2cWrite(void* handle, uint8_t address, uint8_t reg, const uint8_t* buffer, uint16_t size) { + const auto* device = static_cast(handle); + if (tt::hal::i2c::masterWriteRegister(device->configuration->port, address, reg, buffer, size, device->configuration->writeTimeout)) { + return AXP192_OK; + } else { + return 1; + } +} + +bool Axp192::supportsMetric(MetricType type) const { + if (!isInitialized) { + return false; + } -bool Core2Power::supportsMetric(MetricType type) const { switch (type) { using enum MetricType; case BatteryVoltage: @@ -18,7 +36,7 @@ bool Core2Power::supportsMetric(MetricType type) const { } } -bool Core2Power::getMetric(MetricType type, MetricData& data) { +bool Axp192::getMetric(MetricType type, MetricData& data) { switch (type) { using enum MetricType; case BatteryVoltage: { @@ -81,7 +99,7 @@ bool Core2Power::getMetric(MetricType type, MetricData& data) { } } -bool Core2Power::isAllowedToCharge() const { +bool Axp192::isAllowedToCharge() const { uint8_t buffer; if (axp192_read(&axpDevice, AXP192_CHARGE_CONTROL_1, &buffer) == ESP_OK) { return buffer & 0x80; @@ -90,19 +108,10 @@ bool Core2Power::isAllowedToCharge() const { } } -void Core2Power::setAllowedToCharge(bool canCharge) { +void Axp192::setAllowedToCharge(bool canCharge) { uint8_t buffer; if (axp192_read(&axpDevice, AXP192_CHARGE_CONTROL_1, &buffer) == ESP_OK) { buffer = (buffer & 0x7F) + (canCharge ? 0x80 : 0x00); axp192_write(&axpDevice, AXP192_CHARGE_CONTROL_1, buffer); } } - -static std::shared_ptr power; - -std::shared_ptr createPower() { - if (power == nullptr) { - power = std::make_shared(); - } - return power; -} diff --git a/Boards/M5stackCore2/Source/axp192/axp192.c b/Drivers/AXP192/Source/axp192/axp192.c similarity index 99% rename from Boards/M5stackCore2/Source/axp192/axp192.c rename to Drivers/AXP192/Source/axp192/axp192.c index 04a3f0d8..25790b54 100644 --- a/Boards/M5stackCore2/Source/axp192/axp192.c +++ b/Drivers/AXP192/Source/axp192/axp192.c @@ -35,8 +35,8 @@ Version: 0.6.0 #include #include -#include "axp192_config.h" -#include "axp192.h" +#include "axp192/axp192_config.h" +#include "axp192/axp192.h" static axp192_err_t read_coloumb_counter(const axp192_t *axp, float *buffer); static axp192_err_t read_battery_power(const axp192_t *axp, float *buffer); diff --git a/Drivers/ButtonControl/CMakeLists.txt b/Drivers/ButtonControl/CMakeLists.txt new file mode 100644 index 00000000..811725f2 --- /dev/null +++ b/Drivers/ButtonControl/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 driver +) diff --git a/Drivers/ButtonControl/README.md b/Drivers/ButtonControl/README.md new file mode 100644 index 00000000..dbe50b1b --- /dev/null +++ b/Drivers/ButtonControl/README.md @@ -0,0 +1,3 @@ +# Button Control + +A driver for navigating apps with 1 or more GPIO buttons. \ No newline at end of file diff --git a/Drivers/ButtonControl/Source/ButtonControl.cpp b/Drivers/ButtonControl/Source/ButtonControl.cpp new file mode 100644 index 00000000..b8e4a2a9 --- /dev/null +++ b/Drivers/ButtonControl/Source/ButtonControl.cpp @@ -0,0 +1,162 @@ +#include "ButtonControl.h" + +#include + +#include + +constexpr auto* TAG = "ButtonControl"; + +ButtonControl::ButtonControl(const std::vector& pinConfigurations) : pinConfigurations(pinConfigurations) { + pinStates.resize(pinConfigurations.size()); + for (const auto& pinConfiguration : pinConfigurations) { + tt::hal::gpio::configure(pinConfiguration.pin, tt::hal::gpio::Mode::Input, false, false); + } +} + +ButtonControl::~ButtonControl() { + if (driverThread != nullptr && driverThread->getState() != tt::Thread::State::Stopped) { + interruptDriverThread = true; + driverThread->join(); + } +} + +void ButtonControl::readCallback(lv_indev_t* indev, lv_indev_data_t* data) { + // Defaults + data->enc_diff = 0; + data->state = LV_INDEV_STATE_RELEASED; + + auto* self = static_cast(lv_indev_get_driver_data(indev)); + + if (self->mutex.lock(100)) { + + for (int i = 0; i < self->pinConfigurations.size(); i++) { + const auto& config = self->pinConfigurations[i]; + std::vector::reference state = self->pinStates[i]; + const bool trigger = (config.event == Event::ShortPress && state.triggerShortPress) || + (config.event == Event::LongPress && state.triggerLongPress); + state.triggerShortPress = false; + state.triggerLongPress = false; + if (trigger) { + switch (config.action) { + case Action::UiSelectNext: + data->enc_diff = 1; + break; + case Action::UiSelectPrevious: + data->enc_diff = -1; + break; + case Action::UiPressSelected: + data->state = LV_INDEV_STATE_PRESSED; + break; + case Action::AppClose: + // TODO: implement + break; + } + } + } + self->mutex.unlock(); + } +} + +void ButtonControl::updatePin(std::vector::const_reference configuration, std::vector::reference state) { + if (tt::hal::gpio::getLevel(configuration.pin)) { // if pressed + if (state.pressState) { + // check time for long press trigger + auto time_passed = tt::kernel::getMillis() - state.pressStartTime; + if (time_passed > 500) { + // state.triggerLongPress = true; + } + } else { + state.pressStartTime = tt::kernel::getMillis(); + state.pressState = true; + } + } else { // released + if (state.pressState) { + auto time_passed = tt::kernel::getMillis() - state.pressStartTime; + if (time_passed < 500) { + TT_LOG_D(TAG, "Trigger short press"); + state.triggerShortPress = true; + } + state.pressState = false; + } + } +} + +void ButtonControl::driverThreadMain() { + while (!shouldInterruptDriverThread()) { + if (mutex.lock(100)) { + for (int i = 0; i < pinConfigurations.size(); i++) { + updatePin(pinConfigurations[i], pinStates[i]); + } + mutex.unlock(); + } + tt::kernel::delayMillis(5); + } +} + +bool ButtonControl::shouldInterruptDriverThread() const { + bool interrupt = false; + if (mutex.lock(50 / portTICK_PERIOD_MS)) { + interrupt = interruptDriverThread; + mutex.unlock(); + } + return interrupt; +} + +void ButtonControl::startThread() { + TT_LOG_I(TAG, "Start"); + + mutex.lock(); + + interruptDriverThread = false; + + driverThread = std::make_shared("ButtonControl", 4096, [this] { + driverThreadMain(); + return 0; + }); + + driverThread->start(); + + mutex.unlock(); +} + +void ButtonControl::stopThread() { + TT_LOG_I(TAG, "Stop"); + + mutex.lock(); + interruptDriverThread = true; + mutex.unlock(); + + driverThread->join(); + + mutex.lock(); + driverThread = nullptr; + mutex.unlock(); +} + +bool ButtonControl::startLvgl(lv_display_t* display) { + if (deviceHandle != nullptr) { + return false; + } + + startThread(); + + deviceHandle = lv_indev_create(); + lv_indev_set_type(deviceHandle, LV_INDEV_TYPE_ENCODER); + lv_indev_set_driver_data(deviceHandle, this); + lv_indev_set_read_cb(deviceHandle, readCallback); + + return true; +} + +bool ButtonControl::stopLvgl() { + if (deviceHandle == nullptr) { + return false; + } + + lv_indev_delete(deviceHandle); + deviceHandle = nullptr; + + stopThread(); + + return true; +} diff --git a/Drivers/ButtonControl/Source/ButtonControl.h b/Drivers/ButtonControl/Source/ButtonControl.h new file mode 100644 index 00000000..60bd4231 --- /dev/null +++ b/Drivers/ButtonControl/Source/ButtonControl.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include + +class ButtonControl final : public tt::hal::encoder::EncoderDevice { + +public: + + enum class Event { + ShortPress, + LongPress + }; + + enum class Action { + UiSelectNext, + UiSelectPrevious, + UiPressSelected, + AppClose, + }; + + struct PinConfiguration { + tt::hal::gpio::Pin pin; + Event event; + Action action; + }; + +private: + + struct PinState { + long pressStartTime = 0; + long pressReleaseTime = 0; + bool pressState = false; + bool triggerShortPress = false; + bool triggerLongPress = false; + }; + + lv_indev_t* _Nullable deviceHandle = nullptr; + std::shared_ptr driverThread; + bool interruptDriverThread = false; + tt::Mutex mutex; + std::vector pinConfigurations; + std::vector pinStates; + + bool shouldInterruptDriverThread() const; + + static void updatePin(std::vector::const_reference value, std::vector::reference pin_state); + + void driverThreadMain(); + + static void readCallback(lv_indev_t* indev, lv_indev_data_t* data); + + void startThread(); + void stopThread(); + +public: + + explicit ButtonControl(const std::vector& pinConfigurations); + + ~ButtonControl() override; + + std::string getName() const override { return "ButtonControl"; } + std::string getDescription() const override { return "ButtonControl input driver"; } + + bool startLvgl(lv_display_t* display) override; + bool stopLvgl() override; + + lv_indev_t* _Nullable getLvglIndev() override { return deviceHandle; } + + static std::shared_ptr createOneButtonControl(tt::hal::gpio::Pin pin) { + return std::make_shared(std::vector { + PinConfiguration { + .pin = pin, + .event = Event::ShortPress, + .action = Action::UiSelectNext + }, + PinConfiguration { + .pin = pin, + .event = Event::LongPress, + .action = Action::UiPressSelected + } + }); + } + + static std::shared_ptr createTwoButtonControl(tt::hal::gpio::Pin primaryPin, tt::hal::gpio::Pin secondaryPin) { + return std::make_shared(std::vector { + PinConfiguration { + .pin = primaryPin, + .event = Event::ShortPress, + .action = Action::UiPressSelected + }, + PinConfiguration { + .pin = primaryPin, + .event = Event::LongPress, + .action = Action::AppClose + }, + PinConfiguration { + .pin = secondaryPin, + .event = Event::ShortPress, + .action = Action::UiSelectNext + }, + PinConfiguration { + .pin = secondaryPin, + .event = Event::LongPress, + .action = Action::UiSelectPrevious + } + }); + } +}; 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..caf41383 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 @@ -49,6 +51,10 @@ menu "Tactility App" bool "M5Stack Core2" config TT_BOARD_M5STACK_CORES3 bool "M5Stack CoreS3" + config TT_BOARD_M5STACK_STICKC_PLUS + bool "M5Stack StickC Plus" + config TT_BOARD_M5STACK_STICKC_PLUS2 + bool "M5Stack StickC Plus2" config TT_BOARD_UNPHONE bool "unPhone" config TT_BOARD_WAVESHARE_S3_TOUCH_43 diff --git a/Firmware/Source/Boards.h b/Firmware/Source/Boards.h index c7f4ff89..cc1e4e0f 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 @@ -50,6 +53,12 @@ #elif defined(CONFIG_TT_BOARD_M5STACK_CORES3) #include "M5stackCoreS3.h" #define TT_BOARD_HARDWARE &m5stack_cores3 +#elif defined(CONFIG_TT_BOARD_M5STACK_STICKC_PLUS) +#include "M5StackStickCPlus.h" +#define TT_BOARD_HARDWARE &m5stack_stickc_plus +#elif defined(CONFIG_TT_BOARD_M5STACK_STICKC_PLUS2) +#include "M5StackStickCPlus2.h" +#define TT_BOARD_HARDWARE &m5stack_stickc_plus2 #elif defined(CONFIG_TT_BOARD_UNPHONE) #include "UnPhone.h" #define TT_BOARD_HARDWARE &unPhone 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..81e390f0 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); @@ -214,7 +221,9 @@ static void registerAndStartSecondaryServices() { static void registerAndStartPrimaryServices() { TT_LOG_I(TAG, "Registering and starting system services"); addService(service::gps::manifest); - addService(service::sdcard::manifest); + if (hal::hasDevice(hal::Device::Type::SdCard)) { + addService(service::sdcard::manifest); + } addService(service::wifi::manifest); #ifdef ESP_PLATFORM addService(service::development::manifest); @@ -222,13 +231,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/ElfApp.cpp b/Tactility/Source/app/ElfApp.cpp index 2fc629fa..927aedd3 100644 --- a/Tactility/Source/app/ElfApp.cpp +++ b/Tactility/Source/app/ElfApp.cpp @@ -76,7 +76,7 @@ private: assert(elfFileData == nullptr); size_t size = 0; - file::withLock(elf_path, [this, &elf_path, &size]{ + file::getLock(elf_path)->withLock([this, &elf_path, &size]{ elfFileData = file::readBinary(elf_path, size); }); 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/app/launcher/Launcher.cpp b/Tactility/Source/app/launcher/Launcher.cpp index 6eba6729..e6ef1ebf 100644 --- a/Tactility/Source/app/launcher/Launcher.cpp +++ b/Tactility/Source/app/launcher/Launcher.cpp @@ -24,12 +24,17 @@ static int getButtonSize(hal::UiScale scale) { class LauncherApp final : public App { - static lv_obj_t* createAppButton(lv_obj_t* parent, hal::UiScale uiScale, const char* imageFile, const char* appId, int32_t horizontalMargin) { + static lv_obj_t* createAppButton(lv_obj_t* parent, hal::UiScale uiScale, const char* imageFile, const char* appId, int32_t itemMargin, bool isLandscape) { auto button_size = getButtonSize(uiScale); auto* apps_button = lv_button_create(parent); lv_obj_set_style_pad_all(apps_button, 0, LV_STATE_DEFAULT); - lv_obj_set_style_margin_hor(apps_button, horizontalMargin, LV_STATE_DEFAULT); + if (isLandscape) { + lv_obj_set_style_margin_hor(apps_button, itemMargin, LV_STATE_DEFAULT); + } else { + lv_obj_set_style_margin_ver(apps_button, itemMargin, LV_STATE_DEFAULT); + } + lv_obj_set_style_shadow_width(apps_button, 0, LV_STATE_DEFAULT); lv_obj_set_style_bg_opa(apps_button, 0, LV_STATE_DEFAULT); @@ -110,17 +115,23 @@ public: lv_obj_set_flex_flow(buttons_wrapper, LV_FLEX_FLOW_COLUMN); } - const int32_t available_width = lv_display_get_horizontal_resolution(display) - (3 * button_size); - const int32_t margin = is_landscape_display ? std::min(available_width / 16, button_size) : 0; + int32_t margin; + if (is_landscape_display) { + const int32_t available_width = std::max(0, lv_display_get_horizontal_resolution(display) - (3 * button_size)); + margin = std::min(available_width / 16, button_size); + } else { + const int32_t available_height = std::max(0, lv_display_get_vertical_resolution(display) - (3 * button_size)); + margin = std::min(available_height / 16, button_size); + } const auto paths = app.getPaths(); const auto apps_icon_path = lvgl::PATH_PREFIX + paths->getAssetsPath("icon_apps.png"); const auto files_icon_path = lvgl::PATH_PREFIX + paths->getAssetsPath("icon_files.png"); const auto settings_icon_path = lvgl::PATH_PREFIX + paths->getAssetsPath("icon_settings.png"); - createAppButton(buttons_wrapper, ui_scale, apps_icon_path.c_str(), "AppList", margin); - createAppButton(buttons_wrapper, ui_scale, files_icon_path.c_str(), "Files", margin); - createAppButton(buttons_wrapper, ui_scale, settings_icon_path.c_str(), "Settings", margin); + createAppButton(buttons_wrapper, ui_scale, apps_icon_path.c_str(), "AppList", margin, is_landscape_display); + createAppButton(buttons_wrapper, ui_scale, files_icon_path.c_str(), "Files", margin, is_landscape_display); + createAppButton(buttons_wrapper, ui_scale, settings_icon_path.c_str(), "Settings", margin, is_landscape_display); if (shouldShowPowerButton()) { auto* power_button = lv_btn_create(parent); diff --git a/Tactility/Source/app/notes/Notes.cpp b/Tactility/Source/app/notes/Notes.cpp index 2a6bef17..f40f0679 100644 --- a/Tactility/Source/app/notes/Notes.cpp +++ b/Tactility/Source/app/notes/Notes.cpp @@ -83,7 +83,7 @@ class NotesApp final : public App { void openFile(const std::string& path) { // We might be reading from the SD card, which could share a SPI bus with other devices (display) - file::withLock(path, [this, path] { + file::getLock(path)->withLock([this, path] { auto data = file::readString(path); if (data != nullptr) { auto lock = lvgl::getSyncLock()->asScopedLock(); @@ -98,15 +98,15 @@ class NotesApp final : public App { bool saveFile(const std::string& path) { // We might be writing to SD card, which could share a SPI bus with other devices (display) - return file::withLock(path, [this, path] { + bool result = false; + file::getLock(path)->withLock([&result, this, path] { if (file::writeString(path, saveBuffer.c_str())) { TT_LOG_I(TAG, "Saved to %s", path.c_str()); filePath = path; - return true; - } else { - return false; + result = true; } }); + return result; } #pragma endregion Open_Events_Functions diff --git a/Tactility/Source/file/PropertiesFile.cpp b/Tactility/Source/file/PropertiesFile.cpp index 7b908358..5335a8be 100644 --- a/Tactility/Source/file/PropertiesFile.cpp +++ b/Tactility/Source/file/PropertiesFile.cpp @@ -26,7 +26,7 @@ bool loadPropertiesFile(const std::string& filePath, std::function& properties) { - return file::withLock(filePath, [filePath, &properties] { + bool result = false; + getLock(filePath)->withLock([&result, filePath, &properties] { TT_LOG_I(TAG, "Saving properties file %s", filePath.c_str()); FILE* file = fopen(filePath.c_str(), "w"); if (file == nullptr) { TT_LOG_E(TAG, "Failed to open %s", filePath.c_str()); - return false; + return; } for (const auto& [key, value]: properties) { fprintf(file, "%s=%s\n", key.c_str(), value.c_str()); } fclose(file); - return true; + result = true; }); + return result; } } 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/Tactility/Source/lvgl/LabelUtils.cpp b/Tactility/Source/lvgl/LabelUtils.cpp index 0a9965f1..9f6ffd82 100644 --- a/Tactility/Source/lvgl/LabelUtils.cpp +++ b/Tactility/Source/lvgl/LabelUtils.cpp @@ -7,8 +7,9 @@ namespace tt::lvgl { constexpr auto* TAG = "LabelUtils"; bool label_set_text_file(lv_obj_t* label, const char* filepath) { - auto text = file::withLock>(std::string(filepath), [filepath] { - return file::readString(filepath); + std::unique_ptr text; + file::getLock(filepath)->withLock([&text, filepath] { + text = file::readString(filepath); }); if (text != nullptr) { diff --git a/TactilityC/Source/tt_init.cpp b/TactilityC/Source/tt_init.cpp index 6f06c2c0..2dc82920 100644 --- a/TactilityC/Source/tt_init.cpp +++ b/TactilityC/Source/tt_init.cpp @@ -52,6 +52,8 @@ extern "C" { +extern double __floatsidf(int x); + const esp_elfsym main_symbols[] { // stdlib.h ESP_ELFSYM_EXPORT(malloc), @@ -66,6 +68,8 @@ const esp_elfsym main_symbols[] { // esp random ESP_ELFSYM_EXPORT(esp_random), ESP_ELFSYM_EXPORT(esp_fill_random), + // esp other + ESP_ELFSYM_EXPORT(__floatsidf), // unistd.h ESP_ELFSYM_EXPORT(usleep), ESP_ELFSYM_EXPORT(sleep), 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/TactilityCore/Include/Tactility/file/File.h b/TactilityCore/Include/Tactility/file/File.h index e4e26001..a2b45002 100644 --- a/TactilityCore/Include/Tactility/file/File.h +++ b/TactilityCore/Include/Tactility/file/File.h @@ -54,18 +54,6 @@ std::shared_ptr getLock(const std::string& path); void setFindLockFunction(const FindLockFunction& function); -/** - * Acquires a lock, calls the function, then releases the lock. - * @param[in] path the path to find a lock for - * @param[in] fn the code to execute while the lock is acquired - */ -template -ReturnType withLock(const std::string& path, std::function fn) { - const auto lock = getLock(path)->asScopedLock(); - lock.lock(); - return fn(); -} - long getSize(FILE* file); /** Read a file and return its data. 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 diff --git a/sdkconfig.board.m5stack-stickc-plus b/sdkconfig.board.m5stack-stickc-plus new file mode 100644 index 00000000..0384c380 --- /dev/null +++ b/sdkconfig.board.m5stack-stickc-plus @@ -0,0 +1,54 @@ +# 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-4mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-4mb.csv" +CONFIG_TT_BOARD_M5STACK_STICKC_PLUS=y +CONFIG_TT_BOARD_NAME="M5Stack StickC Plus" +CONFIG_TT_BOARD_ID="m5stack-stickc-plus" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_FLASHMODE_QIO=y +# LVGL +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 +CONFIG_LV_DPI_DEF=241 +CONFIG_LV_THEME_DEFAULT_DARK=y +# Fix for IRAM +CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y +CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y diff --git a/sdkconfig.board.m5stack-stickc-plus2 b/sdkconfig.board.m5stack-stickc-plus2 new file mode 100644 index 00000000..9f7bbb16 --- /dev/null +++ b/sdkconfig.board.m5stack-stickc-plus2 @@ -0,0 +1,60 @@ +# 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-8mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-8mb.csv" +CONFIG_TT_BOARD_M5STACK_STICKC_PLUS2=y +CONFIG_TT_BOARD_NAME="M5Stack StickC Plus2" +CONFIG_TT_BOARD_ID="m5stack-stickc-plus2" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +CONFIG_FLASHMODE_QIO=y +# Hardware: SPI RAM +CONFIG_ESP32_SPIRAM_SUPPORT=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_QUAD=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +# LVGL +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 +CONFIG_LV_DPI_DEF=241 +CONFIG_LV_THEME_DEFAULT_DARK=y +# Fix for IRAM +CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH=y +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH=y +CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH=y