ESP32-S3 FATFS文件系统开发
文章目录
ESP32-S3 FATFS文件系统开发教程
版本信息
- 教程版本: v2.0
- ESP-IDF版本: v5.1.6
- 开发板: 立创实战派ESP32-S3
- 最后更新: 2026-01-01
教程概述
本教程基于立创实战派ESP32-S3开发板,详细介绍如何使用ESP-IDF框架操作SD卡的FATFS文件系统。通过本教程,您将学习到SD卡的挂载、文件读写、目录操作、磁盘管理等完整的文件系统操作。
适用对象
- ESP-IDF开发初学者
- 需要在ESP32-S3上实现数据存储功能的开发者
- 学习嵌入式文件系统操作的工程师
学习目标
- 掌握ESP32-S3的SDMMC接口配置
- 理解FATFS文件系统的挂载和卸载流程
- 熟练使用标准C文件操作API
- 掌握目录管理和磁盘空间查询
硬件准备
开发板信息
- 开发板: 立创实战派ESP32-S3
- 芯片: ESP32-S3-WROOM-1
- 存储接口: SDMMC (1-line mode)
- SD卡: 建议使用FAT32格式的Micro SD卡
引脚连接
| 功能 | ESP32-S3引脚 | SD卡引脚 | 说明 |
|---|---|---|---|
| CLK | GPIO 47 | CLK | 时钟线 |
| CMD | GPIO 48 | CMD | 命令线 |
| D0 | GPIO 21 | DAT0 | 数据线0 (1线模式) |
| VCC | 3.3V | VCC | 电源 |
| GND | GND | GND | 地线 |
代码分段详解
第一部分:头文件和宏定义
#include <stdio.h>
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#define BSP_SD_CLK (47)
#define BSP_SD_CMD (48)
#define BSP_SD_D0 (21)
static const char *TAG = "main";
#define MOUNT_POINT "/sdcard"
#define EXAMPLE_MAX_CHAR_SIZE 128
代码说明:
- 标准C库头文件:
stdio.h(标准输入输出)、string.h(字符串操作) - POSIX文件系统头文件:
sys/unistd.h(文件删除)、sys/stat.h(文件状态)、dirent.h(目录操作) - ESP-IDF专用头文件:
esp_vfs_fat.h:FATFS虚拟文件系统接口sdmmc_cmd.h:SD/MMC卡命令接口driver/sdmmc_host.h:SDMMC主机驱动
- 引脚定义:根据立创实战派ESP32-S3的硬件设计定义SD卡引脚
- 挂载点:定义SD卡在文件系统中的挂载路径为
/sdcard
第二部分:文件写入函数
static esp_err_t s_example_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;
}
功能说明:
- 以只写模式(
"w")打开文件,如果文件不存在则创建,存在则清空内容 - 使用
fprintf()将数据写入文件 - 使用
fclose()关闭文件,确保数据写入磁盘 - 返回
ESP_OK表示成功,ESP_FAIL表示失败
关键点:
- 文件路径必须包含挂载点前缀(如
/sdcard/test.txt) - 必须检查
fopen()返回值,避免空指针操作 - 必须调用
fclose()释放资源并刷新缓冲区
第三部分:文件读取函数
static esp_err_t s_example_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'); // 查找字符串中的"\n"并返回其位置
if (pos) {
*pos = '\0'; // 把\n替换成\0
}
ESP_LOGI(TAG, "Read from file: '%s'", line); // 把数组内容输出到终端
return ESP_OK;
}
功能说明:
- 以只读模式(
"r")打开文件 - 使用
fgets()读取一行内容到缓冲区 - 使用
strchr()查找换行符并替换为字符串结束符 - 输出读取的内容到日志
关键点:
fgets()会读取包括换行符在内的一行内容- 需要手动处理换行符以获得纯文本内容
- 缓冲区大小要足够容纳读取的内容
第四部分:文件追加函数
static esp_err_t s_example_append_file(const char *path, char *data)
{
ESP_LOGI(TAG, "Appending to file %s", path);
FILE *f = fopen(path, "a"); // Open in append mode
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file for appending");
return ESP_FAIL;
}
fprintf(f, data);
fclose(f);
ESP_LOGI(TAG, "Content appended");
return ESP_OK;
}
功能说明:
- 以追加模式(
"a")打开文件 - 新内容会添加到文件末尾,不会覆盖原有内容
- 如果文件不存在,会自动创建
应用场景:
- 日志记录系统
- 数据采集追加记录
- 事件记录
第五部分:文件重命名函数
static esp_err_t s_example_rename_file(const char *old_path, const char *new_path)
{
ESP_LOGI(TAG, "Renaming file %s to %s", old_path, new_path);
if (rename(old_path, new_path) != 0) {
ESP_LOGE(TAG, "Rename failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "File renamed successfully");
return ESP_OK;
}
功能说明:
- 使用
rename()函数重命名文件或移动文件 - 可以在同一目录内重命名,也可以移动到其他目录
- 返回0表示成功,非0表示失败
注意事项:
- 新路径的目录必须存在
- 如果新路径已存在同名文件,行为取决于文件系统实现
第六部分:文件删除函数
static esp_err_t s_example_delete_file(const char *path)
{
ESP_LOGI(TAG, "Deleting file %s", path);
if (unlink(path) != 0) {
ESP_LOGE(TAG, "Delete failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "File deleted successfully");
return ESP_OK;
}
功能说明:
- 使用
unlink()函数删除文件 - 删除后文件无法恢复
- 只能删除文件,不能删除目录
安全建议:
- 删除前确认文件路径正确
- 重要文件删除前做好备份
第七部分:创建目录函数
static esp_err_t s_example_create_dir(const char *path)
{
ESP_LOGI(TAG, "Creating directory %s", path);
if (mkdir(path, 0775) != 0) {
ESP_LOGE(TAG, "mkdir failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Directory created successfully");
return ESP_OK;
}
功能说明:
- 使用
mkdir()创建目录 - 第二个参数
0775是权限模式(在嵌入式系统中通常不严格使用) - 返回0表示成功
注意事项:
- 父目录必须存在
- 如果目录已存在,会返回失败
第八部分:目录遍历函数
static esp_err_t s_example_list_dir(const char *path)
{
ESP_LOGI(TAG, "Listing directory: %s", path);
DIR *dir = opendir(path);
if (dir == NULL) {
ESP_LOGE(TAG, "Failed to open directory");
return ESP_FAIL;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s", path, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
ESP_LOGI(TAG, " [DIR] %s", entry->d_name);
} else {
ESP_LOGI(TAG, " [FILE] %s (size: %ld bytes)", entry->d_name, st.st_size);
}
}
}
closedir(dir);
return ESP_OK;
}
功能说明:
- 使用
opendir()打开目录 - 使用
readdir()循环读取目录中的每个条目 - 使用
stat()获取文件/目录的详细信息 - 使用
S_ISDIR()宏判断是否为目录 - 使用
closedir()关闭目录
关键数据结构:
struct dirent:目录条目结构,包含文件名struct stat:文件状态结构,包含大小、时间、权限等信息
第九部分:获取文件状态函数
static esp_err_t s_example_file_stat(const char *path)
{
ESP_LOGI(TAG, "Getting file status: %s", path);
struct stat st;
if (stat(path, &st) != 0) {
ESP_LOGE(TAG, "stat failed");
return ESP_FAIL;
}
ESP_LOGI(TAG, "File size: %ld bytes", st.st_size);
ESP_LOGI(TAG, "Last modified: %s", ctime(&st.st_mtime));
return ESP_OK;
}
功能说明:
- 使用
stat()获取文件的元数据信息 st.st_size:文件大小(字节)st.st_mtime:最后修改时间ctime()将时间戳转换为可读字符串
应用场景:
- 检查文件是否存在
- 获取文件大小用于内存分配
- 检查文件修改时间
第十部分:磁盘空间信息函数
static esp_err_t s_example_disk_info(const char *path)
{
ESP_LOGI(TAG, "Getting disk space info");
FATFS *fs;
DWORD fre_clust;
// Get volume information and free clusters
if (f_getfree("0:", &fre_clust, &fs) != FR_OK) {
ESP_LOGE(TAG, "Failed to get disk info");
return ESP_FAIL;
}
// Calculate total and free space (in bytes)
uint64_t total_bytes = (uint64_t)(fs->n_fatent - 2) * fs->csize * fs->ssize;
uint64_t free_bytes = (uint64_t)fre_clust * fs->csize * fs->ssize;
ESP_LOGI(TAG, "Total space: %llu KB", total_bytes / 1024);
ESP_LOGI(TAG, "Free space: %llu KB", free_bytes / 1024);
ESP_LOGI(TAG, "Used space: %llu KB", (total_bytes - free_bytes) / 1024);
return ESP_OK;
}
功能说明:
- 使用
f_getfree()获取FATFS卷的空闲簇数量 - 通过文件系统参数计算总空间和剩余空间
- 计算公式:
- 总空间 = (FAT表项数 - 2) × 每簇扇区数 × 扇区大小
- 剩余空间 = 空闲簇数 × 每簇扇区数 × 扇区大小
关键参数:
fs->n_fatent:FAT表项总数fs->csize:每簇包含的扇区数fs->ssize:每扇区的字节数fre_clust:空闲簇数量
第十一部分:文件定位操作函数
static esp_err_t s_example_file_seek(const char *path)
{
ESP_LOGI(TAG, "Testing file seek operations on %s", path);
FILE *f = fopen(path, "r");
if (f == NULL) {
ESP_LOGE(TAG, "Failed to open file");
return ESP_FAIL;
}
// Get file size
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
ESP_LOGI(TAG, "File size: %ld bytes", file_size);
// Read from beginning
fseek(f, 0, SEEK_SET);
char buffer[64];
fgets(buffer, sizeof(buffer), f);
ESP_LOGI(TAG, "Content from beginning: %s", buffer);
// Read from middle
if (file_size > 10) {
fseek(f, 5, SEEK_SET);
fgets(buffer, sizeof(buffer), f);
ESP_LOGI(TAG, "Content from offset 5: %s", buffer);
}
fclose(f);
return ESP_OK;
}
功能说明:
- 使用
fseek()移动文件指针到指定位置 - 使用
ftell()获取当前文件指针位置 - 三种定位模式:
SEEK_SET:从文件开头偏移SEEK_CUR:从当前位置偏移SEEK_END:从文件末尾偏移
应用场景:
- 获取文件大小(移动到末尾后使用ftell)
- 随机访问文件内容
- 跳过文件头部信息
第十二部分:主函数 - SD卡初始化和挂载
void app_main(void)
{
esp_err_t ret;
// 配置挂载参数
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true, // 如果挂载不成功是否需要格式化SD卡
.max_files = 5, // 允许打开的最大文件数
.allocation_unit_size = 16 * 1024 // 分配单元大小
};
sdmmc_card_t *card;
const char mount_point[] = MOUNT_POINT;
ESP_LOGI(TAG, "Initializing SD card");
ESP_LOGI(TAG, "Using SDMMC peripheral");
// 配置SDMMC主机
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// 配置SDMMC插槽
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
slot_config.width = 1; // 设置为1线SD模式
slot_config.clk = BSP_SD_CLK;
slot_config.cmd = BSP_SD_CMD;
slot_config.d0 = BSP_SD_D0;
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // 打开内部上拉电阻
ESP_LOGI(TAG, "Mounting filesystem");
// 挂载SD卡
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. ");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). ", esp_err_to_name(ret));
}
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
sdmmc_card_print_info(stdout, card); // 打印SD卡信息
配置说明:
-
挂载配置 (
esp_vfs_fat_sdmmc_mount_config_t):format_if_mount_failed:挂载失败时是否自动格式化max_files:同时打开的最大文件数allocation_unit_size:分配单元大小,影响性能和空间利用率
-
主机配置 (
sdmmc_host_t):- 使用
SDMMC_HOST_DEFAULT()获取默认配置 - 配置SDMMC外设的工作参数
- 使用
-
插槽配置 (
sdmmc_slot_config_t):width:数据线宽度(1线或4线模式)clk/cmd/d0:引脚分配flags:启用内部上拉电阻
-
挂载函数 (
esp_vfs_fat_sdmmc_mount):- 将SD卡挂载到VFS(虚拟文件系统)
- 挂载后可以使用标准C文件操作函数
第十三部分:主函数 - 文件操作测试
// 基础文件写入和读取
const char *file_hello = MOUNT_POINT"/你好hello.txt";
char data[EXAMPLE_MAX_CHAR_SIZE];
snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "你好hello先生", card->cid.name);
ret = s_example_write_file(file_hello, data);
if (ret != ESP_OK) {
return;
}
ret = s_example_read_file(file_hello);
if (ret != ESP_OK) {
return;
}
// Test 1: 文件追加
ESP_LOGI(TAG, "\n=== Test 1: Append to file ===");
ret = s_example_append_file(file_hello, "Appended line 1\n");
if (ret == ESP_OK) {
ret = s_example_append_file(file_hello, "Appended line 2\n");
}
if (ret == ESP_OK) {
s_example_read_file(file_hello);
}
// Test 2: 文件定位操作
ESP_LOGI(TAG, "\n=== Test 2: File seek operations ===");
s_example_file_seek(file_hello);
// Test 3: 获取文件状态
ESP_LOGI(TAG, "\n=== Test 3: File status ===");
s_example_file_stat(file_hello);
// Test 4: 文件重命名
ESP_LOGI(TAG, "\n=== Test 4: Rename file ===");
const char *file_renamed = MOUNT_POINT"/renamed_file.txt";
ret = s_example_rename_file(file_hello, file_renamed);
if (ret == ESP_OK) {
s_example_read_file(file_renamed);
}
// Test 5: 创建目录
ESP_LOGI(TAG, "\n=== Test 5: Create directory ===");
const char *test_dir = MOUNT_POINT"/test_dir";
s_example_create_dir(test_dir);
// Test 6: 在目录中创建文件
ESP_LOGI(TAG, "\n=== Test 6: Create files in directory ===");
const char *file_in_dir1 = MOUNT_POINT"/test_dir/file1.txt";
const char *file_in_dir2 = MOUNT_POINT"/test_dir/file2.txt";
s_example_write_file(file_in_dir1, "Content of file 1\n");
s_example_write_file(file_in_dir2, "Content of file 2\n");
// Test 7-8: 目录遍历
ESP_LOGI(TAG, "\n=== Test 7: List root directory ===");
s_example_list_dir(MOUNT_POINT);
ESP_LOGI(TAG, "\n=== Test 8: List test_dir directory ===");
s_example_list_dir(test_dir);
// Test 9: 磁盘空间信息
ESP_LOGI(TAG, "\n=== Test 9: Disk space information ===");
s_example_disk_info(MOUNT_POINT);
// Test 10: 删除文件和目录
ESP_LOGI(TAG, "\n=== Test 10: Delete files ===");
s_example_delete_file(file_in_dir1);
s_example_delete_file(file_in_dir2);
s_example_delete_file(file_renamed);
ESP_LOGI(TAG, "Removing empty directory");
if (rmdir(test_dir) == 0) {
ESP_LOGI(TAG, "Directory removed successfully");
} else {
ESP_LOGE(TAG, "Failed to remove directory");
}
// 最终目录列表
ESP_LOGI(TAG, "\n=== Final: List root directory after cleanup ===");
s_example_list_dir(MOUNT_POINT);
// 卸载SD卡
esp_vfs_fat_sdcard_unmount(mount_point, card);
ESP_LOGI(TAG, "\nCard unmounted");
}
测试流程说明:
本例程包含10个完整的测试步骤,全面演示FATFS文件系统的各项功能:
- 基础读写:创建文件并写入内容,然后读取验证
- 文件追加:向已有文件追加新内容
- 文件定位:使用fseek/ftell进行文件指针操作
- 文件状态:获取文件大小和修改时间
- 文件重命名:重命名文件并验证
- 创建目录:创建新目录
- 目录文件:在目录中创建多个文件
- 目录遍历:列出目录内容,区分文件和目录
- 磁盘信息:查询磁盘总空间和剩余空间
- 清理操作:删除文件和空目录
ESP-IDF库函数学习要点
一、SD卡挂载和管理函数
1. esp_vfs_fat_sdmmc_mount()
功能:将SD卡挂载到VFS(虚拟文件系统)
函数原型:
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_sdmmc_mount_config_t* mount_config, // 挂载配置
sdmmc_card_t** out_card // 输出卡信息
);
参数说明:
base_path:挂载点,如 “/sdcard”host_config:SDMMC主机接口配置slot_config:SD卡插槽配置(引脚、位宽等)mount_config:挂载选项(最大文件数、格式化选项等)out_card:返回SD卡信息结构体指针
返回值:
ESP_OK:挂载成功ESP_FAIL:挂载失败- 其他错误码
使用示例:
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true,
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
sdmmc_card_t *card;
ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
2. esp_vfs_fat_sdcard_unmount()
功能:卸载SD卡
函数原型:
esp_err_t esp_vfs_fat_sdcard_unmount(
const char *base_path, // 挂载点路径
sdmmc_card_t *card // SD卡信息
);
重要性:
- 确保所有数据写入磁盘
- 释放系统资源
- 避免数据丢失
3. sdmmc_card_print_info()
功能:打印SD卡详细信息
函数原型:
void sdmmc_card_print_info(FILE* stream, const sdmmc_card_t* card);
输出信息:
- SD卡名称
- 卡类型(SDHC/SDXC等)
- 容量大小
- 速度等级
二、标准C文件操作函数
4. fopen()
功能:打开文件
函数原型:
FILE *fopen(const char *filename, const char *mode);
模式参数:
"r":只读,文件必须存在"w":只写,文件不存在则创建,存在则清空"a":追加,文件不存在则创建,写入追加到末尾"r+":读写,文件必须存在"w+":读写,文件不存在则创建,存在则清空"a+":读写追加
5. fclose()
功能:关闭文件
函数原型:
int fclose(FILE *stream);
重要性:
- 刷新缓冲区到磁盘
- 释放文件描述符
- 避免资源泄漏
6. fprintf()
功能:格式化写入文件
函数原型:
int fprintf(FILE *stream, const char *format, ...);
使用示例:
fprintf(f, "Hello World!\n");
fprintf(f, "Number: %d, String: %s\n", 123, "test");
7. fgets()
功能:从文件读取一行
函数原型:
char *fgets(char *str, int n, FILE *stream);
参数说明:
str:存储读取内容的缓冲区n:最大读取字符数(包括’\0’)stream:文件指针
特点:
- 读取到换行符或EOF或达到n-1个字符时停止
- 会保留换行符
8. fseek()
功能:移动文件指针
函数原型:
int fseek(FILE *stream, long offset, int whence);
whence参数:
SEEK_SET:从文件开头偏移SEEK_CUR:从当前位置偏移SEEK_END:从文件末尾偏移
使用示例:
fseek(f, 0, SEEK_END); // 移动到文件末尾
long size = ftell(f); // 获取文件大小
fseek(f, 0, SEEK_SET); // 移动到文件开头
9. ftell()
功能:获取当前文件指针位置
函数原型:
long ftell(FILE *stream);
返回值:当前文件指针位置(字节偏移)
应用:配合fseek获取文件大小
三、文件和目录管理函数
10. rename()
功能:重命名或移动文件
函数原型:
int rename(const char *old_name, const char *new_name);
返回值:成功返回0,失败返回-1
11. unlink()
功能:删除文件
函数原型:
int unlink(const char *pathname);
注意:只能删除文件,不能删除目录
12. mkdir()
功能:创建目录
函数原型:
int mkdir(const char *pathname, mode_t mode);
参数:
pathname:目录路径mode:权限模式(如0775)
13. rmdir()
功能:删除空目录
函数原型:
int rmdir(const char *pathname);
限制:只能删除空目录
14. opendir()
功能:打开目录
函数原型:
DIR *opendir(const char *name);
返回值:目录流指针,失败返回NULL
15. readdir()
功能:读取目录条目
函数原型:
struct dirent *readdir(DIR *dirp);
返回值:指向dirent结构的指针,到达末尾返回NULL
dirent结构:
struct dirent {
ino_t d_ino; // inode编号
char d_name[256]; // 文件名
};
16. closedir()
功能:关闭目录
函数原型:
int closedir(DIR *dirp);
17. stat()
功能:获取文件状态
函数原型:
int stat(const char *pathname, struct stat *statbuf);
stat结构重要字段:
struct stat {
mode_t st_mode; // 文件类型和权限
off_t st_size; // 文件大小(字节)
time_t st_mtime; // 最后修改时间
};
判断文件类型宏:
S_ISDIR(mode):是否为目录S_ISREG(mode):是否为普通文件
四、FATFS特有函数
18. f_getfree()
功能:获取FATFS卷的空闲簇数量和文件系统信息
函数原型:
FRESULT f_getfree(
const TCHAR* path, // 逻辑驱动器号
DWORD* nclst, // 返回空闲簇数量
FATFS** fatfs // 返回文件系统对象指针
);
使用示例:
FATFS *fs;
DWORD fre_clust;
if (f_getfree("0:", &fre_clust, &fs) == FR_OK) {
uint64_t total_bytes = (uint64_t)(fs->n_fatent - 2) * fs->csize * fs->ssize;
uint64_t free_bytes = (uint64_t)fre_clust * fs->csize * fs->ssize;
ESP_LOGI(TAG, "Total: %llu KB", total_bytes / 1024);
ESP_LOGI(TAG, "Free: %llu KB", free_bytes / 1024);
}
FATFS结构关键字段:
n_fatent:FAT表项总数csize:每簇扇区数ssize:扇区大小(字节)
函数分类速查表
| 分类 | 函数名 | 功能 | 头文件 |
|---|---|---|---|
| SD卡管理 | esp_vfs_fat_sdmmc_mount() | 挂载SD卡 | esp_vfs_fat.h |
esp_vfs_fat_sdcard_unmount() | 卸载SD卡 | esp_vfs_fat.h | |
sdmmc_card_print_info() | 打印SD卡信息 | sdmmc_cmd.h | |
| 文件打开/关闭 | fopen() | 打开文件 | stdio.h |
fclose() | 关闭文件 | stdio.h | |
| 文件读写 | fprintf() | 格式化写入 | stdio.h |
fgets() | 读取一行 | stdio.h | |
| 文件定位 | fseek() | 移动文件指针 | stdio.h |
ftell() | 获取当前位置 | stdio.h | |
| 文件管理 | rename() | 重命名文件 | stdio.h |
unlink() | 删除文件 | unistd.h | |
stat() | 获取文件状态 | sys/stat.h | |
| 目录操作 | mkdir() | 创建目录 | sys/stat.h |
rmdir() | 删除空目录 | unistd.h | |
opendir() | 打开目录 | dirent.h | |
readdir() | 读取目录项 | dirent.h | |
closedir() | 关闭目录 | dirent.h | |
| 磁盘管理 | f_getfree() | 获取磁盘空间 | ff.h (FATFS) |
编译和运行
环境准备
# 激活ESP-IDF环境
get_idf
# 设置目标芯片
idf.py set-target esp32s3
编译项目
idf.py build
烧录程序
idf.py -p /dev/tty.wchusbserial1440 flash
查看运行日志
idf.py -p /dev/tty.wchusbserial1440 monitor
退出监视器
按 Ctrl + ]
运行输出示例
I (xxx) main: Initializing SD card
I (xxx) main: Using SDMMC peripheral
I (xxx) main: Mounting filesystem
I (xxx) main: Filesystem mounted
Name: SD32G
Type: SDHC/SDXC
Speed: 20 MHz
Size: 30436MB
I (xxx) main: Opening file /sdcard/你好hello.txt
I (xxx) main: File written
I (xxx) main: Reading file /sdcard/你好hello.txt
I (xxx) main: Read from file: '你好hello先生 SD32G!'
=== Test 1: Append to file ===
I (xxx) main: Appending to file /sdcard/你好hello.txt
I (xxx) main: Content appended
=== Test 2: File seek operations ===
I (xxx) main: File size: 85 bytes
I (xxx) main: Content from beginning: 你好hello先生 SD32G!
=== Test 3: File status ===
I (xxx) main: File size: 85 bytes
I (xxx) main: Last modified: Wed Jan 01 10:00:00 2026
=== Test 7: List root directory ===
I (xxx) main: [FILE] 你好hello.txt (size: 85 bytes)
I (xxx) main: [DIR] test_dir
=== Test 9: Disk space information ===
I (xxx) main: Total space: 30436352 KB
I (xxx) main: Free space: 30435328 KB
I (xxx) main: Used space: 1024 KB
I (xxx) main: Card unmounted
常见问题和解决方案
1. SD卡挂载失败
可能原因:
- SD卡未正确插入
- 引脚连接错误
- SD卡格式不支持(建议FAT32)
- 供电不足
解决方法:
- 检查硬件连接
- 使用电脑格式化SD卡为FAT32
- 确保3.3V供电稳定
- 检查引脚定义是否与硬件匹配
2. 文件操作失败
注意事项:
- 文件路径必须包含挂载点前缀(如
/sdcard/) - 打开文件后必须关闭,否则可能导致数据丢失
- 检查
max_files配置是否足够 - 确保文件名不包含非法字符
3. 中文文件名支持
说明:
- FATFS支持UTF-8编码的中文文件名
- 确保代码文件保存为UTF-8编码
- 终端需要支持UTF-8显示
4. 编译错误:format-truncation
错误信息:
error: 'snprintf' output may be truncated
解决方法:
增大缓冲区大小,例如将256改为512:
char full_path[512];
5. 性能优化建议
提高读写速度:
- 增大
allocation_unit_size可提高大文件写入速度 - 使用4线SDMMC模式可提高传输速度
- 批量操作时减少文件打开/关闭次数
- 使用缓冲区批量读写
4线模式配置:
slot_config.width = 4; // 4线模式
slot_config.d1 = GPIO_NUM_XX;
slot_config.d2 = GPIO_NUM_XX;
slot_config.d3 = GPIO_NUM_XX;
扩展学习建议
1. 日志记录系统
- 创建按日期命名的日志文件
- 实现日志轮转机制(按大小或时间)
- 添加时间戳和日志级别
- 实现异步写入提高性能
2. 数据采集应用
- 定时采集传感器数据
- 保存为CSV格式便于分析
- 实现数据缓冲和批量写入
- 添加数据校验和错误恢复
3. 配置文件管理
- 使用JSON格式存储配置
- 实现配置读取和解析
- 支持配置热更新
- 添加配置备份机制
4. 固件升级
- 从SD卡读取固件文件
- 实现OTA升级功能
- 添加固件校验(CRC/MD5)
- 实现升级失败回滚
参考资源
总结
通过本教程,您已经学习了:
✅ ESP32-S3的SDMMC接口配置方法
✅ FATFS文件系统的挂载和卸载流程
✅ 18个核心函数的使用方法
✅ 标准C文件操作API的完整应用
✅ 目录管理和遍历技巧
✅ 磁盘空间查询方法
✅ 完整的代码实现和测试流程
这些知识可以应用于:
- 数据记录和日志系统
- 配置文件管理
- 传感器数据存储
- 固件升级包存储
- 多媒体文件播放
- 数据备份和恢复
更多推荐


所有评论(0)