Custom SPI pins with ESP32 are not connecting to SD card

Hi,

I am trying to replicate the following project but I'm having trouble talking to the SD card.

Writing the provided compiled code the Tiny Basic works and I am able to read and write to the SD card. So I know the hardware works and it is possible.

But the code for setting custom pin settings I keep finding doesn't seem to work. The pin configurations don't match HSPI or VSPI. Some posts imply either will work but other posts say HSPI and VSPI use specific pins, neither of which correspond to this setup.

If I understood one post correctly, I can use any pins I want but the frequency would be limited to up to 40 MHz.

I am able to get the blue built-in light to light up just like the Tiny Basic version does but it is always telling me "SD Initialization failed!" and the card is unrecognized. Also, when I do this, the serial communication stops working.

Any idea what I might be missing?

// Neither <> nor "" make a difference.
//#include <SPI.h>
//#include <SD.h>
#include "SD.h"
#include "SPI.h"

#define SD_CS_PIN        13
#define SD_CLK_PIN       14
#define SD_MOSI_PIN      12
#define SD_MISO_PIN       2

#define MHz 1000000

void setup() {

  Serial.begin(115200);
  Serial.println("Serial communication started.");

  pinMode(SD_CS_PIN, OUTPUT); // SS

  //SPI.end(); - doesn't seem to do anything

  SPIClass spiSD = SPIClass(HSPI); // Neither HSPI nor VSPI seem to work
  spiSD.begin(SD_CLK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN); 

  if (!SD.begin(SD_CS_PIN, spiSD))//, 1 * MHz)) - no freq work: 1, 25, 40, 50, 80 MHz
      Serial.println("SD Initialization failed!");

  uint8_t cardType = SD.cardType();
  ...
}

(Sorry "new users can only include 2 links" ... for some reason)

ESP board:
Amazon dp/B07QCP2451
KeeYees Development Board 2.4 GHz Dual Core WLAN WiFi + Bluetooth 2-in-1 Microcontroller ESP-WROOM-32 Chip CP2102 for ESP32 for Arduino (2PCS)

SD card:
Amazon /gp/product/B07BJ2P6X6
HiLetgo 5pcs Micro SD TF Card Adater Reader Module 6Pin SPI Interface Driver Module with chip Level Conversion for Arduino UNO R3 MEGA 2560 Due

Thanks

Why not use the natural SPI pins of an ESP32? Does your library do software SPI, which is much slower?

By declaring your SPI object and instantiating in setup() the created SPI object losses its scope. C Variable Scope (w3schools.in)

I already have the hardware and it is using the pins that the original author chose. I'm new to Arudino and all this stuff. This is my learning project. I think I would have done it differently myself. I will probably make my own board later that makes better use of the things I've learned.

I'm just declaring that stuff in setup now just to get something (anything) working. I will shuffle all that stuff around later.

I've learned a lot from this project and even though using these pins isn't ideal, I want to take advantage of this problem to gain more understanding.

As it does work with the TinyBasic version, any idea on what I need to do to get it to work?

Thanks

I recently saw the following but it had no effect. I was already setting the CS pin, not that it seemed to make a difference.

pinMode(SD_MOSI_PIN, OUTPUT);
pinMode(SD_MISO_PIN, INPUT);
pinMode(SD_CLK_PIN, OUTPUT);
pinMode(SD_CS_PIN, OUTPUT);

I have also seen people sometimes passing -1 instead of the CS pin to the spi.begin(). That didn't work for me either.

And how do I know if the frequency is the issue and how to set it if it is. How does one determine the frequency needed?

I also discovered there is an FSPI:

FSPI = 1
HSPI = 2
VSPI = 3

FSPI caused the board to panic.

But if you look at the pin diagram for this ESP32, it shows some pins with VSPI but none with HSPI. If you're choosing your own pins to use, do those pins even matter? Does this mean I stand a better chance to do VSPI with custom pins?

This is very frustrating. Any help would really be appreciated.

Thanks

You can try this not-tested-yet simple code to use SPI2 on ESP32 for SD

#include <SPI.h>
#include <SD.h>

#define SD_CS_PIN        15  // or 5

// These pins will be use for SPI2
#define SD_CLK_PIN       14
#define SD_MOSI_PIN      13
#define SD_MISO_PIN      12

void setup()
{
  Serial.begin(115200);
  Serial.println("Serial communication started.");

  pinMode(SD_CS_PIN, OUTPUT); // SS

  SPIClass SPI2(HSPI);

  if (!SD.begin(SD_CS_PIN, SPI2))
  {
    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");
  }
}

void loop()
{
  // put your main code here, to run repeatedly:
}

Sadly, using the same code except my pin numbers, it doesn't work. It doesn't even light the blue onboard light.

Using a new ESP board not attached to my existing circuit board and using the VSPI pins specified by KeeYees diagram (CS=5, CLK=18, MOSI=23, MISO=19), not surprisingly it works.

With that same new ESP board using your (@khoih-prog) pins (CS=15, CLK=14, MOSI=13, MISO=12) and HSPI, it works.

Using the pins specified by my circuit board (CS=13, CLK=14, MOSI=12, MISO=2) and doing HSPI and

SPIClass SPI2(HSPI);
SPI2.begin(SD_CS_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);

if (!SD.begin(SD_CS_PIN, SPI2))
    Serial.println("SD Initialization failed!");

... it ... worked! ... nnnnnkay.

So I could have a problem with:

  • the circuit board I've been using
  • maybe the FabGl library is interfering in some way (even thought TinyBasic used it)
  • a defect in that ESP board

The copy of that board running TinyBasic works. I checked my connections but maybe I missed something. Or ... something is amiss somewhere.

At least I have something to go on.

Serial communication started.
SD Card Type: SDSC
SD Card Size: 1886MB

:face_exhaling: Back to testing ... When I find the real cause I'll be sure to post it.

Thanks

Good to know that you'e doing better now.

I've just tested and be sure the similar code is working for SPI2 on ESP32


Code


#include <SPI.h>
#include <SD.h>

#define SD_CS_PIN        5    //15  // or 5

// These pins will be use for SPI2
#define SD_CLK_PIN       14
#define SD_MOSI_PIN      13
#define SD_MISO_PIN      12

SPIClass SPI2(HSPI);

void listDir(fs::FS &fs, const char * dirname, uint8_t levels) 
{
  Serial.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  
  if (!root) 
  {
    Serial.println("Failed to open directory");
    return;
  }
  
  if (!root.isDirectory()) 
  {
    Serial.println("Not a directory");
    return;
  }

  File file = root.openNextFile();
  
  while (file) 
  {
    if (file.isDirectory()) 
    {
      Serial.print("  DIR : ");
      Serial.println(file.name());
      
      if (levels) 
      {
        listDir(fs, file.path(), levels - 1);
      }
    } 
    else 
    {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
    }
    
    file = root.openNextFile();
  }
}

void createDir(fs::FS &fs, const char * path) 
{
  Serial.printf("Creating Dir: %s\n", path);
  
  if (fs.mkdir(path)) 
  {
    Serial.println("Dir created");
  } 
  else 
  {
    Serial.println("mkdir failed");
  }
}

void removeDir(fs::FS &fs, const char * path) 
{
  Serial.printf("Removing Dir: %s\n", path);
  
  if (fs.rmdir(path)) 
  {
    Serial.println("Dir removed");
  } 
  else 
  {
    Serial.println("rmdir failed");
  }
}

void readFile(fs::FS &fs, const char * path) 
{
  Serial.printf("Reading file: %s\n", path);

  File file = fs.open(path);
  
  if (!file) 
  {
    Serial.println("Failed to open file for reading");
    return;
  }

  Serial.print("Read from file: ");
  
  while (file.available()) 
  {
    Serial.write(file.read());
  }
  
  file.close();
}

void writeFile(fs::FS &fs, const char * path, const char * message) 
{
  Serial.printf("Writing file: %s\n", path);

  File file = fs.open(path, FILE_WRITE);
  
  if (!file) 
  {
    Serial.println("Failed to open file for writing");
    return;
  }
  
  if (file.print(message)) 
  {
    Serial.println("File written");
  } 
  else 
  {
    Serial.println("Write failed");
  }
  
  file.close();
}

void appendFile(fs::FS &fs, const char * path, const char * message) 
{
  Serial.printf("Appending to file: %s\n", path);

  File file = fs.open(path, FILE_APPEND);
  
  if (!file) 
  {
    Serial.println("Failed to open file for appending");
    return;
  }
  
  if (file.print(message)) 
  {
    Serial.println("Message appended");
  } 
  else 
  {
    Serial.println("Append failed");
  }
  
  file.close();
}

void renameFile(fs::FS &fs, const char * path1, const char * path2) 
{
  Serial.printf("Renaming file %s to %s\n", path1, path2);
  
  if (fs.rename(path1, path2)) 
  {
    Serial.println("File renamed");
  } 
  else 
  {
    Serial.println("Rename failed");
  }
}

void deleteFile(fs::FS &fs, const char * path) 
{
  Serial.printf("Deleting file: %s\n", path);
  
  if (fs.remove(path)) 
  {
    Serial.println("File deleted");
  } 
  else 
  {
    Serial.println("Delete failed");
  }
}

void testFileIO(fs::FS &fs, const char * path) 
{
  File file = fs.open(path);
  static uint8_t buf[512];
  size_t len = 0;
  uint32_t start = millis();
  uint32_t end = start;
  
  if (file) 
  {
    len = file.size();
    size_t flen = len;
    start = millis();
    
    while (len) 
    {
      size_t toRead = len;
      
      if (toRead > 512) 
      {
        toRead = 512;
      }
      
      file.read(buf, toRead);
      len -= toRead;
    }
    
    end = millis() - start;
    Serial.printf("%u bytes read for %u ms\n", flen, end);
    file.close();
  } 
  else 
  {
    Serial.println("Failed to open file for reading");
  }

  file = fs.open(path, FILE_WRITE);
  
  if (!file) 
  {
    Serial.println("Failed to open file for writing");
    return;
  }

  size_t i;
  start = millis();
  
  for (i = 0; i < 2048; i++) 
  {
    file.write(buf, 512);
  }
  
  end = millis() - start;
  
  Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end);
  file.close();
}

void setup()
{
  Serial.begin(115200);
  while (!Serial && millis() < 5000);

  Serial.print(F("\nStart ESP32_SD_SPI2_FileTest on ")); Serial.println(ARDUINO_BOARD);

  Serial.println(F("Current SPI pinout:"));
  Serial.print(F("SD_MOSI_PIN:")); Serial.println(SD_MOSI_PIN);
  Serial.print(F("SD_MISO_PIN:")); Serial.println(SD_MISO_PIN);
  Serial.print(F("SD_CLK_PIN:"));  Serial.println(SD_CLK_PIN);
  Serial.print(F("SD_CS_PIN:"));   Serial.println(SD_CS_PIN);

  Serial.println("Serial communication started.");

  pinMode(SD_CS_PIN, OUTPUT); // SS

  if (!SD.begin(SD_CS_PIN, SPI2))
  {
    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");
  }

  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);

  listDir(SD, "/", 0);
  createDir(SD, "/mydir");
  listDir(SD, "/", 0);
  removeDir(SD, "/mydir");
  listDir(SD, "/", 2);
  writeFile(SD, "/hello.txt", "Hello ");
  appendFile(SD, "/hello.txt", "World!\n");
  readFile(SD, "/hello.txt");
  deleteFile(SD, "/foo.txt");
  renameFile(SD, "/hello.txt", "/foo.txt");
  readFile(SD, "/foo.txt");
  testFileIO(SD, "/test.txt");
  Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
  Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}

void loop()
{
  // put your main code here, to run repeatedly:
  delay(1000);
}

Terminal Output

Start ESP32_SD_SPI2_FileTest on ESP32_DEV
Current SPI pinout:
SD_MOSI_PIN:13
SD_MISO_PIN:12
SD_CLK_PIN:14
SD_CS_PIN:5
Serial communication started.
SD Card Type: SDHC
SD Card Size: 30727MB
Listing directory: /
  FILE: test.txt  SIZE: 0
  FILE: foo.txt  SIZE: 13
  FILE: mydatalog.txt  SIZE: 10
Creating Dir: /mydir
Dir created
Listing directory: /
  FILE: test.txt  SIZE: 0
  FILE: foo.txt  SIZE: 13
  DIR : mydir
  FILE: mydatalog.txt  SIZE: 10
Removing Dir: /mydir
Dir removed
Listing directory: /
  FILE: test.txt  SIZE: 0
  FILE: foo.txt  SIZE: 13
  FILE: mydatalog.txt  SIZE: 10
Writing file: /hello.txt
File written
Appending to file: /hello.txt
Message appended
Reading file: /hello.txt
Read from file: Hello World!
Deleting file: /foo.txt
File deleted
Renaming file /hello.txt to /foo.txt
File renamed
Reading file: /foo.txt
Read from file: Hello World!
0 bytes read for 0 ms
1048576 bytes written for 3027 ms
Total space: 30715MB
Used space: 2MB
1 Like

I've used the ESP32 hardware SPI with two simultaneous displays:

For greater control of the SPI module on the ESP32, I use the ESP32's SPI API.

Using the ESP32's SPI API under the Arduino IDE working code.

The ESP32_SPI_API.h

#include <driver/spi_master.h>
#include "sdkconfig.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
////////////////////////////////////
//
//#define MAGTYPE  true
//#define XGTYPE   false
//////////////////////////
///////////////////////////
//
////////////////////////////
 uint8_t GetLowBits();
 int8_t GetHighBits();
 int fReadSPIdata16bits( spi_device_handle_t &h, int address );
 int fWriteSPIdata8bits( spi_device_handle_t &h, int address, int sendData );
 int fInitializeSPI_Devices( spi_device_handle_t &h, int csPin);
// spi_device_handle_t fInitializeSPI_Devices( int csPin);
int fInitializeSPI_Channel( int spiCLK, int spiMOSI, int spiMISO, spi_host_device_t SPI_Host, bool EnableDMA);

ESP32_SPI_API.cpp

#include "ESP32_SPI_API.h"
/////////////////////////////
///////////////////////////
uint8_t txData[2] = { };
uint8_t rxData[25] = { };
uint8_t low;
int8_t high;
//////
//////////////////////////////////
uint8_t GetLowBits()
{
  return low;
}
int8_t GetHighBits()
{
  return high;
}
////////////////////////////////////////
int fInitializeSPI_Channel( int spiCLK, int spiMOSI, int spiMISO, spi_host_device_t SPI_Host, bool EnableDMA)
{
  esp_err_t intError;
  spi_bus_config_t bus_config = { };
  bus_config.sclk_io_num = spiCLK; // CLK
  bus_config.mosi_io_num = spiMOSI; // MOSI
  bus_config.miso_io_num = spiMISO; // MISO
  bus_config.quadwp_io_num = -1; // Not used
  bus_config.quadhd_io_num = -1; // Not used
  intError = spi_bus_initialize( HSPI_HOST, &bus_config, EnableDMA) ;
  return intError;
}
//////
int fInitializeSPI_Devices( spi_device_handle_t &h, int csPin)
{
  esp_err_t intError;
  spi_device_interface_config_t dev_config = { };  // initializes all field to 0
  dev_config.address_bits     = 0;
  dev_config.command_bits     = 0;
  dev_config.dummy_bits       = 0;
  dev_config.mode             = 3 ;
  dev_config.duty_cycle_pos   = 0;
  dev_config.cs_ena_posttrans = 0;
  dev_config.cs_ena_pretrans  = 0;
  dev_config.clock_speed_hz   = 5000000;
  dev_config.spics_io_num     = csPin;
  dev_config.flags            = 0;
  dev_config.queue_size       = 1;
  dev_config.pre_cb           = NULL;
  dev_config.post_cb          = NULL;
  spi_bus_add_device(HSPI_HOST, &dev_config, &h);
  // return intError;
  // return h;
} // void fInitializeSPI_Devices()
///////////////////////////////////////////////////////////////
int fReadSPIdata16bits( spi_device_handle_t &h, int _address )
{
  uint8_t address = _address;
    esp_err_t intError = 0;
    low=0; high=0;
    spi_transaction_t trans_desc;
    trans_desc = { };
    trans_desc.addr =  0;
    trans_desc.cmd = 0;
    trans_desc.flags = 0;
    trans_desc.length = (8 * 3); // total data bits
    trans_desc.tx_buffer = txData;
    trans_desc.rxlength = 8 * 2 ; // Number of bits NOT number of bytes
    trans_desc.rx_buffer = rxData;
    txData[0] = address | 0x80;
    intError = spi_device_transmit( h, &trans_desc);
    low = rxData[0]; high = rxData[1];
  //  if ( intError != 0 )
  //  {
  //    Serial.print( " WHO I am LSM9DS1. Transmitting error = ");
  //    Serial.println ( esp_err_to_name(intError) );
  //  }
  return intError;
} // void fSendSPI( uint8_t count, uint8_t address, uint8_t DataToSend)
////
int fWriteSPIdata8bits( spi_device_handle_t &h, int _address, int _sendData )
{
  uint8_t address =  _address;
  uint8_t sendData = _sendData;
  esp_err_t intError;
  spi_transaction_t trans_desc;
  trans_desc = { };
  trans_desc.addr =  0;
  trans_desc.cmd = 0;
  trans_desc.flags = 0;
  trans_desc.length = (8 * 2); // total data bits
  trans_desc.tx_buffer = txData;
  trans_desc.rxlength = 0 ; // Number of bits NOT number of bytes
  trans_desc.rx_buffer = NULL;
  txData[0] = address  & 0x7F;
  txData[1] = sendData;
  intError = spi_device_transmit( h, &trans_desc);
  return intError;
//  //  if ( intError != 0 )
//  //  {
//  //    Serial.print( " LSM9DS1_REGISTER_CTRL_REG6_XL. Transmitting error = ");
//  //    Serial.println ( esp_err_to_name(intError) );
//  //  }
} // void fWriteSPIdata8bits(  spi_device_handle_t &h, uint8_t address, uint8_t sendData )
//

The function fInitializeSPI_Channel is used to define the pins on the ESP32 that the internal SPI module will connect to.

An example of a function call to initialize the SPI device.

bool fInitializeDevice( )
{
  esp_err_t intError;
  intError = fInitializeSPI_Channel( spiCLK, spiMOSI, spiMISO, HSPI_HOST, true);
  if ( intError == 0 )
  {
    return true;
  }
}

SPI_HOST is used to define the SPI device to be used such as VSPI or HSPI.

fInitializeSPI_Devices is used to setup the SPI module to use. The parameter spi_device_handle_t will hold the handle that will be generated that will be used to talk to the initialized SPI device.

An example of declaring the SPI_Handle variable:

spi_device_handle_t hAG;
spi_device_handle_t hM;

Here is an example of the calling function to initialize the device.

bool fInitializeM()
{
  esp_err_t intError;

  intError = fInitializeSPI_Devices( hM, csPinM );
  if ( intError == 0 )
  {
    if ( (int)hM != 0 )
    {
      return true;
    }
  }
} // bool fInitializeM()

The SPI device will operate in duplex mode with this setting
dev_config.mode = 3 The setting dev_config.queue_size = 1; sets a queue size that transmits and receives SPI data from a device in the background. A request for data can be sent to the SPI device and with a freeRTOS queue the response from the device is placed into a queue. The freeRTOS task can then be alerted that data has arrived and either tend to the awaiting SPI queued data or process the SPI queued data at a later time.

Another nice thing with using the ESP32's SPI API is the ability to set and tune the SPI buss speed to what the SPI device can handle. I tweaked the ```dev_config.clock_speed_hz = 5000000; parameter with an LSMDS91 and was able get a SPI buss speed of 5000000.

By using the ESP32's SPI API there is a direct link to the GPIO pin matrix and using custom pins should be possible.

Here is a link to the ESP32's API. API Reference - ESP32 - — ESP-IDF Programming Guide latest documentation. The link allows for the selection of the ESP32 model you are using.

Note, With the code examples provided in the APi is written for the IDF. Structure initialization is a bit different under the Arduino IDE.
see:

  spi_device_interface_config_t dev_config = { };  // initializes all field to 0
  dev_config.address_bits     = 0;

For the proper way to initialize a configuration structure under the Arduino IDE.

Here is a link to to the ESP32's SPI API, SPI Master Driver - ESP32 - — ESP-IDF Programming Guide latest documentation.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.