SD.begin() not returning anything on ESP32S3

Hi, I have designed a custom PCB running on an ESP32 S3.
This is version 2 of this board, so I know that all the basic stuff works :slight_smile:
I am trying to get my micro SD card to mount in SPI mode, but the SD.begin() function of the Arduino framework doesn't return anything and just hangs forever.
Does someone maybe know what I am doing wrong here?
Can I for example use these pins?
Or is there something else I can maybe try for debugging?

Thanks in advance!

Current code:

#define SPI_SCK 40 //connected to SCLK on the sd
#define SPI_MISO 42 //conencted to DO on the sd
#define SPI_MOSI 41 //connected to DI on the sd
#define SD_CS 39 //connected to CS on the sd
void initSPI(){
  spi = SPIClass(HSPI);
  spi.begin(SPI_SCK, SPI_MISO, SPI_MOSI, SD_CS); // setup SPI pins
}

void initSD(){
  // prints this correctly
  Serial.println("[SD] Mounting sd!");
  if (!SD.begin(SD_CS, spi)) {
    // never gets here (teseted with serial.print)
    error("SD card mount failed.", ERROR_CODE_SD);
  }
 // never gets here either (tested with serial.print)
}

void setup(){
    initSPI();
    initSD();
}

Does the code you posted compile, with no errors, for an ESP32S3 ?

I have removed some of my code, because otherwise it becomes way too long, but this is all the important code, and it compiles without errors.

The current code as shown wouldn't display anything as the serial port does not appear to be initialized - unless, of course, you have cut that part or its in the error() function?

As for whether these pins can be used, it would seem the answer is yes by default, unless they have been enabled for the JTAG debug interface at some point:
`Tips and Quirks - ESP32-S3 - — ESP-IDF Programming Guide latest documentation
The relevant information is about 3/4 of the way down the page.

Post code that demonstrates the problem

I did cut that part :slight_smile:
I also found that you can disable the JTAG adapter, but maybe I am not doing this correctly?
I am using PlatformIO to compile the code, so I don't know if I can get support for that here?
I used this flag:
-D CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=false

Configuration Options Reference - ESP32-S3 - — ESP-IDF Programming Guide latest documentation
But I don't know if that does what I want it to?

I believe this is the only part of the code that matters, but if you need some more, please tell me exactly what, because the code is way too long to post here fully :slight_smile:

Does not compile for me, the error I get is;

error: 'spi' was not declared in this scope; did you mean 'sei'?

And SD.begin(SD_CS, spi) will cause another error since the code does not have an;

#include <SPI.h>

And of course;

Serial.println("[SD] Mounting sd!");

Wont produce any output since the alleged code does not have a Serial.begin().

Are you really sure the code posted in #1 compiles error free ?

Have you perhaps tried here:

https://community.platformio.org/ ?

There are a number of relevant things we have not been informed about:

  • have you tried re-seating the card?
  • which SD library you are using (SD or SdFat)?
  • is the SD Card already formatted FAT16 or FAT32? (or possibly exFat if using the SdFat library)
  • does the card work OK when mounted elsewhere (e.g. on a PC)?

I am a little surprised that the compile didn't complain about this:

if (!SD.begin(SD_CS, spi))

The class object spi is instantiated and then initialised in void initSPI() but is being passed as a reference to SD.begin() in the separate context of void initSD(), so I am wondering why the compiler didn't spit out an "spi is not defined" error for that SD.begin() statement? Could it be that spi is also being instantiated at global level, in which case then there would be two separate instances of the SPI object being created and the pin assignments applied only to the instance inside void initSPI(), which is not visible outside of that function or indeed inside void initSD(). That would mean that SD.begin() might not be being initialised the way you think it is. Of course, since we cannot see the code you have at global level (i.e. code above all the functions at the start of the sketch which has not been shown), we can only guess at what might be going on....

I appreciate your code might be quite large, but I have also had to learn this lesson, namely to post a minimal but complete (one that can be loaded into an IDE and compiled) example demonstrating the problem. It helps us to help you.

Post crossed and haven't attempted to run it, but yes, was wondering about that was well for slightly different, but perhaps related reasons.
Seems the relevant bit might be the bit we haven't been shown.....

If it is really that necessary, I will just post it fully :slight_smile:

main.cpp:

// deps
#include "Adafruit_LSM6DSO32.h"
#include "Adafruit_NeoPixel.h"
#include "Adafruit_BMP3XX.h"
#include "main.h"
#include "peripherals.h"
#include "TinyGPSPlus.h"
#include "Arduino.h"
#include "Wire.h"
#include "FS.h"
#include "map"

Adafruit_NeoPixel WS2812B;

void initLed(){
  WS2812B = Adafruit_NeoPixel(1, PIN_RGB, NEO_GRB + NEO_KHZ800);
  WS2812B.begin();
  WS2812B.setPixelColor(0, Adafruit_NeoPixel::Color(255, 255, 255));
  WS2812B.setBrightness(100);
  WS2812B.show();
}

void setup(){
  delay(5000);
  initLed();
  delay(5000);
	Serial.println("Computer starting");
	initPeripherals();
}

void loop(){

  delay(200);
}


peripherals.cpp:

#include "Adafruit_LSM6DSO32.h"
#include "Adafruit_NeoPixel.h"
#include "Adafruit_BMP3XX.h"
#include "main.h"
#include "TinyGPSPlus.h"
#include "W25N_origin.h"
#include "Arduino.h"
#include "Wire.h"
#include "SPI.h"
#include "FS.h"
#include "SD.h"
#include "map"

#include "peripherals.h"
#include "origin_errors.h"

// module definitions
SPIClass spi;
W25N flash;
Adafruit_LSM6DSO32 sensor_imu;
Adafruit_BMP3XX sensor_bar;
TinyGPSPlus gps;
// Adafruit_NeoPixel WS2812B;

// tests
std::map<std::string, std::function<void()>> test_registry;
#define REGISTER_TEST(name) \
void name(); \
struct name##_registrar { \
name##_registrar() { \
test_registry[#name] = name; \
} \
} name##_registrar_instance; \
void name()

void initSerial(){
#if SERIAL_INFO == true
    Serial.begin(115200);
    Serial.println("Computer starting");
#endif
}

// void initLed(){
//   WS2812B = Adafruit_NeoPixel(1, PIN_RGB, NEO_GRB + NEO_KHZ800);
//   WS2812B.begin();
//   WS2812B.setPixelColor(0, Adafruit_NeoPixel::Color(255, 255, 255));
//   WS2812B.setBrightness(100);
//   WS2812B.show();
// }

void initBuzzer(){
  pinMode(PIN_BUZZER, OUTPUT);
  digitalWrite(PIN_BUZZER, HIGH);
  delay(100);
  digitalWrite(PIN_BUZZER, LOW);
  delay(100);
  digitalWrite(PIN_BUZZER, HIGH);
  delay(100);
  digitalWrite(PIN_BUZZER, LOW);
}

void initSPI(){
  spi = SPIClass(HSPI);
  spi.begin(SPI_SCK, SPI_MISO, SPI_MOSI, SD_CS); // setup SPI pins
}

void initSD(){
  #if SERIAL_INFO == true
    Serial.println("[SD] Mounting sd!");
#endif
  if (!SD.begin(SD_CS, spi)) { // mount card
    #if SERIAL_INFO == true
      Serial.println("[SD] Error occurred!");
#endif
    error("SD card mount failed.", ERROR_CODE_SD);
  }
  #if SERIAL_INFO == true
    Serial.println("[SD] Mounted!");
#endif
  uint8_t cardType = SD.cardType();
  #if SERIAL_INFO == true
    Serial.println("[SD] Checking sd type!");
#endif
  if (cardType == CARD_NONE) {
    #if SERIAL_INFO == true
      Serial.println("[SD] Error occurred!");
#endif
    error("No SD card attached.", ERROR_CODE_SD);
  }
}

REGISTER_TEST(SD_TEST)
{
  const unsigned int randomNumber = esp_random();
  const String randomString = String(randomNumber);
  if (SD.exists("/test.txt"))
  {
    SD.remove("/test.txt");
  }
  File testFile = SD.open("/test.txt", FILE_WRITE);
  if (!testFile)
  {
    error("Testfile failed to open.", ERROR_CODE_SD_TEST);
  }
  testFile.println(randomString);
  testFile.close();

  File testFile2 = SD.open("/test.txt", FILE_READ);
  if (!testFile2)
  {
    error("Testfile failed save.", ERROR_CODE_SD_TEST);
  }
  const String read_data = testFile2.readStringUntil(0x15);
  if (!read_data.equals(randomString+"\r\n"))
  {
#if SERIAL_INFO == true
    Serial.print("Expected:");
    for (const auto x : randomString)
    {
      Serial.print('"');
      Serial.print(x);
      Serial.print("\",");
    }
    Serial.println(" ");
    Serial.print("Got:");
    for (const auto x : read_data)
    {
      Serial.print('"');
      Serial.print(x);
      Serial.print("\",");
    }
    Serial.println(" ");
#endif
    error("Read incorrect data from SD.", ERROR_CODE_SD_TEST);
  }
  testFile2.close();
  if (SD.exists("/test.txt"))
  {
    SD.remove("/test.txt");
  }
}

void initFlash(){
#if SERIAL_INFO == true
  Serial.println("[FLASH] beginning flash");
#endif
  flash = W25N();
  if(flash.begin(FLASH_CS, &spi)){
#if SERIAL_INFO == true
    Serial.println("[FLASH] flash error!");
#endif
    error("Flash init failed.", ERROR_CODE_FLASH);
  }
}

REGISTER_TEST(FLASH_TEST){
  char buf[512] = {};
  const unsigned int randomNumber = esp_random();
  const String randomString = String(randomNumber);

  memcpy(buf, randomString.c_str(), strlen(randomString.c_str()) + 1);
  //erase pages 0-63 (block is 64 pages) (sets pages to all 1)
  if (flash.blockErase(0)){
    error("Block did not erase.", ERROR_CODE_FLASH_TEST);
  }
  flash.block_WIP();
  // send max 2111 bytes to buffer
  if (flash.loadProgData(0, buf, strlen(randomString.c_str())+1)){
    error("Program did not load.", ERROR_CODE_FLASH_TEST);
  }
  flash.block_WIP();
  // load data in page 0
  if (flash.ProgramExecute(0)){
    error("Program did not execute.", ERROR_CODE_FLASH_TEST);
  }
  flash.block_WIP();

  if (flash.pageDataRead(0)){
    error("Page did not read.", ERROR_CODE_FLASH_TEST);
  }
  flash.block_WIP();
  // read out of buffer from index 0 to data len
  if(flash.read(0, buf, sizeof(buf))){
    error("Page did not read from buffer.", ERROR_CODE_FLASH_TEST);
  }
  if (strncmp(randomString.c_str(), buf, sizeof(buf)) != 0){
#if SERIAL_INFO == true
      Serial.println("String is incorrect. (FLASH ERROR)");
      Serial.println("Expecting:");
      Serial.println(randomString);
      Serial.println("Got:");
      Serial.println(buf);
#endif
    error("String is incorrect.", ERROR_CODE_FLASH_TEST);
  }
}

void initI2C(){
  if (!Wire.begin(PIN_SDA, PIN_SCL, 100000L)){
    error("I2C could not be initialized.", ERROR_CODE_I2C);
  }
}

void initIMU(){
  if (!sensor_imu.begin_I2C()) {
    error("IMU could not be initialized.", ERROR_CODE_IMU);
  }
  sensor_imu.setAccelRange(LSM6DSO32_ACCEL_RANGE_32_G);
  sensor_imu.setAccelDataRate(LSM6DS_RATE_6_66K_HZ); // TODO test this
}

REGISTER_TEST(IMU_TEST)
{

}

void initBarometer(){
  if (!sensor_bar.begin_I2C(118, &Wire)){
    error("Barometer could not be initialized.", ERROR_CODE_BAROMETER);
  }
  bool res = true;
  res = res & sensor_bar.setTemperatureOversampling(BMP3_NO_OVERSAMPLING);
  res = res & sensor_bar.setPressureOversampling(BMP3_OVERSAMPLING_2X);
  res = res & sensor_bar.setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3);
  res = res & sensor_bar.setOutputDataRate(BMP3_ODR_100_HZ);
  if (!res)
  {
    error("Barometer settings could not be set.", ERROR_CODE_BAROMETER);
  }
}

REGISTER_TEST(BAROMETER_TEST)
{
  if (!sensor_bar.performReading()){
    error("Failed to read from barometer.", ERROR_CODE_BAROMETER_TEST);
  }
  if (!(300 < (sensor_bar.pressure/100.0) && (sensor_bar.pressure/100.0) < 1250))
  {
    error("Barometer pressure measurement out of range.", ERROR_CODE_BAROMETER_TEST);
  }

  if (!(-40 < (sensor_bar.temperature) && (sensor_bar.temperature) < 85))
  {
    error("Barometer temperature measurement out of range.", ERROR_CODE_BAROMETER_TEST);
  }
}


void initGPS(){
  GPSSerial.begin(GPSBaud, SERIAL_8N1, GPS_RXPin, GPS_TXPin);
}

void initEverything(){
  #if SERIAL_INFO == true
    Serial.println("Starting init!");
#endif
  initSerial();
  #if SERIAL_INFO == true
    Serial.println("Serial initialized!");
#endif
  // initLed();
  #if SERIAL_INFO == true
    Serial.println("RGB Led initialized!");
#endif
  initBuzzer();
  #if SERIAL_INFO == true
    Serial.println("Buzzer initialized!");
#endif
  initI2C();
  #if SERIAL_INFO == true
    Serial.println("I2C initialized!");
#endif
  initSPI();
  #if SERIAL_INFO == true
    Serial.println("SPI initialized!");
#endif
  initSD();
  #if SERIAL_INFO == true
    Serial.println("SD initialized!");
#endif
  initFlash();
  #if SERIAL_INFO == true
    Serial.println("Flash initialized!");
#endif
  initIMU();
  #if SERIAL_INFO == true
    Serial.println("IMU initialized!");
#endif
  initBarometer();
  #if SERIAL_INFO == true
    Serial.println("Barometer initialized!");
#endif
  initGPS();
  #if SERIAL_INFO == true
   Serial.println("GPS initialized!");
    Serial.println("Init done!");
#endif
}

void testEverything(){
  for (const auto& test : test_registry) {
    Serial.print("Running test: ");
    Serial.println(test.first.c_str());
    test.second();
    Serial.print("Finished test: ");
    Serial.println(test.first.c_str());
  }
}

void initPeripherals(){
  initEverything();
  delay(1000); // wait for sensors to settle
  testEverything();
}

More info:
The serial prints work
The sd card works on another pc, and has worked on board version 1
I have reseated the card multiple times

But I don't think it is an issue with the card itself, I think if no card was available, that the SD.begin function should have returned false then right? Currently, it returns nothing... (which is also why it is so hard to debug...)

So those essential bits were missing from the code you posted in post #1, but it still apparently complied, you are sure about that ?

I meant that my own code compiled, I am sorry I misunderstood.
I thought it was clear that that was a stripped down sample :slight_smile:

Here we are declaring and initialising a new SPIClass object called spi

Here we reference the same object and re-define it to be something else.

Just for the purpose of an experiment, change:

SPIClass spi;

to:

SPIClass spi(HSPI);

Then comment out:

// spi = SPIClass(HSPI);

in void initSPI().

It may or may not change anything but might be worth a try.

As an aside:

In this bit of code, there is an attempt to print to Serial before it has been initialized in initSerial() on the next line. Likewise in main, Serial.print() is being used before initPerpiherals() which calls initEvemrything():

Of course, that should have no bearing on subsequent messages, but I imagine that you are not seeing these two 'starting' messages?

This actually fixed it!
Maybe I don't understand c++ as good as I thought, lol.
Thanks!

I am not 100% sure what was going on here, but in the sketch I could see that the SPIclass object was first being created in the global context and then also in the "peripherals" module. The line SPIClass spi creates an objet of class SPIclass and then stores the adress of the object in the variable spi.

The object was then being redefined in void initSPI() to become SPIClass(HSPI). My suspicion is that this was actually creating a new class object with the HSPI characteristics. You might expect that its address is being stored in the same global spi variable so it should be accessible globally but I think there are two issues here.

  1. the first object is not being destroyed and is taking up memory
  2. the second object was being created in the context of void initSPI(). Would it be visible to void initSPI()? Yes, the spi variable Is in the global context, but the new object wasn't. I wasn't sure how that would behave.

I am sure there is someone more versed in the intricacies of what is going on here. I don't think it is quite the same as variable shadowing (global and local variable of the same name), unless this was also creating a new instance of the variable spi in the function context, but there does appear to have been a duplication of the SPIClass object. The easiest way to test whether that was the problem was to enure that just one SPIclass object with the neccessary configuration was created in the global context only. That would allow both funtions to "see" and reference that object.

Glad that solved it.

This same issue was raised a few days back, and the method of putting the SD card on a different SPI bus was explained;

https://forum.arduino.cc/t/how-to-change-default-spi-pins-with-esp32s3-and-arducam-mega-3mpx/1384983/8

However the OP caused quite a bit of confusion by posting an edited snipit of code which apparently did compile OK.