From 582149309b0402752ac43278d20dca56ebe942de Mon Sep 17 00:00:00 2001 From: Thomas Peterson Date: Thu, 22 Jan 2026 22:16:48 +0100 Subject: [PATCH] Fixes --- dependencies.lock | 39 ++++++++++- main/CMakeLists.txt | 4 +- main/SdCard.cpp | 109 ++++++++++++++++++++++++++--- main/SdCard.hpp | 7 ++ main/WebServer.cpp | 48 +++++++++++++ main/WebServer.hpp | 2 + main/idf_component.yml | 1 + sdcard_content/webseite/index.html | 19 +++++ 8 files changed, 215 insertions(+), 14 deletions(-) diff --git a/dependencies.lock b/dependencies.lock index f68e82d..9d3cef8 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -229,6 +229,26 @@ dependencies: registry_url: https://components.espressif.com type: service version: 1.1.2 + espressif/esp_tinyusb: + component_hash: + 6f1f0c140990bf27a86611e3c1f47f9fa8468bffc3bf1eb6d551cb09f31f8908 + dependencies: + - name: idf + require: private + version: '>=5.0' + - name: espressif/tinyusb + registry_url: https://components.espressif.com + require: public + version: '>=0.17.0~2' + source: + registry_url: https://components.espressif.com/ + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 + version: 2.0.1~1 espressif/esp_wifi_remote: component_hash: e5cb66acbf9b3e115dab6b8e1262a5473e7de6e4106d32a2a8ca7384bd21fc00 @@ -306,6 +326,22 @@ dependencies: registry_url: https://components.espressif.com type: service version: 1.6.54 + espressif/tinyusb: + component_hash: + 5ea9d3b6d6b0734a0a0b3491967aa0e1bece2974132294dbda5dd2839b247bfa + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 + version: 0.19.0~2 espressif/wifi_remote_over_eppp: component_hash: e1b4c485ed5afe36615b9b555dfdcbe4be33898dc3732b5bedf235bba45bd286 @@ -369,10 +405,11 @@ direct_dependencies: - espressif/esp_lcd_touch_gt911 - espressif/esp_lvgl_adapter - espressif/esp_lvgl_port +- espressif/esp_tinyusb - espressif/esp_wifi_remote - idf - lvgl/lvgl - waveshare/esp_lcd_jd9365_10_1 -manifest_hash: 0e36b3139b75b9c8d45ce136355b7427d1e8020070f386f167b627461b004349 +manifest_hash: 77a45d81439c2b8bff313cb08784154731b4451f54eecd31bcd4c2fc83b0f096 target: esp32p4 version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index f38c1d4..9d41746 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register(SRCS "KnxWorker.cpp" "Nvs.cpp" "main.cpp" "Display.cpp" "Touch.cpp" "Gui.cpp" "Wifi.cpp" "LvglIdle.c" "Gui/WifiSetting.cpp" "Gui/EthSetting.cpp" "Hardware/Eth.cpp" "WidgetManager.cpp" "WebServer.cpp" "SdCard.cpp" - PRIV_REQUIRES spi_flash esp_driver_ppa esp_lcd - REQUIRES esp_mm esp_eth esp_driver_ppa esp_timer lvgl knx ethernet_init esp_wifi_remote esp_netif esp_event nvs_flash esp_http_server fatfs sdmmc json + PRIV_REQUIRES spi_flash esp_driver_ppa esp_lcd usb + REQUIRES esp_mm esp_eth esp_driver_ppa esp_timer lvgl knx ethernet_init esp_wifi_remote esp_netif esp_event nvs_flash esp_http_server fatfs sdmmc json tinyusb INCLUDE_DIRS "") diff --git a/main/SdCard.cpp b/main/SdCard.cpp index c4e7703..7e1ebf2 100644 --- a/main/SdCard.cpp +++ b/main/SdCard.cpp @@ -3,11 +3,14 @@ #include "esp_vfs_fat.h" #include "sdmmc_cmd.h" #include "driver/sdmmc_host.h" +#include "sd_pwr_ctrl_by_on_chip_ldo.h" +#include "tinyusb.h" +#include "tinyusb_msc.h" #include static const char* TAG = "SdCard"; -// ESP32-P4 Waveshare Board SDMMC Pins +// ESP32-P4 Waveshare Board SDMMC Pins (from official docs) #define SDMMC_CLK_GPIO 43 #define SDMMC_CMD_GPIO 44 #define SDMMC_D0_GPIO 39 @@ -15,6 +18,12 @@ static const char* TAG = "SdCard"; #define SDMMC_D2_GPIO 41 #define SDMMC_D3_GPIO 42 +// SD card power control LDO channel (from Waveshare example) +#define SD_PWR_CTRL_LDO_CHANNEL 4 + +static sdmmc_card_t* s_card = nullptr; +static sd_pwr_ctrl_handle_t s_pwr_ctrl_handle = nullptr; + SdCard& SdCard::instance() { static SdCard inst; return inst; @@ -26,46 +35,62 @@ bool SdCard::init() { return true; } - ESP_LOGI(TAG, "Initializing SD card"); + ESP_LOGI(TAG, "Initializing SD card (SDMMC 4-wire mode)"); // Configure SDMMC host sdmmc_host_t host = SDMMC_HOST_DEFAULT(); - host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; + host.max_freq_khz = SDMMC_FREQ_DEFAULT; // 20 MHz - // Configure SDMMC slot with GPIO pins + // Initialize SD card power control via internal LDO + sd_pwr_ctrl_ldo_config_t ldo_config = { + .ldo_chan_id = SD_PWR_CTRL_LDO_CHANNEL, + }; + + esp_err_t ret = sd_pwr_ctrl_new_on_chip_ldo(&ldo_config, &s_pwr_ctrl_handle); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to create SD power control driver: %s", esp_err_to_name(ret)); + return false; + } + host.pwr_ctrl_handle = s_pwr_ctrl_handle; + ESP_LOGI(TAG, "SD power control LDO initialized (channel %d)", SD_PWR_CTRL_LDO_CHANNEL); + + // Configure SDMMC slot with official Waveshare pinout sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); + slot_config.width = 4; slot_config.clk = static_cast(SDMMC_CLK_GPIO); slot_config.cmd = static_cast(SDMMC_CMD_GPIO); slot_config.d0 = static_cast(SDMMC_D0_GPIO); slot_config.d1 = static_cast(SDMMC_D1_GPIO); slot_config.d2 = static_cast(SDMMC_D2_GPIO); slot_config.d3 = static_cast(SDMMC_D3_GPIO); - slot_config.width = 4; // 4-bit mode slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // Mount FAT filesystem esp_vfs_fat_sdmmc_mount_config_t mount_config = { - .format_if_mount_failed = true, + .format_if_mount_failed = false, .max_files = 5, .allocation_unit_size = 16 * 1024 }; - sdmmc_card_t* card; - esp_err_t ret = esp_vfs_fat_sdmmc_mount(MOUNT_POINT, &host, &slot_config, &mount_config, &card); + ESP_LOGI(TAG, "Mounting SD card..."); + ret = esp_vfs_fat_sdmmc_mount(MOUNT_POINT, &host, &slot_config, &mount_config, &s_card); if (ret != ESP_OK) { if (ret == ESP_FAIL) { - ESP_LOGE(TAG, "Failed to mount filesystem. If you want the card to be formatted, set format_if_mount_failed = true."); + ESP_LOGE(TAG, "Failed to mount filesystem."); } else { - ESP_LOGE(TAG, "Failed to initialize the card (%s). Make sure SD card lines have pull-up resistors.", esp_err_to_name(ret)); + ESP_LOGE(TAG, "Failed to initialize SD card (%s).", esp_err_to_name(ret)); } + // Clean up power control on failure + sd_pwr_ctrl_del_on_chip_ldo(s_pwr_ctrl_handle); + s_pwr_ctrl_handle = nullptr; return false; } mounted_ = true; // Print card info - sdmmc_card_print_info(stdout, card); + sdmmc_card_print_info(stdout, s_card); ESP_LOGI(TAG, "SD card mounted at %s", MOUNT_POINT); // Create directories if they don't exist @@ -87,3 +112,65 @@ bool SdCard::init() { return true; } + +bool SdCard::enableUsbMsc() { + if (usbMscActive_) { + ESP_LOGW(TAG, "USB MSC already active"); + return true; + } + + if (!s_card) { + ESP_LOGE(TAG, "SD card not initialized"); + return false; + } + + ESP_LOGI(TAG, "Enabling USB Mass Storage mode..."); + + // Unmount filesystem first (so PC has exclusive access) + if (mounted_) { + ESP_LOGI(TAG, "Unmounting SD card from filesystem..."); + esp_err_t ret = esp_vfs_fat_sdcard_unmount(MOUNT_POINT, s_card); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to unmount SD card: %s", esp_err_to_name(ret)); + return false; + } + mounted_ = false; + } + + // Initialize TinyUSB + ESP_LOGI(TAG, "Initializing TinyUSB..."); + const tinyusb_config_t cfg = { + .external_phy = false, + }; + esp_err_t ret = tinyusb_driver_install(&tusb_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to install TinyUSB driver: %s", esp_err_to_name(ret)); + return false; + } + + // Initialize MSC storage with SD card + ESP_LOGI(TAG, "Initializing USB MSC with SD card..."); + const tinyusb_msc_sdmmc_config_t msc_cfg = { + .card = s_card, + }; + + ret = tinyusb_msc_storage_init_sdmmc(&msc_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize MSC storage: %s", esp_err_to_name(ret)); + return false; + } + + // Mount MSC storage + ret = tinyusb_msc_storage_mount(MOUNT_POINT); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount MSC storage: %s", esp_err_to_name(ret)); + return false; + } + + usbMscActive_ = true; + ESP_LOGI(TAG, "USB Mass Storage mode enabled!"); + ESP_LOGI(TAG, "Connect USB cable to access SD card from PC."); + ESP_LOGI(TAG, "Reboot device to return to normal mode."); + + return true; +} diff --git a/main/SdCard.hpp b/main/SdCard.hpp index e882c94..e73bdaa 100644 --- a/main/SdCard.hpp +++ b/main/SdCard.hpp @@ -5,6 +5,12 @@ public: static SdCard& instance(); bool init(); bool isMounted() const { return mounted_; } + bool isUsbMscActive() const { return usbMscActive_; } + + // Enable USB Mass Storage mode (unmounts SD from ESP, exposes via USB) + // Returns true if successful. Requires reboot to return to normal mode. + bool enableUsbMsc(); + static constexpr const char* MOUNT_POINT = "/sdcard"; private: @@ -14,4 +20,5 @@ private: SdCard& operator=(const SdCard&) = delete; bool mounted_ = false; + bool usbMscActive_ = false; }; diff --git a/main/WebServer.cpp b/main/WebServer.cpp index bdefb8c..044acfa 100644 --- a/main/WebServer.cpp +++ b/main/WebServer.cpp @@ -4,6 +4,7 @@ #include "KnxWorker.hpp" #include "Gui.hpp" #include "esp_log.h" +#include "esp_system.h" #include #include #include @@ -109,6 +110,24 @@ void WebServer::start() { }; httpd_register_uri_handler(server_, &getKnxAddresses); + // POST /api/usb-mode - Enable USB Mass Storage mode + httpd_uri_t postUsbMode = { + .uri = "/api/usb-mode", + .method = HTTP_POST, + .handler = postUsbModeHandler, + .user_ctx = nullptr + }; + httpd_register_uri_handler(server_, &postUsbMode); + + // GET /api/status - Get system status + httpd_uri_t getStatus = { + .uri = "/api/status", + .method = HTTP_GET, + .handler = getStatusHandler, + .user_ctx = nullptr + }; + httpd_register_uri_handler(server_, &getStatus); + ESP_LOGI(TAG, "HTTP server started successfully"); } @@ -324,3 +343,32 @@ esp_err_t WebServer::getKnxAddressesHandler(httpd_req_t* req) { httpd_resp_send(req, buf, strlen(buf)); return ESP_OK; } + +esp_err_t WebServer::postUsbModeHandler(httpd_req_t* req) { + ESP_LOGI(TAG, "Enabling USB Mass Storage mode"); + + bool success = SdCard::instance().enableUsbMsc(); + + if (success) { + httpd_resp_set_type(req, "application/json"); + const char* response = "{\"status\":\"ok\",\"message\":\"USB MSC enabled. Connect USB cable to access SD card. Reboot to return to normal mode.\"}"; + httpd_resp_send(req, response, strlen(response)); + } else { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to enable USB MSC"); + } + + return success ? ESP_OK : ESP_FAIL; +} + +esp_err_t WebServer::getStatusHandler(httpd_req_t* req) { + char buf[256]; + snprintf(buf, sizeof(buf), + "{\"sdMounted\":%s,\"usbMscActive\":%s}", + SdCard::instance().isMounted() ? "true" : "false", + SdCard::instance().isUsbMscActive() ? "true" : "false" + ); + + httpd_resp_set_type(req, "application/json"); + httpd_resp_send(req, buf, strlen(buf)); + return ESP_OK; +} diff --git a/main/WebServer.hpp b/main/WebServer.hpp index 3c806f9..f1ffcd5 100644 --- a/main/WebServer.hpp +++ b/main/WebServer.hpp @@ -27,6 +27,8 @@ private: static esp_err_t postSaveHandler(httpd_req_t* req); static esp_err_t postResetHandler(httpd_req_t* req); static esp_err_t getKnxAddressesHandler(httpd_req_t* req); + static esp_err_t postUsbModeHandler(httpd_req_t* req); + static esp_err_t getStatusHandler(httpd_req_t* req); // Helper functions static const char* getContentType(const char* filepath); diff --git a/main/idf_component.yml b/main/idf_component.yml index 0751256..ac6a67f 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -20,3 +20,4 @@ dependencies: espressif/esp_lcd_touch_gt911: '*' espressif/esp_lvgl_adapter: '*' espressif/esp_wifi_remote: '*' + espressif/esp_tinyusb: ^2.0.1 \ No newline at end of file diff --git a/sdcard_content/webseite/index.html b/sdcard_content/webseite/index.html index a7f7c09..d8dd0cd 100644 --- a/sdcard_content/webseite/index.html +++ b/sdcard_content/webseite/index.html @@ -166,6 +166,8 @@ color: #cdd6f4; } button.secondary:hover { background: #585b70; } + button.usb { background: #fab387; } + button.usb:hover { background: #f9e2af; } /* Status */ .status { position: fixed; @@ -196,6 +198,7 @@

GUI Editor

KNX Display Konfiguration
+
@@ -612,6 +615,22 @@ } } + async function enableUsbMode() { + if (!confirm('USB-Modus aktivieren?\n\nDie SD-Karte wird als USB-Laufwerk am PC verfuegbar.\nZum Beenden: Geraet neu starten.')) return; + try { + const resp = await fetch('/api/usb-mode', {method: 'POST'}); + const data = await resp.json(); + if (data.status === 'ok') { + showStatus('USB-Modus aktiv! SD-Karte am PC verfuegbar.'); + alert('USB Mass Storage aktiviert!\n\nVerbinden Sie das USB-Kabel mit Ihrem PC.\nDie SD-Karte erscheint als Wechseldatentraeger.\n\nZum Beenden: Geraet neu starten.'); + } else { + showStatus('USB-Modus fehlgeschlagen', true); + } + } catch (e) { + showStatus('Fehler beim Aktivieren des USB-Modus', true); + } + } + function showStatus(msg, isError = false) { const el = document.getElementById('status'); el.textContent = msg;