文档类型:ESP-IDF 通用 API 教程,不绑定单一开发板。
板级例程见 热门例程详解。
目录
简介
TF卡(TransFlash卡,也称为microSD卡)是一种常用的存储介质,广泛应用于嵌入式系统中。ESP32-S3支持通过SPI和SDMMC两种接口方式连接TF卡,其中SDMMC又分为1线模式和4线模式。本教程将详细介绍这三种连接方式的硬件连接和软件配置,帮助初学者快速上手ESP32-S3的TF卡应用。
方案一:SPI模式
SPI模式是最通用的连接方式,几乎所有ESP32系列芯片都支持,但速度相对较慢。
1. 硬件连接(SPI模式)
在SPI模式下,TF卡通过SPI总线与ESP32-S3连接:
| TF卡引脚 | ESP32-S3引脚 | 说明 |
|---|---|---|
| CS | GPIO10 | 片选信号 |
| SCK | GPIO12 | 时钟信号 |
| MOSI | GPIO11 | 主机输出 |
| MISO | GPIO13 | 主机输入 |
| VDD | 3.3V | 电源 |
| GND | GND | 地 |
注意:以上引脚分配可以根据实际需要进行调整,但需要在软件中相应修改。
2. 软件配置(SPI模式)
ESP-IDF提供了FATFS文件系统组件,可以方便地操作TF卡。以下是SPI模式下的配置步骤:
- 首先,需要初始化SPI总线
- 然后,挂载FATFS文件系统
- 最后,就可以使用标准的文件操作函数读写TF卡
3. 完整示例(SPI模式)
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/spi_common.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"
static const char *TAG = "spi_tf_example";
// SPI引脚定义
#define PIN_NUM_MISO 13
#define PIN_NUM_MOSI 11
#define PIN_NUM_CLK 12
#define PIN_NUM_CS 10
void app_main(void)
{
ESP_LOGI(TAG, "初始化SPI总线");
// 配置SPI总线
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
spi_bus_config_t bus_cfg = {
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = PIN_NUM_MISO,
.sclk_io_num = PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
// 初始化SPI总线
esp_err_t ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPI总线初始化失败: %s", esp_err_to_name(ret));
return;
}
// 配置SD卡
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = PIN_NUM_CS;
slot_config.host_id = host.slot;
// 挂载文件系统
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;
ret = esp_vfs_fat_sdspi_mount("/sdcard", &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "挂载文件系统失败。如果您想格式化SD卡,请设置 format_if_mount_failed = true。");
} else {
ESP_LOGE(TAG, "初始化SD卡失败: %s", esp_err_to_name(ret));
}
return;
}
// 打印SD卡信息
sdmmc_card_print_info(stdout, card);
// 创建并写入文件
ESP_LOGI(TAG, "打开文件");
FILE *f = fopen("/sdcard/hello.txt", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行写入");
} else {
fprintf(f, "Hello %s!\n", card->cid.name);
fclose(f);
ESP_LOGI(TAG, "文件写入成功");
}
// 读取文件
ESP_LOGI(TAG, "读取文件");
f = fopen("/sdcard/hello.txt", "r");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行读取");
} else {
char line[64];
fgets(line, sizeof(line), f);
fclose(f);
// 去除换行符
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "读取内容: \"%s\"", line);
}
// 列出根目录文件
ESP_LOGI(TAG, "列出根目录文件");
DIR *dir = opendir("/sdcard");
if (dir == NULL) {
ESP_LOGE(TAG, "无法打开目录");
} else {
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
ESP_LOGI(TAG, "找到文件: %s", entry->d_name);
}
closedir(dir);
}
// 卸载文件系统
esp_vfs_fat_sdcard_unmount("/sdcard", card);
ESP_LOGI(TAG, "卡已卸载");
// 释放SPI总线
spi_bus_free(host.slot);
}
方案二:SDMMC模式(1线模式)
SDMMC 1线模式(也称为SDIO 1位模式)比SPI模式速度更快,但仍然只使用一条数据线。
1. 硬件连接(SDMMC 1线模式)
在SDMMC 1线模式下,TF卡与ESP32-S3的连接如下:
| TF卡引脚 | ESP32-S3引脚 | 说明 |
|---|---|---|
| CMD | GPIO35 | 命令线 |
| CLK | GPIO36 | 时钟线 |
| D0 | GPIO37 | 数据线0 |
| VDD | 3.3V | 电源 |
| GND | GND | 地 |
注意:ESP32-S3的SDMMC控制器对引脚有特定要求,上述引脚分配是推荐的配置。
2. 软件配置(SDMMC 1线模式)
SDMMC 1线模式的配置与SPI模式类似,但使用的是SDMMC主机驱动而非SPI驱动:
- 初始化SDMMC主机
- 配置为1线模式
- 挂载FATFS文件系统
3. 完整示例(SDMMC 1线模式)
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/sdmmc_host.h"
#include "driver/gpio.h"
#include "sdmmc_cmd.h"
static const char *TAG = "sdmmc_1bit_example";
// SDMMC引脚定义
#define PIN_CMD 35
#define PIN_CLK 36
#define PIN_D0 37
void app_main(void)
{
ESP_LOGI(TAG, "初始化SDMMC主机(1线模式)");
// 配置SDMMC主机
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// 使用1线模式
host.flags = SDMMC_HOST_FLAG_1BIT;
host.max_freq_khz = SDMMC_FREQ_DEFAULT; // 20MHz
// 配置SD卡引脚
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.clk = PIN_CLK;
slot_config.cmd = PIN_CMD;
slot_config.d0 = PIN_D0;
slot_config.width = 1; // 1线模式
// 挂载文件系统
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;
esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "挂载文件系统失败。如果您想格式化SD卡,请设置 format_if_mount_failed = true。");
} else {
ESP_LOGE(TAG, "初始化SD卡失败: %s", esp_err_to_name(ret));
}
return;
}
// 打印SD卡信息
sdmmc_card_print_info(stdout, card);
// 创建并写入文件
ESP_LOGI(TAG, "打开文件");
FILE *f = fopen("/sdcard/hello_1bit.txt", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行写入");
} else {
fprintf(f, "Hello SDMMC 1-bit mode!\n");
fclose(f);
ESP_LOGI(TAG, "文件写入成功");
}
// 读取文件
ESP_LOGI(TAG, "读取文件");
f = fopen("/sdcard/hello_1bit.txt", "r");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行读取");
} else {
char line[64];
fgets(line, sizeof(line), f);
fclose(f);
// 去除换行符
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "读取内容: \"%s\"", line);
}
// 测试写入速度
ESP_LOGI(TAG, "测试写入速度");
const size_t buf_size = 32 * 1024;
uint8_t *buf = heap_caps_malloc(buf_size, MALLOC_CAP_DMA);
if (buf == NULL) {
ESP_LOGE(TAG, "无法分配内存");
} else {
// 填充随机数据
for (size_t i = 0; i < buf_size; i++) {
buf[i] = i & 0xFF;
}
// 创建测试文件
f = fopen("/sdcard/speedtest.bin", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法创建测试文件");
} else {
// 写入10MB数据
const int iterations = 10 * 1024 * 1024 / buf_size;
int64_t start_time = esp_timer_get_time();
for (int i = 0; i < iterations; i++) {
size_t written = fwrite(buf, 1, buf_size, f);
if (written != buf_size) {
ESP_LOGE(TAG, "写入错误");
break;
}
}
fclose(f);
int64_t end_time = esp_timer_get_time();
float elapsed_time = (end_time - start_time) / 1000000.0;
float speed = (iterations * buf_size) / (1024.0 * 1024.0) / elapsed_time;
ESP_LOGI(TAG, "写入速度: %.2f MB/s", speed);
}
free(buf);
}
// 卸载文件系统
esp_vfs_fat_sdcard_unmount("/sdcard", card);
ESP_LOGI(TAG, "卡已卸载");
}
方案三:SDMMC模式(4线模式)
SDMMC 4线模式是三种方式中速度最快的,它使用4条数据线并行传输数据。
1. 硬件连接(SDMMC 4线模式)
在SDMMC 4线模式下,TF卡与ESP32-S3的连接如下:
| TF卡引脚 | ESP32-S3引脚 | 说明 |
|---|---|---|
| CMD | GPIO35 | 命令线 |
| CLK | GPIO36 | 时钟线 |
| D0 | GPIO37 | 数据线0 |
| D1 | GPIO38 | 数据线1 |
| D2 | GPIO33 | 数据线2 |
| D3 | GPIO34 | 数据线3 |
| VDD | 3.3V | 电源 |
| GND | GND | 地 |
注意:ESP32-S3的SDMMC控制器对引脚有特定要求,上述引脚分配是推荐的配置。
2. 软件配置(SDMMC 4线模式)
SDMMC 4线模式的配置与1线模式类似,但需要配置为4线模式:
- 初始化SDMMC主机
- 配置为4线模式
- 挂载FATFS文件系统
3. 完整示例(SDMMC 4线模式)
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "driver/sdmmc_host.h"
#include "driver/gpio.h"
#include "sdmmc_cmd.h"
static const char *TAG = "sdmmc_4bit_example";
// SDMMC引脚定义
#define PIN_CMD 35
#define PIN_CLK 36
#define PIN_D0 37
#define PIN_D1 38
#define PIN_D2 33
#define PIN_D3 34
void app_main(void)
{
ESP_LOGI(TAG, "初始化SDMMC主机(4线模式)");
// 配置SDMMC主机
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// 使用默认配置(4线模式)
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; // 40MHz
// 配置SD卡引脚
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.clk = PIN_CLK;
slot_config.cmd = PIN_CMD;
slot_config.d0 = PIN_D0;
slot_config.d1 = PIN_D1;
slot_config.d2 = PIN_D2;
slot_config.d3 = PIN_D3;
slot_config.width = 4; // 4线模式
// 挂载文件系统
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;
esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "挂载文件系统失败。如果您想格式化SD卡,请设置 format_if_mount_failed = true。");
} else {
ESP_LOGE(TAG, "初始化SD卡失败: %s", esp_err_to_name(ret));
}
return;
}
// 打印SD卡信息
sdmmc_card_print_info(stdout, card);
// 创建并写入文件
ESP_LOGI(TAG, "打开文件");
FILE *f = fopen("/sdcard/hello_4bit.txt", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行写入");
} else {
fprintf(f, "Hello SDMMC 4-bit mode!\n");
fclose(f);
ESP_LOGI(TAG, "文件写入成功");
}
// 读取文件
ESP_LOGI(TAG, "读取文件");
f = fopen("/sdcard/hello_4bit.txt", "r");
if (f == NULL) {
ESP_LOGE(TAG, "无法打开文件进行读取");
} else {
char line[64];
fgets(line, sizeof(line), f);
fclose(f);
// 去除换行符
char *pos = strchr(line, '\n');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "读取内容: \"%s\"", line);
}
// 测试写入速度
ESP_LOGI(TAG, "测试写入速度");
const size_t buf_size = 32 * 1024;
uint8_t *buf = heap_caps_malloc(buf_size, MALLOC_CAP_DMA);
if (buf == NULL) {
ESP_LOGE(TAG, "无法分配内存");
} else {
// 填充随机数据
for (size_t i = 0; i < buf_size; i++) {
buf[i] = i & 0xFF;
}
// 创建测试文件
f = fopen("/sdcard/speedtest.bin", "w");
if (f == NULL) {
ESP_LOGE(TAG, "无法创建测试文件");
} else {
// 写入10MB数据
const int iterations = 10 * 1024 * 1024 / buf_size;
int64_t start_time = esp_timer_get_time();
for (int i = 0; i < iterations; i++) {
size_t written = fwrite(buf, 1, buf_size, f);
if (written != buf_size) {
ESP_LOGE(TAG, "写入错误");
break;
}
}
fclose(f);
int64_t end_time = esp_timer_get_time();
float elapsed_time = (end_time - start_time) / 1000000.0;
float speed = (iterations * buf_size) / (1024.0 * 1024.0) / elapsed_time;
ESP_LOGI(TAG, "写入速度: %.2f MB/s", speed);
}
free(buf);
}
// 卸载文件系统
esp_vfs_fat_sdcard_unmount("/sdcard", card);
ESP_LOGI(TAG, "卡已卸载");
}
常见问题与解决方案
1. 无法识别SD卡
如果ESP32-S3无法识别SD卡,可能的原因和解决方法:
-
硬件连接问题:
- 检查所有引脚连接是否正确
- 确保SD卡电源供应稳定(3.3V)
- 检查SD卡是否插入正确
-
SD卡格式问题:
- 确保SD卡已格式化为FAT32或exFAT
- 尝试在电脑上重新格式化SD卡
-
SD卡兼容性问题:
- 尝试使用不同品牌或型号的SD卡
- 某些高容量或高速SD卡可能需要特殊配置
2. 读写速度慢
如果SD卡读写速度慢,可能的原因和解决方法:
-
接口模式:
- SPI模式速度最慢,SDMMC 4线模式速度最快
- 如果条件允许,尽量使用SDMMC 4线模式
-
时钟频率:
- 增加SD卡时钟频率(但不要超过卡的规格)
- 对于高速卡,可以使用SDMMC_FREQ_HIGHSPEED(40MHz)
-
DMA传输:
- 确保使用DMA进行数据传输
- 使用MALLOC_CAP_DMA分配缓冲区
3. 文件系统错误
如果遇到文件系统错误,可能的原因和解决方法:
-
文件系统损坏:
- 设置format_if_mount_failed = true重新格式化SD卡
- 在电脑上使用专业工具修复SD卡
-
文件操作问题:
- 确保正确关闭文件(调用fclose())
- 避免同时打开过多文件(默认最大为5个)
-
卡写保护:
- 检查SD卡是否启用了写保护
- 某些SD卡有物理写保护开关
高级应用
1. 使用SPIFFS或LittleFS
除了FAT文件系统外,ESP-IDF还支持SPIFFS和LittleFS文件系统,它们更适合闪存设备:
#include "esp_spiffs.h"
void init_spiffs(void)
{
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = NULL,
.max_files = 5,
.format_if_mount_failed = true
};
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "SPIFFS挂载失败");
}
}
2. 文件加密
对SD卡上的敏感数据进行加密:
#include "mbedtls/aes.h"
// 简单的AES加密示例
void encrypt_file(const char *src_path, const char *dst_path, const uint8_t *key)
{
FILE *f_src = fopen(src_path, "rb");
FILE *f_dst = fopen(dst_path, "wb");
if (f_src == NULL || f_dst == NULL) {
ESP_LOGE(TAG, "无法打开文件");
return;
}
mbedtls_aes_context aes;
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, key, 256);
uint8_t buf_in[16];
uint8_t buf_out[16];
while (!feof(f_src)) {
size_t read_len = fread(buf_in, 1, 16, f_src);
if (read_len == 0) break;
// 填充到16字节
if (read_len < 16) {
memset(buf_in + read_len, 16 - read_len, 16 - read_len);
}
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, buf_in, buf_out);
fwrite(buf_out, 1, 16, f_dst);
}
fclose(f_src);
fclose(f_dst);
mbedtls_aes_free(&aes);
}
3. 数据日志记录
使用SD卡进行数据日志记录:
void log_sensor_data(float temperature, float humidity)
{
static FILE *log_file = NULL;
static uint32_t log_count = 0;
// 每天创建新的日志文件
time_t now;
time(&now);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
char filename[64];
sprintf(filename, "/sdcard/log_%04d%02d%02d.csv",
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday);
// 检查是否需要打开新文件
if (log_file == NULL) {
log_file = fopen(filename, "a");
if (log_file == NULL) {
ESP_LOGE(TAG, "无法打开日志文件");
return;
}
// 如果是新文件,添加标题行
if (ftell(log_file) == 0) {
fprintf(log_file, "时间戳,温度,湿度\n");
}
}
// 写入数据
fprintf(log_file, "%04d-%02d-%02d %02d:%02d:%02d,%.2f,%.2f\n",
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec,
temperature, humidity);
// 定期刷新缓冲区
if (++log_count % 10 == 0) {
fflush(log_file);
}
// 每小时关闭并重新打开文件,以防文件损坏
if (timeinfo.tm_min == 0 && timeinfo.tm_sec == 0) {
fclose(log_file);
log_file = NULL;
}
}
总结
本教程详细介绍了ESP32-S3连接TF卡的三种方式:SPI模式、SDMMC 1线模式和SDMMC 4线模式。每种方式都有其优缺点:
-
SPI模式:
- 优点:兼容性好,几乎所有ESP32系列芯片都支持
- 缺点:速度较慢,通常在1-4MB/s
- 适用场景:对速度要求不高的应用,或者需要与其他SPI设备共享总线的场景
-
SDMMC 1线模式:
- 优点:比SPI模式速度快,实现简单
- 缺点:速度不如4线模式,通常在4-10MB/s
- 适用场景:对速度有一定要求,但引脚资源有限的应用
-
SDMMC 4线模式:
- 优点:速度最快,可达20MB/s以上
- 缺点:需要更多引脚资源
- 适用场景:对读写速度要求较高的应用,如音视频录制、大文件传输等
在实际应用中,应根据具体需求选择合适的连接方式。如果对速度要求不高,可以选择SPI模式;如果对速度有一定要求,但引脚资源有限,可以选择SDMMC 1线模式;如果对速度要求较高,且有足够的引脚资源,则应选择SDMMC 4线模式。
无论选择哪种连接方式,都可以使用ESP-IDF提供的FATFS文件系统组件进行文件操作,API接口保持一致,这使得在不同连接方式之间切换变得简单。
性能对比
以下是三种连接方式的性能对比(基于ESP32-S3和Class 10 TF卡):
| 连接方式 | 读取速度 | 写入速度 | 所需引脚数 |
|---|---|---|---|
| SPI模式 | 1-4MB/s | 0.5-2MB/s | 4 |
| SDMMC 1线模式 | 4-10MB/s | 2-5MB/s | 3 |
| SDMMC 4线模式 | 15-25MB/s | 10-20MB/s | 6 |
注意:实际速度会受到多种因素影响,包括SD卡等级、时钟频率、DMA配置等。
最佳实践
-
选择合适的SD卡:
- 使用知名品牌的SD卡,避免使用劣质卡
- 对于SDMMC模式,建议使用Class 10或UHS-I卡
- 容量通常选择8GB-32GB,过大的卡可能会有兼容性问题
-
优化硬件连接:
- 保持连接线尽可能短
- 使用上拉电阻(特别是对于SPI模式)
- 考虑添加去耦电容稳定电源
-
软件优化:
- 使用DMA传输提高效率
- 选择合适的缓冲区大小(通常为4KB的倍数)
- 批量读写而非频繁小量读写
-
文件系统选择:
- 对于大多数应用,FAT32是最佳选择
- 对于需要支持大于4GB文件的应用,可以使用exFAT
- 考虑使用适当的分配单元大小(通常为16KB或32KB)
参考资料
希望本教程能帮助您在ESP32-S3项目中成功使用TF卡。如有任何问题,欢迎在Espressif论坛或GitHub上提问。