XIAO ESP32C3 Expansion Board SD Card MISO Line Stays Low - Fix

There are several other posts on various forums about the issue with SD cards in SPI mode and how the MISO is not released to high-Z when the chip select (CS) is returned high. I discovered this problem when testing out my new Xiao ESP32C3 board plugged down to the expansion board with the SD slot. I pulled up the example Arduino IDE sketch SD_Test, compiled it for XIAO_ESP32C3 and it ran perfect. I saw the disk info, etc reported to the serial monitor. Then I hit the reset button to see it again and I noticed the serial monitor just said “wait usb download”. For some reason it is now hanging out in bootloader mode? Each time I hit reset, I get the same thing, unless I eject the SD card and push it back in. Then it works again.

I looked at Xiao ESP32C3 schematics and Xiao Expansion Board schematics and noticed the MISO line (D9) is also used as the ESP32 BOOT button pin. This is a fixed chip feature, not an arbitrary pin selection by Seeed.

It turns out there is a much-propagated bug in the arduino SD card libraries using SPI such that the SD card is not properly clocked after CS goes high to cause the SD card to release MISO to high-Z. This is unlike most other SPI slave devices. Therefore, after the SD_Test sketch finished, the MISO line continues to pull low by the SD card, and when I hit reset, the ESP32 sees the BOOT pin pulled low and enters the bootloader.

I read about the SD card interface issue when I found the Arduino forum post “SD Card - MISO doesn’t release [Bad MISO, Bad MISO!!]”. The fix within that post doesn’t apply exactly to the XIAO_ESP32C3 library. I found the analogous cpp file called sd_diskio.cpp. It has a function called sdDeselectCard(). I added a single line to run the clock for a byte right after the CS goes high. This fixes it.

void sdDeselectCard(uint8_t pdrv)
{
    ardu_sdcard_t * card = s_cards[pdrv];
    digitalWrite(card->ssPin, HIGH);
    card->spi->transfer(0XFF);  // <- Add this line
}

Actually, this issue would happen with the Xiao ESP32C3 whether or not you are using the expansion board. As long as you are using D9 for MISO to the SD card. I suspect it would be an issue with any ESP32 board that shares the BOOT pin with MISO on D9 (GPIO9).

Hello gleebit,

we have the same problem with the XIAO ESP32C3. If we use the default MISO pin and after the reset the device will go in bootloading mode. But if we try to implement your solution we fail read and write file. We added the suggested line exactly as you did, into the file located “C:\Users<myname>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.7\libraries\SD\src” and called “sd_diskio.cpp” at line 117/118.

Without that specific code line (card->spi->transfer(0XFF)) all works fine except for the entering into bootloading after reset. I mean all works fine about sd card operativity.

Do you have read/write success? Do you have further suggestion?

Thank you in advance

@Picchio
Hi. I did have read/write success. After adding the line I discussed in the original post, the SD card continued to work correctly in all functions, AND the reset action worked correctly (because the MISO line was released after being clocked a few more cycles). That was the only line I needed to change. I noticed that you are using the version 2.0.7 and I am using version 2.0.6. Not sure if that will make a difference. You might want to compare the source, or just try downgrading to that version to see if it works?

I am using the SEEED XIAO ESP32 C3 with the SEEED expansion board.

Are you using any other devices on the same SPI channel? If so, I would just try a simple SD_Test sketch (from the Arduino IDE) with only the SD card attached.

Is your card formatted as FAT32? I would also try just a blank freshly formatted card. I’m not sure of the size limitations for this SD card library, or directory depth. I was using a few different cards that worked equally well. They were Gigastone 8GB and 16GB, and some non-branded 16GB cards, for what it’s worth.

I also noticed in my top level sketch I needed to create a constant for the slave select pin and set it to 4. Then I use that constant as an argument to SD.Begin().

This is my slave select pin definition:

const uint8_t SD_SS = 4;

Then when calling SD.Begin:

SD.begin(SD_SS);

I recall looking at the SD library and confirming that the other SPI pins were correct for the XIAO ESP32. Just the slave select was incorrect.

I hope this helps. Let us know what you find.

I will try 2.0.6 in order to see if there are differences or maybe I will compare two libraries.

Im using SEEED XIAO ESP32C3 with my own custom board (easy easy design).
And before that I used the same SEEED XIAO ESP32C3 with breadboard cables.

Both had the same result.

If I use the default MISO, I enter in bootload mode.
If I use the default MISO, I can read / write correctly.
If I apply the line code you suggest, I can not write file correctly (I can even read directories and files, but not delete them, not remove directories, and so on).

If I use as MISO the TX pin (GPIO21) all works fine.

I use global define for the CS (or SS) pin (#define MY_CS 20… I use the GPIO20, RX pin).

The SD card is Fat32, with a small dimension 2 GB, after I tried 8 GB and then 16 GB.

I don’t know.

Next step I will produce a board for our project with the GPIO21 pin connected to MISO of the SD card.
But in this way, 1 pin becomes unusable.

Any news or updates on this one ?
I think I have the same issues with the ESP32C3 with my custom board using default MISO.
I have done the fix but Im running v2.09 and it seems I can no longer read the SD.
Will be testing more tomorrow before I end up using a different pin.

@Steven_Bulmer
You said you can no longer read the SD. Does that mean you WERE reading the SD at one point after you did the fix? Using a different pin for MISO might be another adventure of itself, but worth a try. There are very few pins on this little device to work with, so hopefully you have another option that you aren’t using already. If you are using the expansion board, without any mods, then your SPI pin assignments are rigid and routed to the SD slot.

If I correctly interpret your statement as… you did the fix and were successfully reading SD, and then updated to v2.09, and now it’s broke, then I would expect the update to v2.09 wrote over your fix, and you would need to do the fix again. I haven’t done any work on this since my original post, so I don’t have any experience with other versions of the library. The basics of the fix is to clock the SD card for 1 byte, with anything, right after setting CS high. This just runs the clock to the SD card for 8 bits and causes the SD card to release the MISO pin. With a different library, you may have to dig to find where the CS is released and add an instruction to send another byte while CS is high. With CS high, the byte doesn’t go anywhere, but the clock cycles, and that’s the key.

If your issue is really the same as what I saw, then you would find that, without the fix, the SD card works fine until the end of the program. But at the end, the SD card is still holding the MISO line low. Then when you reset the device, that line is playing the role of the BOOT button pin and is seen as low and thinks you want to enter bootloader mode. You would have to remove the card and reset in order to get the boot pin to be seen as high (to skip bootloader and run your program). If you are experiencing some different type of symptom, then I’m not sure you are dealing with the same issue and “fix” as what I described.

Please keep us posted with what you find, for future readers.

Ive only recently started with ESP32C3 so never had anything other than v2.09
I have had the SDTest sketch working, writing and reading to a file on the SD card, but not working after a reset. I see MISO being held low and it enters boot mode.
I have applied the fix as mentioned, finding the sdDeselectCard function in sd_diskio.cpp, but after that fix, SDTest fails to read back the test file that it has just written too. I’ll be looking at this on the scope soon.

Initializing SD card…initialization done.
Writing to test.txt…done.
error opening test.txt

I have another SPI device hanging off the same bus, so I’ll be trying a few more things before I start hacking the board and changing the MISO line.

  • I have seen in the ESP32C3 documentation that there is a SPI_SLAVE_REG (0x00E0) with bits [1:0] setting SPI_CLK_MODE where 0x11 is SPI clock always on. I just have to work out how to write to this register in the Arduino Environment.
  • Use the ESP-IDF libraries in Platform IO and see if that library is any better.
  • Hack my PCB and move MISO elsewhere. Swap with a pin Im using as an LED.

Thanks for your help so far. Hope we can find a fix for me and then for others :wink:

@Steven_Bulmer
If you disconnect the other SPI device, and disable the other SPI device in software, and just have the SD card interface, with the additional byte clocking fix while CS high, does the SD test work then?

@Steven_Bulmer
Also, did you set the CS pin to 4 and pass that as an argument to the SD.begin() function? The SD library defaults have the Clk, MOSI and MISO as the correct pins, but not the CS.

Im testing on a board without the other SPI device populated yet so all these issues are just with the SD on SPI.
CS has been set correctly and I can see it behaving on a scope.

A bit of good news for me in that a colleague offered to help out and debug some bits on his pc.
He went back to v2.0.6 with the fix that was suggested ( card->spi->transfer(0XFF); ) and all was working as it should, so he removed the fix thinking it would take him back to the issue but it didnt. He then moved onto the latest v2.0.13 and still had no issues.
He then checked the other SPI device, which is an ADC, and that responds well. Next stage is to have ADC writing to SD card to see if they work together.

Summary is that some library on my computer is upsetting the SD on SPI. I will try and find any differences between my machine and his, whilst also trying a fresh install on another computer and see how that goes.

Too much of an issue for me to get the libraries exactly right.
If I eventually start to sell these I dont want the hassle of telling customers to install old libraries xyz just to make software compile.
At this stage my plan is to build my design with an ESP32C3 with no SD option and sell with an ESP32S3 with the SD option

Not ideal but it works and gives me two options.

Hi there,
Idunno if it’s of use to you but I have this working on the Xiao Expansion board
Xiao ESP32C3 this Code and this Output Testing the SD card AOK :wink: :ok_hand:

// adapted from Raui Shanchez
// Xiao Esp32C3 , 32 gig SD , formatted Fat 32 with 32k sector size.
// BSP 2.0.8
//
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#define SS D2


void setup(){
  Serial.begin(9600);
  delay (2000);
  Serial.println();
  Serial.println("Power ON \n ");  // Let's BEGIN!!
  Serial.println("Test program compiled on " __DATE__ " at " __TIME__);
  Serial.println();
  Serial.println("Processor came out of reset.");
  Serial.println();

 // pinMode(fspi->pinSS(), OUTPUT); //VSPI SS
 // pinMode(hspi->pinSS(), OUTPUT); //HSPI SS

 **digitalWrite(SS, HIGH); // <-- Set CS pin HIGH to deselect**
  Serial.println("--------------");
  Serial.println(SS);
  Serial.println(MOSI); // master out, slave in
  Serial.println(MISO); // master in, slave out
  Serial.println(SCK);  // clock
  Serial.println("--------------");

  if(!SD.begin(SS)){
    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(){
delay (500);
}

//

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.name(), 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();
}


Power ON 
 
Test program compiled on Mar 16 2024 at 13:16:16

Processor came out of reset.
--------------
4
10
9
8
--------------
SD Card Type: SDHC
SD Card Size: 29827MB
Listing directory: /
  DIR : System Volume Information
  FILE: test.txt  SIZE: 1048576
  FILE: foo.txt  SIZE: 13
Creating Dir: /mydir
Dir created
Listing directory: /
  DIR : System Volume Information
  FILE: test.txt  SIZE: 1048576
  FILE: foo.txt  SIZE: 13
  DIR : mydir
Removing Dir: /mydir
Dir removed
Listing directory: /
  DIR : System Volume Information
Listing directory: System Volume Information
Failed to open directory
  FILE: test.txt  SIZE: 1048576
  FILE: foo.txt  SIZE: 13
Writing file: /hello.txt
File written
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!
1048576 bytes read for 2439 ms
1048576 bytes written for 2544 ms
Total space: 29815MB
Used space: 1MB

compiler output

FQBN: esp32:esp32:XIAO_ESP32C3
Using board 'XIAO_ESP32C3' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.8
Using core 'esp32' from platform in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.8
--**--
Using library FS at version 2.0.0 in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.8\libraries\FS 
Using library SD at version 2.0.0 in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.8\libraries\SD 
Using library SPI at version 2.0.0 in folder: C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.8\libraries\SPI 
"C:\\Users\\Dude\\AppData\\Local\\Arduino15\\packages\\esp32\\tools\\riscv32-esp-elf-gcc\\esp-2021r2-patch5-8.4.0/bin/riscv32-esp-elf-size" -A "C:\\Users\\Dude\\AppData\\Local\\Temp\\arduino\\sketches\\12EDFA38154FA8D69920B5DEB88B17A8/sketch_mar16a_SD_Exp_c3.ino.elf"
Sketch uses 287826 bytes (21%) of program storage space. Maximum is 1310720 bytes.
Global variables use 16532 bytes (5%) of dynamic memory, leaving 311148 bytes for local variables. Maximum is 327680 bytes.
"C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\tools\esptool_py\4.5.1/esptool.exe" --chip esp32c3 --port "COM3" --baud 921600  --before default_reset --after hard_reset write_flash  -z --flash_mode dio --flash_freq 80m --flash_size 4MB 0x0 "C:\Users\Dude\AppData\Local\Temp\arduino\sketches\12EDFA38154FA8D69920B5DEB88B17A8/sketch_mar16a_SD_Exp_c3.ino.bootloader.bin" 0x8000 "C:\Users\Dude\AppData\Local\Temp\arduino\sketches\12EDFA38154FA8D69920B5DEB88B17A8/sketch_mar16a_SD_Exp_c3.ino.partitions.bin" 0xe000 "C:\Users\Dude\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.8/tools/partitions/boot_app0.bin" 0x10000 "C:\Users\Dude\AppData\Local\Temp\arduino\sketches\12EDFA38154FA8D69920B5DEB88B17A8/sketch_mar16a_SD_Exp_c3.ino.bin" 
esptool.py v4.5.1
Serial port COM3
Connecting...
Chip is ESP32-C3 (revision v0.3)
Features: WiFi, BLE
Crystal is 40MHz
MAC: a0:76:4e:3f:a3:88

HTH
GL :slight_smile: PJ
for me the biggest plus in repeatability impact after a push button reset.
was the line
digitalWrite(SS, HIGH); // ← Set CS pin HIGH to deselect
with out it it would hang sometimes? YMMV