diff --git a/usermods/sd_card/readme.md b/usermods/sd_card/readme.md new file mode 100644 index 00000000..299b68eb --- /dev/null +++ b/usermods/sd_card/readme.md @@ -0,0 +1,34 @@ +# SD-card mod + +## Build +- modify `platformio.ini` and add to the `build_flags` of your configuration the following +- choose the way your SD is connected + 1. via `-D WLED_USE_SD_MMC` when connected via MMC + 2. via `-D WLED_USE_SD_SPI` when connected via SPI (use usermod page to setup SPI pins) + +### Test +- enable `-D SD_PRINT_HOME_DIR` and `-D WLED_DEBUG` +- this will print all files in `/` on boot via serial + +## Configuration +### MMC +- The MMC port / pins needs no configuration as they are specified by Espressif +### SPI +- The SPI port / pins can be modified via the WLED web-UI: `Config → Usermod → SD Card` + | option | effect | default | + | ----------------- | ------------------------------------------------------------------------------------------------ | ------- | + | `pinSourceSelect` | GPIO that is connected to SD's `SS`(source select) / `CS`(chip select) | 16 | + | `pinSourceClock` | GPIO that is connected to SD's `SCLK` (source clock) / `CLK`(clock) | 14 | + | `pinPoci` | GPIO that is connected to SD's `POCI` (Peripheral-Out-Ctrl-In) / `MISO` (deprecated) | 36 | + | `pinPico` | GPIO that is connected to SD's `PICO` (Peripheral-In-Ctrl-Out) / `MOSI` (deprecated) | 14 | + | `sdEnable` | Enable to read data from the SD-card | true | + + Following new naming convention of [OSHWA](https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/) + +## Usage in other mods +- creates a macro `SD_ADAPTER` which is either mapped to `SD` or `SD_MMC` (see `SD_Test.ino` how to use SD / SD_MMC functions) + +- checks if the specified file is available on the SD card + ```cpp + bool file_onSD(const char *filepath) {...} + ``` \ No newline at end of file diff --git a/usermods/sd_card/usermod_sd_card.h b/usermods/sd_card/usermod_sd_card.h new file mode 100644 index 00000000..5dac7915 --- /dev/null +++ b/usermods/sd_card/usermod_sd_card.h @@ -0,0 +1,243 @@ +#pragma once + +#include "wled.h" + +// SD connected via MMC / SPI +#if defined(WLED_USE_SD_MMC) + #define USED_STORAGE_FILESYSTEMS "SD MMC, LittleFS" + #define SD_ADAPTER SD_MMC + #include "SD_MMC.h" +// SD connected via SPI (adjustable via usermod config) +#elif defined(WLED_USE_SD_SPI) + #define SD_ADAPTER SD + #define USED_STORAGE_FILESYSTEMS "SD SPI, LittleFS" + #include "SD.h" + #include "SPI.h" +#endif + +#ifdef WLED_USE_SD_MMC +#elif defined(WLED_USE_SD_SPI) + SPIClass spiPort = SPIClass(VSPI); +#endif + +void listDir( const char * dirname, uint8_t levels); + +class UsermodSdCard : public Usermod { + private: + bool sdInitDone = false; + + #ifdef WLED_USE_SD_SPI + int8_t configPinSourceSelect = 16; + int8_t configPinSourceClock = 14; + int8_t configPinPoci = 36; // confusing names? Then have a look :) + int8_t configPinPico = 15; // https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/ + + //acquired and initialize the SPI port + void init_SD_SPI() + { + if(!configSdEnabled) return; + if(sdInitDone) return; + + PinManagerPinType pins[5] = { + { configPinSourceSelect, true }, + { configPinSourceClock, true }, + { configPinPoci, false }, + { configPinPico, true } + }; + + if (!pinManager.allocateMultiplePins(pins, 4, PinOwner::UM_SdCard)) { + DEBUG_PRINTF("[%s] SD (SPI) pin allocation failed!\n", _name); + sdInitDone = false; + return; + } + + bool returnOfInitSD = false; + + #if defined(WLED_USE_SD_SPI) + spiPort.begin(configPinSourceClock, configPinPoci, configPinPico, configPinSourceSelect); + returnOfInitSD = SD_ADAPTER.begin(configPinSourceSelect, spiPort); + #endif + + if(!returnOfInitSD) { + DEBUG_PRINTF("[%s] SPI begin failed!\n", _name); + sdInitDone = false; + return; + } + + sdInitDone = true; + } + + //deinitialize the acquired SPI port + void deinit_SD_SPI() + { + if(!sdInitDone) return; + + SD_ADAPTER.end(); + + DEBUG_PRINTF("[%s] deallocate pins!\n", _name); + pinManager.deallocatePin(configPinSourceSelect, PinOwner::UM_SdCard); + pinManager.deallocatePin(configPinSourceClock, PinOwner::UM_SdCard); + pinManager.deallocatePin(configPinPoci, PinOwner::UM_SdCard); + pinManager.deallocatePin(configPinPico, PinOwner::UM_SdCard); + + sdInitDone = false; + } + + // some SPI pin was changed, while SPI was initialized, reinit to new port + void reinit_SD_SPI() + { + deinit_SD_SPI(); + init_SD_SPI(); + } + #endif + + #ifdef WLED_USE_SD_MMC + void init_SD_MMC() { + if(sdInitDone) return; + bool returnOfInitSD = false; + returnOfInitSD = SD_ADAPTER.begin(); + DEBUG_PRINTF("[%s] MMC begin\n", _name); + + if(!returnOfInitSD) { + DEBUG_PRINTF("[%s] MMC begin failed!\n", _name); + sdInitDone = false; + return; + } + + sdInitDone = true; + } + #endif + + public: + static bool configSdEnabled; + static const char _name[]; + + void setup() { + DEBUG_PRINTF("[%s] usermod loaded \n", _name); + #if defined(WLED_USE_SD_SPI) + init_SD_SPI(); + #elif defined(WLED_USE_SD_MMC) + init_SD_MMC(); + #endif + + #if defined(SD_ADAPTER) && defined(SD_PRINT_HOME_DIR) + listDir("/", 0); + #endif + } + + void loop(){ + + } + + uint16_t getId() + { + return USERMOD_ID_SD_CARD; + } + + void addToConfig(JsonObject& root) + { + #ifdef WLED_USE_SD_SPI + JsonObject top = root.createNestedObject(FPSTR(_name)); + top["pinSourceSelect"] = configPinSourceSelect; + top["pinSourceClock"] = configPinSourceClock; + top["pinPoci"] = configPinPoci; + top["pinPico"] = configPinPico; + top["sdEnabled"] = configSdEnabled; + #endif + } + + bool readFromConfig(JsonObject &root) + { + #ifdef WLED_USE_SD_SPI + JsonObject top = root[FPSTR(_name)]; + if (top.isNull()) { + DEBUG_PRINTF("[%s] No config found. (Using defaults.)\n", _name); + return false; + } + + uint8_t oldPinSourceSelect = configPinSourceSelect; + uint8_t oldPinSourceClock = configPinSourceClock; + uint8_t oldPinPoci = configPinPoci; + uint8_t oldPinPico = configPinPico; + bool oldSdEnabled = configSdEnabled; + + getJsonValue(top["pinSourceSelect"], configPinSourceSelect); + getJsonValue(top["pinSourceClock"], configPinSourceClock); + getJsonValue(top["pinPoci"], configPinPoci); + getJsonValue(top["pinPico"], configPinPico); + getJsonValue(top["sdEnabled"], configSdEnabled); + + if(configSdEnabled != oldSdEnabled) { + configSdEnabled ? init_SD_SPI() : deinit_SD_SPI(); + DEBUG_PRINTF("[%s] SD card %s\n", _name, configSdEnabled ? "enabled" : "disabled"); + } + + if( configSdEnabled && ( + oldPinSourceSelect != configPinSourceSelect || + oldPinSourceClock != configPinSourceClock || + oldPinPoci != configPinPoci || + oldPinPico != configPinPico) + ) + { + DEBUG_PRINTF("[%s] Init SD card based of config\n", _name); + DEBUG_PRINTF("[%s] Config changes \n - SS: %d -> %d\n - MI: %d -> %d\n - MO: %d -> %d\n - En: %d -> %d\n", _name, oldPinSourceSelect, configPinSourceSelect, oldPinSourceClock, configPinSourceClock, oldPinPoci, configPinPoci, oldPinPico, configPinPico); + reinit_SD_SPI(); + } + #endif + + return true; + } +}; + +const char UsermodSdCard::_name[] PROGMEM = "SD Card"; +bool UsermodSdCard::configSdEnabled = true; + +#ifdef SD_ADAPTER +//checks if the file is available on SD card +bool file_onSD(const char *filepath) +{ + #ifdef WLED_USE_SD_SPI + if(!UsermodSdCard::configSdEnabled) return false; + #endif + + uint8_t cardType = SD_ADAPTER.cardType(); + if(cardType == CARD_NONE) { + DEBUG_PRINTF("[%s] not attached / cardType none\n", UsermodSdCard::_name); + return false; // no SD card attached + } + if(cardType == CARD_MMC || cardType == CARD_SD || cardType == CARD_SDHC) + { + return SD_ADAPTER.exists(filepath); + } + + return false; // unknown card type +} + +void listDir( const char * dirname, uint8_t levels){ + DEBUG_PRINTF("Listing directory: %s\n", dirname); + + File root = SD_ADAPTER.open(dirname); + if(!root){ + DEBUG_PRINTF("Failed to open directory\n"); + return; + } + if(!root.isDirectory()){ + DEBUG_PRINTF("Not a directory\n"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(file.isDirectory()){ + DEBUG_PRINTF(" DIR : %s\n",file.name()); + if(levels){ + listDir(file.name(), levels -1); + } + } else { + DEBUG_PRINTF(" FILE: %s SIZE: %d\n",file.name(), file.size()); + } + file = root.openNextFile(); + } +} + +#endif \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index c62cd21b..f092fe63 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -96,6 +96,7 @@ #define USERMOD_ID_ANALOG_CLOCK 33 //Usermod "Analog_Clock.h" #define USERMOD_ID_PING_PONG_CLOCK 34 //Usermod "usermod_v2_ping_pong_clock.h" #define USERMOD_ID_ADS1115 35 //Usermod "usermod_ads1115.h" +#define USERMOD_ID_SD_CARD 37 //Usermod "usermod_sd_card.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 4a0c6889..db211961 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -57,7 +57,8 @@ enum struct PinOwner : uint8_t { UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h" UM_BME280 = USERMOD_ID_BME280, // 0x18 // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins UM_BH1750 = USERMOD_ID_BH1750, // 0x19 // Usermod "usermod_bme280.h -- Uses "standard" HW_I2C pins - UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE // 0x1E // Usermod "audio_reactive.h" + UM_Audioreactive = USERMOD_ID_AUDIOREACTIVE, // 0x1E // Usermod "audio_reactive.h" + UM_SdCard = USERMOD_ID_SD_CARD // 0x24 // Usermod "usermod_sd_card.h" }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); diff --git a/wled00/usermods_list.cpp b/wled00/usermods_list.cpp index f161bef7..e40ccf75 100644 --- a/wled00/usermods_list.cpp +++ b/wled00/usermods_list.cpp @@ -160,6 +160,18 @@ #include "../usermods/ADS1115_v2/usermod_ads1115.h" #endif +#if defined(WLED_USE_SD_MMC) || defined(WLED_USE_SD_SPI) +// This include of SD.h and SD_MMC.h must happen here, else they won't be +// resolved correctly (when included in mod's header only) + #ifdef WLED_USE_SD_MMC + #include "SD_MMC.h" + #elif defined(WLED_USE_SD_SPI) + #include "SD.h" + #include "SPI.h" + #endif + #include "../usermods/sd_card/usermod_sd_card.h" +#endif + void registerUsermods() { /* @@ -307,4 +319,8 @@ void registerUsermods() #ifdef USERMOD_ADS1115 usermods.add(new ADS1115Usermod()); #endif + + #ifdef SD_ADAPTER + usermods.add(new UsermodSdCard()); + #endif }