In an actual project, I'm getting into trouble with the usage of SD cards with SPI by an ESP32-S2 on the ESP32-S2-Saola-1 board from Espressif. I tried to use the Ethernet.h
libary from Arduino within PlatformIO. But there might be a problem with the SPI configuration of the Ethernet object. As mentioned here, upon creation a new instance there's no possibility to set an appropriate SPI class like e.g. for SD cards. There was an approach by Adafruit wih the Ethernet2.h
libary, but this code isn't maintained anymore.
There's another confusing part, the naming of the two available SPI drivers on ESP32 and ESP32-S2. For clarifications sake:
System | SPI2 | SPI3 |
---|---|---|
ESP32 | HSPI |
VSPI |
ESP32-S2 | HSPI |
FSPI |
Which driver is used by Ethernet.h?
So, to make sure that the SD card and the W5500 Ethernet board are using different drivers. I'd like to know which driver is used by Ethernet.h by default. In a testing setup, just consisting of the ESP32-S2-Saola-1 and the W5500, the following code returns the SPI pins used by default:
#include <Arduino.h>
#include <SPI.h>
#include <Ethernet.h>
byte mac[] = {0xDE, 0xDE, 0x3A, 0x4B, 0x55, 0x34 }; // some random MAC for DHCP
void setup() {
// https://randomnerdtutorials.com/esp32-spi-communication-arduino/
Serial.begin(9600);
Serial.print("MOSI: ");
Serial.println(MOSI);
Serial.print("MISO: ");
Serial.println(MISO);
Serial.print("SCK: ");
Serial.println(SCK);
Serial.print("SS: ");
Serial.println(SS);
Ethernet.init(34);
Serial.println("Here we go ...");
if (Ethernet.begin(mac)) { // Dynamic IP setup
Serial.println("DHCP OK!");
Serial.print("Ethernet IP is: ");
Serial.println(Ethernet.localIP());
} else {
Serial.println("Failed to configure Ethernet using DHCP");
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
while (true) {
delay(1000); // do nothing, no point running without Ethernet hardware
}
}
}
}
void loop() {
Serial.print("Ethernet IP is: ");
Serial.println(Ethernet.localIP());
delay(5000);
}
That will return the following GPIOs on the ESP32-S2-Saola-1:
GPIO | Function |
---|---|
34 | CS |
35 | MOSI |
36 | SCK |
37 | MISO |
I'm not that deep into the code to figure out if the ESP32-S2 is using HSPI or VPSI. But I didn't modificate the pins for Ethernet. So somewhere there must be written which pins are used by default for those two drivers on the current board. Like described here the default pins for the ESP32 VSPI are the higher ones so it's a huge guess that Ethernet.h is using FSPI. Therefore HSPI for SD might do the trick.
By now someone could ask, but why not using SD and Ethernet on the same SPI bus? Well, the internet is full of failure descriptions for the handling of SD cards with ESP32. Some of them discuss the clock rate of the SPI bus. To be more flexible for later configuration, it might be suitable to use one SPI bus exclusively for SD card communication.
I read some other articles that struggle with the correct SD format process. Yes the card is formatted as FAT32 and I downloaded the format tool from the SD coorporation which was recommended in these articles.
With an inserted SD card I got the error:
[ 42117][E][sd_diskio.cpp:802] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
and with no SD card inserted I got the error:
[116801][E][sd_diskio.cpp:802] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
[117301][E][sd_diskio.cpp:126] sdSelectCard(): Select Failed
Going down to hardware level
To get an impression of the signal quality of the SPI bus, I tried to have a look at the signals with an oscilloscope.
Back to program level
Reloading the sketch from the unit test:
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
// usage with ESP32 only
// https://www.arduinoforum.de/arduino-Thread-ESP32-S2-erkennen?page=3
#if SOC_UART_NUM == 3 // preprocessor for ESP32-WROOM or ESP32-WROVER
#define PIN_SPI_SD_CS -1
#define PIN_SPI_SD_MOSI -1
#define PIN_SPI_SD_MISO -1
#define PIN_SPI_SD_CLK 1
#elif SOC_UART_NUM < 3 // preprocessor for ESP32-S2 or ESP32-C3
#define PIN_SPI_SD_CS 10
#define PIN_SPI_SD_MOSI 11
#define PIN_SPI_SD_MISO 13
#define PIN_SPI_SD_CLK 12
#else
#error "Only for ESP32."
#endif
SPIClass sd_spi = SPIClass(HSPI); // 3 did not work, VSPI or HSPI for esp32, FSPI for esp32s2
String folderName = "/2022-12-25"; // leading slash, no trailing slash! fixed, will be dynamic in later program according to actual NTP/RTC response
String fileName = "20-36-49.csv"; // point of time where file is created, e.g. after restart or insertion of SD card
void setup() {
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.println("SD Card Test");
sd_spi.begin(PIN_SPI_SD_CLK, PIN_SPI_SD_MISO, PIN_SPI_SD_MOSI, PIN_SPI_SD_CS);
Serial.println("SD SPI begin");
//if (!SD.begin(PIN_SPI_SD_CS, sd_spi, 40000000))
if (!SD.begin(PIN_SPI_SD_CS, sd_spi))
{
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
}
else if (cardType == CARD_SD) {
Serial.println("SDSC");
}
else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
}
else {
Serial.println("UNKNOWN");
}
float cardSize = SD.cardSize() / (1024 * 1024); // card size in MB
float totalMBytes = SD.totalBytes() / (1024 * 1024);
float usedMBytes = SD.usedBytes() / (1024 * 1024);
float cardUsage = usedMBytes / totalMBytes; // card usage in percent; e.g. 0.12 represents 12 %
Serial.print("Card size: ");
Serial.print(cardSize, 1); // no of fractional digits to display
Serial.println(" MB");
Serial.print("Total: ");
Serial.print(totalMBytes, 1);
Serial.println(" MB");
Serial.print("Used: ");
Serial.print(usedMBytes, 1);
Serial.println(" MB");
Serial.print("Used: ");
Serial.print(cardUsage * 100, 1);
Serial.println(" %");
// check if directory exists otherwise try to create it
if (!SD.exists(folderName)) {
if (SD.mkdir(folderName)) {
Serial.print("Folder ");
Serial.print(folderName);
Serial.println(" created.");
} else {
Serial.print("Error: could not create folder ");
Serial.println(folderName);
}
} else {
Serial.print("Folder ");
Serial.print(folderName);
Serial.println(" already exists.");
}
// create file
String fullPath = folderName + "/" + fileName;
File f;
f = SD.open(fullPath, FILE_APPEND);
if (f) {
Serial.print("Writing to ");
Serial.println(fullPath);
f.println("Hello World."); // Append this to file
Serial.println("Hello World.");
} else {
Serial.println("File could not be opened.");
}
// finally
if (f) {
f.close();
}
}
void loop() {
Serial.println(".");
delay(1000);
}
resulted in the error
[ 509][E][sd_diskio.cpp:802] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
In this issue it is assumed, that a power reset would solve the problem resetting the SD card. Tried that manually with no effort.