【立创·实战派ESP32-S3】文档教程
第 6 章 读写 SD 卡
ESP32-S3 有 SDIO 接口,支持当前的 SDIO3.0 协议,可以使用 SDIO 总线读写 SD 卡。同时呢,也可以使用 SPI 接口读写 SD 卡。在实战派 ESP32-S3 开发板上,使用 SDIO 接口读写 SD 卡,由于 ESP32-S3 引脚太少了,所以开发板上采用了 1-SD 模式,即 1 条数据线。可以回过头去看一下 SD 卡的原理图接线。
Micro SD 卡,也叫做 TF 卡,称呼哪个都行,早期可能有点区别,现在其实就是一个东西。
目前市面上的 TF 卡,32GB 以下一般是 FAT32 系统,32GB 以上的卡,一般是 exFAT 系统。在电脑 U 盘图标上右键菜单选择属性,可以查看文件系统名称。
ESP32 IDF 目前只支持 FAT32 系统,不支持 exFAT 系统。如果你的 TF 卡是 exFAT 系统,且在程序中开启了“如果挂载不成功就格式化 SD 卡”,开发板上电后,会自动把你的 SD 卡格式化为 FAT,你的卡上的东西会全部消失,所以在上电之前,请把你的 SD 卡重要文件全部拷贝走。
6.1 使用例程
把开发板提供的【03-micro_sd】例程复制到你的实验文件夹当中,并使用 VSCode 打开工程。
准备一张 microSD 卡,把重要的文件拷贝走,以免做实验的时候损坏或删除文件。把 microSD 卡插入开发板卡槽,再把开发板插入电脑。在 VSCode 上选择串口号,选择目标芯片为 esp32s3,串口下载方式,然后点击“一键三联”按钮,等待编译下载打开终端。
终端自动打开后,输出结果如下所示:
I (346) main_task: Calling app_main()
I (346) main: Initializing SD card
I (356) main: Using SDMMC peripheral
I (356) main: Mounting filesystem
I (366) gpio: GPIO[47]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (376) gpio: GPIO[48]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (376) gpio: GPIO[21]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (426) main: Filesystem mounted
Name: SD16G
Type: SDHC/SDXC
Speed: 20.00 MHz (limit: 20.00 MHz)
Size: 30726MB
CSD: ver=2, sector_size=512, capacity=62926848 read_bl_len=9
SSR: bus_width=1
I (436) main: Opening file /sdcard/你好hello.txt
I (456) main: File written
I (456) main: Reading file /sdcard/你好hello.txt
I (466) main: Read from file: '你好hello先生 SD16G!'
I (466) main: Card unmounted
I (466) main_task: Returned from app_main()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
按 Ctrl+]退出终端后,开发板断电,把开发板上的 SD 卡插入读卡器,在电脑上可以看到 SD 卡里面多了一个“你好 hello.txt”文件。
打开 txt 文件,可以看到里面的内容:
6.2 例程讲解
本例程主要实现的功能是挂载 SD 卡,在 SD 卡上新建文件,然后再给文件里面写入内容,最后卸载弹出 SD 卡。
本例程只有一个 main.c 文件,点击打开,找到 app_main 函数。最开始的几行代码如下所示:
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false, // 如果挂载不成功是否需要格式化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_host_t host = SDMMC_HOST_DEFAULT(); // SDMMC主机接口配置
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); // SDMMC插槽配置
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");
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card); // 挂载SD卡
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
以上程序,重点在最后一条语句,使用 esp_vfs_fat_sdmmc_mount 函数挂载 SD 卡,其中的各个参数,就是在此函数前面配置好的。配置了挂载点、引脚、模式之类的。
接下来判断是否挂载成功,代码如下:
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卡的一些信息
2
3
4
5
6
7
8
9
10
如果没有挂载成功,打印失败原因,如果挂载成功,打印获取到的 SD 卡的一些信息,刚才在终端看到的关于 SD 卡的信息就是 sdmmc_card_print_info 这个函数打印的,有 Name、Type、Speed、Size、CSD、SSR。
再往下看,是在 SD 卡上新建文件的代码:
// 新建一个txt文件 并且给文件中写入几个字符
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;
}
2
3
4
5
6
7
8
file_hello 定义了文件的路径是"/sdcard""/你好 hello.txt"。
定义了 data 字符数组。
snprintf 函数用来给 data 字符数组里面写入内容。第 1 个参数是目标数组,这里就是 data。第 2 个参数是写入多少字节。第 3 个参数是要写入的字符内容,这是带有格式化参数,两个 %s 分别是后面的两个字符串。即给 data 写入的字符串是:“你好 hello 先生 SD16G!”。SD16G 是 card->cid.name 获得的 SD 卡名称。
s_example_write_file 函数用于给 SD 卡写入内容,该函数在 app_main 函数前面定义,如下所示:
// 写文件内容 path是路径 data是内容
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这里面使用了三个 C 库函数,分别是 fopen、fprintf、fclose 函数。
fopen 函数用于打开一个文件,第 1 个参数是路径,第 2 个参数是打开方式,“w”表示创建一个用于写入的空文件,如果文件名已经存在,将会清空里面的内容,作为一个新的文件。
fprintf 函数用于发送格式化输出到文件 f 中,f 指向刚才 fopen 的文件,data 是发送的内容。
fclose 函数用于关闭 fopen 打开的文件,与 fopen 成对出现。
回到主函数中继续往下,是读出文件内容的代码:
// 打开txt文件,并读出文件中的内容
ret = s_example_read_file(file_hello);
if (ret != ESP_OK) {
return;
}
2
3
4
5
s_example_read_file 函数在 app_main 函数前面定义,内容如下:
// 读文件内容 path是路径
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
函数中,首先使用 fopen 打开文件,第 1 个参数 path 是文件路径,第 2 个参数"r"表示打开一个用于读取的文件。
定义一个字符串数组 line,用于保存文件中的内容。
fgets 函数用于读取文件中的一行内容。第 1 个参数是要保存内容的数组,第 2 个参数是要读取的最大字符数,一般就是数组的大小,f 指向文件名称。
fclose 关闭文件。
接下来,处理了一下读出的字符串。把字符串最后的\n 替换成了\0,\n 是换行符,\0 表示一个字符串的结尾。如果这里不把\n 替换成\0 的话,最后一行 ESP_LOGI 输出到终端的结果将是下面这个样子,符号'到了第 2 行,因为在'之前有个换行符。
I (466) main: Read from file: '你好hello先生 SD16G!
'
2
回到主函数当中,就剩下下面的代码了:
// 卸载SD卡
esp_vfs_fat_sdcard_unmount(mount_point, card);
ESP_LOGI(TAG, "Card unmounted");
2
3
esp_vfs_fat_sdcard_unmount 函数用于卸载 SD 卡,或者叫做弹出 SD 卡。
以上就是 SD 卡例程代码的全部讲解。
6.3 例程制作过程
我们还是使用 sample project 作为模板,复制 sample_project 这个工程到我们的实验文件夹,然后把这个文件夹的名称修改为 03-micro_sd,修改后我的工程路径为 D:\esp32s3\03-micro_sd。
使用 VSCode 打开 micro_sd 工程。再打开一个 VSCode 打开 esp-idf 整个文件夹,我们需要参考官方的 sdmmc 例程,该例程在 esp-idf 中的路径为 examples\storage\sd_card\sdmmc。
我们先点击打开 micro_sd 工程目录下的 CMakeList.txt 文件,修改工程的名称为 micro_sd,然后保存关闭此文件。
project(micro_sd)
把官方 sdmmc 例程的 app_main 函数最开始处的第 64~124 行代码复制到 micro_sd 例程的 app_main 中,复制完以后,micro_sd 例程中的 app_main 函数如下代码所示:
void app_main(void)
{
esp_err_t ret;
// Options for mounting the filesystem.
// If format_if_mount_failed is set to true, SD card will be partitioned and
// formatted in case when mounting fails.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
sdmmc_card_t *card;
const char mount_point[] = MOUNT_POINT;
ESP_LOGI(TAG, "Initializing SD card");
// Use settings defined above to initialize SD card and mount FAT filesystem.
// Note: esp_vfs_fat_sdmmc/sdspi_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, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 40MHz for SDMMC)
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
// Set bus width to use:
#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
slot_config.width = 4;
#else
slot_config.width = 1;
#endif
// On chips where the GPIOs used for SD card can be configured, set them in
// the slot_config structure:
#ifdef CONFIG_SOC_SDMMC_USE_GPIO_MATRIX
slot_config.clk = CONFIG_EXAMPLE_PIN_CLK;
slot_config.cmd = CONFIG_EXAMPLE_PIN_CMD;
slot_config.d0 = CONFIG_EXAMPLE_PIN_D0;
#ifdef CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
slot_config.d1 = CONFIG_EXAMPLE_PIN_D1;
slot_config.d2 = CONFIG_EXAMPLE_PIN_D2;
slot_config.d3 = CONFIG_EXAMPLE_PIN_D3;
#endif // CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_4
#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");
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
上述代码的作用是挂载 SD 卡。
代码中出现的第 1 个条件编译,是判断如果挂载 SD 卡失败,是否需要格式化 SD 卡,通过分析条件编译中的代码我们知道,只需要更改 .format_if_mount_failed
为 true
和 false
就可以了,true
表示需要格式化,false
表示不需要格式化。我们可以把这里的条件编译去掉,只剩下 .format_if_mount_failed=false
就可以了。如果想开启“挂载不成功就格式化”的话,再把这里写为 true
就行。
第 2 个条件编译是定义 SDIO 通信方式,是 4 线 SDIO 还是 1 线 SDIO,官方的例程需要兼容很多开发板,而我们的开发板这里只能是 1 线 SDIO,所以,我们把这里的条件编译也去掉,只剩下 slot_config.width = 1
就可以了。
第 3 个条件编译是定义 SDIO 引脚,这里也根据我们的开发板只有 1 线模式,把条件编译去掉,只剩下定义 clk cmd 和 d0 的定义就可以了。
代码中那些大段的注释,我们也把它删除,最后整理后的代码如下所示:
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_host_t host = SDMMC_HOST_DEFAULT(); // SDMMC主机接口配置
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); // SDMMC插槽配置
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");
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card); // 挂载SD卡
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
精简完之后,我们看看上面的代码到底做了一些什么事情。
我们先看上面代码中的最后一行代码,它的作用是要挂载 SD 卡,这其中,需要用到 5 个参数。这个挂载 SD 卡函数前面的那些代码,就是为了准备这 5 个参数。
第 1 个参数,是挂载点 mount_point
,在代码中,定义了挂载点是 MOUNT_POINT
,从 sdmmc 例程中我们可以知道,这个宏定义是 "/sdcard"
,我们需要在 micro_sd 例程中的 app_main 函数前面也加一个宏定义,或者直接在这里把 MOUNT_POINT
删除,修改为 "/sdcard"
也可以,两种方法都行。
第 2 个参数是 host
,是主机接口配置,直接使用了默认配置,我们可以在 sdmmc 例程中的 SDMMC_HOST_DEFAULT()
代码上单击右键,然后选择转到定义,看看它给 host 有哪些成员变量并且分别是怎么赋值的。
第 3 个参数是 slot_config
,slot 在这里是插槽的意思,代码中,首先给插槽做了默认的配置,然后修改了配置中的一些参数,包括 SDIO 总线模式,引脚名称等。
第 4 个参数是 mount_config
,配置如果加载失败,是否需要格式化 SD 卡,最大打开文件数,以及分配单元大小。
第 5 个参数是 card
,只需要在前面定义一下,在之后会使用它操作 SD 卡。
接下来再复制 sdmmc 例程中的第 126~139 行代码复制到 micro_sd 例程中的 app_main 中,放到刚才的代码后面就行。
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));
}
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, card);
2
3
4
5
6
7
8
9
10
11
12
13
14
这段代码用来判断 SD 卡是否挂载成功。
如果没有挂载成功,进入第一个 if,然后判断 ret 是否是 ESP_FAIL,如果是的话,给终端打印提示:“挂载失败,如果你想在挂载失败后格式化 SD 卡,在 menuconfig 中勾选 EXAMPLE_FORMAT_IF_MOUNT_FAILED”,因为我们前面已经把这个条件编译删除了,所以这里的注释,我们改一下,把第 2 行注释去掉,只剩下第 1 行注释,提示挂载失败,就可以了。
如果没有挂载成功,且 ret 不是 ESP_FAIL,说明发生了其它错误,这里提示:“初始化 SD 失败,确保 SD 卡接了上拉电阻”,因为我们的 SD 卡总线已经接了上拉电阻,所以这里就没有必要提示了,只保留第 1 个提示初始化 SD 卡失败就可以了,错误名称提示(esp_err_to_name
)可以保留。
如果挂载成功,打印获取到的 SD 卡的一些信息。
修改好后的这一段代码如下所示:
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卡的一些信息
}
2
3
4
5
6
7
8
9
10
11
然后我们需要在 micro_sd 例程的 app_main 前面加一些定义,如下所示:
#define BSP_SD_CLK (47)
#define BSP_SD_CMD (48)
#define BSP_SD_D0 (21)
static const char *TAG = "main";
#define MOUNT_POINT "/sdcard"
2
3
4
5
6
7
然后把需要的头文件加入,最后的头文件为:
#include <stdio.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
2
3
4
现在,虽然还没有写 SD 卡的读写程序,但是可以编译下载代码看一下 SD 卡是否可以挂载成功。
在 menuconfig 中设置 FLASH 大小为 16MB,然后在搜索框输入 FAT,把 Block Size 修改为 4096,保存关闭。
把 micro sd 卡插入到开发板的插槽,然后选择好串口号、下载方式,点击“一键三联”按钮,开始编译下载程序。注意,先备份你的 micro sd 卡上的重要文件,否则做实验过程中,可能会造成损失。
单片机运行后,终端输出如下所示:
I (320) main: Initializing SD card
I (330) main: Using SDMMC peripheral
I (330) main: Mounting filesystem
I (340) gpio: GPIO[47]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (350) gpio: GPIO[48]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (350) gpio: GPIO[21]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (410) main: Filesystem mounted
Name: SD16G
Type: SDHC/SDXC
Speed: 20.00 MHz (limit: 20.00 MHz)
Size: 30726MB
CSD: ver=2, sector_size=512, capacity=62926848 read_bl_len=9
SSR: bus_width=1
I (420) main_task: Returned from app_main()
2
3
4
5
6
7
8
9
10
11
12
13
14
在终端输出中,可以看到文件系统挂载成功,并且输出了 SD 卡的一些信息。
接下来,我们继续完成 SD 卡的读写功能。
复制 sdmmc 例程中的第 143~150 行到 micro_sd 中的 app_main 函数中,复制到最后面。
// First create a file.
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;
}
2
3
4
5
6
7
8
这些代码用来在 SD 上创建一个 hello.txt 文件,并且给文件中写入一些字符,字符的内容是 SD 卡名称和 Hello,比如,我的 SD 卡名称是 SD16G,就会写入 Hello SD16G。这部分代码不用修改。
代码中的 EXAMPLE_MAX_CHAR_SIZE
表示可以处理的最大字节数,这个宏定义定义到 app_main 前面。
#define EXAMPLE_MAX_CHAR_SIZE 64
代码中用到了 s_example_write_file
函数,这个函数就在 sdmmc 例程 app_main 的文件中定义,到时候我们把这个函数复制过去就行。
然后我们复制 sdmmc 例程中 app_main 中的第 167~170 行代码到 micro_sd 例程中的 app_main 中,放到最后面。
ret = s_example_read_file(file_foo);
if (ret != ESP_OK) {
return;
}
2
3
4
上面这个代码用来读取文件中的内容。我们需要把函数的参数 file_foo 改成 file_hello。修改后的代码为:
// 打开hello.txt文件,并读出文件中的内容
ret = s_example_read_file(file_hello);
if (ret != ESP_OK) {
return;
}
2
3
4
5
这里用到了 s_example_read_file
函数,这个函数就在 sdmmc 例程 app_main 的文件中定义,现在我们可以把这个函数以及上面提到的写入函数一起复制到我们的例程中。
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;
}
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');
if (pos) {
*pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);
return ESP_OK;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
上买读写代码,不需要修改。
在读文件的函数中,用到了 strchr 函数,所以需要在前面包含 string.h 头文件。
#include <string.h>
最后,读写完 SD 卡后,我们把 SD 卡卸载,相当于电脑上的“弹出 U 盘”。复制 sdmmc 例程中 app_main 函数的最后 2 行代码到我们例程的 app_main 函数最后面。
esp_vfs_fat_sdcard_unmount(mount_point, card);
ESP_LOGI(TAG, "Card unmounted");
2
现在,SD 卡的例程就全部写完了,可以继续编译下载看结果了。
程序运行的终端输出如下所示:
I (318) main: Initializing SD card
I (328) main: Using SDMMC peripheral
I (328) main: Mounting filesystem
I (338) gpio: GPIO[47]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (348) gpio: GPIO[48]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (348) gpio: GPIO[21]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (398) main: Filesystem mounted
Name: SD16G
Type: SDHC/SDXC
Speed: 20.00 MHz (limit: 20.00 MHz)
Size: 30726MB
CSD: ver=2, sector_size=512, capacity=62926848 read_bl_len=9
SSR: bus_width=1
I (408) main: Opening file /sdcard/hello.txt
I (428) main: File written
I (428) main: Reading file /sdcard/hello.txt
I (438) main: Read from file: 'Hello SD16G!'
I (438) main: Card unmounted
I (438) main_task: Returned from app_main()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
从终端输出中,我们可以看出,首先 SD 卡挂载成功,然后显示 SD 卡一些信息,然后创建一个 hello.txt 文件并写入字符,然后读取里面的字符,最后弹出 SD 卡。
我们让开发板断电(断电之前先使用 Ctrl+]关闭退出终端),然后取出 micro SD 卡,使用读卡器连接到电脑,我们可以在电脑端看到 SD 卡中有一个文件,文件的内容为 Hello SD16G,如下图所示:
不要带电插拔 SD 卡。
例程中使用的文件系统是 FatFS,这是一个开源的小型嵌入式文件系统,如果需要玩转 SD 卡,可以自己找资料深入的学习 FatFS 这个文件系统。
目前,还没有打开支持长文件名的设置,所以文件名称最多 8 个字符,且只能是英文,要想支持更长的文件名称,以及中文文件名称,需要在 menuconfig 中打开对应的功能,如下图所示:
上图中,第一个箭头配置支持长文件名,第二个箭头配置支持简体中文字符,第三个箭头配置字符使用 UTF-8 编码。
接下来,文件名称就可以超过 8 个字符,且可以定义成中文,例如,我把文件名称改为“你好 hello”,把写入的内容改为“你好 hello 先生”。
再次编译下载,如果没有问题的话,使用 idf.py save-defconfig 命令生成 sdkconfig.defaults 文件,此文件保存了你在 menuconfig 中做的所有改动配置,不包含默认的配置。
6.4 修复 SD 卡
如果在实验过程中,由于程序的问题,引起 SD 卡文件系统损坏,造成 ESP32 和电脑都无法识别 SD 卡,可以使用下面方法恢复。
SD 卡插入读卡器后连接到电脑。
在 windows 上打开 power shell,如下图所示:
输入 diskpart 命令回车,进入磁盘管理工具,如下图所示:
输入 list disk 命令回车,列出当前电脑上的所有磁盘,如下图所示:
找到的你的损坏的磁盘序号,例如,我的 SD 卡大小是 32G,那么上面列表中的大小为 30G 的磁盘 2,应该就是我的 SD 卡。
输入 select disk 2,选择磁盘 2(你的是磁盘几,就输入 select disk 几),如下图所示:
输入 clean 命令回车,清除磁盘,如下图所示:
输入 create partition primary 命令回车,创建分区,如下图所示:
输入 list partition 命令回车,列出分区表,如下图所示:
输入 select partition 1 命令回车,选择分区 1,如下图所示:
输入 format quick 命令回车,快速格式化 SD 卡,如下图所示:
这时候你就可以看到电脑已经识别了 SD 卡,并且里面内容为空。