This commit is contained in:
Thomas Peterson 2026-01-22 22:16:48 +01:00
parent 315ae3ce34
commit 582149309b
8 changed files with 215 additions and 14 deletions

View File

@ -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

View File

@ -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 "")

View File

@ -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 <sys/stat.h>
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<gpio_num_t>(SDMMC_CLK_GPIO);
slot_config.cmd = static_cast<gpio_num_t>(SDMMC_CMD_GPIO);
slot_config.d0 = static_cast<gpio_num_t>(SDMMC_D0_GPIO);
slot_config.d1 = static_cast<gpio_num_t>(SDMMC_D1_GPIO);
slot_config.d2 = static_cast<gpio_num_t>(SDMMC_D2_GPIO);
slot_config.d3 = static_cast<gpio_num_t>(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;
}

View File

@ -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;
};

View File

@ -4,6 +4,7 @@
#include "KnxWorker.hpp"
#include "Gui.hpp"
#include "esp_log.h"
#include "esp_system.h"
#include <cstring>
#include <cstdio>
#include <sys/stat.h>
@ -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;
}

View File

@ -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);

View File

@ -20,3 +20,4 @@ dependencies:
espressif/esp_lcd_touch_gt911: '*'
espressif/esp_lvgl_adapter: '*'
espressif/esp_wifi_remote: '*'
espressif/esp_tinyusb: ^2.0.1

View File

@ -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 @@
<h1>GUI Editor</h1>
<span style="color:#6c7086">KNX Display Konfiguration</span>
<div class="header-buttons">
<button class="usb" onclick="enableUsbMode()">USB-Modus</button>
<button class="secondary" onclick="resetConfig()">Zuruecksetzen</button>
<button onclick="saveConfig()">Speichern & Anwenden</button>
</div>
@ -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;