diff --git a/App/idf_component.yml b/App/idf_component.yml
index 06b8eb02..335109d9 100644
--- a/App/idf_component.yml
+++ b/App/idf_component.yml
@@ -4,4 +4,8 @@ dependencies:
espressif/esp_lcd_touch_gt911: "1.1.1~2"
espressif/esp_lcd_touch_ft5x06: "1.0.6~1"
espressif/esp_lcd_touch: "1.1.2"
+ espressif/esp_tinyusb:
+ version: "1.5.0"
+ rules:
+ - if: "target == esp32s3"
idf: '5.3.2'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2232815f..d26df98c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,13 +32,13 @@ if (DEFINED ENV{ESP_IDF_VERSION})
set(EXCLUDE_COMPONENTS "Simulator")
- # ESP32 boards
+ # Non-ESP32 boards
if(NOT "${IDF_TARGET}" STREQUAL "esp32")
set(EXCLUDE_COMPONENTS "YellowBoard")
set(EXCLUDE_COMPONENTS "M5stackCore2")
endif()
- # ESP32-S3 boards
+ # Non-ESP32-S3 boards
if(NOT "${IDF_TARGET}" STREQUAL "esp32s3")
set(EXCLUDE_COMPONENTS "LilygoTdeck")
set(EXCLUDE_COMPONENTS "M5stackCoreS3")
diff --git a/Data/assets/boot_logo_usb.png b/Data/assets/boot_logo_usb.png
new file mode 100644
index 00000000..ae3dd613
Binary files /dev/null and b/Data/assets/boot_logo_usb.png differ
diff --git a/Data/assets_sources/boot_logo_usb.svg b/Data/assets_sources/boot_logo_usb.svg
new file mode 100644
index 00000000..5be88072
--- /dev/null
+++ b/Data/assets_sources/boot_logo_usb.svg
@@ -0,0 +1,41 @@
+
+
diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp
index aaaaf295..087f2799 100644
--- a/Tactility/Source/Tactility.cpp
+++ b/Tactility/Source/Tactility.cpp
@@ -54,6 +54,7 @@ namespace app {
namespace settings { extern const AppManifest manifest; }
namespace systeminfo { extern const AppManifest manifest; }
namespace textviewer { extern const AppManifest manifest; }
+ namespace usbsettings { extern const AppManifest manifest; }
namespace wifiapsettings { extern const AppManifest manifest; }
namespace wificonnect { extern const AppManifest manifest; }
namespace wifimanage { extern const AppManifest manifest; }
@@ -86,6 +87,7 @@ static const std::vector system_apps = {
&app::selectiondialog::manifest,
&app::systeminfo::manifest,
&app::textviewer::manifest,
+ &app::usbsettings::manifest,
&app::wifiapsettings::manifest,
&app::wificonnect::manifest,
&app::wifimanage::manifest,
diff --git a/Tactility/Source/app/boot/Boot.cpp b/Tactility/Source/app/boot/Boot.cpp
index 5317f53b..1aaf8c43 100644
--- a/Tactility/Source/app/boot/Boot.cpp
+++ b/Tactility/Source/app/boot/Boot.cpp
@@ -9,6 +9,7 @@
#include "lvgl.h"
#include "Tactility.h"
+#include "hal/usb/Usb.h"
#ifdef ESP_PLATFORM
#include "kernel/PanicHandler.h"
@@ -31,29 +32,33 @@ struct Data {
};
static int32_t bootThreadCallback(TT_UNUSED void* context) {
- TickType_t start_time = tt::kernel::getTicks();
+ TickType_t start_time = kernel::getTicks();
auto* lvgl_display = lv_display_get_default();
tt_assert(lvgl_display != nullptr);
- auto* hal_display = (tt::hal::Display*)lv_display_get_user_data(lvgl_display);
+ auto* hal_display = (hal::Display*)lv_display_get_user_data(lvgl_display);
tt_assert(hal_display != nullptr);
if (hal_display->supportsBacklightDuty()) {
int32_t backlight_duty = app::display::getBacklightDuty();
hal_display->setBacklightDuty(backlight_duty);
}
- TickType_t end_time = tt::kernel::getTicks();
- TickType_t ticks_passed = end_time - start_time;
- TickType_t minimum_ticks = (CONFIG_TT_SPLASH_DURATION / portTICK_PERIOD_MS);
- if (minimum_ticks > ticks_passed) {
- tt::kernel::delayTicks(minimum_ticks - ticks_passed);
+ if (hal::usb::isUsbBootMode()) {
+ TT_LOG_I(TAG, "Rebooting into mass storage device mode");
+ hal::usb::resetUsbBootMode();
+ hal::usb::startMassStorageWithSdmmc();
+ } else {
+ TickType_t end_time = tt::kernel::getTicks();
+ TickType_t ticks_passed = end_time - start_time;
+ TickType_t minimum_ticks = (CONFIG_TT_SPLASH_DURATION / portTICK_PERIOD_MS);
+ if (minimum_ticks > ticks_passed) {
+ kernel::delayTicks(minimum_ticks - ticks_passed);
+ }
+
+ tt::service::loader::stopApp();
+ startNextApp();
}
- tt::service::loader::stopApp();
-
-
- startNextApp();
-
return 0;
}
@@ -84,7 +89,13 @@ static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) {
lv_obj_t* image = lv_image_create(parent);
lv_obj_set_size(image, LV_PCT(100), LV_PCT(100));
- lv_image_set_src(image, TT_ASSETS_BOOT_LOGO);
+
+ if (hal::usb::isUsbBootMode()) {
+ lv_image_set_src(image, TT_ASSETS_BOOT_LOGO_USB);
+ } else {
+ lv_image_set_src(image, TT_ASSETS_BOOT_LOGO);
+ }
+
lvgl::obj_set_style_bg_blacken(parent);
data->thread.start();
diff --git a/Tactility/Source/app/usbsettings/UsbSettings.cpp b/Tactility/Source/app/usbsettings/UsbSettings.cpp
new file mode 100644
index 00000000..fa59148c
--- /dev/null
+++ b/Tactility/Source/app/usbsettings/UsbSettings.cpp
@@ -0,0 +1,45 @@
+#include "lvgl.h"
+#include "lvgl/Toolbar.h"
+#include "hal/usb/Usb.h"
+
+#define TAG "usb_settings"
+
+namespace tt::app::usbsettings {
+
+static void onRebootMassStorage(TT_UNUSED lv_event_t* event) {
+ hal::usb::rebootIntoMassStorageSdmmc();
+}
+
+static void onShow(AppContext& app, lv_obj_t* parent) {
+ auto* toolbar = lvgl::toolbar_create(parent, app);
+ lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
+
+ if (hal::usb::canRebootIntoMassStorageSdmmc()) {
+ auto* button = lv_button_create(parent);
+ auto* label = lv_label_create(button);
+ lv_label_set_text(label, "Reboot as USB storage");
+ lv_obj_align(button, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_add_event_cb(button, onRebootMassStorage, LV_EVENT_SHORT_CLICKED, nullptr);
+ } else {
+ bool supported = hal::usb::isSupported();
+ const char* first = supported ? "USB storage not available:" : "USB driver not supported";
+ const char* second = supported ? "SD card not mounted" : "on this hardware";
+ auto* label_a = lv_label_create(parent);
+ lv_label_set_text(label_a, first);
+ lv_obj_align(label_a, LV_ALIGN_CENTER, 0, 0);
+ auto* label_b = lv_label_create(parent);
+ lv_label_set_text(label_b, second);
+ lv_obj_align_to(label_b, label_a, LV_ALIGN_OUT_BOTTOM_MID, 0, 4);
+ }
+
+}
+
+extern const AppManifest manifest = {
+ .id = "UsbSettings",
+ .name = "USB",
+ .icon = LV_SYMBOL_USB,
+ .type = TypeSettings,
+ .onShow = onShow
+};
+
+} // namespace
diff --git a/TactilityHeadless/CMakeLists.txt b/TactilityHeadless/CMakeLists.txt
index 0a092d7d..6ce8bcda 100644
--- a/TactilityHeadless/CMakeLists.txt
+++ b/TactilityHeadless/CMakeLists.txt
@@ -6,11 +6,16 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (DEFINED ENV{ESP_IDF_VERSION})
file(GLOB_RECURSE SOURCE_FILES Source/*.c*)
+ list(APPEND REQUIRES_LIST TactilityCore esp_wifi nvs_flash driver spiffs vfs fatfs )
+ if("${IDF_TARGET}" STREQUAL "esp32s3")
+ list(APPEND REQUIRES_LIST esp_tinyusb)
+ endif()
+
idf_component_register(
SRCS ${SOURCE_FILES}
INCLUDE_DIRS "Source/"
PRIV_INCLUDE_DIRS "Private/"
- REQUIRES TactilityCore esp_wifi nvs_flash driver spiffs vfs fatfs
+ REQUIRES ${REQUIRES_LIST}
)
if (NOT DEFINED TACTILITY_SKIP_SPIFFS)
diff --git a/TactilityHeadless/Source/Assets.h b/TactilityHeadless/Source/Assets.h
index 5cb52342..8c3e715a 100644
--- a/TactilityHeadless/Source/Assets.h
+++ b/TactilityHeadless/Source/Assets.h
@@ -5,6 +5,7 @@
// Splash
#define TT_ASSETS_BOOT_LOGO TT_ASSET("boot_logo.png")
+#define TT_ASSETS_BOOT_LOGO_USB TT_ASSET("boot_logo_usb.png")
// UI
#define TT_ASSETS_UI_SPINNER TT_ASSET("spinner.png")
diff --git a/TactilityHeadless/Source/hal/SpiSdCard.h b/TactilityHeadless/Source/hal/SpiSdCard.h
index eae1aaaa..c7067e18 100644
--- a/TactilityHeadless/Source/hal/SpiSdCard.h
+++ b/TactilityHeadless/Source/hal/SpiSdCard.h
@@ -73,6 +73,8 @@ public:
bool mount(const char* mountPath) override;
bool unmount() override;
State getState() const override;
+
+ sdmmc_card_t* _Nullable getCard() { return card; }
};
}
diff --git a/TactilityHeadless/Source/hal/usb/Usb.cpp b/TactilityHeadless/Source/hal/usb/Usb.cpp
new file mode 100644
index 00000000..f4edfcd4
--- /dev/null
+++ b/TactilityHeadless/Source/hal/usb/Usb.cpp
@@ -0,0 +1,108 @@
+#ifdef ESP_PLATFORM
+
+#include
+#include "Usb.h"
+#include "UsbTusb.h"
+#include "TactilityHeadless.h"
+#include "hal/SpiSdCard.h"
+
+namespace tt::hal::usb {
+
+#define TAG "usb"
+
+#define BOOT_FLAG 42
+
+struct BootMode {
+ uint32_t flag = 0;
+};
+
+static Mode currentMode = ModeDefault;
+static RTC_NOINIT_ATTR BootMode bootMode;
+
+sdmmc_card_t* _Nullable getCard() {
+ auto sdcard = getConfiguration().sdcard;
+ if (sdcard == nullptr) {
+ TT_LOG_W(TAG, "No SD card configuration found");
+ return nullptr;
+ }
+
+ if (!sdcard->isMounted()) {
+ TT_LOG_W(TAG, "SD card not mounted");
+ return nullptr;
+ }
+
+ auto spi_sdcard = std::static_pointer_cast(sdcard);
+ if (spi_sdcard == nullptr) {
+ TT_LOG_W(TAG, "SD card interface is not supported (must be SpiSdCard)");
+ return nullptr;
+ }
+
+ auto* card = spi_sdcard->getCard();
+ if (card == nullptr) {
+ TT_LOG_W(TAG, "SD card has no card object available");
+ return nullptr;
+ }
+
+ return card;
+}
+
+static bool canStartNewMode() {
+ return isSupported() && (currentMode == ModeDefault || currentMode == ModeNone);
+}
+
+bool isSupported() {
+ return tusbIsSupported();
+}
+
+bool startMassStorageWithSdmmc() {
+ if (!canStartNewMode()) {
+ TT_LOG_E(TAG, "Can't start");
+ return false;
+ }
+
+ auto result = tusbStartMassStorageWithSdmmc();
+ if (result != ESP_OK) {
+ TT_LOG_E(TAG, "Failed to init mass storage: %s", esp_err_to_name(result));
+ return false;
+ } else {
+ currentMode = ModeMassStorageSdmmc;
+ return true;
+ }
+}
+
+void stop() {
+ if (canStartNewMode()) {
+ return;
+ }
+
+ tusbStop();
+
+ currentMode = ModeNone;
+}
+
+Mode getMode() {
+ return currentMode;
+}
+
+bool canRebootIntoMassStorageSdmmc() {
+ return tusbIsSupported() && getCard() != nullptr;
+}
+
+void rebootIntoMassStorageSdmmc() {
+ if (tusbIsSupported()) {
+ bootMode.flag = BOOT_FLAG;
+ esp_restart();
+ }
+}
+
+bool isUsbBootMode() {
+ return bootMode.flag == BOOT_FLAG;
+}
+
+void resetUsbBootMode() {
+ bootMode.flag = 0;
+}
+
+}
+
+#endif
diff --git a/TactilityHeadless/Source/hal/usb/Usb.h b/TactilityHeadless/Source/hal/usb/Usb.h
new file mode 100644
index 00000000..aab33d5c
--- /dev/null
+++ b/TactilityHeadless/Source/hal/usb/Usb.h
@@ -0,0 +1,21 @@
+#pragma once
+
+namespace tt::hal::usb {
+
+enum Mode {
+ ModeDefault, // Default state of USB stack
+ ModeNone, // State after TinyUSB was used and (partially) deinitialized
+ ModeMassStorageSdmmc
+};
+
+bool startMassStorageWithSdmmc();
+void stop();
+Mode getMode();
+bool isSupported();
+
+bool canRebootIntoMassStorageSdmmc();
+void rebootIntoMassStorageSdmmc();
+bool isUsbBootMode();
+void resetUsbBootMode();
+
+}
\ No newline at end of file
diff --git a/TactilityHeadless/Source/hal/usb/UsbMock.cpp b/TactilityHeadless/Source/hal/usb/UsbMock.cpp
new file mode 100644
index 00000000..431520db
--- /dev/null
+++ b/TactilityHeadless/Source/hal/usb/UsbMock.cpp
@@ -0,0 +1,21 @@
+#ifndef ESP_PLATFORM
+
+#include "Usb.h"
+
+#define TAG "usb"
+
+namespace tt::hal::usb {
+
+bool startMassStorageWithSdmmc() { return false; }
+void stop() {}
+Mode getMode() { return ModeDefault; }
+bool isSupported() { return false; }
+
+bool canRebootIntoMassStorageSdmmc() { return false; }
+void rebootIntoMassStorageSdmmc() {}
+bool isUsbBootMode() { return false; }
+void resetUsbBootMode() {}
+
+}
+
+#endif
diff --git a/TactilityHeadless/Source/hal/usb/UsbTusb.cpp b/TactilityHeadless/Source/hal/usb/UsbTusb.cpp
new file mode 100644
index 00000000..368a6ec3
--- /dev/null
+++ b/TactilityHeadless/Source/hal/usb/UsbTusb.cpp
@@ -0,0 +1,167 @@
+#ifdef ESP_PLATFORM
+
+#include "UsbTusb.h"
+#include "sdkconfig.h"
+
+#if CONFIG_TINYUSB_MSC_ENABLED == 1
+
+#include "Log.h"
+#include "tinyusb.h"
+#include "tusb_msc_storage.h"
+
+#define TAG "usb"
+#define EPNUM_MSC 1
+#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN)
+
+namespace tt::hal::usb {
+ extern sdmmc_card_t* _Nullable getCard();
+}
+
+enum {
+ ITF_NUM_MSC = 0,
+ ITF_NUM_TOTAL
+};
+
+enum {
+ EDPT_CTRL_OUT = 0x00,
+ EDPT_CTRL_IN = 0x80,
+
+ EDPT_MSC_OUT = 0x01,
+ EDPT_MSC_IN = 0x81,
+};
+
+static bool driverInstalled = false;
+
+static tusb_desc_device_t descriptor_config = {
+ .bLength = sizeof(descriptor_config),
+ .bDescriptorType = TUSB_DESC_DEVICE,
+ .bcdUSB = 0x0200,
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+ .idVendor = 0x303A, // TODO: Espressif VID. Do we need to change this?
+ .idProduct = 0x4002,
+ .bcdDevice = 0x100,
+ .iManufacturer = 0x01,
+ .iProduct = 0x02,
+ .iSerialNumber = 0x03,
+ .bNumConfigurations = 0x01
+};
+
+static char const* string_desc_arr[] = {
+ (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
+ "Espressif", // 1: Manufacturer
+ "Tactility Device", // 2: Product
+ "42", // 3: Serials
+ "Tactility Mass Storage", // 4. MSC
+};
+
+static uint8_t const msc_fs_configuration_desc[] = {
+ // Config number, interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+ // Interface number, string index, EP Out & EP In address, EP size
+ TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64),
+};
+
+#if (TUD_OPT_HIGH_SPEED)
+ static const tusb_desc_device_qualifier_t device_qualifier = {
+ .bLength = sizeof(tusb_desc_device_qualifier_t),
+ .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
+ .bcdUSB = 0x0200,
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+ .bNumConfigurations = 0x01,
+ .bReserved = 0
+};
+
+static uint8_t const msc_hs_configuration_desc[] = {
+ // Config number, interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+ // Interface number, string index, EP Out & EP In address, EP size
+ TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 512),
+};
+#endif // TUD_OPT_HIGH_SPEED
+
+static void storage_mount_changed_cb(tinyusb_msc_event_t* event) {
+ if (event->mount_changed_data.is_mounted) {
+ TT_LOG_I(TAG, "Mounted");
+ } else {
+ TT_LOG_I(TAG, "Unmounted");
+ }
+}
+
+static bool ensureDriverInstalled() {
+ if (driverInstalled) {
+ return true;
+ }
+
+ const tinyusb_config_t tusb_cfg = {
+ .device_descriptor = &descriptor_config,
+ .string_descriptor = string_desc_arr,
+ .string_descriptor_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]),
+ .external_phy = false,
+#if (TUD_OPT_HIGH_SPEED)
+ .fs_configuration_descriptor = msc_fs_configuration_desc,
+ .hs_configuration_descriptor = msc_hs_configuration_desc,
+ .qualifier_descriptor = &device_qualifier,
+#else
+ .configuration_descriptor = msc_fs_configuration_desc,
+#endif // TUD_OPT_HIGH_SPEED
+ .self_powered = false,
+ .vbus_monitor_io = 0
+ };
+
+ if (tinyusb_driver_install(&tusb_cfg) != ESP_OK) {
+ TT_LOG_E(TAG, "Failed to install TinyUSB driver");
+ return false;
+ }
+
+ driverInstalled = true;
+ return true;
+}
+
+bool tusbIsSupported() { return true; }
+
+bool tusbStartMassStorageWithSdmmc() {
+ ensureDriverInstalled();
+
+ auto* card = tt::hal::usb::getCard();
+ if (card == nullptr) {
+ TT_LOG_E(TAG, "SD card not mounted");
+ return false;
+ }
+
+ const tinyusb_msc_sdmmc_config_t config_sdmmc = {
+ .card = card,
+ .callback_mount_changed = storage_mount_changed_cb,
+ .callback_premount_changed = nullptr,
+ .mount_config = {
+ .format_if_mount_failed = false,
+ .max_files = 5,
+ .allocation_unit_size = 0,
+ .disk_status_check_enable = false,
+ .use_one_fat = false
+ }
+ };
+
+ return tinyusb_msc_storage_init_sdmmc(&config_sdmmc) == ESP_OK;
+}
+
+void tusbStop() {
+ tinyusb_msc_storage_deinit();
+}
+
+#else
+
+bool tusbIsSupported() { return false; }
+bool tusbStartMassStorageWithSdmmc() { return false; }
+void tusbStop() {}
+
+#endif // TinyUSB enabled
+
+#endif // ESP_PLATFORM
diff --git a/TactilityHeadless/Source/hal/usb/UsbTusb.h b/TactilityHeadless/Source/hal/usb/UsbTusb.h
new file mode 100644
index 00000000..a9f9a3ee
--- /dev/null
+++ b/TactilityHeadless/Source/hal/usb/UsbTusb.h
@@ -0,0 +1,5 @@
+#pragma once
+
+bool tusbIsSupported();
+bool tusbStartMassStorageWithSdmmc();
+void tusbStop();
diff --git a/sdkconfig.board.lilygo-tdeck b/sdkconfig.board.lilygo-tdeck
index 41a00457..9ed497c8 100644
--- a/sdkconfig.board.lilygo-tdeck
+++ b/sdkconfig.board.lilygo-tdeck
@@ -24,6 +24,8 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y
CONFIG_FATFS_LFN_HEAP=y
+CONFIG_TINYUSB_MSC_ENABLED=y
+CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"
# Hardware: Main
CONFIG_TT_BOARD_LILYGO_TDECK=y
diff --git a/sdkconfig.board.m5stack-cores3 b/sdkconfig.board.m5stack-cores3
index 667b98e2..9cc6b8cb 100644
--- a/sdkconfig.board.m5stack-cores3
+++ b/sdkconfig.board.m5stack-cores3
@@ -24,6 +24,8 @@ CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y
CONFIG_FATFS_LFN_HEAP=y
+CONFIG_TINYUSB_MSC_ENABLED=y
+CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"
# Hardware: Main
CONFIG_TT_BOARD_M5STACK_CORES3=y
diff --git a/sdkconfig.defaults b/sdkconfig.defaults
index 94e17271..97e96523 100644
--- a/sdkconfig.defaults
+++ b/sdkconfig.defaults
@@ -23,6 +23,8 @@ CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_ELF_LOADER_CUSTOMER_SYMBOLS=y
+CONFIG_TINYUSB_MSC_ENABLED=y
+CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"
# Hardware defaults
CONFIG_TT_BOARD_CUSTOM=y