看了无数资料,各种折腾了4-5天后还是没有折腾通ESP32 通过1bit sdmmc方式访问TF卡,经常报错如下:
E (6054) sdmmc_periph: sdmmc_host_clock_update_command(200): sdmmc_host_start_command returned 0x107
E (7055) sdmmc_periph: sdmmc_host_clock_update_command(200): sdmmc_host_start_command returned 0x107
E (7055) sdmmc_req: handle_idle_state_events unhandled: 00001000 00000000
E (7059) sdmmc_req: handle_idle_state_events unhandled: 00001000 00000000
E (11065) sdmmc_periph: sdmmc_host_clock_update_command(200): sdmmc_host_start_command returned 0x107
E (11066) sdmmc_common: sdmmc_init_ocr: send_op_cond (1) returned 0x107
E (11070) vfs_fat_sdmmc: sdmmc_card_init failed (0x107).
通过关键信息:
sdmmc_periph: sdmmc_host_clock_update_command(200): sdmmc_host_start_command returned 0x107
去各种搜索,各种尝试始终没有答案。
如果你看到这里,别急,后面会有答案
我是为了使用TF卡,自己打了一块pcb板
最初看网上的资料说D0,D1,D2,D3,CMD 这些都需要接上拉10K的电阻, CLK不需要接
出于保险,CLK设计了电阻位,最初没有放上电阻
esp32 对于tf卡的访问有三种模式:
4线SDMMC
1线SDMMC
SPI访问
4线访问由于端口冲突需要Setup中delay后再接入数据线
void setup()
{
Serial.begin(115200);
Serial.printf("\r\n开始SDMMC读取.");
delay(7000);
if (!SD_MMC.begin())
{
Serial.printf("\n SDMMC模式失败");
return;
}
Serial.printf("\n启动成功");
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD_MMC card attached");
return;
}
Serial.print("SD_MMC Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024));
SD_MMC.end();
}
除开D1,D2,D3不用接外1线SDMMC的访问大不同就是中间那句:
if (!SD_MMC.begin("/sdcard",true))
代码就是这么简单,但是无论怎么都是报错:
E (6054) sdmmc_periph: sdmmc_host_clock_update_command(200): sdmmc_host_start_command returned 0x107
E (7055) sdmmc_periph: sdmmc_host_clock_update_command(200): sdmmc_host_start_command returned 0x107
E (7055) sdmmc_req: handle_idle_state_events unhandled: 00001000 00000000
E (7059) sdmmc_req: handle_idle_state_events unhandled: 00001000 00000000
E (11065) sdmmc_periph: sdmmc_host_clock_update_command(200): sdmmc_host_start_command returned 0x107
E (11066) sdmmc_common: sdmmc_init_ocr: send_op_cond (1) returned 0x107
E (11070) vfs_fat_sdmmc: sdmmc_card_init failed (0x107).
于是检查接线,反复检查,错误依然;
于是怀疑电路,接上CLK电阻,错误依然;反复贴和反复取下验证,反复用万用表测电压
网上有些说法是有些电阻可以不要,于是重新贴片,某些电阻不贴,还是不行
换单片机,从一块esp32换成了另外一块esp32.不行
怀疑卡座问题,飞线把引脚接地,最初是没有接GND的
怀疑没接的CD引脚问题
去esp32 forum中留言,无果
在4线和1线中反复横跳,无果
换线材,无果
从arduino换成了idf,从arduino ide换成platformio,还是无果
你能想到的可能的方案都尝试过
中间好像正确过,然后又统统不行了
不过,SPI方式倒是验证通过了,sdmmc方式始终不行。
反复研究官方示例:
/*
* pin 1 - D2 | Micro SD card |
* pin 2 - D3 | /
* pin 3 - CMD | |__
* pin 4 - VDD (3.3V) | |
* pin 5 - CLK | 8 7 6 5 4 3 2 1 /
* pin 6 - VSS (GND) | ▄ ▄ ▄ ▄ ▄ ▄ ▄ ▄ /
* pin 7 - D0 | ▀ ▀ █ ▀ █ ▀ ▀ ▀ |
* pin 8 - D1 |_________________|
* ║ ║ ║ ║ ║ ║ ║ ║
* ╔═══════╝ ║ ║ ║ ║ ║ ║ ╚═════════╗
* ║ ║ ║ ║ ║ ║ ╚══════╗ ║
* ║ ╔═════╝ ║ ║ ║ ╚═════╗ ║ ║
* Connections for ║ ║ ╔═══╩═║═║═══╗ ║ ║ ║
* full-sized ║ ║ ║ ╔═╝ ║ ║ ║ ║ ║
* SD card ║ ║ ║ ║ ║ ║ ║ ║ ║
* ESP32-S3 DevKit | 21 47 GND 39 3V3 GND 40 41 42 |
* ESP32-S3-USB-OTG | 38 37 GND 36 3V3 GND 35 34 33 |
* ESP32 | 4 2 GND 14 3V3 GND 15 13 12 |
* Pin name | D1 D0 VSS CLK VDD VSS CMD D3 D2 |
* SD pin number | 8 7 6 5 4 3 2 1 9 /
* | █/
* |__▍___▊___█___█___█___█___█___█___/
* WARNING: ALL data pins must be pulled up to 3.3V with an external 10k Ohm resistor!
* Note to ESP32 pin 2 (D0): Add a 1K Ohm pull-up resistor to 3.3V after flashing
*
* SD Card | ESP32
* D2 12
* D3 13
* CMD 15
* VSS GND
* VDD 3.3V
* CLK 14
* VSS GND
* D0 2 (add 1K pull up after flashing)
* D1 4
*
去看别人淘宝卖的模块,都是spi的
最后,阴差阳错的情况下去看了IDF的部分代码,在sdmmc_default_configs.h其中看到了如下内容:
/**
* Macro defining default configuration of SDMMC host slot
*/
#if CONFIG_IDF_TARGET_ESP32P4
#define SDMMC_SLOT_CONFIG_DEFAULT() {\
.clk = GPIO_NUM_43, \
.cmd = GPIO_NUM_44, \
.d0 = GPIO_NUM_39, \
.d1 = GPIO_NUM_40, \
.d2 = GPIO_NUM_41, \
.d3 = GPIO_NUM_42, \
.d4 = GPIO_NUM_45, \
.d5 = GPIO_NUM_46, \
.d6 = GPIO_NUM_47, \
.d7 = GPIO_NUM_48, \
.cd = SDMMC_SLOT_NO_CD, \
.wp = SDMMC_SLOT_NO_WP, \
.width = SDMMC_SLOT_WIDTH_DEFAULT, \
.flags = 0, \
}
#elif CONFIG_IDF_TARGET_ESP32S3
#define SDMMC_SLOT_CONFIG_DEFAULT() {\
.clk = GPIO_NUM_14, \
.cmd = GPIO_NUM_15, \
.d0 = GPIO_NUM_2, \
.d1 = GPIO_NUM_4, \
.d2 = GPIO_NUM_12, \
.d3 = GPIO_NUM_13, \
.d4 = GPIO_NUM_33, \
.d5 = GPIO_NUM_34, \
.d6 = GPIO_NUM_35, \
.d7 = GPIO_NUM_36, \
.cd = SDMMC_SLOT_NO_CD, \
.wp = SDMMC_SLOT_NO_WP, \
.width = SDMMC_SLOT_WIDTH_DEFAULT, \
.flags = 0, \
}
#endif // GPIO Matrix chips
#endif
这里写的是ESP32P4和esp32 s3,难道???
arduino中网上很多sdmmc能找到的一些说明都是2022年前的,而这份文件更新于2023年。
于是大胆找出嘉立创买的那块S3,开始折腾,最初按照默认接线不成功
后来做其他尝试,换成了 39,40,47 ,居然可以1线SDMMC访问了。
在esp32 s3 下,使用setpin经过验证还可以使用其他GPIO.
SD_MMC.setPins(39,40,48);
几天折腾下来总结的经验如下:
- 根据乐鑫对于TF模块的电路设计,最好都是10k上拉电阻;但是有些不上拉也没事;事后验证过两块不同上拉的电阻的pcb板都可以正常读取和写入;
- SPI方法访问TF卡很方便,SDMMC方法非常折腾人,如果你不像我这样偏执那就使用SPI方式吧,某宝卖的TF模块都是使用了SPI方式;
- 如果你ESP32 通过SDMMC方式访问不了,那就换成ESP32 S3试试;记得使用setpin方法,但是文档中又说setpin方法对于esp32无效;
- 好像曾经在esp32下访问成功过,但是后来不知啥原因不能重现了;
- SD.begin方法要注意第三个参数:if(!SD_MMC.begin("/sdcard",true,true);
- 网上很多示例代码都是错误的,通过SPI和SDMMC方法tf卡调用的类库不一样,一个SD.h,一个是SD_MMC.h;
- 通过网络上的测速:1线sdmmc和4线sdmmc的速度基本是一样的,sdmmc大约比spi方式快一倍;不过这个测试结果比较古早,是通过128m 的TF卡的结果;
- IDF方式相对arduino能看到更多的提示信息,更深入;
- 有些文档写的是"/sdcard",有些是"/cdcard",对于启动无任何影响;
以下是经过验证,esp32通过SPI方法访问TF的代码:
// #include "FS.h"
#include "SD.h"
// #include <sd_defines.h>
// #include "driver/sdmmc_types.h"
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.printf("\r\n 开始运行,暂停5秒,等待SD上电");
delay(1000);
// if(!SD_MMC.begin("/sdcard",true)){
// Serial.printf("挂载SD读卡器失败");
// return;
// }
SPIClass spi = SPIClass(HSPI);
spi.begin(18 /* SCK */, 19 /* MISO */, 23 /* MOSI */, 5 /* SS */);
if (!SD.begin(5 /* SS */, spi, 120000000,"/cdcard")) {
Serial.println("Card Mount Failed");
return;
}
Serial.printf("\r\n SD装载成功");
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE)
{
Serial.println("未连接存储卡");
// return;
}
else if (cardType == CARD_MMC)
{
Serial.println("挂载了MMC卡");
}
else if (cardType == CARD_SD)
{
Serial.println("挂载了SDSC卡");
}
else if (cardType == CARD_SDHC)
{
Serial.println("挂载了SDHC卡");
}
else
{
Serial.println("挂载了未知存储卡");
}
File file = SD.open("/test.txt", FILE_WRITE);
if (file)
{
Serial.println("打开/建立 根目录下 test.txt 文件!");
}
char data[] = "hello world\r\n";
file.write((uint8_t *)data, strlen(data));
file.close();
//重命名文件
if (SD.rename("/test.txt", "/retest.txt"))
{
Serial.println("test.txt 重命名为 retest.txt !");
}
//读取文件数据
file = SD.open("/retest.txt", FILE_READ);
if (file)
{
Serial.print("文件内容是:");
while (file.available())
{
Serial.print((char)file.read());
}
}
//读取文件数据
file = SD.open("/1.txt", FILE_READ);
if (file)
{
Serial.print("文件内容是:");
while (file.available())
{
Serial.print((char)file.read());
}
}
Serial.printf("存储卡总大小是: %lluMB \n", SD.cardSize() / (1024 * 1024)); // "/ (1024 * 1024)"可以换成">> 20"
Serial.printf("文件系统总大小是: %lluMB \n", SD.totalBytes()/ (1024 * 1024));
Serial.printf("文件系统已用大小是: %lluB \n", SD.usedBytes());
}
void loop() {
// put your main code here, to run repeatedly:
}
以下是经过验证,esp32 s3通过1线sdmmc方法访问TF的代码:
#include <SD_MMC.h>
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
}
file = root.openNextFile();
}
}
void createDir(fs::FS &fs, const char *path) {
Serial.printf("Creating Dir: %s\n", path);
if (fs.mkdir(path)) {
Serial.println("Dir created");
} else {
Serial.println("mkdir failed");
}
}
void removeDir(fs::FS &fs, const char *path) {
Serial.printf("Removing Dir: %s\n", path);
if (fs.rmdir(path)) {
Serial.println("Dir removed");
} else {
Serial.println("rmdir failed");
}
}
void readFile(fs::FS &fs, const char *path) {
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
while (file.available()) {
Serial.write(file.read());
}
}
void writeFile(fs::FS &fs, const char *path, const char *message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
if (file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
}
void appendFile(fs::FS &fs, const char *path, const char *message) {
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if (!file) {
Serial.println("Failed to open file for appending");
return;
}
if (file.print(message)) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
}
void renameFile(fs::FS &fs, const char *path1, const char *path2) {
Serial.printf("Renaming file %s to %s\n", path1, path2);
if (fs.rename(path1, path2)) {
Serial.println("File renamed");
} else {
Serial.println("Rename failed");
}
}
void deleteFile(fs::FS &fs, const char *path) {
Serial.printf("Deleting file: %s\n", path);
if (fs.remove(path)) {
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void testFileIO(fs::FS &fs, const char *path) {
File file = fs.open(path);
static uint8_t buf[512];
size_t len = 0;
uint32_t start = millis();
uint32_t end = start;
if (file) {
len = file.size();
size_t flen = len;
start = millis();
while (len) {
size_t toRead = len;
if (toRead > 512) {
toRead = 512;
}
file.read(buf, toRead);
len -= toRead;
}
end = millis() - start;
Serial.printf("%u bytes read for %lu ms\n", flen, end);
file.close();
} else {
Serial.println("Failed to open file for reading");
}
file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
size_t i;
start = millis();
for (i = 0; i < 2048; i++) {
file.write(buf, 512);
}
end = millis() - start;
Serial.printf("%u bytes written for %lu ms\n", 2048 * 512, end);
file.close();
}
void setup()
{
Serial.begin(115200);
Serial.printf("\r\n开始SDMMC读取.");
// delay(1000);
SD_MMC.setPins(39,40,48);
// SD_MMC.setPins(14,15,2);
if (!SD_MMC.begin("/sdcard", true))
{
Serial.printf("\n SDMMC模式失败");
return;
}
Serial.printf("\n启动成功");
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD_MMC card attached");
return;
}
Serial.print("SD_MMC Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
listDir(SD_MMC, "/", 0);
createDir(SD_MMC, "/mydir");
Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024));
SD_MMC.end();
}
void loop()
{
}
上面两份代码中的GPIO端口都是在esp32和esp32 s3中验证过的,如果出现错误,请检查其他原因。