SD card support: MMC or configurable SPI (#2877)
Co-authored-by: constantin wolf <constantin.wolf@pwc.com>
This commit is contained in:
parent
75e410e4b4
commit
f104fb0586
34
usermods/sd_card/readme.md
Normal file
34
usermods/sd_card/readme.md
Normal file
@ -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`<sup>☨</sup> (Peripheral-Out-Ctrl-In) / `MISO` (deprecated) | 36 |
|
||||||
|
| `pinPico` | GPIO that is connected to SD's `PICO`<sup>☨</sup> (Peripheral-In-Ctrl-Out) / `MOSI` (deprecated) | 14 |
|
||||||
|
| `sdEnable` | Enable to read data from the SD-card | true |
|
||||||
|
|
||||||
|
<sup>☨</sup><sub>Following new naming convention of [OSHWA](https://www.oshwa.org/a-resolution-to-redefine-spi-signal-names/)</sub>
|
||||||
|
|
||||||
|
## 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) {...}
|
||||||
|
```
|
243
usermods/sd_card/usermod_sd_card.h
Normal file
243
usermods/sd_card/usermod_sd_card.h
Normal file
@ -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
|
@ -96,6 +96,7 @@
|
|||||||
#define USERMOD_ID_ANALOG_CLOCK 33 //Usermod "Analog_Clock.h"
|
#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_PING_PONG_CLOCK 34 //Usermod "usermod_v2_ping_pong_clock.h"
|
||||||
#define USERMOD_ID_ADS1115 35 //Usermod "usermod_ads1115.h"
|
#define USERMOD_ID_ADS1115 35 //Usermod "usermod_ads1115.h"
|
||||||
|
#define USERMOD_ID_SD_CARD 37 //Usermod "usermod_sd_card.h"
|
||||||
|
|
||||||
//Access point behavior
|
//Access point behavior
|
||||||
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
|
||||||
|
@ -57,7 +57,8 @@ enum struct PinOwner : uint8_t {
|
|||||||
UM_QuinLEDAnPenta = USERMOD_ID_QUINLED_AN_PENTA, // 0x17 // Usermod "quinled-an-penta.h"
|
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_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_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<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");
|
static_assert(0u == static_cast<uint8_t>(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected");
|
||||||
|
|
||||||
|
@ -160,6 +160,18 @@
|
|||||||
#include "../usermods/ADS1115_v2/usermod_ads1115.h"
|
#include "../usermods/ADS1115_v2/usermod_ads1115.h"
|
||||||
#endif
|
#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()
|
void registerUsermods()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -307,4 +319,8 @@ void registerUsermods()
|
|||||||
#ifdef USERMOD_ADS1115
|
#ifdef USERMOD_ADS1115
|
||||||
usermods.add(new ADS1115Usermod());
|
usermods.add(new ADS1115Usermod());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef SD_ADAPTER
|
||||||
|
usermods.add(new UsermodSdCard());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user