少走弯路,ESP32 使用Micro SD(TF)的经验说明

34 篇文章 2 订阅
26 篇文章 0 订阅

看了无数资料,各种折腾了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中验证过的,如果出现错误,请检查其他原因。

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值