Testing SD Card Read/Write with ESP32

Introduction

The previous article introduced a circuit board I designed for a mobile storage device, and I have received the physical PCB. Now that I have soldered the circuit board, I would like to first test whether the SD card’s read and write capabilities are functioning properly.

Testing SD Card Read/Write with ESP32
img

Other articles in this series

  1. 1. Mobile Storage Device Based on ESP32
  2. 2. A Mobile Storage Device Based on ESP32’s Rich Connectivity – Circuit Design

Testing SD Card Functionality

Using Official Example

To quickly test the functionality of the SD card, I first found a C language example in ESP-IDF 5.3, located at <span>examples/storage/sd_card/sdmmc</span>. The pin connections for the SD card slot in the example differ from my PCB, so I need to modify the GPIO pin numbers for CLK, CMD, etc., via <span>idf.py menuconfig</span>. The modified results are as follows.

Testing SD Card Read/Write with ESP32
img

Then, I used the <span>idf.py build</span> command to compile the code and used <span>idf.py flash</span> to upload the firmware. After inserting the SD card, the testing went smoothly.

I (897) example:Reading file /sdcard/foo.txt
I (897) example:Read from file:'Hello SL16G!'
I (897) example:Opening file /sdcard/nihao.txt
I (1117) example:File written
I (1117) example:Reading file /sdcard/nihao.txt
I (1117) example:Read from file:'Nihao SL16G!'
I (1117) example:Card unmounted
I (1127) main_task:Returned from app_main()

Developing Rust Code

Having verified that my SD card hardware design works fine with the official example, let’s access the SD card using Rust. Although I found a Rust module called embeded-sdmmc through online searches, this project currently only supports SD cards connected via the SPI protocol. Since I chose the SDMMC interface for better read/write speeds, I cannot use this module. Further searches did not yield any other modules supporting SDMMC, so I will have to directly call the C interface from ESP-IDF.

By examining the code from the official example, we learned that the key interface is the <span>esp_vfs_fat_sdmmc_mount</span> function.

/**
 * @brief Convenience function to get FAT filesystem on SD card registered in VFS
 *
 * This is an all-in-one function which does the following:
 * - initializes SDMMC driver or SPI driver with configuration in host_config
 * - initializes SD card with configuration in slot_config
 * - mounts FAT partition on SD card using FATFS library, with configuration in mount_config
 * - registers FATFS library with VFS, with prefix given by base_prefix variable
 *
 * This function is intended to make example code more compact.
 * For real world applications, developers should implement the logic of
 * probing SD card, locating and mounting partition, and registering FATFS in VFS,
 * with proper error checking and handling of exceptional conditions.
 *
 * @note Use this API to mount a card through SDSPI is deprecated. Please call
 *       `esp_vfs_fat_sdspi_mount()` instead for that case.
 *
 * @param base_path     path where partition should be registered (e.g. "/sdcard")
 * @param host_config   Pointer to structure describing SDMMC host. When using
 *                      SDMMC peripheral, this structure can be initialized using
 *                      SDMMC_HOST_DEFAULT() macro. When using SPI peripheral,
 *                      this structure can be initialized using SDSPI_HOST_DEFAULT()
 *                      macro.
 * @param slot_config   Pointer to structure with slot configuration.
 *                      For SDMMC peripheral, pass a pointer to sdmmc_slot_config_t
 *                      structure initialized using SDMMC_SLOT_CONFIG_DEFAULT.
 * @param mount_config  pointer to structure with extra parameters for mounting FATFS
 * @param[out] out_card  if not NULL, pointer to the card information structure will be returned via this argument
 * @return
 *      - ESP_OK on success
 *      - ESP_ERR_INVALID_STATE if esp_vfs_fat_sdmmc_mount was already called
 *      - ESP_ERR_NO_MEM if memory can not be allocated
 *      - ESP_FAIL if partition can not be mounted
 *      - other error codes from SDMMC or SPI drivers, SDMMC protocol, or FATFS drivers
 */
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path,
                                  const sdmmc_host_t* host_config,
                                  const void* slot_config,
                                  const esp_vfs_fat_mount_config_t* mount_config,
                                  sdmmc_card_t** out_card);

This function takes a filesystem path, SD driver configuration, pin configuration, mount configuration, and outputs a handle for the SD card object. Once we prepare these configuration objects, we can read and write to the SD card. Since the official C example uses some macros to initialize <span>sdmmc_host_t</span> and <span>sdmmc_slot_config_t</span>, which are not accessible in Rust, we need to refer to the C source code to initialize these structures.

We initialize the <span>sdmmc_slot_config_t</span> structure in Rust, and the pin assignments need to follow the circuit design.

  fn get_slot_config() -> sdmmc_slot_config_t {
    sdmmc_slot_config_t {
        clk: 7,
        cmd: 6,
        d0: 15,
        d1: 16,
        d2: 4,
        d3: 5,
        d4: -1,
        d5: -1,
        d6: -1,
        d7: -1,
        __bindgen_anon_1: sdmmc_slot_config_t__bindgen_ty_1 {
            cd: 17,
        },
        __bindgen_anon_2: sdmmc_slot_config_t__bindgen_ty_2 {
            wp: -1,
        },
        width: 4,
        flags: SDMMC_SLOT_FLAG_INTERNAL_PULLUP,
    }
}

Construct the parameters <span>mount_config</span>, <span>mount_point</span>, and <span>host_config</span>.

let sdmmc_mount_config = esp_vfs_fat_sdmmc_mount_config_t {
    format_if_mount_failed: false,
    max_files: 4,
    allocation_unit_size: 16 * 1024,
    disk_status_check_enable: false,
    use_one_fat: false,
};

let mount_point = CString::new("/sdcard").unwrap();

let sd_host = sdmmc_host_t {
    flags: SDMMC_HOST_FLAG_1BIT | SDMMC_HOST_FLAG_4BIT | SDMMC_HOST_FLAG_8BIT | SDMMC_HOST_FLAG_DDR,
    slot: SDMMC_HOST_SLOT_1,
    max_freq_khz: SDMMC_FREQ_DEFAULT,
    io_voltage: 3.3,
    init: Some(sdmmc_host_init),
    set_bus_width: Some(sdmmc_host_set_bus_width),
    get_bus_width: Some(sdmmc_host_get_slot_width),
    set_bus_ddr_mode: Some(sdmmc_host_set_bus_ddr_mode),
    set_card_clk: Some(sdmmc_host_set_card_clk),
    set_cclk_always_on: Some(sdmmc_host_set_cclk_always_on),
    do_transaction: Some(sdmmc_host_do_transaction),
    __bindgen_anon_1: sdmmc_host_t__bindgen_ty_1 {
        deinit: Some(sdmmc_host_deinit),
    },
    io_int_enable: Some(sdmmc_host_io_int_enable),
    io_int_wait: Some(sdmmc_host_io_int_wait),
    command_timeout_ms: 0,
    get_real_freq: Some(sdmmc_host_get_real_freq),
    input_delay_phase: SDMMC_DELAY_PHASE_0,
    set_input_delay: Some(sdmmc_host_set_input_delay),
    dma_aligned_buffer: std::ptr::null_mut(),
    pwr_ctrl_handle: std::ptr::null_mut(),
    get_dma_info: Some(sdmmc_host_get_dma_info),
};

let slot_config = get_slot_config();

All parameters are prepared, and finally, we call <span>esp_vfs_fat_sdmmc_mount</span> to mount the filesystem.

let mut card_handle: *mut sdmmc_card_t = std::ptr::null_mut();

let ret = unsafe {
    esp_vfs_fat_sdmmc_mount(
        mount_point.as_ptr(),
        &sd_host,
        &slot_config as *const sdmmc_slot_config_t as *const c_void,
        &sdmmc_mount_config,
        &mut card_handle,
    )
};

match ret {
    ESP_OK => log::info!("SD Card mounted"),
    _ => {
        log::error!("Failed to mount SD Card");
        return;
    }
}

Next, we test the basic file read and write functionality of the SD card.

let file_res = std::fs::File::create_new("/sdcard/test.txt");
let mut file = match file_res {
    Ok(mut file) => {
        file.write_all(b"Hello, world!").unwrap();
        std::fs::File::open("/sdcard/test.txt").unwrap()
    }
    Err(_) => {
        std::fs::File::open("/sdcard/test.txt").unwrap()
    }
};

let mut content = String::new();
file.read_to_string(&mut content).unwrap();

log::info!("File content: {}", content);

The logs are as follows, and it seems that the SD card functionality is normal.

I (417) main_task:Started on CPU0
I (427) main_task:Calling app_main()
I (427) sd_card_test:Hello, world!
I (427) gpio: GPIO[7]|InputEn:0|OutputEn:0|OpenDrain:0|Pullup:1|Pulldown:0|Intr:0
I (437) gpio: GPIO[6]|InputEn:0|OutputEn:0|OpenDrain:0|Pullup:1|Pulldown:0|Intr:0
I (447) gpio: GPIO[15]|InputEn:0|OutputEn:0|OpenDrain:0|Pullup:1|Pulldown:0|Intr:0
I (457) gpio: GPIO[16]|InputEn:0|OutputEn:0|OpenDrain:0|Pullup:1|Pulldown:0|Intr:0
I (467) gpio: GPIO[4]|InputEn:0|OutputEn:0|OpenDrain:0|Pullup:1|Pulldown:0|Intr:0
I (477) gpio: GPIO[5]|InputEn:0|OutputEn:1|OpenDrain:0|Pullup:0|Pulldown:0|Intr:0
I (517) gpio: GPIO[5]|InputEn:0|OutputEn:0|OpenDrain:0|Pullup:1|Pulldown:0|Intr:0
I (527) sd_card_test: SD Card mounted
I (527) sd_card_test: File content:Hello, world!
I (527) main_task:Returned from app_main()

These are the key points for using the SD card in Rust; it is actually quite simple.

Postscript

Interested friends can find the complete code in the sd_card_rust project, and everyone is welcome to leave comments for discussion.

Link List

  1. 1. sd_card_rust – https://github.com/paul356/sd_card_rust

Leave a Comment