SD card write speed

RiccardoKM
Posts: 3
Joined: Tue Feb 15, 2022 3:18 pm

SD card write speed

Postby RiccardoKM » Tue Feb 15, 2022 3:30 pm

Hello,

I just benchmarked the write speed to an SD card and found out that it caps at about 400kB/s.
I was surprised of such a low speed and read posts where users claim writing speeds of more than 1MB/s (up to 9MB/s)
while others have speeds comparable to mine but could not figure out the difference in their configurations.
I tried to change parameters and models of SD card but cannot improve.

I think I'm missing something pretty evident. Any hint or solution would be appreciated.

Here a short code replicating what I'm doing (ESP-IDF release/v4.1 and ES-ADF v2.3, cannot change versions for the moment)
in a board alike the LyraT 4.3.

Thanks in advance for any help.

Code: Select all

//Code tested in ESP-IDF release v4.1 with ESP-ADF v2.3

#define BASESTR1000 "..." //a 1000 characters string

esp_periph_config_t periph_cfg = DEFAULT_ESP_PERIPH_SET_CONFIG();
esp_periph_set_handle_t set = esp_periph_set_init(&periph_cfg);
periph_sdcard_cfg_t sdcard_cfg = {
    .root = "/sdcard",
    .card_detect_pin = get_sdcard_intr_gpio(),
    .mode = SD_MODE_4_LINE,
};

esp_periph_handle_t sdcard_handle = periph_sdcard_init(&sdcard_cfg);
//In ESP-ADF components/esp_pheripherals/lib/sdcard/sdcard: host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; => 40MHz 
esp_err_t ret = esp_periph_start(set, sdcard_handle);
while(!periph_sdcard_is_mounted(sdcard_handle)){vTaskDelay(500 / portTICK_PERIOD_MS);}

FILE * test = fopen("/sdcard/test", "w");

TickType_t startTick = xTaskGetTickCount();

for(int i=0; i<1000; i++){fwrite((const void *)BASESTR1000, strlen(BASESTR1000), 1, test);}

TickType_t endTick = xTaskGetTickCount();

fclose(test);

ESP_LOGI(TAG, "Benchmark writing 1MB in %d milliseconds (%.2fMB/s)", endTick-startTick, 1000./(endTick-startTick));

/////////OUTPUT: Benchmark writing 1MB in 2156 milliseconds (0.46MB/s)
/////////From the logs in verbose mode, the SD card is mounted in 4-lines at 40MHz clock

ESP_Sprite
Posts: 9580
Joined: Thu Nov 26, 2015 4:08 am

Re: SD card write speed

Postby ESP_Sprite » Wed Feb 16, 2022 3:18 am

Decently sure the fact that you're writing a low amount of bytes plus a non-power-of-2 leads the SD-card to need to do expensive sector read/modify-write operations. Make your string length a power of 2 and, say, 4K or 8K, and I think you'll get better speeds.

RiccardoKM
Posts: 3
Joined: Tue Feb 15, 2022 3:18 pm

Re: SD card write speed

Postby RiccardoKM » Wed Feb 16, 2022 8:56 am

@ESP_Sprite, thanks for your reply.
In the example I posted, I'm writing chunks of 1000 bytes at each call of fwrite. I verified afterwards the output file
and it's written correctly.
I tried writing chunks of 1024, 4096, 65536 bytes as well and I obtained the same writing speed.

To give some more details following some tests I made:
- the SD card is a class 4 (max 4MB/s). On the PC the read/write speed is actually about 4MB/s;
- changing SD_MODE_4_LINE to SD_MODE_1_LINE does not change the speed;

ESP_Sprite
Posts: 9580
Joined: Thu Nov 26, 2015 4:08 am

Re: SD card write speed

Postby ESP_Sprite » Wed Feb 16, 2022 8:59 am

Odd... only explanation I can find is that perhaps the others that had high-speed results wrote directly to the SD, without a FS layer, but that's it.

gingerologist
Posts: 7
Joined: Fri Nov 12, 2021 12:49 am

Re: SD card write speed

Postby gingerologist » Thu Feb 24, 2022 5:57 am

I encountered the same problem on the same board, lyrat 4.3. Writing 64MB data took over 150 seconds on a sandisk 32GB card in 1bit mode. and Changing to 4bit mode does not help at all. Also, using the sdmm_write_sectors() without fatfs got a similar result, which is very weird.

gingerologist
Posts: 7
Joined: Fri Nov 12, 2021 12:49 am

Re: SD card write speed

Postby gingerologist » Thu Feb 24, 2022 6:10 am

Also, read 64MB data took around 50s in 1bit mode and 40s in 4bit mode. No idea why.

The code are simple and straightforward:

```
sdmmc_card_t card;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;

sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.width = 1;

sdmmc_host_init();
sdmmc_host_init_slot(SDMMC_HOST_SLOT_1, &slot_config);
sdmmc_card_init(&host, &card);

sdmmc_card_info(&card);

int64_t before_time = esp_timer_get_time();
char *buf = (char *)malloc(64 * 1024);
memset(buf, 0, 64 * 1024);
for (int i = 0; i < 1024; i++) {
sdmmc_read_sectors(&card, buf, i * 128, 128);
}

ESP_LOGI(TAG, "direct sdmmc read 64MB consumes: %lld ms",
(esp_timer_get_time() - before_time) / 1024);

vTaskDelay(portMAX_DELAY);
```

RiccardoKM
Posts: 3
Joined: Tue Feb 15, 2022 3:18 pm

Re: SD card write speed

Postby RiccardoKM » Thu Feb 24, 2022 8:48 am

@ESP_sprite, I did some further tests:

- using a class 10 SD card (instead of the class 4 I was speaking above) I get just about a 10-15% speed improvement

Following the observations of @gingerologist, the issue seems:
- not linked to the SD card (a class 10 is more than twice as fast as a class 4 but the improvement in speed is marginal)
- not linked to the use of FATfs (direct sdmmc read/write does not change the speed)

Any insight you could give us on this behaviour would be extremely appreciated

TheVisionariesInk
Posts: 5
Joined: Mon Dec 20, 2021 11:05 am

Re: SD card write speed

Postby TheVisionariesInk » Sun Feb 27, 2022 6:49 pm

I have also been having issues with SD speed and have been troubleshooting.
I found a benchmarking sketch that helped me. (https://www.instructables.com/Select-SD ... for-ESP32/)

I modified it heavily to test with an ESP-Cam board. (MISO:2 MOSI:15 SCK:14 CS:13) and runs in 1bit/4bit/SPI modes. (well SPI is more a crawl)
I tested with this code on the current Arduino IDE and found the throughput to be very low (read of about 1KB/Sec).
After looking for other examples I tried the same code under PlatformIO's Arduino Framework, and it just worked.

Further reading got me to check the speed that the SPI interface is set at.
This is not the fix for the current ESP32 board package but makes a ridiculous improvement on the ones that do work.
By default the SD library sets the SPI speed at 4MHz.
After a lot of reading and testing I have found 40MHz works much better on an ESP32.
SD.begin(13,spi,40000000)
Then I started with older versions in Arduino.

From what I can see:
Arduino ESP32 board package V2.0.2 is based on SDK V4.4-beta (latest as at writing) : SPI effectively a fail, 1bit & 4bit are good but not terrific.
Arduino ESP32 board package V2.0.0 is based on SDK V4.4-dev : SPI works but second slowest, 1bit & 4bit is quickest on reads.
PlatformIO-Arduino Framework is V3.5.0 is based on SDK V3.3.5 (latest as at writing) : SPI works really well - 2 to 3 times the rate of V2.0.0, 1bit & 4bit are good.
Arduino ESP32 board package 1.0.6 is based on SDK V3.3.5 : Works almost same identically as PlatformIO (expected as is same version of SDK).

I have not been able to find the solution yet to get the speed of the SD-SPI rates of 1.0.6 in 2.0.x, but can at least get SD cards working in SPI mode in 2.0.0.
I have a large project that I am working on, and conversion to back to 1.0.6 is complicated due to dependencies, and I would also prefer to use SPI so I can use different pins to not conflict with flashing via serial.

I hope the details help, please post if you find a solution.

Here are my speed results across the 3 modes and across 7 block sizes. my code
Image

Code: Select all

/*
 * Original Benchmark code from : https://www.instructables.com/Select-SD-Interface-for-ESP32/ by 陳亮
 * 
 * Updated by James Wallis 2022
 * Connect the SD card to the following pins: *
 * SD Card | ESP32
 *    CMD      15
 *    VSS      GND
 *    VDD      3.3V
 *    CLK      14
 *    VSS      GND
 *    D0       2  //some notes say to add 1K pull up after flashing, seems to work without it.
 *    D1       4  //only for 4bit
 *    D2       12 //only for 4bit
 *    D3       13 //only for 4bit
 */
#define SD_MODE_4pin
#define SD_MODE_1pin
//#define SD_MODE_SPI
 
#include "FS.h"
#include "SD.h"
#include "SD_MMC.h"
#include "core_version.h"

int TEST_FILE_SIZE = (1024 * 1024); // 1 MB
//int  TEST_FILE_SIZE =(64* 1024); // 64K for when it gets so very slow.

void TestSDFile(fs::FS &fs, const char *path, uint8_t *buf, int len)
{
  // First do write benchmark
  unsigned long start_time = millis();
  File file = fs.open(path, FILE_WRITE);
  if (!file) { Serial.println("Failed to open file for writing"); return;  }  
  int  loop = TEST_FILE_SIZE / len;
  Serial.printf("%5d passes @ %2dKB = %d\t",loop,len/1024,TEST_FILE_SIZE);
  while (loop--) { if (!file.write(buf, len)) {Serial.println("Write failed");return; }}
  file.flush();
  file.close();
  unsigned long time_used = millis() - start_time;
  Serial.printf("%5d\t",  int(TEST_FILE_SIZE / time_used));
  
  // Secondly do read benchmark
  start_time = millis();
  file = fs.open(path);
  if (!file) {Serial.println("Failed to open file for reading"); return; }
  loop = TEST_FILE_SIZE / len;    
  while (loop--)  { if (!file.read(buf, len)) { Serial.println("Read failed"); return; }}     
  file.close();
  time_used = millis() - start_time;
  Serial.printf("%5d\r\n",  int(TEST_FILE_SIZE / time_used));
}  


void testIO(fs::FS &fs)
{
  uint8_t *buf = (uint8_t*) malloc(64 * 1024);   /* malloc will not reset all bytes to zero, so it is a random data */
  TestSDFile(fs, "/test_1k.bin", buf, 1024); 
  TestSDFile(fs, "/test_2k.bin", buf, 2 * 1024);
  TestSDFile(fs, "/test_4k.bin", buf, 4 * 1024);
  TestSDFile(fs, "/test_8k.bin", buf, 8 * 1024);
  TestSDFile(fs, "/test_16k.bin", buf, 16 * 1024);
  TestSDFile(fs, "/test_32k.bin", buf, 32 * 1024);
  TestSDFile(fs, "/test_64k.bin", buf, 64 * 1024);  
  Serial.println("Done.");
}

void setup() {
  Serial.begin(115200);
  delay(100);
  Serial.println("5 seconds to clear screen and insert SD");
  delay(5000);
  Serial.printf("SDK Version : %s    Arduino Version : %s\r\n",ESP.getSdkVersion(),ARDUINO_ESP32_RELEASE);
 
#ifdef SD_MODE_4pin
  Serial.println("4bit");
  if (!SD_MMC.begin("/sdcard", false)) Serial.println("Card Mount Failed");
  else testIO(SD_MMC);
#endif

#ifdef SD_MODE_1pin
  Serial.println("1bit");
  if (!SD_MMC.begin("/sdcard", true)) Serial.println("Card Mount Failed");
  else testIO(SD_MMC);
#endif

#ifdef SD_MODE_SPI
  Serial.println("SPI_HSPI"); 
  SPIClass spi = SPIClass(HSPI);  
  spi.begin(14 , 2  , 15  , 13   );// spi.begin(SCK,MISO,MOSI,CS);
 // SD.begin defaults to 4MHz, 
  if (!SD.begin(13,spi,40000000)) Serial.println("Card Mount Failed");
  else testIO(SD); 
#endif

}

void loop() {}
Attachments
SD_Benchmark.ino.txt
(3.18 KiB) Downloaded 476 times
SD_Benchmark.PNG
SD_Benchmark.PNG (58.21 KiB) Viewed 23284 times

TheVisionariesInk
Posts: 5
Joined: Mon Dec 20, 2021 11:05 am

Re: SD card write speed

Postby TheVisionariesInk » Mon Feb 28, 2022 5:19 am


TheVisionariesInk
Posts: 5
Joined: Mon Dec 20, 2021 11:05 am

Re: SD card write speed

Postby TheVisionariesInk » Tue Mar 01, 2022 8:07 am

Thanks for that link, it lead me down a few paths to test.

Arduino version 1.0.6 library works correctly
Arduino version 2.0.0 library works but is slow
Arduino version 2.0.2 library fails badly as we know

https://github.com/espressif/arduino-esp32/issues/6081
leads to
https://github.com/espressif/arduino-esp32/pull/6103

From those I compared the 1.0.6, 2.0.0, 2.0.2 versions of the sd_diskio.cpp, and benchmarked the performance.
----
From 2.0.0->2.0.2 function “ff_sd_status” has the following added (near line 610)

Code: Select all

    if(sdCommand(pdrv, SEND_STATUS, 0, NULL) == 0xFF)
    {
        log_e("Check status failed");
        return STA_NOINIT;
    }
https://github.com/espressif/arduino-esp32/pull/6103 recommends removing these lines.
By commenting the lines out, it works, but I still can only read and write at 2.0.0 speeds, but still fails sometimes
---
From 1.0.6->2.0.0 function “sdSelectCard” had the following lines added (near line 120)

Code: Select all

 
        digitalWrite(card->ssPin, HIGH);
        return false;
6103 recommends removing these lines, but I suggest that keeping the “return false;” it will give an error when it fails, and thus the ability to retry.

With both these sets of lines disabled, I can read and write correctly.
SPI access to the SD is about 10% of write and about 20% of read speed of 1bit SD mode.
If I override the SD speed to 40Mhz instead of the 4Mhz it defaults to, that jumps to 22% & 60% respectively.

Code: Select all

  spi.begin(14 , 2  , 15  , 13   );// spi.begin(SCK,MISO,MOSI,CS);
  if (!SD.begin(13,spi,40000000)) Serial.println("Card Mount Failed");
 

Who is online

Users browsing this forum: cistern and 252 guests