prettyprint

2022年2月7日 星期一

EPS32 ESP-IDF開發環境儲存設備與檔案系統實驗(一) -- SDMMC 與SDSPI (A Study on ESP32 Storage Devices and File System--SDMMC and SDSPI using ESP-IDF)

    ESP32 MCU常用的儲存設備為SD(SDMMC & SDSPI) 或SPI Flash,本篇實驗記錄主樣針對SD ,而上層檔案系統使用FatFs,實驗使用ESP32-CAM開發版,因為本開發版已含有SD Card模組。有關SPI Flash探討將於下篇記錄中說明。

一、FatFS

    FatFs是一個為小型系統發展的FAT/exFAT檔案系統,軟體系統架構如下圖所示,

(本圖片取自FatFs官網,http://elm-chan.org/fsw/ff/00index_e.html)

MCU的Application使用統一的FatFs Module,透過中間層使用不同device提供的Low Level device controls去存取各自的devices(例如SD, Flash, RTC等)。

    FatFs Module 與 device controls是完全獨立的,每個設備提供自己的device controls,FatFs與storage device controls則透過Media Access Interface(MAI)中間層去銜接。如下同所示。

(本圖片取自FatFs官網,http://elm-chan.org/fsw/ff/00index_e.html)

FatFs MAI使用下列五個API銜接FatFs Module與Storage device controls:
  1. disk_status - 取得device status。
  2. disk_initialize - 初始化 device
  3. disk_read - Read data
  4. disk_write - Write data
  5. disk_ioctl - Control device dependent functions

註:

二、ESP32 FAT 檔案系統

    ESP-IDF使用FatFs library FAT檔案系統,可直接使用FatFs functions(f_xxxx),或與ESP-IDF VFS整合後使用 C standard library and POSIX API functions.

  • ESP-IDF 使用FatFs Library 實作FAT檔案系統,可直接使用f_open, f_xxx等FatFs Library:
        1. esp_vfs_fat_register: 介接 POSIX and C standard library IO function 與 FATFS. 
        2. ff_diskio_register: 連結FatFs Driver 與FatFs MAI(disk_read, disk_write,...)

    3. 相關函式說明請參閱ESP-IDF 文件(https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/fatfs.html#)

  •  ESP-IDF 以一個mount API整合上述functions 讓開發者很快實作:

            1.esp_vfs_fat_sdmmc_mount: 掛載 SDMMC(1, 4, or 8 bits) 以VFS 來操作FAT 檔案系統。
            2.esp_vfs_fat_sdspi_mount and:掛載 SD card在SPI Bus上, 以VFS 來操作FAT 檔案系統。
            3.esp_vfs_fat_sdcard_unmount:卸載SD card。
            4. 當掛載完畢後,即以fopen來開啟掛載的base_path,使用fget, fput, fread, fwrite等操作檔案。

    本實驗以此簡便方式來操作ESP32-CAM上的SD card(SDMMC or SPI mode),並分別掛載三種模式(SDMMC 1-bit, SDMMC 4-bit and SD SPI)來操作

  • ESP32-CAM SDMMC Host:    

        ESP32’s SDMMC host peripheral has two slots
    • Slot 0 (SDMMC_HOST_SLOT_0) is an 8-bit slot. It uses HS1_* signals in the PIN MUX.
    • Slot 1 (SDMMC_HOST_SLOT_1) is a 4-bit slot. It uses HS2_* signals in the PIN MUX.
    • SDMMC slot 0 and slot 1 相對應GPIO Pin Number如下表所示:

      Signal

      Slot 0

      Slot 1

      CMD

      GPIO11

      GPIO15

      CLK

      GPIO6

      GPIO14

      D0

      GPIO7

      GPIO2

      D1

      GPIO8

      GPIO4

      D2

      GPIO9

      GPIO12

      D3

      GPIO10

      GPIO13

      D4

      GPIO16

      D5

      GPIO17

      D6

      GPIO5

      D7

      GPIO18

      CD

      any input via GPIO matrix

      WP

      any input via GPIO matrix

    根據ESP32-CAM Datasheet,開發板上的SD card模組連接為SLOT 1,因此SDMMC 可設為1bit bus width與4 bits bus width。但D2(GPIO4)與閃光燈共用,因此以4-bits bus width 模式存取時,會連帶啟動閃光燈LED。

  • mount SDMMC mode API:

 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)

相關參數說明:
  1. base_path: 使用者指定掛載path,例如/sdcard。
  2. sdmmc_host_t: 指定使用哪一個SDMMC host,bus width等。例如:
    sdmmc_host_t host_config = SDMMC_HOST_DEFAULT();
  3. sdmmc_slot_config_t:其他額外slot設定,例如:
    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
    SDMMC_SLOT_WIDTH_DEFAULT:使用最大BUS Width,使用slot 1最大為4, 因此若要使用1-bit則須於mount前改變bus width,例如:slot_config.width = 1;
  4. esp_vfs_fat_mount_config_t:
    format_if_mount_failed:mount失敗是否要格式化。
    max_files:能夠同時開啟的檔案數目
    allocation_unit_size:格式化的allocation size。
  5. fopen(base_path, mode)開啟檔案。

  • ESP32-CAM SDSPI Host:

ESP32 有4 SPI peripherals. SPI2與SPI3(HSPI and VSPI)為一般用途SPI,本實驗使用SPI2 Bus。
ESP32-CAM board SDMMC與SDSPI GPIO Pins 表如下:
   SDMMC    |    SDSPI    |    GPIO PIN
        D2              -                    gpio12
        D3            CS                   gpio13
        CMD        MOSI              gpio15
        VSS          GND              gnd
        VDD        3.3V                3.3v
        CLK        SCK                gpio14
        VSS         GND              gnd
        D0            MISO           gpio2
        D1                               gpio4 + LED flash also
 本實驗選用SPI bus為Pin 15,Pin 14 and Pin 2, CS 為Pin 13,在mont sdspi之前必須先完成spi_bus_initialize。
spi_bus_initialize參數說明:
sdspi_device_config_t sdspi_device = SDSPI_DEVICE_CONFIG_DEFAULT();
使用host SDSPI_DEFAULT_HOST為SPI2_HOST`,default cs pin 為GPIO_NUM_13。

esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t * bus_config, spi_dma_chan_t dma_chan)
  1. host_id: sdspi_device.host_id即為SPI2_HOST
  2. spi_bus_config_t:
  3. spi_dma_chan: SPI_DMA_CH1 or SPI_DMA_CH2

  • mout SDSPI mode API:

esp_err_t esp_vfs_fat_sdspi_mount(const char *base_path, const sdmmc_host_t *host_config_input, const sdspi_device_config_t *slot_config, const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t **out_card)
相關參數說明:
  1. base_path:例如:/sdspi
  2. sdmmc_host_t *host_config_input:
    sdmmc_host_t host_config_input = SDSPI_HOST_DEFAULT();
  3. sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); 
  4. const esp_vfs_fat_mount_config_t *mount_config:
    format_if_mount_failed:mount失敗是否要格式化。
    max_files:能夠同時開啟的檔案數目
    allocation_unit_size:格式化的allocation size。
  5. fopen(base_path, mode)開啟檔案。

三、實驗過程

使用ESP32-CAM sd card 模組分別以SDMMC 1-bit、SDMMC 4-bit與SDSPI模式連接使用SD 儲存設備:
  1. 分別開啟文字檔寫入與讀取測試
  2. 將一個預存的影片檔,分別以三種模式讀取後寫入新檔案,測試三種模式所需的時間

  • 測試結果

檔案讀寫正確,如下圖。

註: video.mp4為20MB檔案,用來測試三種模式讀寫所需時間。

  • 三種模式檔案讀寫速度比較
ESP32-CAM板上有64Mbit(8MB)的PSRAM,測試使用PSRAM與使用internal RAM當檔案I/O buffer時的比較。

    不使用PSRAM,menuconfig中設定不開啟SPI RAM,

 程式中malloc(16*1024)當file I/O buffer,測試結果,讀寫20MB檔案所需時間:
  1. SDMMC  4-bit: 56秒
  2. SDMMC 1-bit: 68秒
  3. SDSPI             : 79秒






使用PSRAM,menuconfig中設定開啟SPI RAM, 程式中malloc(256*1024)當file I/O buffer,測試結果,讀寫20MB檔案所需時間:
  1. SDMMC  4-bit: 130秒
  2. SDMMC 1-bit: 142秒
  3. SDSPI             : 151秒







四、測試的程式碼

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "driver/sdmmc_host.h"
#include "driver/sdspi_host.h"
#include "esp_vfs_fat.h"
#include "esp_vfs.h"

#define TAG "SD Card Test"
#define SDMMC_MOUNT  "/sdmmc"
#define SDSPI_MOUNT   "/sdspi"

void sdcard_test(uint8_t bus_width) {
    esp_err_t ret;
    sdmmc_host_t sdmmc_host = SDMMC_HOST_DEFAULT();
    sdmmc_slot_config_t sdmmc_slot = SDMMC_SLOT_CONFIG_DEFAULT();
    esp_vfs_fat_mount_config_t mount_config = {
        .format_if_mount_failed=true,
        .max_files = 5,
        .allocation_unit_size=16*512   //8192 
    };
    sdmmc_card_t *sdmmc_card;
    FILE *fp, *f_video_in;
    char fname[256];
    char buff[100];

    sdmmc_slot.width = bus_width;
    
    ret = esp_vfs_fat_sdmmc_mount(SDMMC_MOUNT, &sdmmc_host, &sdmmc_slot, &mount_config, &sdmmc_card);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "Mount SDMMC Error: bus_width:%d, error:%s", bus_width, esp_err_to_name(ret));
        return;
    }

    sprintf(fname, SDMMC_MOUNT"/sdtest_%d.txt", bus_width);

    fp = fopen(fname, "wb");
    if (!fp) {
        ESP_LOGI(TAG, "SDMMC open file error:%s(write)", fname);
        return;
    }
    for (int i = 0; i < 10; i++) {  // write 10 lines
        fprintf(fp, "SDMMC:bus_width-%d test Line %d\n", bus_width, i);
    }
    fclose(fp);
    ESP_LOGI(TAG, "write SDMMC buswidth:%d", bus_width);

    fp = fopen(fname, "rb");
    if (!fp) {
        ESP_LOGI(TAG, "SDMMC open file error:%s(read)", fname);
        return;
    }
    while(fgets(buff, 100, fp)) {
        printf(buff);
    }
    fclose(fp);

    unsigned char *mem_buf;
    size_t read_size;

    mem_buf = malloc(16*1024); // = 266,144 alloc 256k, will use psram(8M in esp32-cam) 
                                // 16k no psram

    sprintf(fname, SDMMC_MOUNT"/video.mp4");
    f_video_in = fopen(fname, "rb");
    if (!f_video_in) {
        ESP_LOGI(TAG, "SDMMC open file error:%s( video.mp4 read in)", fname);
        return;
    }

    sprintf(fname, SDMMC_MOUNT"/sdv_%d.mp4", bus_width);
    fp = fopen(fname, "wb");
    if (!fp) {
        ESP_LOGI(TAG, "SDMMC open file error:%s( sdv_%d.mp4 wirte in)", fname, bus_width);
        return;
    }

    uint32_t beginTick, endTick;
    ESP_LOGI(TAG, "BEGIN copy video file with bus_width:%d", bus_width);
    beginTick = xTaskGetTickCount();
    while(1) {
        read_size = fread(mem_buf, sizeof(unsigned char), 16384, f_video_in);
        fwrite(mem_buf, sizeof(unsigned char), read_size, fp);
        if (read_size < 16384) {
            break;
        }
    }
    endTick = xTaskGetTickCount();
    ESP_LOGI(TAG, "END copy video file with bus_width:%d", bus_width);
    ESP_LOGI(TAG, "Total Time:%d seconds\n\n", (endTick-beginTick)*portTICK_PERIOD_MS/1000);
    fclose(fp);
    fclose(f_video_in);


    free(mem_buf);
    esp_vfs_fat_sdcard_unmount(SDMMC_MOUNT, sdmmc_card);

    
}

void sdspi_test() {
    esp_err_t ret;
    char buff[100];
    FILE *fp, *f_video_in;
    sdmmc_card_t *sdspi_card;
    sdmmc_host_t sdspi_host = SDSPI_HOST_DEFAULT();
    sdspi_device_config_t sdspi_device = SDSPI_DEVICE_CONFIG_DEFAULT();
    spi_bus_config_t bus_config = {
        .miso_io_num = GPIO_NUM_2,
        .mosi_io_num = GPIO_NUM_15,
        .sclk_io_num = GPIO_NUM_14
    };
    spi_dma_chan_t dma_chan = SPI_DMA_CH1;
    esp_vfs_fat_mount_config_t mount_config = {
        .format_if_mount_failed=true,
        .max_files = 5,
        .allocation_unit_size=16*512   //8192 
    };
    ret = spi_bus_initialize(sdspi_device.host_id, &bus_config, dma_chan);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "SPI BUS initialize error:%s", esp_err_to_name(ret));
        return;
    }
    ret = esp_vfs_fat_sdspi_mount(SDSPI_MOUNT, &sdspi_host, &sdspi_device, &mount_config, &sdspi_card);
    if(ret != ESP_OK) {
        ESP_LOGI(TAG, "SDSPI mount error:%s", esp_err_to_name(ret));
        return;
    }



    fp = fopen(SDSPI_MOUNT"/spitest.txt", "wb");
    if (!fp) {
        ESP_LOGI(TAG, "SDSPI open file error(write)");
        return;
    }
    for (int i = 0; i < 10; i++) {  // write 10 lines
        fprintf(fp, "SDSPI: test Line %d\n", i);
    }
    fclose(fp);
    ESP_LOGI(TAG, "write SDSPI END");

    fp = fopen(SDSPI_MOUNT"/spitest.txt", "rb");
    if (!fp) {
        ESP_LOGI(TAG, "SDSPI open file error(read)");
        return;
    }
    while(fgets(buff, 100, fp)) {
        printf(buff);
    }
    fclose(fp);

    unsigned char *mem_buf;
    size_t read_size;

    mem_buf = malloc(16*1024); // = 266,144 alloc 256k, will use psram(8M in esp32-cam) 
                                // 16k no psram

    f_video_in = fopen(SDSPI_MOUNT"/video.mp4", "rb");
    if (!f_video_in) {
        ESP_LOGI(TAG, "SDSPI open file error:( video.mp4 read in)");
        return;
    }

    fp = fopen(SDSPI_MOUNT"/spivideo.mp4", "wb");
    if (!fp) {
        ESP_LOGI(TAG, "SDSPI open file error( spivideo.mp4 wirte in)");
        return;
    }

    uint32_t beginTick, endTick;
    ESP_LOGI(TAG, "BEGIN copy video file(SDSPI)" );
    beginTick = xTaskGetTickCount();
    while(1) {
        read_size = fread(mem_buf, sizeof(unsigned char), 16384, f_video_in);
        fwrite(mem_buf, sizeof(unsigned char), read_size, fp);
        if (read_size < 16384) {
            break;
        }
    }
    endTick = xTaskGetTickCount();
    ESP_LOGI(TAG, "END copy video file (SDSPI)");
    ESP_LOGI(TAG, "Total Time:%d seconds\n\n", (endTick-beginTick)*portTICK_PERIOD_MS/1000);
    fclose(fp);
    fclose(f_video_in);


    free(mem_buf);
    esp_vfs_fat_sdcard_unmount(SDSPI_MOUNT, sdspi_card);

}

void app_main(void)
{
    sdcard_test(1);
    sdcard_test(4);
    sdspi_test();
    
}

沒有留言:

張貼留言