espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.6k stars 7.4k forks source link

Speeding up SD card writes #1117

Closed schlaegerz closed 5 years ago

schlaegerz commented 6 years ago

Hardware:

Board: ESP32 Dev module Core Installation/update date: 2/13/2018 IDE name: Arduino ID Flash Frequency: 40Mhz Upload Speed: ?115200?

Description:

I am trying to figure out how to speed up my SD card writes. I don't really care about the file structure of the SD card, and only want to write a lot of data to an SD card. I was hoping to get at least 1 MB/s of writing but I seem to be getting significantly lower than that no matter what I try I cant get much better than .25 MB/s.

I am not sure if this is a hardware issue, or something that I can fix with software, or if this is just the max speed I can expect.

I am using a micro sd card with an adapter like this:

https://www.amazon.com/SenMod-Adapter-Reader-Module-Arduino/dp/B01JYNEX56/ref=sr_1_2?s=pc&ie=UTF8&qid=1518737045&sr=1-2&keywords=micro+sd+card+adapter+board

I have tried the SD_MMC but haven't been able to get it to work.

Sketch:

#include <SD.h>

#define PIN_NUM_MISO 2
#define PIN_NUM_MOSI 15
#define PIN_NUM_CLK  14
#define PIN_NUM_CS   13
void setup() {
    Serial.begin(9600);
    delay(3000);
    auto SPI = SPIClass();
    SPI.begin(PIN_NUM_CLK, PIN_NUM_MISO, PIN_NUM_MOSI, PIN_NUM_CS);
    auto speed = 4000000;
    if(!SD.begin(PIN_NUM_CS, SPI, speed))
       {
           Serial.println("NOT INIT");
           return;
        }

    testFileIO( "/test.txt", 512, 1);
    testFileIO( "/test.txt", 1024, 1);
    testFileIO( "/test.txt", 2046, 1);

    testFileIO("/test.txt", 512, 10);
    testFileIO("/test.txt", 1024, 10);
    testFileIO("/test.txt", 2046, 10);

}

void loop() {

}

void testFileIO(const char * path, uint32_t buffSize, uint32_t numMB) {
    //File file = fs.open(path, FILE_WRITE);
    File file = SD.open(path, "w");
    size_t i;
    auto start = millis();
    uint8_t * buff = new uint8_t[buffSize];
    auto numToWrite = (numMB * 1024 * 1024) / buffSize;
    for (i = 0; i<numToWrite; i++) {
        //file.write(buf, buffSize);
        file.write(buff, buffSize);
        yield();
    }
    auto end = millis() - start;
    double seconds = end / 1000;
    if (seconds < 0)
    {
        seconds = 1;
    }
    double MBS = numMB / seconds;
    Serial.printf("%d: MB per Second: %6g :", end, MBS);
    Serial.printf("Seconds per MB: %6g: numMB %d buffSize %d\n", 1 / MBS, numMB, buffSize);
    file.close();
    delete[] buff;
}

I get: 5004: MB per Second: 0.2 :Seconds per MB: 5: numMB 1 buffSize 512 4330: MB per Second: 0.25 :Seconds per MB: 4: numMB 1 buffSize 1024 4812: MB per Second: 0.25 :Seconds per MB: 4: numMB 1 buffSize 2046 46069: MB per Second: 0.217391 :Seconds per MB: 4.6: numMB 10 buffSize 512 45727: MB per Second: 0.222222 :Seconds per MB: 4.5: numMB 10 buffSize 1024 45967: MB per Second: 0.222222 :Seconds per MB: 4.5: numMB 10 buffSize 2046

Debug Messages:

ghost commented 6 years ago

I see the same ~220KB/s write speed, so.. my suggestions are hypothetical. But I've had no issue with SPI speed = 25,000,000 (25MHz) on many SD cards, which should allow a faster write speed if the SD card can deliver it. That leads me to what would be my only other suggestion, I've had one SD card only write at ~22KB/s on the one-bit SPI interface (but is much faster in SDIO mode used by PC USB SD adaptors, and just reading it on SPI was much faster too) and other cheapish SD cards I own write at 220KB/s as I wrote above. I've not spent any large amounts of money on more highly rated SD cards to try - but if I needed fast writes, that would be my next move. 0.25MB/s is faster than I need though.

schlaegerz commented 6 years ago

Well on my esp8266 I was able to get it going faster by using this library, https://github.com/greiman/SdFat and mainly by making sure it just pre-allocated the file in one contiguous block, and by allowing it to do multi block writes. Sadly, the making of that library won't be making it work for esp32, is there any similar library that works for esp32 or any way to get more control of the functionality?

ghost commented 6 years ago

Are you sure that library doesn't work with ESP32? I see this issue saying it doesn't work https://github.com/greiman/SdFat/issues/88 however, I just tried it and reduced the SPI frequency from 50MHz to 10MHz and it ran the SdInfo.ino example without any error. I have "hacked" my ESP32 SPI library to make _spi_num "public" (and my very first statement in setup() is SPI._spi_num = HSPI; so when it gets around to initialise the default SPI it doesn't use VSPI which I don't use for my SD card). I don't think that small hack is important here though.

I personally use ChaNFS, but that's got glue functions to implement for access to the hardware layer (see smoothieboard firmware for an example).

schlaegerz commented 6 years ago

When I include the headers for SDFat library there are a whole bunch of collisions and it looks like it will be a pain to even get it to compile. I am not sure what issues will pop up,

ghost commented 6 years ago

Ah, maybe you have another SDFat library in your "libraries" folder (I removed my old that one I had, to a temporary directory elsewhere) and I installed the new one correctly on my 2nd attempt (you've got to follow the instructions pretty closely). When I did follow the instructions, it didn't complain when compiling or linking (I had link issues on my first attempt, missing bits and pieces because I only had the "src" directory and not the "examples" and "extras" in the right place). I'm not sure I can help further - and I never had any "collisions" - but I would suggest you persist, practice makes perfect (and all that crap).

ghost commented 6 years ago

Oh, I did comment out two SysCall::Yeild(); function calls. But other than that,the HSPI thing, and the speed change from 50MHz to 10MHz, nothing else.

makserge commented 6 years ago

Hi jasoroony Could you please share patched version of SDFat ? I have the same troubles with get it working on ESP32

ghost commented 6 years ago

I didn't modify the SDFat library, but I did remove an older version first. I modified the SdInfo.ino: Line 150 - comment out SysCall::yield(); Line 183 - same as line 150 Line 195 - alter SD_SCK_MHZ(50) -> SD_SCK_MHZ(10)

lightcalamar commented 6 years ago

Hello jasoroony. You could explain the process of, or share your previous comment of

I have "hacked" my ESP32 SPI library to make _spi_num "public" (and my very first statement in setup() is SPI._spi_num = HSPI; so when it gets around to initialise the default SPI it doesn't use VSPI which I don't use for my SD card

Thanks!

ghost commented 6 years ago

In Arduino\hardware\espressif\esp32\libraries\SPI\src\SPI.h:

Change the class definition from:

class SPIClass
{
private:
int8_t _spi_num;
spi_t * _spi;
...

to instead say:

class SPIClass
{
public:
int8_t _spi_num;
private:
spi_t * _spi;
...
lightcalamar commented 6 years ago

Thank you very much for your attention. I have tried with the reader that I have, it is a simpe that has 10K pull-up resistors in the MISO, MOSI and SCLK lines, and it does not work. I believe it is possible to be this reader, I have used it in HSPI.

My reader is this;

Badge Badge

Are you very kind please tell what reader to use?

Thanks in advance.

ghost commented 6 years ago

I have mine wired into a Rep Rap 12864 full graphic LCD + SD card reader. The LCD is on VSPI alone (the LCD doesn't share SPI nicely, it's a real hog) while the SD is on HSPI along with an 8 bit serial latch for more digital outputs.

As such, I've got no pull-ups but those 3 lines go through a 74HC4050 to convert any 5v signal to 3.3v, which isn't really needed since I'm running on 3.3v. I do pass 5v to the LCD, so the whole circuit has its own power supply and 3.3v regulator.

If your device works with the normal SD library, it should work with this library. If it doesn't work, I'm afraid I can't suggest anything else that I know works. I thought pull up resistors could make the SPI bus less reliable at higher speeds? I don't really know though. You could try running very slow settings (1MHz) and see if it works better? Might give you a clue as to what's the problem.

themindfactory commented 6 years ago

hoping ghost is listening :-) is it easy to "hack" again the code to allow the SPI pins on the ESP32 to be not in the normal location?? I have a circuit that uses alternate pins... thanks!

alieslam commented 6 years ago

@ghost

I never had any "collisions"

Collisions occur if you are using some of the included libraries from esp32. With me I am having collisions with the Wifi library, but mostly any connectivity library has the same collisions like (HTTPClient, WiFiClientSecure, WebServer) libraries compiled tested it so far are all giving the same errors which happens specifically between the _default_fcntl.h preproessor values and the FatApiConstants.h from SdFat library.

I am doing some investigation about the SdFat library performance along with the official SD library, so that I can either switch to the SD library at all or make some modifications with the SdFat library to make it work with ESP32 arduino core. I am not sure about the solution yet but I think someone faced the same issue and this person solved the problem by renaming the constants in the SdFat library.

@schlaegerz That's what i faced so far, maybe there are other libraries with other collisions, if so please let me know.

esp32_sdfat_collision

stale[bot] commented 5 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] commented 5 years ago

This stale issue has been automatically closed. Thank you for your contributions.

harshilishere commented 4 years ago

@schlaegerz taking the reference of following link: https://esp32.com/viewtopic.php?t=2055 is there a way we can switch to 40Mhz speed? The write speed will then increase.

And at default speeds, how much time will it take to fetch a 1 or 2 mb file!?

schlaegerz commented 4 years ago

I'm attempting this again after a year or so and cannot for the life of me figure out how to wire this again to get it working, what is the best way to wire the sd card ti the dev module?

I would prefer not to use external resistors, but I have the option if need be.

I have both the 6-pin and 9-pin breakout boards for the sd card, but cannot get either one to work.

harshilishere commented 4 years ago

@schlaegerz hey i checked on the internet. It says everywhere that SPI is used. https://www.instructables.com/id/Micro-SD-Card-Tutorial/ for reference. Can you name exact board that you have? maybe then we could figure out the connections! and possible a pic or datasheet of it?

schlaegerz commented 4 years ago

So I am using the esp32 dev kit and have the following 9 pin as card adapter. C1C57DF4-1768-414E-A6F1-0794759AEE86 B332D826-D22C-4F71-A4BA-3F760DE50C91

I know the basics of how to get it to work, as I’ve been using an esp8266 with an sd card for years but I cannot get it to work with the esp32 right now, even with pins that are supposed to work

harshilishere commented 4 years ago

yeah the wiring actually seems okay to me......maybe there are voltage difference?! SD card works on 5v logic and esp works on 3.3v logic. So is that a problem?!

On Fri, Apr 17, 2020 at 5:54 AM schlaegerz notifications@github.com wrote:

So I am using the esp32 dev kit and have the following 9 pin as card adapter. [image: C1C57DF4-1768-414E-A6F1-0794759AEE86] https://user-images.githubusercontent.com/8098360/79506580-12f04600-7feb-11ea-88a6-9176ca7a921c.jpeg [image: B332D826-D22C-4F71-A4BA-3F760DE50C91] https://user-images.githubusercontent.com/8098360/79506587-184d9080-7feb-11ea-9cea-6def27e097d4.jpeg

I know the basics of how to get it to work, as I’ve been using an esp8266 with an sd card for years but I cannot get it to work with the esp32 right now, even with pins that are supposed to work

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/espressif/arduino-esp32/issues/1117#issuecomment-614965989, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOYZRIANDKEBANPC4YWJ32LRM6OSNANCNFSM4EQ6VN2Q .

ketan commented 4 years ago

Sort of using the SDFat library (which does not seem to be supported on ESP32), does anyone have alternatives to make SD writes faster than .25MB/s?

Thanks.

ketan commented 4 years ago

tl;dr;

Use SD_MMC interface: it offers a much better performance (2x at the very least), and the code is very similar to SPI.

#include "SD_MMC.h"
void setup() {
  // true for 1bit mode
  if(!SD_MMC.begin("/sd", true)){
      Serial.println("Card Mount Failed");
      return;
  }
  File f = SD_MMC.open("/foo.txt");
}

Long version

So I spent some time with SD card writes on my esp32 board. Here's what I found so far using a Samsung EVO Plus 32GB card and the sketch at the bottom of this coment.

As far as the code for SD_MMC is concerned, it's very similar to the SPI interface, mostly needs a couple of lines of change in code, but offers a significant performance improvement.

SPI/SD Performance

Here are the results of writing a 2MB file using various buffer sizes using the SD interface. SD uses the underlying SPI interface. As you can see, increasing the size of the size of the buffer increases the write speeds from about 230kbps (for 512 byte buffer) to about 420kbps (64k buffer). The read speeds don't change at all.

2 MB written using 512 byte buffer for 8948 ms @ 234.000000 KBps
2 MB read using 512 byte buffer for 6380 ms @ 328.000000 KBps
2 MB written using 1024 byte buffer for 10230 ms @ 205.000000 KBps
2 MB read using 1024 byte buffer for 6194 ms @ 338.000000 KBps
2 MB written using 2048 byte buffer for 7415 ms @ 282.000000 KBps
2 MB read using 2048 byte buffer for 6005 ms @ 349.000000 KBps
2 MB written using 4096 byte buffer for 6003 ms @ 349.000000 KBps
2 MB read using 4096 byte buffer for 6018 ms @ 348.000000 KBps
2 MB written using 8192 byte buffer for 5312 ms @ 394.000000 KBps
2 MB read using 8192 byte buffer for 5853 ms @ 358.000000 KBps
2 MB written using 16384 byte buffer for 4946 ms @ 424.000000 KBps
2 MB read using 16384 byte buffer for 5870 ms @ 357.000000 KBps
2 MB written using 32768 byte buffer for 4947 ms @ 423.000000 KBps
2 MB read using 32768 byte buffer for 5893 ms @ 355.000000 KBps
2 MB written using 65536 byte buffer for 4949 ms @ 423.000000 KBps
2 MB read using 65536 byte buffer for 5913 ms @ 354.000000 KBps

SD_MMC performance

There is an immediate 2x performance boost for 512 byte buffer (compared to SPI). Increasing the buffer size gets you significant performance boost.

2 MB written using 512 byte buffer for 4432 ms @ 473.000000 KBps
2 MB read using 512 byte buffer for 1901 ms @ 1103.000000 KBps
2 MB written using 1024 byte buffer for 2192 ms @ 956.000000 KBps
2 MB read using 1024 byte buffer for 2104 ms @ 996.000000 KBps
2 MB written using 2048 byte buffer for 1251 ms @ 1676.000000 KBps
2 MB read using 2048 byte buffer for 2111 ms @ 993.000000 KBps
2 MB written using 4096 byte buffer for 850 ms @ 2467.000000 KBps
2 MB read using 4096 byte buffer for 1943 ms @ 1079.000000 KBps
2 MB written using 8192 byte buffer for 688 ms @ 3048.000000 KBps
2 MB read using 8192 byte buffer for 1957 ms @ 1071.000000 KBps
2 MB written using 16384 byte buffer for 581 ms @ 3609.000000 KBps
2 MB read using 16384 byte buffer for 2190 ms @ 957.000000 KBps
2 MB written using 32768 byte buffer for 610 ms @ 3437.000000 KBps
2 MB read using 32768 byte buffer for 2036 ms @ 1030.000000 KBps
2 MB written using 65536 byte buffer for 581 ms @ 3609.000000 KBps
2 MB read using 65536 byte buffer for 1989 ms @ 1054.000000 KBps

The pin connections

These are documented in code, but I'll reproduce it here for clarity:

const int ss_cs = 13;
const int mosi = 15;
const int sck = 14;
const int miso = 2;

The test program

// uncomment to use SPI. comment to use SD_MMC
//#define USE_SPI

#include "math.h"

#include "FS.h"

#ifdef USE_SPI
#include "SD.h"
#include "SPI.h"
#define SD_CARD_FS SD

const int ss_cs = 13;
const int mosi = 15;
const int sck = 14;
const int miso = 2;
#else
#include "SD_MMC.h"
#define SD_CARD_FS SD_MMC
#endif

void testFileIO(fs::FS &fs, const char * path, uint32_t buffSize, uint32_t numMB) {
  uint8_t * buff = new uint8_t[buffSize];

  File file = fs.open(path, FILE_WRITE);
  if (file) {
    size_t i;
    uint32_t start = millis();
    auto numToWrite = (numMB * 1024 * 1024) / buffSize;
    for (i = 0; i < numToWrite; i++) {
      file.write(buff, buffSize);
      yield();
    }
    uint32_t end = millis() - start;
    float kbps = numMB * 1024 * 1024 / end;

    Serial.printf("%u MB written using %d byte buffer for %u ms @ %f KBps\n", numMB, buffSize, end, kbps);
    file.close();
  } else {
    Serial.println("Failed to open file for writing");
  }

  file = fs.open(path);
  if (file) {
    uint32_t len = file.size();
    size_t flen = len;
    uint32_t start = millis();
    while (len) {
      size_t toRead = len;
      if (toRead > buffSize) {
        toRead = buffSize;
      }
      file.read(buff, toRead);
      len -= toRead;
    }
    uint32_t end = millis() - start;
    float kbps = numMB * 1024 * 1024 / end;
    Serial.printf("%u MB read using %d byte buffer for %u ms @ %f KBps\n", flen / 1024 / 1024, buffSize, end, kbps);
    file.close();
  } else {
    Serial.println("Failed to open file for reading");
  }

  delete[] buff;
}

void setup() {
  Serial.begin(115200);

#ifdef USE_SPI
  SPI.begin(sck, miso, mosi, ss_cs);
  if (!SD.begin(ss_cs, SPI)) {
    Serial.println("Card Mount Failed");
    return;
  }
#else
  if (!SD_MMC.begin("/sd", true)) {
    Serial.println("Card Mount Failed");
    return;
  }
#endif

  for (int i = 0; i <= 7; i++) {
    testFileIO(SD_CARD_FS, "/test.txt", 512 * pow(2, i), 2);
  }
}

void loop() {

}
Joe91 commented 1 year ago

@ketan Thanks a lot for this code! I just ran it and found out, that there now seems to be a limit at 4K buffers. Everything bigger thant that slows down the performance a lot. Do you have any idea what could cause that and where to search for? Thanks a lot in advance and best regards!

2 MB written using 512 byte buffer for 1168 ms @ 1795.000000 KBps
2 MB written using 1024 byte buffer for 1119 ms @ 1874.000000 KBps
2 MB written using 2048 byte buffer for 1123 ms @ 1867.000000 KBps
2 MB written using 4096 byte buffer for 971 ms @ 2159.000000 KBps
2 MB written using 8192 byte buffer for 6002 ms @ 349.000000 KBps
2 MB written using 16384 byte buffer for 6016 ms @ 348.000000 KBps
2 MB written using 32768 byte buffer for 6004 ms @ 349.000000 KBps
2 MB written using 65536 byte buffer for 6042 ms @ 347.000000 KBps

Arduino version: 2.0.9 ESP-IDF version: v4.4.4 platform = espressif32 6.3.1

ketan commented 1 year ago

Do you have any idea what could cause that and where to search for?

Sorry. I don't have an idea. I merely documented what I had observed at the time.