#include <stdlib.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_check.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include "vfs_fat_internal.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"
#include "diskio_impl.h"
#include "diskio_sdmmc.h"
#include "soc/soc_caps.h"
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "sd_protocol_defs.h"
#else
#include "driver/sdmmc_defs.h"
#endif
#if SOC_SDMMC_HOST_SUPPORTED
#include "driver/sdmmc_host.h"
#endif
static const char *TAG = "example";
#define SDMMC_BOARD_ESP32_EMMC_TEST 0 // Set to 1 if you're using ESP32 eMMC test board
#define EXAMPLE_PIN_CMD 11
#define EXAMPLE_PIN_CLK 6
#define EXAMPLE_PIN_D0 7
#define EXAMPLE_PIN_D1 8
#define EXAMPLE_PIN_D2 9
#define EXAMPLE_PIN_D3 10
#define EXAMPLE_PIN_D4 16
#define EXAMPLE_PIN_D5 17
#define EXAMPLE_PIN_D6 5
#define EXAMPLE_PIN_D7 18
#define SDMMC_SLOT SDMMC_HOST_SLOT_0 // or SDMMC_HOST_SLOT_1
#define MOUNT_POINT1 "/emmc1"
#define MOUNT_POINT2 "/emmc2"
const char base_path1[] = MOUNT_POINT1;
const char base_path2[] = MOUNT_POINT2;
// Here you assign how much space each partition takes up (right now 50% each),
// read the example in http://elm-chan.org/fsw/ff/doc/fdisk.html
DWORD partitions[] = {50, 50, 0, 0}; // 1 physical drive, 2 partitions (max 4)
#define CHECK_EXECUTE_RESULT(err, str) do { \
if ((err) !=ESP_OK) { \
ESP_LOGE(TAG, str" (0x%x).", err); \
goto cleanup; \
} \
} while(0)
#if SDMMC_BOARD_ESP32_EMMC_TEST
#define SD_TEST_BOARD_VSEL_GPIO 26
#define SD_TEST_BOARD_VSEL_3V3 1
#define SD_TEST_BOARD_VSEL_1V8 0
#define SD_TEST_BOARD_EN_GPIO 27
#define SD_TEST_BOARD_EN_LEVEL 1
#define SD_TEST_BOARD_PWR_RST_DELAY_MS 5
#define SD_TEST_BOARD_PWR_ON_DELAY_MS 50
static void card_power_set_esp32_emmc(bool en)
{
if (en) {
/* set voltage */
gpio_set_direction(SD_TEST_BOARD_VSEL_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(SD_TEST_BOARD_VSEL_GPIO, SD_TEST_BOARD_VSEL_3V3);
/* power off to make sure card is reset */
gpio_set_direction(SD_TEST_BOARD_EN_GPIO, GPIO_MODE_OUTPUT);
gpio_set_level(SD_TEST_BOARD_EN_GPIO, !SD_TEST_BOARD_EN_LEVEL);
usleep(SD_TEST_BOARD_PWR_RST_DELAY_MS * 1000);
/* power on */
gpio_set_level(SD_TEST_BOARD_EN_GPIO, SD_TEST_BOARD_EN_LEVEL);
usleep(SD_TEST_BOARD_PWR_ON_DELAY_MS * 1000);
} else {
gpio_set_level(SD_TEST_BOARD_EN_GPIO, !SD_TEST_BOARD_EN_LEVEL);
gpio_set_level(SD_TEST_BOARD_VSEL_GPIO, 0);
gpio_set_direction(SD_TEST_BOARD_VSEL_GPIO, GPIO_MODE_INPUT);
gpio_set_direction(SD_TEST_BOARD_EN_GPIO, GPIO_MODE_INPUT);
}
}
#endif
static esp_err_t partition_card_2(const esp_vfs_fat_mount_config_t *mount_config, BYTE pdrv)
{
FRESULT res = FR_OK;
esp_err_t err = ESP_OK;
const size_t workbuf_size = 4096;
void* workbuf = NULL;
ESP_LOGW(TAG, "partitioning card");
workbuf = ff_memalloc(workbuf_size);
if (workbuf == NULL) {
return ESP_ERR_NO_MEM;
}
res = f_fdisk(pdrv, partitions, workbuf);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGE(TAG, "f_fdisk failed (%d)", res);
}
free(workbuf);
return err;
}
static esp_err_t format_card(const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t *card)
{
FRESULT res = FR_OK;
esp_err_t err;
const size_t workbuf_size = 4096;
void* workbuf = NULL;
ESP_LOGW(TAG, "partitioning card");
workbuf = ff_memalloc(workbuf_size);
if (workbuf == NULL) {
return ESP_ERR_NO_MEM;
}
size_t alloc_unit_size = esp_vfs_fat_get_allocation_unit_size(
card->csd.sector_size,
mount_config->allocation_unit_size);
ESP_LOGW(TAG, "formatting card, allocation unit size=%d", alloc_unit_size);
// Foramt partition 0
char drv0[3] = {(char)('0' + 0), ':', 0};
res = f_mkfs(drv0, FM_ANY, alloc_unit_size, workbuf, workbuf_size);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGE(TAG, "f_mkfs failed (%d)", res);
f_mount(NULL, drv0, 0);
goto fail;
}
// Foramt partition 1
char drv1[3] = {(char)('1' + 0), ':', 0};
res = f_mkfs(drv1, FM_ANY, alloc_unit_size, workbuf, workbuf_size);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGE(TAG, "f_mkfs failed (%d)", res);
f_mount(NULL, drv0, 0);
f_mount(NULL, drv1, 0);
goto fail;
}
free(workbuf);
return ESP_OK;
fail:
free(workbuf);
return err;
}
static esp_err_t init_sdmmc_host(int slot, const void *slot_config, int *out_slot)
{
*out_slot = slot;
return sdmmc_host_init_slot(slot, (const sdmmc_slot_config_t*) slot_config);
}
static esp_err_t register_to_vfs_fat(const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t *card, uint8_t pdrv,
const char *base_path, FATFS **out_fs)
{
FATFS* fs = NULL;
esp_err_t err;
ff_diskio_register_sdmmc(pdrv, card);
ESP_LOGD(TAG, "using pdrv=%i", pdrv);
char drv[3] = {(char)('0' + pdrv), ':', 0};
// connect FATFS to VFS
err = esp_vfs_fat_register(base_path, drv, mount_config->max_files, &fs);
if (err == ESP_ERR_INVALID_STATE) {
// it's okay, already registered with VFS
} else if (err != ESP_OK) {
ESP_LOGD(TAG, "esp_vfs_fat_register failed 0x(%x)", err);
goto fail;
}
*out_fs = fs;
return ESP_OK;
fail:
if (fs) {
f_mount(NULL, drv, 0);
}
esp_vfs_fat_unregister_path(base_path);
ff_diskio_unregister(pdrv);
return err;
}
static void call_host_deinit(const sdmmc_host_t *host_config)
{
if (host_config->flags & SDMMC_HOST_FLAG_DEINIT_ARG) {
host_config->deinit_p(host_config->slot);
} else {
host_config->deinit();
}
}
static esp_err_t custom_mount_emmc_2_partitions(const sdmmc_host_t *host_config, const void *slot_config, const esp_vfs_fat_mount_config_t *mount_config, sdmmc_card_t** out_card, bool partition_and_format)
{
esp_err_t err;
int card_handle = -1; //uninitialized
bool host_inited = false;
sdmmc_card_t* card = NULL;
BYTE pdrv1 = FF_DRV_NOT_USED;
BYTE pdrv2 = FF_DRV_NOT_USED;
char* dup_path1 = NULL;
char* dup_path2 = NULL;
FATFS *fs1 = NULL;
FATFS *fs2 = NULL;
dup_path1 = strdup(base_path1);
dup_path2 = strdup(base_path2);
card = (sdmmc_card_t*) malloc(sizeof(sdmmc_card_t)); // One eMMC storage device
// Initialize SDMMC peripheral (only once!)
err = (*host_config->init)();
CHECK_EXECUTE_RESULT(err, "host init failed");
//deinit() needs to be called to revert the init
host_inited = true;
// If this failed (indicated by card_handle != -1), slot deinit needs to called()
// leave card_handle as is to indicate that (though slot deinit not implemented yet.
err = init_sdmmc_host(host_config->slot, slot_config, &card_handle);
CHECK_EXECUTE_RESULT(err, "slot init failed");
// Probe and initialize the SD card/eMMC
err = sdmmc_card_init(host_config, card);
CHECK_EXECUTE_RESULT(err, "sdmmc_card_init failed");
// Register partitions to VFS
// Partition 1 `/emmc1`
ff_diskio_get_drive(&pdrv1); // Get the next available drive number
err = register_to_vfs_fat(mount_config, card, pdrv1, dup_path1, &fs1);
CHECK_EXECUTE_RESULT(err, "register_to_vfs_fat failed");
// Partition 2 `/emmc2`
ff_diskio_get_drive(&pdrv2); // Get the next available drive number
err = register_to_vfs_fat(mount_config, card, pdrv2, dup_path2, &fs2);
CHECK_EXECUTE_RESULT(err, "register_to_vfs_fat failed");
*out_card = card;
if (partition_and_format) { // Partition and format the SD card/eMMC
err = partition_card_2(mount_config, pdrv1);
if (err != ESP_OK) {
goto fail_partition_format;
}
err = format_card(mount_config, card);
if (err != ESP_OK) {
goto fail_partition_format;
}
// Formatting successful, unmount the partitions and disable SDMMC peripheral
// so this function can be run again with `partition_and_format == false` to mount the partitions
goto fail_partition_format;
} else { // Mount the partitions
char drv1[3] = {(char)('0' + pdrv1), ':', 0};
FRESULT res = f_mount(fs1, drv1, 1);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGE(TAG, "failed to mount card - partition 1 (%d)", res);
goto fail_partition_format;
}
char drv2[3] = {(char)('0' + pdrv2), ':', 0};
res = f_mount(fs2, drv2, 1);
if (res != FR_OK) {
err = ESP_FAIL;
ESP_LOGE(TAG, "failed to mount card - partition 2 (%d)", res);
goto fail_partition_format;
}
}
return ESP_OK;
fail_partition_format:
esp_vfs_fat_unregister_path(dup_path1);
esp_vfs_fat_unregister_path(dup_path2);
ff_diskio_unregister(pdrv1);
ff_diskio_unregister(pdrv2);
goto cleanup;
cleanup:
if (host_inited) {
call_host_deinit(host_config);
}
free(card);
free(dup_path1);
free(dup_path2);
return err;
}
void app_main(void)
{
esp_err_t ret;
#if SDMMC_BOARD_ESP32_EMMC_TEST
card_power_set_esp32_emmc(true);
#endif
// Options for mounting the filesystem.
// If format_if_mount_failed is set to true, eMMC will be partitioned and
// formatted in case when mounting fails.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
sdmmc_card_t *card = NULL;
ESP_LOGI(TAG, "Initializing eMMC");
// Use settings defined above to initialize eMMC and mount FAT filesystem.
// Note: esp_vfs_fat_sdmmc_mount is all-in-one convenience functions.
// Please check its source code and implement error recovery when developing
// production applications.
ESP_LOGI(TAG, "Using SDMMC peripheral");
// By default, eMMC frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 52MHz for SDMMC)
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_52M;
host.slot = SDMMC_SLOT;
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Other fields will be initialized to zero
sdmmc_slot_config_t slot_config = {
.cd = SDMMC_SLOT_NO_CD,
.wp = SDMMC_SLOT_NO_WP,
};
// Set bus width to use:
slot_config.width = 8;
// Set bus IOs
#if CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
slot_config.clk = EXAMPLE_PIN_CLK;
slot_config.cmd = EXAMPLE_PIN_CMD;
slot_config.d0 = EXAMPLE_PIN_D0;
slot_config.d1 = EXAMPLE_PIN_D1;
slot_config.d2 = EXAMPLE_PIN_D2;
slot_config.d3 = EXAMPLE_PIN_D3;
slot_config.d4 = EXAMPLE_PIN_D4;
slot_config.d5 = EXAMPLE_PIN_D5;
slot_config.d6 = EXAMPLE_PIN_D6;
slot_config.d7 = EXAMPLE_PIN_D7;
#endif // CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
// Enable internal pullups on enabled pins. The internal pullups
// are insufficient however, please make sure 10k external pullups are
// connected on the bus. This is for debug / example purpose only.
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
ESP_LOGI(TAG, "Mounting filesystem");
// Last argument is partition_and_format, set to true to partition and format the eMMC
ret = custom_mount_emmc_2_partitions(&host, &slot_config, &mount_config, &card, true);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed partition and format.");
} else {
ESP_LOGE(TAG, "Failed to initialize the eMMC (%s). "
"Make sure eMMC lines have pull-up resistors in place.", esp_err_to_name(ret));
}
return;
}
// Last argument is partition_and_format, set to false to mount partitions on the eMMC
ret = custom_mount_emmc_2_partitions(&host, &slot_config, &mount_config, &card, false);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. "
"If you want the eMMC to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
} else {
ESP_LOGE(TAG, "Failed to initialize the eMMC (%s). "
"Make sure eMMC lines have pull-up resistors in place.", esp_err_to_name(ret));
}
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, card);
// First create a file.
const char *file_a = MOUNT_POINT2"/a.txt";
ESP_LOGI(TAG, "Opening file %s", file_a);
FILE *f = fopen(file_a, "w");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
fprintf(f, "Hello %s!\n", card->cid.name);
fclose(f);
ESP_LOGI(TAG, "File written");
// First create a file.
const char *file_b = MOUNT_POINT1"/b.txt";
ESP_LOGI(TAG, "Opening file %s", file_b);
f = fopen(file_b, "w");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for writing");
return;
}
fprintf(f, "Hello %d!\n", card->cid.serial);
fclose(f);
ESP_LOGI(TAG, "File written");
// Open renamed file for reading
ESP_LOGI(TAG, "Reading file %s", file_b);
f = fopen(file_b, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
// Read a line from file
char line[64];
fgets(line, sizeof(line), f);
fclose(f);
// Strip newline
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
// Open renamed file for reading
ESP_LOGI(TAG, "Reading file %s", file_a);
f = fopen(file_a, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for reading");
return;
}
// Read a line from file
fgets(line, sizeof(line), f);
fclose(f);
// Strip newline
pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
ESP_LOGI(TAG, "Done");
}