SdFat icon indicating copy to clipboard operation
SdFat copied to clipboard

Using SDIO and SPI at the same time

Open stevstrong opened this issue 4 years ago • 10 comments

Hi,

I observed that in V2 the SDIO usage has changed. In V1 this was implemented by declaring SdFatSdio sd; see https://github.com/greiman/SdFat/blob/master/examples/examplesV1/TeensySdioDemo/TeensySdioDemo.ino#L16 If I am not wrong, from the Teensy SDIO demo, the sd declaration is now identical to the case when using SPI. If this is true, then how can I distinguish between using either SDIO or SPI interface? Does it go by defining HAS_SDIO_CLASS to 0 or 1 in the configuration file?

However, this would mean that I cannot use both SDIO and SPI interfaces at the same time, right? STM32 chips would allow to use 2 cards, one on SDIO and the other on SPI2. How could that be supported by the SdFat lib?

stevstrong avatar Feb 25 '21 22:02 stevstrong

In the sd.begin(config) call:

bool begin(SdSpiConfig config); will use SPI.

bool begin(SdioConfig config); will use SDIO.

You can have a SDIO card and multiple cards on each SPI port. I have tested with four cards, SDIO, two on SPI and one on SPI1.

greiman avatar Feb 26 '21 06:02 greiman

Thanks for the clarification. If I use SdioConfig config, then HAS_SDIO_CLASS has to be set to 1 in the configuration file, right?

stevstrong avatar Feb 26 '21 08:02 stevstrong

You need to provide a SDIO driver for the SdioCard class and set HAS_SDIO_CLASS non-zero.

You may not be able to write a driver for the FIFO_SDIO option. Most STM32 SDMMC controllers are from the stone age. The DMA option has ugly performance.

Here are Teensy FIFO rates. The limit is the 50 MHz SDIO bus. Transfer into the controller goes at 100 MB/sec so a sector write takes 5 μs and you can avoid any SD write busy waits with the file.isBusy() call.

FIFO SDIO mode.
size,write,read
bytes,KB/sec,KB/sec
512,22632.34,22818.13
1024,22677.06,22847.53
2048,22661.44,22902.55
4096,22394.58,22927.59
8192,22103.79,22941.57
16384,22235.67,22953.87
32768,22654.40,22953.94
DMA SDIO mode - slow for small transfers.
size,write,read
bytes,KB/sec,KB/sec
512,633.41,2493.74
1024,660.85,2693.79
2048,1430.41,5077.50
4096,3269.56,8791.10
8192,5736.62,12984.50
16384,8815.10,17185.19
32768,13010.66,20073.34

I have a pile of STM32 boards and have hope for the second version of the STM32H7 Nucleo board. It has a 512 byte FIFO and conforms to a more recent SD Standard.

greiman avatar Feb 26 '21 14:02 greiman

There is a simpler more versatile way to add a driver to SdFat. I use it with USB devices and in my RTOS based projects since I hate Arduino.

You write a block driver or wrap an existing RTOS driver to provide the BlockDriver interface. I use a striped-down version of SdFat on RTOS systems. You see some strange artifacts of that in SdFat.

You use the driver like this:

class myBlockDriver : public BlockDeviceInterface {
  // code for BlockDeviceInterface.
  ....
} myDriver;

FsVolume vol;
FsFile file;

void setup() {
  // init myDriver here.
  
  if (!vol.begin(&myDriver)) {
    // handle error
  }
  if (!file.open("test.txt", FILE_WRITE)) {
    Serial.println("file.open failed");
    while(1) {}    
  }
  file.println("test line");
  file.close();
  vol.ls(LS_DATE | LS_SIZE);
}

Here is the code in SdFat that uses the BlockDevice level. When you call sd.begin(config) this happens.

 /** Initialize SD card and file system for SDIO mode.
   *
   * \param[in] sdioConfig SDIO configuration.
   * \return true for success or false for failure.
   */
  bool begin(SdioConfig sdioConfig) {
    return cardBegin(sdioConfig) && Vol::begin(m_card);
  }

greiman avatar Feb 26 '21 15:02 greiman

A real example of my USB wrapper for a USB key library:

class UsbMscDriver : public BlockDeviceInterface {
 public:
  UsbMscDriver(BulkOnly* bulk)  : m_lun(0), m_bulk(bulk) {}
  bool isBusy() {return false;}
  bool readSector(uint32_t sector, uint8_t* dst) {
    uint8_t rc = m_bulk->Read(m_lun, sector, 512, 1, dst);
    return 0 == rc;
  }
  uint32_t sectorCount() {
    return m_bulk->GetCapacity(m_lun);
  }
  bool syncDevice() {return true;}
  bool writeSector(uint32_t sector, const uint8_t* src) {
    return  0 == m_bulk->Write(m_lun, sector, 512, 1, src);    
  }
 
  bool readSectors(uint32_t sector, uint8_t* dst, size_t ns) {
    return 0 == m_bulk->Read(m_lun, sector, 512, ns, dst);   
  }
  bool writeSectors(uint32_t sector, const uint8_t* src, size_t ns) {
    return  0 == m_bulk->Write(m_lun, sector, 512, ns, src);      
  }

 private:
  uint8_t m_lun;
  BulkOnly* m_bulk;   
};

greiman avatar Feb 26 '21 15:02 greiman

Thanks for the info, I have already written an SDIO driver for my (Libmaple) core based on my forked SdFat V1.

I have observed in V2 the block read/write functions are now sector read/write. Is there anything more which I have to adapt for V2?

stevstrong avatar Feb 27 '21 08:02 stevstrong

If it compiles with the SdioCard class it should work.

Expect Your driver to be slower than SPI in V2 for writes of less than 4096 bytes, like the above DMA example.

Your driver has alignment problem for multiple block writes. If a file has has a other than a multiple of four bytes, SdFat will call your driver with unaligned data, even if your buffer is aligned, so you will use slow single block transfers.

The Teensy driver solves these problems by starting a multi-block read or write, continuing the transfer as long as possible and using the FIFO for unaligned transfers. I have done GB reads/writes as a single multi-block transfer.

SD cards are designed for devices like phones, with GBs of RAM, for huge file caches. So Cortex-M turns 150 MB/sec cards into KB/sec cards.

greiman avatar Feb 27 '21 16:02 greiman

Ok, I can compile an example using only sd.begin(SdioConfig(DMA_SDIO)), but I think there is still something I do not understand because is seems that also the SPI library is linked with the program in this case, although not used. Can you confirm? If yes, is there a way to avoid that?

stevstrong avatar Feb 28 '21 09:02 stevstrong

I managed to get a sketch up and running in Teensy 4.1 using both SDIO and the Arduino SPI1 interface (pins - MISO: 1, MOSI: 26, SCLK: 27), with dedicated SPI. As an IDE, I'm using PlatformIO. Downloaded the current version of SdFat today, so we're in v2.0, commit "a9f2b6f". I will be posting it here as an example for other people looking how to do it, since it gave me quite a headache.

It looks to me as if there's only one global volume reference in SdFat for SD file systems. We can declare and use different instances of the SdFs class, but there will always just be one unique Volume reference used to open a File. To indicate a change between volumes of the two SD cards, one must use the chvol() function of the SdFs class. I learned this by reading the TwoCards.ino example.

I'm attaching my PlatformIO project as a .zip file, so that if needed you can use the same version of SdFat as I did. But it should probably work with the next versions as well, if functions don't get deprecated. I'll also be adding the individual main.cpp file as a TXT:

main.txt SDIO-SPI_cards.zip

Hope it solves your problems! :)

P.S.: More details are written in the code comments.

xylow avatar Apr 12 '21 19:04 xylow

To indicate a change between volumes of the two SD cards, one must use the chvol() function of the SdFs class. I learned this by reading the TwoCards.ino example.

You can open a file on a specified volume. bool open(FsVolume* vol, const char* path, oflag_t oflag);

  SdFat sd1;
  SdFat sd2;
  File file1;
  File file2;
  ...
  if (!file1.open(&sd1, path1, <open options>)) {
   // handle error
  }
  if (!file2.open(&sd2, path2, <open options>)) {
   // handle error
  }

You can also set the working directory for each volume. sd1.chdir(dir1) set the volume working directory for sd1 and makes sd1 the working volume. sd2.chdir(dir2) sets the volume working directory for sd2 and makes sd2 the working volume. The above open calls will use dir1 on sd1 and dir2 on sd2. The current working volume is only used if you call open(path, );

You may want to use SdFat-beta with PlatformIO. I have made options in SdFatConfig.h conditional so you can define options in the IDE.

You can also use:

  file1 = sd1.open(path1, option1);
  file2 = sd2.open(paht2, option2);

greiman avatar Apr 13 '21 11:04 greiman