Board Support: Heltec v3 (#407)

This commit is contained in:
NellowTCS 2025-11-05 01:04:40 -07:00 committed by GitHub
parent 0d8c0a37cc
commit 8335611796
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 527 additions and 0 deletions

View File

@ -31,6 +31,7 @@ jobs:
{ id: elecrow-crowpanel-basic-28, arch: esp32 }, { id: elecrow-crowpanel-basic-28, arch: esp32 },
{ id: elecrow-crowpanel-basic-35, arch: esp32 }, { id: elecrow-crowpanel-basic-35, arch: esp32 },
{ id: elecrow-crowpanel-basic-50, arch: esp32s3 }, { id: elecrow-crowpanel-basic-50, arch: esp32s3 },
{ id: heltec-wifi-lora-32-v3, arch: esp32s3 },
{ id: lilygo-tdeck, arch: esp32s3 }, { id: lilygo-tdeck, arch: esp32s3 },
{ id: lilygo-tdongle-s3, arch: esp32s3 }, { id: lilygo-tdongle-s3, arch: esp32s3 },
{ id: lilygo-tdisplay-s3, arch: esp32s3 }, { id: lilygo-tdisplay-s3, arch: esp32s3 },

View File

@ -0,0 +1,7 @@
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source"
REQUIRES Tactility EspLcdCompat SSD1306 ButtonControl EstimatedPower driver
)

View File

@ -0,0 +1,71 @@
#include "devices/Display.h"
#include "devices/Power.h"
#include "devices/Constants.h"
#include <Tactility/hal/Configuration.h>
#include <Tactility/lvgl/LvglSync.h>
#include <ButtonControl.h>
#include "driver/gpio.h"
#include "driver/i2c.h"
#include <Tactility/Log.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
static void enableOledPower() {
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << DISPLAY_PIN_POWER),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE, // The board has an external pull-up
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(DISPLAY_PIN_POWER, 0); // Active low
vTaskDelay(pdMS_TO_TICKS(500)); // Add a small delay for power to stabilize
TT_LOG_I("OLED_POWER", "OLED power enabled");
}
static bool initBoot() {
// Enable power to the OLED before doing anything else
enableOledPower();
return true;
}
using namespace tt::hal;
static std::vector<std::shared_ptr<Device>> createDevices() {
return {
createPower(),
ButtonControl::createOneButtonControl(0),
createDisplay()
};
}
extern const Configuration hardwareConfiguration = {
.initBoot = initBoot,
.uiScale = UiScale::Smallest,
.createDevices = createDevices,
.i2c = {
tt::hal::i2c::Configuration {
.name = "Internal",
.port = DISPLAY_I2C_PORT,
.initMode = tt::hal::i2c::InitMode::ByTactility,
.isMutable = true,
.config = (i2c_config_t) {
.mode = I2C_MODE_MASTER,
.sda_io_num = DISPLAY_PIN_SDA,
.scl_io_num = DISPLAY_PIN_SCL,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master = {
.clk_speed = DISPLAY_I2C_SPEED
},
.clk_flags = 0
}
}
},
.spi {},
};

View File

@ -0,0 +1,16 @@
#pragma once
#include <driver/gpio.h>
#include <driver/i2c.h>
#include <driver/spi_common.h>
// Display
constexpr auto DISPLAY_I2C_ADDRESS = 0x3C;
constexpr auto DISPLAY_I2C_SPEED = 200000;
constexpr auto DISPLAY_I2C_PORT = I2C_NUM_0;
constexpr auto DISPLAY_PIN_SDA = GPIO_NUM_17;
constexpr auto DISPLAY_PIN_SCL = GPIO_NUM_18;
constexpr auto DISPLAY_PIN_RST = GPIO_NUM_21;
constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 128;
constexpr auto DISPLAY_VERTICAL_RESOLUTION = 64;
constexpr auto DISPLAY_PIN_POWER = GPIO_NUM_36;

View File

@ -0,0 +1,19 @@
#include "Display.h"
#include "Constants.h"
#include <Ssd1306Display.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay() {
auto configuration = std::make_unique<Ssd1306Display::Configuration>(
DISPLAY_I2C_PORT,
DISPLAY_I2C_ADDRESS,
DISPLAY_PIN_RST,
DISPLAY_HORIZONTAL_RESOLUTION,
DISPLAY_VERTICAL_RESOLUTION,
nullptr, // no touch
false // invert
);
auto display = std::make_shared<Ssd1306Display>(std::move(configuration));
return std::static_pointer_cast<tt::hal::display::DisplayDevice>(display);
}

View File

@ -0,0 +1,5 @@
#pragma once
#include <Tactility/hal/display/DisplayDevice.h>
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -0,0 +1,20 @@
#include "Power.h"
#include <ChargeFromAdcVoltage.h>
#include <EstimatedPower.h>
#include <Tactility/hal/gpio/Gpio.h>
#include <memory>
// ADC enable pin on GPIO37
constexpr auto ADC_CTRL = 37;
std::shared_ptr<tt::hal::power::PowerDevice> createPower() {
ChargeFromAdcVoltage::Configuration configuration;
// 2.0 ratio, but +0.11 added as display voltage sag compensation.
configuration.adcMultiplier = 2.11;
// Configure the ADC enable pin as an output
tt::hal::gpio::configure(ADC_CTRL, tt::hal::gpio::Mode::Output, false, false);
return std::make_shared<EstimatedPower>(configuration);
}

View File

@ -0,0 +1,7 @@
#pragma once
#include <memory>
#include <Tactility/hal/power/PowerDevice.h>
#include <driver/gpio.h>
std::shared_ptr<tt::hal::power::PowerDevice> createPower();

View File

@ -86,6 +86,12 @@ vendor=Elecrow
boardName=CrowPanel Basic 5" boardName=CrowPanel Basic 5"
incubating=false incubating=false
[heltec-wifi-lora-32-v3]
vendor=Heltec
boardName=v3
incubating=true
infoMessage=Due to the small size of the screen, the icons don't render properly.
[lilygo-tdeck] [lilygo-tdeck]
vendor=LilyGO vendor=LilyGO
boardName=T-Deck,T-Deck Plus boardName=T-Deck,T-Deck Plus

View File

@ -0,0 +1,5 @@
idf_component_register(
SRC_DIRS "Source"
INCLUDE_DIRS "Source"
REQUIRES Tactility driver EspLcdCompat esp_lcd lvgl
)

View File

@ -0,0 +1,3 @@
# SSD1306
A very custom ESP32 LVGL driver for SSD1306 displays.

View File

@ -0,0 +1,204 @@
#include "Ssd1306Display.h"
#include <Tactility/Log.h>
#include <esp_lcd_panel_commands.h>
#include <esp_lcd_panel_dev.h>
#include <esp_lcd_panel_ssd1306.h>
#include <esp_lvgl_port.h>
#include <esp_lcd_panel_ops.h>
#include <driver/i2c.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#define TAG "ssd1306_display"
// SSD1306 commands
#define SSD1306_CMD_SET_CHARGE_PUMP 0x8D
#define SSD1306_CMD_SET_SEGMENT_REMAP 0xA0
#define SSD1306_CMD_SET_COM_SCAN_DIR 0xC0
#define SSD1306_CMD_SET_COM_PIN_CFG 0xDA
#define SSD1306_CMD_SET_CONTRAST 0x81
#define SSD1306_CMD_SET_PRECHARGE 0xD9
#define SSD1306_CMD_SET_VCOMH_DESELECT 0xDB
#define SSD1306_CMD_DISPLAY_INVERT 0xA6
#define SSD1306_CMD_DISPLAY_ON 0xAF
#define SSD1306_CMD_SET_MEMORY_ADDR_MODE 0x20
#define SSD1306_CMD_SET_COLUMN_RANGE 0x21
#define SSD1306_CMD_SET_PAGE_RANGE 0x22
// Helper to send I2C commands directly
static bool ssd1306_i2c_send_cmd(i2c_port_t port, uint8_t addr, uint8_t cmd) {
uint8_t data[2] = {0x00, cmd}; // 0x00 = command mode
esp_err_t ret = i2c_master_write_to_device(port, addr, data, sizeof(data), pdMS_TO_TICKS(1000));
if (ret != ESP_OK) {
TT_LOG_E(TAG, "Failed to send command 0x%02X: %d", cmd, ret);
return false;
}
return true;
}
bool Ssd1306Display::createIoHandle(esp_lcd_panel_io_handle_t& ioHandle) {
const esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = configuration->deviceAddress,
.control_phase_bytes = 1,
.dc_bit_offset = 6,
.flags = {
.dc_low_on_data = false,
.disable_control_phase = false,
},
};
if (esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)configuration->port, &io_config, &ioHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create IO handle");
return false;
}
return true;
}
bool Ssd1306Display::createPanelHandle(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t& panelHandle) {
// Manual hardware reset with proper timing for Heltec V3
if (configuration->resetPin != GPIO_NUM_NC) {
gpio_config_t reset_gpio_config = {
.pin_bit_mask = 1ULL << configuration->resetPin,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&reset_gpio_config);
gpio_set_level(configuration->resetPin, 0);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(configuration->resetPin, 1);
vTaskDelay(pdMS_TO_TICKS(100));
}
// Create ESP-IDF panel (but don't call init - we'll do custom init)
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = GPIO_NUM_NC, // Already handled above
.color_space = ESP_LCD_COLOR_SPACE_MONOCHROME,
.bits_per_pixel = 1, // Must be 1 for monochrome
.flags = {
.reset_active_high = false,
},
.vendor_config = nullptr,
};
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)
esp_lcd_panel_ssd1306_config_t ssd1306_config = {
.height = static_cast<uint8_t>(configuration->verticalResolution),
};
panel_config.vendor_config = &ssd1306_config;
#endif
if (esp_lcd_new_panel_ssd1306(ioHandle, &panel_config, &panelHandle) != ESP_OK) {
TT_LOG_E(TAG, "Failed to create panel");
return false;
}
// Don't call esp_lcd_panel_init() - it doesn't configure correctly for Heltec V3!
// Instead, send our custom initialization sequence directly via I2C
auto port = configuration->port;
auto addr = configuration->deviceAddress;
TT_LOG_I(TAG, "Sending Heltec V3 custom init sequence");
// Display off while configuring
ssd1306_i2c_send_cmd(port, addr, 0xAE);
vTaskDelay(pdMS_TO_TICKS(10)); // Important: let display stabilize after turning off
// Set oscillator frequency (MUST come early in sequence)
ssd1306_i2c_send_cmd(port, addr, 0xD5);
ssd1306_i2c_send_cmd(port, addr, 0x80);
// Set multiplex ratio
ssd1306_i2c_send_cmd(port, addr, 0xA8);
ssd1306_i2c_send_cmd(port, addr, configuration->verticalResolution - 1);
// Set display offset
ssd1306_i2c_send_cmd(port, addr, 0xD3);
ssd1306_i2c_send_cmd(port, addr, 0x00);
// Set display start line
ssd1306_i2c_send_cmd(port, addr, 0x40);
// Enable charge pump (required for Heltec V3 - must be before memory mode)
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_SET_CHARGE_PUMP);
ssd1306_i2c_send_cmd(port, addr, 0x14); // Enable
// Horizontal addressing mode
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_SET_MEMORY_ADDR_MODE);
ssd1306_i2c_send_cmd(port, addr, 0x00);
// Segment remap (0xA1 for Heltec V3 orientation)
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_SET_SEGMENT_REMAP | 0x01);
// COM scan direction (0xC8 = reversed)
ssd1306_i2c_send_cmd(port, addr, 0xC8);
// COM pin configuration
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_SET_COM_PIN_CFG);
if (configuration->verticalResolution == 64) {
ssd1306_i2c_send_cmd(port, addr, 0x12); // Alternative COM pin config for 64-row displays
} else {
ssd1306_i2c_send_cmd(port, addr, 0x02); // Sequential COM pin config for 32-row displays
}
// Contrast (0xCF = bright, good for Heltec OLED)
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_SET_CONTRAST);
ssd1306_i2c_send_cmd(port, addr, 0xCF);
// Precharge period (0xF1 for Heltec OLED)
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_SET_PRECHARGE);
ssd1306_i2c_send_cmd(port, addr, 0xF1);
// VCOMH deselect level
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_SET_VCOMH_DESELECT);
ssd1306_i2c_send_cmd(port, addr, 0x40);
// Normal display mode (not inverse/all-on)
ssd1306_i2c_send_cmd(port, addr, 0xA6);
// Invert display (0xA7)
// This is what your old working driver did unconditionally
ssd1306_i2c_send_cmd(port, addr, 0xA7);
// Display ON
ssd1306_i2c_send_cmd(port, addr, SSD1306_CMD_DISPLAY_ON);
vTaskDelay(pdMS_TO_TICKS(100)); // Let display stabilize
TT_LOG_I(TAG, "Heltec V3 display initialized successfully");
return true;
}
lvgl_port_display_cfg_t Ssd1306Display::getLvglPortDisplayConfig(esp_lcd_panel_io_handle_t ioHandle, esp_lcd_panel_handle_t panelHandle) {
return {
.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 = true, // ESP-LVGL-port handles the conversion!
.rotation = {
.swap_xy = false,
.mirror_x = false,
.mirror_y = false,
},
.color_format = LV_COLOR_FORMAT_RGB565, // Use RGB565, monochrome flag makes it work!
.flags = {
.buff_dma = false,
.buff_spiram = false,
.sw_rotate = false,
.swap_bytes = false,
.full_refresh = true,
.direct_mode = false
}
};
}

View File

@ -0,0 +1,90 @@
#pragma once
#include <EspLcdDisplay.h>
#include <Tactility/hal/display/DisplayDevice.h>
#include <driver/gpio.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_types.h>
class Ssd1306Display final : public EspLcdDisplay {
public:
class Configuration {
public:
Configuration(
i2c_port_t port,
uint8_t deviceAddress,
gpio_num_t resetPin,
unsigned int horizontalResolution, // Typically 128
unsigned int verticalResolution, // 32 or 64
std::shared_ptr<tt::hal::touch::TouchDevice> touch = nullptr,
bool invertColor = false
) : port(port),
deviceAddress(deviceAddress),
resetPin(resetPin),
horizontalResolution(horizontalResolution),
verticalResolution(verticalResolution),
invertColor(invertColor),
touch(std::move(touch))
{}
i2c_port_t port;
uint8_t deviceAddress;
gpio_num_t resetPin = GPIO_NUM_NC;
unsigned int horizontalResolution;
unsigned int verticalResolution;
bool invertColor = false;
std::shared_ptr<tt::hal::touch::TouchDevice> touch;
uint32_t bufferSize = 0; // Size in pixel count. 0 means default (full screen / 8)
int gapX = 0; // Column offset
int gapY = 0; // Not used for SSD1306
};
private:
std::unique_ptr<Configuration> 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 Ssd1306Display(std::unique_ptr<Configuration> inConfiguration) :
EspLcdDisplay(nullptr),
configuration(std::move(inConfiguration))
{
assert(configuration != nullptr);
if (configuration->bufferSize == 0) {
// For monochrome displays, ESP-LVGL-PORT expects full pixel count
// It handles the monochrome conversion internally
configuration->bufferSize = configuration->horizontalResolution * configuration->verticalResolution;
}
}
std::string getName() const override { return "SSD1306"; }
std::string getDescription() const override { return "SSD1306 monochrome OLED display with ESP-LVGL-PORT monochrome support"; }
std::shared_ptr<tt::hal::touch::TouchDevice> _Nullable getTouchDevice() override { return configuration->touch; }
void setBacklightDuty(uint8_t backlightDuty) override {
// SSD1306 does not have backlight control
}
bool supportsBacklightDuty() const override { return false; }
void setGammaCurve(uint8_t index) override {
// SSD1306 does not support gamma curves
}
uint8_t getGammaCurveCount() const override { return 0; }
};
std::shared_ptr<tt::hal::display::DisplayDevice> createDisplay();

View File

@ -43,6 +43,8 @@ menu "Tactility App"
bool "Elecrow CrowPanel Basic 3.5" bool "Elecrow CrowPanel Basic 3.5"
config TT_BOARD_ELECROW_CROWPANEL_BASIC_50 config TT_BOARD_ELECROW_CROWPANEL_BASIC_50
bool "Elecrow CrowPanel Basic 5.0" bool "Elecrow CrowPanel Basic 5.0"
config TT_BOARD_HELTEC_V3
bool "Heltec v3"
config TT_BOARD_LILYGO_TDECK config TT_BOARD_LILYGO_TDECK
bool "LilyGo T-Deck" bool "LilyGo T-Deck"
config TT_BOARD_LILYGO_TDONGLE_S3 config TT_BOARD_LILYGO_TDONGLE_S3

View File

@ -38,10 +38,22 @@ class LauncherApp final : public App {
lv_obj_set_style_shadow_width(apps_button, 0, 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); lv_obj_set_style_bg_opa(apps_button, 0, LV_STATE_DEFAULT);
// create the image first
auto* button_image = lv_image_create(apps_button); auto* button_image = lv_image_create(apps_button);
lv_image_set_src(button_image, imageFile); lv_image_set_src(button_image, imageFile);
// Recolor handling:
// For color builds use theme primary color
// For 1-bit/monochrome builds force a visible color (black)
#if LV_COLOR_DEPTH == 1
// Try forcing black recolor on monochrome builds
lv_obj_set_style_image_recolor(button_image, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
#else
lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT); lv_obj_set_style_image_recolor(button_image, lv_theme_get_color_primary(parent), LV_STATE_DEFAULT);
lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT); lv_obj_set_style_image_recolor_opa(button_image, LV_OPA_COVER, LV_STATE_DEFAULT);
#endif
// Ensure buttons are still tappable when the asset fails to load // Ensure buttons are still tappable when the asset fails to load
// Icon images are 40x40, so we get some extra padding too // Icon images are 40x40, so we get some extra padding too
lv_obj_set_size(button_image, button_size, button_size); lv_obj_set_size(button_image, button_size, button_size);

View File

@ -0,0 +1,59 @@
# Software defaults
# Increase stack size for WiFi (fixes crash after scan)
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
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
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
# 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_HELTEC_V3=y
CONFIG_TT_BOARD_NAME="Heltec v3"
CONFIG_TT_BOARD_ID="heltec-wifi-lora-32-v3"
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_8MB=y
CONFIG_FLASHMODE_QIO=y
# Hardware: No PSRAM
# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved)
CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
# LVGL
CONFIG_LV_DPI_DEF=90
CONFIG_LV_DISP_DEF_REFR_PERIOD=10
CONFIG_LV_THEME_DEFAULT_DARK=y
CONFIG_LV_COLOR_DEPTH=1
CONFIG_LV_USE_THEME_MONO=y
CONFIG_LV_LOG_LEVEL=LV_LOG_LEVEL_TRACE
# USB
CONFIG_TINYUSB_MSC_ENABLED=y
CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"