在ESP32-S3开发板上使用MicroPython连接TF(TransFlash,也称为microSD)卡是一个常见的需求,可以用来存储和读取大量数据。ESP32S3和C3都可以使用MicroPython连TF卡读写文件。
提前学习和准备
所需硬件
- ESP32-S3开发板 (约25元)
- MicroSD卡 容量适中即可
- MicroSD卡适配器模块(约3元)
- 连接线(USB线用于供电和编程,母对母杜邦线)
所需软件
- MicroPython固件(适用于ESP32-S3)
- 调试开发环境(如Arduino、Putty、Screen等),只要能连上调试开发板即可。
学习Micropython文档
参考:SDCard 类 – 安全数字存储卡 — MicroPython中文 1.17 文档
参考:Quick reference for the ESP32 — MicroPython latest documentation
学习ESP32 SD/TF卡连接管脚:
手册上显示的默认(和首选)引脚分配如下(估计这是ESP32的,而不是C3和S3版本的):
Slot
0
1
2
3
Signal
Pin
Pin
Pin
Pin
sck
6
14
18
14
cmd
11
15
cs
5
15
miso
19
12
mosi
23
13
D0
7
2
D1
8
4
D2
9
12
D3
10
13
D4
16
D5
17
D6
5
D7
18
默认0号是板载Flash用的,我们不能用。
这是ESP32的,所以还需要自己找C3和S3的接口,可以通过找参考代码,看看比人用的什么端口。
MicroPython提供的读写SD卡函数
支持的参数的确切子集因平台而异。
class machine.
SDCard
(slot=1, width=1, cd=None, wp=None, sck=None, miso=None, mosi=None, cs=None, freq=20000000)
此类使用专用 SD/MMC 接口硬件或通过 SPI 通道提供对 SD 或 MMC 存储卡的访问。该类实现了由 定义的块协议uos.AbstractBlockDev。这使得 SD 卡的安装变得如此简单:
uos.mount(machine.SDCard(), "/sd")
构造函数采用以下参数:
slot选择要使用的可用接口。不设置此项将选择默认界面。
width选择 SD/MMC 接口的总线宽度。
cd可用于指定卡检测引脚。
wp可用于指定写保护引脚。
sck可用于指定 SPI 时钟引脚。
miso可用于指定 SPI miso 引脚。
mosi可用于指定 SPI mosi 引脚。
cs可用于指定 SPI 片选引脚。
freq以 Hz 为单位选择 SD/MMC 接口频率(仅在 ESP32 上支持)。
不过这个函数跟开发板具体型号绑定,ESP32S3没有试成功。
C代码参考
ESP32C3
/*
* pin 1 - not used | Micro SD card |
* pin 2 - CS (SS) | /
* pin 3 - DI (MOSI) | |__
* pin 4 - VDD (3.3V) | |
* pin 5 - SCK (SCLK) | 8 7 6 5 4 3 2 1 /
* pin 6 - VSS (GND) | ▄ ▄ ▄ ▄ ▄ ▄ ▄ ▄ /
* pin 7 - DO (MISO) | ▀ ▀ █ ▀ █ ▀ ▀ ▀ |
* pin 8 - not used |_________________|
* ║ ║ ║ ║ ║ ║ ║ ║
* ╔═══════╝ ║ ║ ║ ║ ║ ║ ╚═════════╗
* ║ ║ ║ ║ ║ ║ ╚══════╗ ║
* ║ ╔═════╝ ║ ║ ║ ╚═════╗ ║ ║
* Connections for ║ ║ ╔═══╩═║═║═══╗ ║ ║ ║
* full-sized ║ ║ ║ ╔═╝ ║ ║ ║ ║ ║
* SD card ║ ║ ║ ║ ║ ║ ║ ║ ║
* Pin name | - DO VSS SCK VDD VSS DI CS - |
* SD pin number | 8 7 6 5 4 3 2 1 9 /
* | █/
* |__▍___▊___█___█___█___█___█___█___/
*
* Note: The SPI pins can be manually configured by using `SPI.begin(sck, miso, mosi, cs).`
* Alternatively, you can change the CS pin and use the other default settings by using `SD.begin(cs)`.
*
* +--------------+---------+-------+----------+----------+----------+----------+----------+
* | SPI Pin Name | ESP8266 | ESP32 | ESP32‑S2 | ESP32‑S3 | ESP32‑C3 | ESP32‑C6 | ESP32‑H2 |
* +==============+=========+=======+==========+==========+==========+==========+==========+
* | CS (SS) | GPIO15 | GPIO5 | GPIO34 | GPIO10 | GPIO7 | GPIO18 | GPIO0 |
* +--------------+---------+-------+----------+----------+----------+----------+----------+
* | DI (MOSI) | GPIO13 | GPIO23| GPIO35 | GPIO11 | GPIO6 | GPIO19 | GPIO25 |
* +--------------+---------+-------+----------+----------+----------+----------+----------+
* | DO (MISO) | GPIO12 | GPIO19| GPIO37 | GPIO13 | GPIO5 | GPIO20 | GPIO11 |
* +--------------+---------+-------+----------+----------+----------+----------+----------+
* | SCK (SCLK) | GPIO14 | GPIO18| GPIO36 | GPIO12 | GPIO4 | GPIO21 | GPIO10 |
* +--------------+---------+-------+----------+----------+----------+----------+----------+
*
* For more info see file README.md in this library or on URL:
* https://github.com/espressif/arduino-esp32/tree/master/libraries/SD
*/
#include "FS.h"
#include "SD.h"
#include "SPI.h"
/*
Uncomment and set up if you want to use custom pins for the SPI communication
#define REASSIGN_PINS
int sck = -1;
int miso = -1;
int mosi = -1;
int cs = -1;
*/
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());
}
file.close();
}
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");
}
file.close();
}
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");
}
file.close();
}
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);
while (!Serial) {
delay(10);
}
#ifdef REASSIGN_PINS
SPI.begin(sck, miso, mosi, cs);
if (!SD.begin(cs)) {
#else
if (!SD.begin()) {
#endif
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.print("SD 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.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024));
}
void loop() {}
根据这份C代码,可以看到开发板与SD卡模块连通的线序图:
也就是默认连线
ESP32C3使用:7、 6、 5、 4
ESP32S3使用:10、 11、 13、 12
对应sd卡模块:cs mosi clk miso
这样终于拿到接线图了。
python代码参考
import machine, os
# Slot 2 uses pins sck=18, cs=5, miso=19, mosi=23
sd = machine.SDCard(slot=2)
os.mount(sd, '/sd') # mount
os.listdir('/sd') # list directory contents
os.umount('/sd') # eject
但是发现ESP32C3开发板没有machine.SDCard。而ESP32S3开发板有这个函数,但是使用这句也不对:sd = machine.SDCard(slot=2) 。 主要是这个slot=2是写死的,但是我的布线连接方式没法保证跟例子代码里一样啊。况且例子代码对应的是ESP32,而不是ESP32S3开发板。所以这段代码没法用,除非以后增加对ESP32S3开发板的支持(个人理解,不知道是否正确,请大家指点)。
咨询百度AI探索版
发现百度搜索有了个“探索版”按钮,发现这个探索版真不错,回答问题很全面。
问:ESP32s3 怎么用micropython 读取sd卡
回答:
在ESP32-S3上使用MicroPython读取SD卡,你需要先确保硬件连接正确,并且已经安装了支持SD卡的MicroPython固件。以下是一个基本的步骤指南,包括硬件连接、固件安装和代码示例。
硬件连接
-
准备硬件:
- ESP32-S3开发板
- MicroSD卡模块(如带有SPI接口的SD卡模块)
- 连接线
-
连接SD卡模块到ESP32-S3:
- 将SD卡模块的
MISO
连接到ESP32-S3的MISO
(通常是GPIO12) - 将SD卡模块的
MOSI
连接到ESP32-S3的MOSI
(通常是GPIO13) - 将SD卡模块的
SCK
连接到ESP32-S3的SCK
(通常是GPIO14) - 将SD卡模块的
CS
(Chip Select)连接到ESP32-S3的一个可用GPIO(如GPIO15) - 接好电源(3.3V)和地线(GND)
- 将SD卡模块的
安装MicroPython固件
- 前往MicroPython官网下载适用于ESP32的固件。
- 使用工具(如
esptool.py
)将固件刷入ESP32-S3开发板。
esptool.py --chip esp32-s3 --port /dev/ttyUSB0 erase_flash
esptool.py --chip esp32-s3 --port /dev/ttyUSB0 write_flash -z 0x0 path_to_firmware.bin
编写MicroPython代码
- 使用串行终端或IDE(如Thonny)连接到ESP32-S3。
- 编写以下代码来初始化SD卡并读取其内容:
import machine
import os
import sdcard
# 初始化SPI总线
spi = machine.SPI(2,
baudrate=1000000,
polarity=0,
phase=0,
sck=machine.Pin(14),
mosi=machine.Pin(13),
miso=machine.Pin(12))
# 初始化SD卡模块
sd = sdcard.SDCard(spi, machine.Pin(15))
# 挂载文件系统
vfs = os.VfsFat(sd)
os.mount(vfs, "/sd")
# 读取SD卡内容
print("Files on SD card:")
for file in os.listdir("/sd"):
print(file)
# 读取某个文件的内容
with open("/sd/yourfile.txt", "r") as file:
content = file.read()
print(content)
# 卸载文件系统
os.umount("/sd")
注意事项
- 引脚配置:确保你使用的引脚与你的硬件连接一致。如果你使用的是不同的引脚,请相应地修改代码。
- 模块选择:本文假设你使用的是带有SPI接口的SD卡模块。如果你使用的是其他类型的模块(如SDIO),请查阅相关文档进行配置。
- 文件系统:确保SD卡上已经有文件系统(如FAT32),并且包含你要读取的文件。
通过这些步骤,你应该能够在ESP32-S3上使用MicroPython成功读取SD卡的内容。如果遇到问题,请检查硬件连接、固件版本和代码配置是否正确。
实践
在使用ESP32C3的时候,发现没有machine.SDCard,于是转而使用了ESP32S3开发板。其实后来才知道,不需要再使用machine.SDCard,只要使用sdcard.SDCard即可,当然sdcard这个软件包需要用mip来安装。
连线
根据C语言代码,我们知道了线序:
ESP32S3使用:10、 11、 13、 12
对应sd卡模块:cs mosi clk miso
根据这份线序图,上面那段Python代码的连线部分应该进行相应改动,最终为:
spi = machine.SPI(2,
baudrate=1000000,
polarity=0,
phase=0,
sck=machine.Pin(13),
mosi=machine.Pin(11),
miso=machine.Pin(12))
# 初始化SD卡模块
sd = sdcard.SDCard(spi, machine.Pin(10))
前期需要处理事项:
1 打开开发板wifi
import wifi
这样wifi就打开了。wifi.py文件是通过webrepl传送上来的,内容:
import network
import time
nic = network.WLAN(network.STA_IF)
nic.active(True)
if not nic.isconnected():
nic.connect("showmemoney", "xxxx")
print("Waiting for h...")
while not nic.isconnected():
time.sleep(1)
print(nic.ipconfig("addr4"))
直接执行上面内容即可。也可以打开webrepl,然后把wifi.py文件上传到开发板,这样每次只要执行import wifi即可打开WiFi。打开webrepl使用命令:
import webrepl
webrepl.start()
2 安装sdcard软件包
wifi启动后,就可以上网安装sdcard软件包了,命令:
import mip
mip.install("sdcard")
安装完成sdcard软件包,就可以运行下面的代码了:
最终MicroPython读写SD/TF卡代码:
import machine
import os
import sdcard
# 初始化SPI总线
spi = machine.SPI(2,
baudrate=1000000,
polarity=0,
phase=0,
sck=machine.Pin(13),
mosi=machine.Pin(11),
miso=machine.Pin(12))
# 初始化SD卡模块
sd = sdcard.SDCard(spi, machine.Pin(10))
# 挂载文件系统
vfs = os.VfsFat(sd)
os.mount(vfs, "/sd")
# 写文件
with open("/sd/test.txt", "w") as file:
file.write("hello ")
file.write("world!")
# 读取SD卡内容
print("Files on SD card:")
for file in os.listdir("/sd"):
print(file)
# 读取某个文件的内容
with open("/sd/test.txt", "r") as file:
content = file.read()
print(content)
# 卸载文件系统
os.umount("/sd")
运行输出:
>>> with open("/sd/test.txt", "w") as file:
... file.write("hello")
... file.write("world!")
...
5
6
>>> # SD
>>> print("Files on SD card:")
Files on SD card:
>>> for file in os.listdir("/sd"):
... print(file)
...
test.txt
>>> #
>>> with open("/sd/test.txt", "r") as file:
... content = file.read()
... print(content)
...
helloworld!
>>> #
>>> os.umount("/sd")
非常棒,读取sd卡就这么简单啊!