【ESP32-IDF笔记】08-SD(SDMMC)卡配置和使用

配置环境

Visual Studio Code :版本1.98.2
ESP32:ESP32-S3
ESP-IDF:V5.4
支持型号:ESP32、’ESP32-P4、ESP32-S3

SDMMC 主机驱动

概述

ESP32-S3 的 SDMMC 主机外设共有两个卡槽,用于插入 SD 卡、连接 SDIO 设备或连接 eMMC 芯片,每个卡槽均可单独使用。

卡槽 SDMMC_HOST_SLOT_0SDMMC_HOST_SLOT_1 都支持 1、4、8 线的 SD 接口,这些卡槽通过 GPIO 交换矩阵连接到 ESP32-S3 的 GPIO,即每个 SD 卡信号都可以使用任意 GPIO 连接

支持的速率模式

SDMMC 主机驱动支持以下速率模式:

  • 默认速率 (20 MHz):对于 SD 卡,支持 1 线或 4 线传输;对于 3.3 V eMMC,支持 1 线、4 线或 8 线传输。
  • 高速模式 (40 MHz):对于 SD 卡,支持 1 线或 4 线传输;对于 3.3 V eMMC,支持 1 线、4 线或 8 线传输。
  • 高速 DDR 模式 (40 MHz):对于 3.3 V eMMC,支持 4 线传输。

当前尚不支持的速率模式:

  • 高速 DDR 模式:不支持 8 线 eMMC 传输

使用 SDMMC 主机驱动

在大多数应用程序中,只有下列函数会被直接调用:

其他函数将通过 sdmmc_host_t 结构体中的函数指针由 SD/MMC 协议层调用,例如:

配置总线宽度和频率

使用 sdmmc_host_tsdmmc_slot_config_t 的默认初始化配置,即 SDMMC_HOST_DEFAULTSDMMC_SLOT_CONFIG_DEFAULT 时,SDMMC 主机驱动会尝试以当前卡所支持的最大总线宽度进行通信(SD 卡为 4 线,eMMC 为 8 线),并使用 20 MHz 的通信频率。

在支持 40 MHz 频率通信的设计中,可以调整 sdmmc_host_t 结构体中的 max_freq_khz 字段,提升总线频率:

sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;

如需选择标准速率以外的特定频率,请根据所使用的 SD 接口(SDMMC 或 SDSPI)确定适当频率范围,并选择其中的任意值。然而,实际的时钟频率会由底层驱动程序计算,可能与你所需的值不同。

使用 SDMMC 接口时,max_freq_khz 即频率上限,因此最终的频率值应始终低于或等于该上限。而使用 SDSPI 接口时,驱动程序会提供最接近的适配频率,因此该值可以大于、等于或小于 max_freq_khz

请配置 sdmmc_slot_config_twidth 字段,配置总线宽度。例如,配置 1 线模式的代码如下:

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

配置 GPIO

通过配置结构体 sdmmc_slot_config_t,ESP32-S3 的 SDMMC 主机可以根据需要,为每个信号配置任意的 GPIO 管脚。

例如,使用以下代码,可以将 GPIO 1-6 分别用于 CLK、CMD、D0-D3 信号:

sdmmc_slot_config_t slot = SDMMC_SLOT_CONFIG_DEFAULT();
slot.clk = GPIO_NUM_1;
slot.cmd = GPIO_NUM_2;
slot.d0 = GPIO_NUM_3;
slot.d1 = GPIO_NUM_4;
slot.d2 = GPIO_NUM_5;
slot.d3 = GPIO_NUM_6;

也可以配置 CD 和 WP 管脚。与配置其他信号的方法类似,你只需配置相同结构体的 cdwp 参数:

slot.cd = GPIO_NUM_7;
slot.wp = GPIO_NUM_8;

SDMMC_SLOT_CONFIG_DEFAULT 将 CD 和 WP 管脚都配置为 GPIO_NUM_NC,表明默认情况下不会使用这两个管脚。

通过上述方式初始化 sdmmc_slot_config_t 结构体后,即可在调用 sdmmc_host_init_slot() 或其他任意高层函数(如 esp_vfs_fat_sdmmc_mount())时使用该结构体。

eMMC 芯片的 DDR 模式

默认情况下,如果满足以下条件,将使用 DDR 模式:

  • sdmmc_host_t 结构体中将 SDMMC 主机频率配置为 SDMMC_FREQ_HIGHSPEED,且
  • eMMC 芯片在其 CSD 寄存器中报告支持 DDR 模式

DDR 模式对信号完整性要求更高。如果要在保持 SDMMC_FREQ_HIGHSPEED 频率的同时禁用 DDR 模式,请在 sdmmc_host_t 结构体的 sdmmc_host_t::flags 字段中清除 SDMMC_HOST_FLAG_DDR 位:

sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
host.flags &= ~SDMMC_HOST_FLAG_DDR;

相关文档

API 参考

头文件

头文件包含

#include "driver/sdmmc_host.h"

组件

  • components/esp_driver_sdmmc/include/driver/sdmmc_host.h
 REQUIRES esp_driver_sdmmc

功能函数

初始化 SDMMC 主机外围设备
esp_err_t sdmmc_host_init(void)

返回:

  • 成功时ESP_OK sdmmc_host_init或者已使用此函数初始化

  • ESP_ERR_NO_MEM 如果无法分配内存

初始化 SDMMC 外设的给定插槽
esp_err_t sdmmc_host_init_slot(int slot, const sdmmc_slot_config_t *slot_config)

在 ESP32 上,SDMMC 外设有两个插槽:

  • 插槽 0:8 位宽,映射到 PIN 多路复用器中的 HS1_* 信号
  • 插槽 1:4 位宽,映射到 PIN 多路复用器中的 HS2_* 信号

卡检测和写保护信号可以使用 GPIO 矩阵路由到任意 GPIO。

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

  • slot_config – 插槽的附加配置

返回:

  • 成功ESP_OK

  • ESP_ERR_INVALID_STATE 如果尚未使用 sdmmc_host_init 初始化主机

  • ESP_ERR_INVALID_ARG 如果 slot_config 的 GPIO 引脚无效

选择要用于数据传输的总线宽度
esp_err_t sdmmc_host_set_bus_width(int slot, size_t width)

在此命令之前,必须初始化 SD/MMC 卡,并且必须向卡发送设置总线宽度的命令(例如 SD_APP_SET_BUS_WIDTH)

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

  • width – 总线宽度(插槽 0 为 1、4 或 8;插槽 1 为 1 或 4)

返回:* 成功ESP_OK

  • ESP_ERR_INVALID_ARG 插槽编号或宽度是否无效
获取配置为用于数据传输的总线宽度
size_t sdmmc_host_get_slot_width(int slot ))

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

返回:

配置指定插槽的 bus 宽度。
设置卡时钟频率
esp_err_t sdmmc_host_set_card_clk(int slot, uint32_t freq_khz)

目前只能使用 40MHz clock 的整数分数。对于高速卡,可以使用 40MHz。对于 Default Speed 卡,可以使用 20MHz。

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

  • freq_khz – 卡时钟频率,单位为 kHz

返回:

  • 成功ESP_OK

  • 将来可能会返回其他错误代码

启用或禁用 SD 接口的 DDR 模式
esp_err_t sdmmc_host_set_bus_ddr_mode(int slot, bool ddr_enabled)

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

  • ddr_enabled – 启用或禁用 DDR 模式

返回:* 成功ESP_OK

  • ESP_ERR_NOT_SUPPORTED 如果此插槽不支持 DDR 模式
启用或禁用永远开启的卡时钟
esp_err_t sdmmc_host_set_cclk_always_on(int slot, bool cclk_always_on)

启用或禁用永远开启的卡时钟 当 cclk_always_on 为 false 时,允许主机控制器在命令之间关闭卡时钟。当 cclk_always_on 为 true 时,即使没有命令正在进行,也会生成 clock 。

参数:

  • 锁定 (Lock) – 插槽编号 (Slot Number)

  • cclk_always_on – 启用或禁用始终开启的时钟

返回:* 成功ESP_OK

  • ESP_ERR_INVALID_ARG 插槽编号是否无效
发送数据
esp_err_t sdmmc_host_do_transaction(int slot, sdmmc_command_t *cmdinfo)

此函数在发送命令并收到响应、传输数据或发生超时时返回。

注意力在 cmdinfo->data 中传递的数据缓冲区必须位于支持 DMA 的内存中,并与 4 字节边界对齐。如果它位于缓存后面,则 cmdinfo->data 和 cmdinfo->buflen 都需要与缓存行边界对齐。

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

  • cmdinfo – 指向描述要传输的命令和数据的结构的指针

返回:

  • 成功ESP_OK

  • ESP_ERR_TIMEOUT 响应或数据传输是否已超时

  • ESP_ERR_INVALID_CRC响应或数据传输 CRC 检查是否失败

  • ESP_ERR_INVALID_RESPONSE卡是否发送了无效响应

  • ESP_ERR_INVALID_SIZE 如果数据传输大小在 SD 协议中无效

  • ESP_ERR_INVALID_ARG 数据缓冲区是否不在支持 DMA 的内存中

启用 IO 中断
esp_err_t sdmmc_host_io_int_enable(int slot )

此功能将主机配置为接受 SDIO 中断。

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

返回:

  • ESP_OK,将来可能出现其他错误
等待IO中断
esp_err_t sdmmc_host_io_int_wait(int slot, TickType_t timeout_ticks)

阻塞,直到收到 SDIO 中断或发生超时。

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

  • timeout_ticks – 等待中断的 RTOS 滴答数

返回:* 成功时ESP_OK (收到中断)

  • ESP_ERR_TIMEOUT 如果中断未在 timeout_ticks 内发生
禁用 SDMMC 主机并正常释放分配的资源
esp_err_t sdmmc_host_deinit_slot(int slot )

参数:

  • slot – 插槽编号 (SDMMC_HOST_SLOT_0 或 SDMMC_HOST_SLOT_1)

返回:

  • 成功ESP_OK

  • ESP_ERR_INVALID_STATE 如果 SDMMC 主机尚未初始化

  • ESP_ERR_INVALID_ARG 如果使用了无效的插槽编号

注意

如果活动插槽超过 1 个,则此函数只会减少引用计数,并且在禁用最后一个插槽之前不会实际禁用主机

禁用 SDMMC 主机并强制释放分配的资源
esp_err_t sdmmc_host_deinit(void)

返回:

  • 成功ESP_OK

  • ESP_ERR_INVALID_STATE 如果 SDMMC 主机尚未初始化

注意

此函数将立即取消初始化主机,而不管活动槽的数量如何

应用示例代码

sd_card.h

#ifndef _SD_CARD_H_
#define _SD_CARD_H_

#ifdef __cplusplus
extern "C" {
#endif

#include "esp_err.h"
#include "driver/sdmmc_host.h"
#include "esp_log.h"

#include "wav_file.h"

#define MOUNT_POINT "/sdcard"   //挂载点名称
#define EXAMPLE_MAX_CHAR_SIZE    64     //最大路径长度



esp_err_t sd_card_init(const char* base_path,sdmmc_slot_config_t *sdmc_cfg);
esp_err_t sd_write_file(const char *path, char *data);
esp_err_t sd_read_file(const char *path);
void sd_traverse_directory(const char *dir_path);

#ifdef __cplusplus
}
#endif
#endif

sd_card.c

#include "sd_card.h"
#include "esp_log.h"


#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"

#include "dirent.h"

static const char *TAG = "SDMMC";




static char *mount_point;  
static sdmmc_card_t *card; 

//初始化SD卡,同时进行挂在
esp_err_t sd_card_init(const char* base_path,sdmmc_slot_config_t *sdmc_cfg)
{    
    ESP_LOGI(TAG, "Initializing SD card");
    if(sdmc_cfg==NULL){
        ESP_LOGI(TAG, "sdmc_cfg  is NULL");
        return ESP_FAIL;
    }
    esp_err_t ret;
    esp_vfs_fat_sdmmc_mount_config_t mount_config={
        .format_if_mount_failed = true,    //挂载失败是否执行格式化
        .max_files = 5,                     //最大可打开文件数
        .allocation_unit_size = 4 * 1024   //执行格式化时的分配单元大小(分配单元越大,读写越快,但同时也更容易浪费)
    };    
    ESP_LOGI(TAG, "Using SDMMC peripheral");
    sdmmc_host_t host = SDMMC_HOST_DEFAULT();  //默认配置,速度20MHz,使用卡槽1

    sdmmc_slot_config_t slot_config;
    memcpy(&slot_config, sdmc_cfg, sizeof(slot_config));
    mount_point = malloc(sizeof(base_path));
    memcpy(mount_point,base_path,sizeof(base_path));
    ESP_LOGI(TAG, "Mounting filesystem");
    
    ret = esp_vfs_fat_sdmmc_mount(base_path, &host, &slot_config, &mount_config, &card);
    if (ret != ESP_OK) {
        if (ret == ESP_FAIL) {
            ESP_LOGE(TAG, "Failed to mount filesystem. "
                     "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
        } else {
            ESP_LOGE(TAG, "Failed to initialize the card (%s). "
                     "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
        }
        //esp_vfs_fat_sdcard_unmount(mount_point, card);
        return ret;
    }
    ESP_LOGI(TAG, "Filesystem mounted");

    sdmmc_card_print_info(stdout, card);//已初始化卡片,请打印其属性。 

    // First create a file.
    return ret  ;
}

void sd_card_unmout(){
    esp_vfs_fat_sdcard_unmount(mount_point, card); 
    ESP_LOGI(TAG, "Card unmounted");
}
esp_err_t sd_write_file(const char *path, char *data)
{
    ESP_LOGI(TAG, "Opening file %s", path);
    FILE *f = fopen(path, "w");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return ESP_FAIL;
    }
    fprintf(f, data);
    fclose(f);
    ESP_LOGI(TAG, "File written");

    return ESP_OK;
}

esp_err_t sd_read_file(const char *path)
{
    ESP_LOGI(TAG, "Reading file %s", path);
    FILE *f = fopen(path, "r");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for reading");
        return ESP_FAIL;
    }
    char line[EXAMPLE_MAX_CHAR_SIZE];
    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);

    return ESP_OK;
}
//遍历指定目录所有文件
void sd_traverse_directory(const char *dir_path) 
{
    DIR *dir = opendir(dir_path);
    if (dir == NULL) {
        printf("Failed to open directory: %sn", dir_path);
        return;
    }

    struct dirent *entry;
    printf("Files in directory %s:n", dir_path);
    while ((entry = readdir(dir)) != NULL) {
        printf("%sn", entry->d_name);
    }

    // 关闭目录
    closedir(dir);
}

使用

	sdmmc_slot_config_t slot_config;
    slot_config.width   = 1;                               //数据位
    slot_config.clk = SD_CLK_GPIO_NUM; 
    slot_config.cmd = SD_CMD_GPIO_NUM;
    slot_config.d0  = SD_DAT0_GPIO_NUM;
    slot_config.cd = SDMMC_SLOT_NO_CD; 
    slot_config.wp = SDMMC_SLOT_NO_WP;   
    
    sd_card_init(SD_BASE_PATH,&slot_config);  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Hwang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值