目录
配置环境
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_0
和 SDMMC_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_t
和 sdmmc_slot_config_t
的默认初始化配置,即 SDMMC_HOST_DEFAULT
和 SDMMC_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_t
的 width
字段,配置总线宽度。例如,配置 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 管脚。与配置其他信号的方法类似,你只需配置相同结构体的 cd
和 wp
参数:
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;
相关文档
- SD/SDIO/MMC 驱动程序:介绍了实现协议层的高层驱动程序。
- SD SPI 主机驱动程序:介绍了一种类似驱动,该驱动使用 SPI 控制器且受限于 SD 协议的 SPI 模式。
- SD 上拉需求 介绍了模组和开发套件上的上拉支持和兼容信息。
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);