Skip to content

SD_MMC is unexpectedly slow in ESP32-P4 #12027

@cruzjuniel

Description

@cruzjuniel

Board

ESP32P4 Dev Module, 4D Systems ESP32-P4 MIPI Displays

Device Description

Only the SD card

Hardware Configuration

None

Version

v3.3.4

Type

Task

IDE Name

Arduino IDE

Operating System

Windows 11

Flash frequency

80MHz

PSRAM enabled

yes

Upload speed

921600

Description

The ESP32-P4 SD_MMC library seems to be underperforming at both 4-bit SDIO and 1-bit SDIO. We expect it at least perform around 2.5x the speed of 1-bit SDIO.

Using the same SD card, we performed a benchmark test for 4-bit and 1-bit SDIO for ESP32-P4 and 1-bit SDIO for ESP32-S3:

  • FAT Type: FAT32
  • Bytes per sector: 512
  • Sectors per cluster: 128
  • Cluster size (bytes): 65536
  • Total clusters: 243168
  • Volume size (MB): 2914
  • SD Card Type: SDHC
  • SD Card Size: 15203 MB
  • Test File Size: 524288 bytes
  • Test Buffer Size: 4096 bytes

We got the following results (all in MB/s):

Test ESP32-P4 (4-bit SDIO) ESP32-P4 (1-bit SDIO) ESP32-S3 (1-bit SDIO)
Sequential Write 4.21 0.70 2.74
Sequential Read 2.50 1.77 3.66
Random Write 0.54 0.53 0.59
Random Read 0.14 0.10 0.15

I found this discussion that was started months ago but there isn't any progress: #11719

We are particularly interested in read speed since our application primarily utilizes resources from the SD card. Notice that using 4-bit SDIO for P4 seems to be slower than S3 at 1-bit SDIO when performing sequential reads.

Sketch

#include "FS.h"
#include "SD_MMC.h"

// Benchmark Configuration
#define TEST_FILE_SIZE    (512 * 1024)  // 512KB test file (smaller for faster testing)
#define BUFFER_SIZE       4096          // 4KB buffer
#define NUM_ITERATIONS    3             // Number of test iterations

uint8_t testBuffer[BUFFER_SIZE];

// Read a raw sector from SD_MMC
bool readSector(uint32_t sector, uint8_t* buffer) {
  return SD_MMC.readRAW(buffer, sector);
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    delay(10);
  }

  Serial.println("\nESP32 SD_MMC Benchmark");
  Serial.println("======================");
  
  // Initialize test buffer with data
  for (size_t i = 0; i < BUFFER_SIZE; i++) {
    testBuffer[i] = i % 256;
  }
  
  // Initialize SD_MMC
  Serial.println("Initializing SD_MMC...");
  
  // SD_MMC.setPins(12, 11, 13); // gen4-ESP32Q-43 series (only 1-bit)
  // if (!SD_MMC.begin("/sdcard", true)) { // 1 bit
  if (!SD_MMC.begin("/sdcard")) {
    Serial.println("SD_MMC initialization failed!");
    return;
  }
  
  Serial.println("SD_MMC initialized successfully.");

  uint8_t sector[512];

  // --- Step 1: Read MBR ---
  if (!readSector(0, sector)) {
    Serial.println("MBR read failed");
    return;
  }

  // Partition 1 entry starts at byte 446
  uint32_t partLBA =
      sector[454] |
      (sector[455] << 8) |
      (sector[456] << 16) |
      (sector[457] << 24);

  Serial.printf("Partition starts at LBA: %u\n", partLBA);

  // --- Step 2: Read FAT Boot Sector ---
  if (!readSector(partLBA, sector)) {
    Serial.println("Boot Sector read failed");
    return;
  }

  // Extract FAT info
  uint16_t bytesPerSector = sector[11] | (sector[12] << 8);
  uint8_t sectorsPerCluster = sector[13];
  uint16_t reservedSectors = sector[14] | (sector[15] << 8);
  uint8_t fatCount = sector[16];
  uint32_t fatSize;

  uint16_t fat16Size = sector[22] | (sector[23] << 8);  // FAT16
  uint32_t fat32Size = sector[36] |
                       (sector[37] << 8) |
                       (sector[38] << 16) |
                       (sector[39] << 24);  // FAT32

  if (fat16Size != 0) {
    fatSize = fat16Size;
  } else {
    fatSize = fat32Size;
  }

  uint32_t totalSectors =
      (sector[19] | (sector[20] << 8));  // FAT16 field
  if (totalSectors == 0) {
    totalSectors = sector[32] |
                   (sector[33] << 8) |
                   (sector[34] << 16) |
                   (sector[35] << 24);  // FAT32 field
  }

  uint32_t dataStart =
      partLBA + reservedSectors + fatCount * fatSize;

  uint32_t dataSectors = totalSectors - (reservedSectors + fatCount * fatSize);
  uint32_t clusterCount = dataSectors / sectorsPerCluster;

  // FAT12/16 FS type string is at offset 0x36 (54)
  char fatId16[9];
  memcpy(fatId16, &sector[54], 8);
  fatId16[8] = 0;

  // FAT32 FS type string is at offset 0x52 (82)
  char fatId32[9];
  memcpy(fatId32, &sector[82], 8);
  fatId32[8] = 0;

  const char* fatType;

  if (strncmp(fatId32, "FAT32", 5) == 0) {
      fatType = "FAT32";
  } else if (strncmp(fatId16, "FAT16", 5) == 0) {
      fatType = "FAT16";
  } else if (strncmp(fatId16, "FAT12", 5) == 0) {
      fatType = "FAT12";
  } else {
      fatType = "Unknown";
  }

  Serial.printf("Detected FS: %s\n", fatType);  

  // Cluster size in bytes
  uint32_t clusterBytes = bytesPerSector * sectorsPerCluster;

  Serial.println("=== SD CARD FILESYSTEM INFO ===");
  Serial.printf("FAT Type: %s\n", fatType);
  Serial.printf("Bytes per sector: %u\n", bytesPerSector);
  Serial.printf("Sectors per cluster: %u\n", sectorsPerCluster);
  Serial.printf("Cluster size (bytes): %u\n", clusterBytes);
  Serial.printf("Total clusters: %u\n", clusterCount);
  Serial.printf("Volume size (MB): %u\n", (totalSectors * bytesPerSector) / (1024 * 1024));    
  
  // Print card information
  printCardInfo();
  
  // Run benchmark tests
  runBenchmark();
}

void loop() {
  // Add option to re-run benchmark
  Serial.println("\nPress 'r' to run benchmark again");
  
  while (!Serial.available()) {
    delay(100);
  }
  
  char command = Serial.read();
  if (command == 'r' || command == 'R') {
    runBenchmark();
  }
  
  // Clear serial buffer
  while (Serial.available()) {
    Serial.read();
  }
}

void printCardInfo() {
  uint8_t cardType = SD_MMC.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_MMC.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %llu MB\n", cardSize);
}

float testWriteSpeed(const char* filename, size_t fileSize) {
  File file = SD_MMC.open(filename, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return 0;
  }
  
  unsigned long startTime = micros();
  size_t bytesWritten = 0;
  
  while (bytesWritten < fileSize) {
    size_t toWrite = min((size_t)BUFFER_SIZE, fileSize - bytesWritten);
    size_t written = file.write(testBuffer, toWrite);
    if (written == 0) break;
    bytesWritten += written;
  }
  
  file.close();
  unsigned long endTime = micros();
  
  float duration = (endTime - startTime) / 1000000.0;
  float speed = (bytesWritten / (1024.0 * 1024.0)) / duration;
  
  return speed;
}

float testReadSpeed(const char* filename) {
  File file = SD_MMC.open(filename, FILE_READ);
  if (!file) {
    Serial.println("Failed to open file for reading");
    return 0;
  }
  
  uint8_t readBuffer[BUFFER_SIZE];
  unsigned long startTime = micros();
  size_t bytesRead = 0;
  
  while (file.available()) {
    size_t read = file.read(readBuffer, BUFFER_SIZE);
    if (read == 0) break;
    bytesRead += read;
  }
  
  file.close();
  unsigned long endTime = micros();
  
  float duration = (endTime - startTime) / 1000000.0;
  float speed = (bytesRead / (1024.0 * 1024.0)) / duration;
  
  return speed;
}

float testRandomReadSpeed(const char* filename) {
  File file = SD_MMC.open(filename, FILE_READ);
  if (!file) {
    Serial.println("Failed to open file for random read");
    return 0;
  }
  
  uint8_t readBuffer[512];
  unsigned long startTime = micros();
  const int numReads = 500;
  size_t fileSize = file.size();
  
  for (int i = 0; i < numReads; i++) {
    size_t pos = random(fileSize - sizeof(readBuffer));
    file.seek(pos);
    file.read(readBuffer, sizeof(readBuffer));
  }
  
  file.close();
  unsigned long endTime = micros();
  
  float duration = (endTime - startTime) / 1000000.0;
  float totalData = (numReads * sizeof(readBuffer)) / (1024.0 * 1024.0);
  return totalData / duration;
}

float testRandomWriteSpeed(const char* filename) {
  File file = SD_MMC.open(filename, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for random write");
    return 0;
  }
  
  // Ensure file is large enough
  file.seek(TEST_FILE_SIZE - 1);
  file.write(0);
  file.flush();
  
  unsigned long startTime = micros();
  const int numWrites = 300;
  
  for (int i = 0; i < numWrites; i++) {
    size_t pos = random(TEST_FILE_SIZE - BUFFER_SIZE);
    file.seek(pos);
    file.write(testBuffer, BUFFER_SIZE);
  }
  
  file.close();
  unsigned long endTime = micros();
  
  float duration = (endTime - startTime) / 1000000.0;
  float totalData = (numWrites * BUFFER_SIZE) / (1024.0 * 1024.0);
  return totalData / duration;
}

void runBenchmark() {
  Serial.println("\n=== SD_MMC Benchmark Started ===");
  Serial.printf("Test File Size: %u bytes\n", TEST_FILE_SIZE);
  Serial.printf("Buffer Size: %u bytes\n", BUFFER_SIZE);
  Serial.printf("Iterations: %u\n", NUM_ITERATIONS);
  
  // Test sequential write
  Serial.println("\n--- Sequential Write Test ---");
  float writeSum = 0;
  for (int i = 0; i < NUM_ITERATIONS; i++) {
    char filename[32];
    snprintf(filename, sizeof(filename), "/test_write_%d.bin", i);
    
    float speed = testWriteSpeed(filename, TEST_FILE_SIZE);
    Serial.printf("  Iteration %d: %.2f MB/s\n", i + 1, speed);
    writeSum += speed;
    
    SD_MMC.remove(filename);
    delay(50);
  }
  float avgWriteSpeed = writeSum / NUM_ITERATIONS;
  
  // Create test file for read tests
  const char* readTestFile = "/read_test.bin";
  testWriteSpeed(readTestFile, TEST_FILE_SIZE);
  delay(200);
  
  // Test sequential read
  Serial.println("\n--- Sequential Read Test ---");
  float readSum = 0;
  for (int i = 0; i < NUM_ITERATIONS; i++) {
    float speed = testReadSpeed(readTestFile);
    Serial.printf("  Iteration %d: %.2f MB/s\n", i + 1, speed);
    readSum += speed;
    delay(50);
  }
  float avgReadSpeed = readSum / NUM_ITERATIONS;
  
  // Test random read
  Serial.println("\n--- Random Read Test ---");
  float randomReadSum = 0;
  for (int i = 0; i < NUM_ITERATIONS; i++) {
    float speed = testRandomReadSpeed(readTestFile);
    Serial.printf("  Iteration %d: %.2f MB/s\n", i + 1, speed);
    randomReadSum += speed;
    delay(50);
  }
  float avgRandomRead = randomReadSum / NUM_ITERATIONS;
  
  // Test random write
  Serial.println("\n--- Random Write Test ---");
  float randomWriteSum = 0;
  for (int i = 0; i < NUM_ITERATIONS; i++) {
    char filename[32];
    snprintf(filename, sizeof(filename), "/random_write_%d.bin", i);
    
    float speed = testRandomWriteSpeed(filename);
    Serial.printf("  Iteration %d: %.2f MB/s\n", i + 1, speed);
    randomWriteSum += speed;
    
    SD_MMC.remove(filename);
    delay(50);
  }
  float avgRandomWrite = randomWriteSum / NUM_ITERATIONS;
  
  // Clean up
  SD_MMC.remove(readTestFile);
  
  // Print summary
  Serial.println("\n=== Benchmark Summary ===");
  Serial.printf("Average Sequential Write: %.2f MB/s\n", avgWriteSpeed);
  Serial.printf("Average Sequential Read:  %.2f MB/s\n", avgReadSpeed);
  Serial.printf("Average Random Read:      %.2f MB/s\n", avgRandomRead);
  Serial.printf("Average Random Write:     %.2f MB/s\n", avgRandomWrite);
  
  Serial.println("\nBenchmark completed!");
}

Debug Message

ESP-ROM:esp32p4-eco2-20240710
Build:Jul 10 2024
rst:0x1 (POWERON),boot:0x30f (SPI_FAST_FLASH_BOOT)
SPI mode:DIO, clock div:1
load:0x4ff33ce0,len:0x11dc
load:0x4ff29ed0,len:0xcac
load:0x4ff2cbd0,len:0x33a0
entry 0x4ff29ed0

ESP32 SD_MMC Benchmark
======================
Initializing SD_MMC...
SD_MMC initialized successfully.
Partition starts at LBA: 2048
Detected FS: FAT32
=== SD CARD FILESYSTEM INFO ===
FAT Type: FAT32
Bytes per sector: 512
Sectors per cluster: 8
Cluster size (bytes): 4096
Total clusters: 3883520
Volume size (MB): 2914
SD Card Type: SDHC
SD Card Size: 15203 MB

=== SD_MMC Benchmark Started ===
Test File Size: 524288 bytes
Buffer Size: 4096 bytes
Iterations: 3

--- Sequential Write Test ---
  Iteration 1: 4.20 MB/s
  Iteration 2: 3.98 MB/s
  Iteration 3: 4.21 MB/s

--- Sequential Read Test ---
  Iteration 1: 2.47 MB/s
  Iteration 2: 2.48 MB/s
  Iteration 3: 2.47 MB/s

--- Random Read Test ---
  Iteration 1: 0.14 MB/s
  Iteration 2: 0.14 MB/s
  Iteration 3: 0.14 MB/s

--- Random Write Test ---
  Iteration 1: 0.52 MB/s
  Iteration 2: 0.54 MB/s
  Iteration 3: 0.54 MB/s

=== Benchmark Summary ===
Average Sequential Write: 4.13 MB/s
Average Sequential Read:  2.47 MB/s
Average Random Read:      0.14 MB/s
Average Random Write:     0.53 MB/s

Benchmark completed!

Press 'r' to run benchmark again

Other Steps to Reproduce

No response

I have checked existing issues, online documentation and the Troubleshooting Guide

  • I confirm I have checked existing issues, online documentation and Troubleshooting guide.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions