ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ESP32 SD卡读写全攻略:从SPI接线到数据记录仪实战

2026/9/20 5:00:23 拓冰建站 浏览量
ESP32 SD卡读写全攻略:从SPI接线到数据记录仪实战 ESP32 这块板子好玩的地方在于它自带的那点 Flash 存储通常 4MB 左右跑跑代码、存点配置参数还行一旦你想做数据采集记录仪、离线音频播放器、或者给摄像头拍的照片找个落脚的地方那点空间瞬间就捉襟见肘了。我最早做环境监测项目时就吃过这个亏DHT22 温湿度数据每 10 秒采一次跑了不到两天就把 Flash 写满了程序直接卡死。后来把数据往 SD 卡上一挂同样的采集频率一张 8GB 的卡够我连续记录大半年。所以这篇内容就是要把“ESP32 怎么读写 SD 卡”这件事从头到尾讲透不管你是刚拿到板子的新手还是已经会点灯但没碰过存储的玩家跟着走一遍就能让 ESP32 真正拥有海量存储空间。1. 先搞清楚 ESP32 和 SD 卡之间到底怎么连1.1 为什么 SD 卡是 ESP32 最省心的扩容方案给单片机扩存储常见路子就那么几条外挂 SPI Flash 芯片、接 EEPROM、上 SD 卡。前两种容量小、价格也不便宜而且读写起来还得自己管理地址和擦除块麻烦得很。SD 卡就不一样了它本身就是为海量数据存储设计的容量从几 GB 到几十 GB 随便挑价格便宜到几乎可以忽略更关键的是它内部自带控制器文件系统层面的读写由 ESP32 的库帮你搞定你只需要调几个函数就能像在电脑上操作文件一样存取数据。从协议层面看SD 卡支持两种通信模式SD 模式和SPI 模式。SD 模式速度快需要 4 根数据线并行传输但 ESP32 的 SD 模式对引脚有硬性要求不是随便哪个 GPIO 都能用。SPI 模式虽然速度慢一些但胜在灵活任意 GPIO 都能凑出一组 SPI 来用而且 MicroPython 和 Arduino 对 SPI 模式的支持都非常成熟。对于绝大多数中小规模的数据记录场景SPI 模式完全够用我实测下来用 SPI 模式写文本数据每秒几百 KB 的吞吐量毫无压力。1.2 SPI 模式下的引脚连接与硬件准备SPI 是“串行外设接口”的缩写它用四根线完成全双工通信SCK时钟、MOSI主机输出从机输入、MISO主机输入从机输出、CS片选。SD 卡在 SPI 模式下就是把自己伪装成一个 SPI 从设备ESP32 作为主机去读写它。接线的时候有个细节容易被忽略SD 卡的供电必须是3.3V绝对不能接 5V否则卡直接烧掉。市面上常见的 MicroSD 卡座模块一般自带电平转换和稳压直接接 3.3V 就行如果是裸卡座那就要确保供电稳定在 3.3V并且 MISO 线上最好加一个上拉电阻10kΩ 左右防止总线悬空时读到乱码。下面是我常用的接线方案以 ESP32 DevKit V1 为例SD 卡座引脚ESP32 GPIO说明CSGPIO 5片选低电平有效SCKGPIO 18SPI 时钟MOSIGPIO 23主机发、从机收MISOGPIO 19主机收、从机发VCC3.3V供电严禁接 5VGNDGND共地这套引脚组合是 ESP32 的默认 SPI 引脚VSPI用起来最省事。当然你也可以换成其他 GPIO只要在代码里把引脚号改对就行。我试过用 GPIO 12/13/14/15 这组也能正常工作但要注意 GPIO 12 在上电时如果被拉高会导致启动失败所以尽量避开它做片选。1.3 卡座模块和裸卡座的选型差异新手我建议直接买那种带电平转换芯片的 MicroSD 卡座模块几块钱一个插上就能用省去自己搭电路的麻烦。这种模块通常还带一个 3.3V 稳压芯片输入可以接 5V 也可以接 3.3V输出稳定在 3.3V 给卡供电对 ESP32 的 3.3V 引脚压力小很多。如果你用的是裸卡座那就得自己确保供电质量。ESP32 的 3.3V 输出电流有限如果同时带 WiFi 和 SD 卡可能会因为电流不足导致卡初始化失败。我遇到过这种情况WiFi 一开SD 卡就掉线后来在卡座的 VCC 和 GND 之间并了一个 100μF 的电解电容问题就解决了。这个电容的作用是缓冲瞬时电流需求相当于给 SD 卡配了个小水库。2. MicroPython 环境下让 SD 卡跑起来2.1 烧录固件与挂载 SD 卡的前置操作用 MicroPython 玩 ESP32第一步是烧录固件。去官网下载最新的 ESP32 固件.bin 文件然后用 esptool 烧进去。烧录命令大概长这样esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 esp32-20230426-v1.20.0.bin烧完之后用串口工具连上能看到 MicroPython 的 REPL 提示符就说明成功了。接下来要挂载 SD 卡MicroPython 提供了一个machine.SDCard类专门用来操作 SD 卡。挂载的本质是让 MicroPython 的文件系统认识这块卡挂载之后你就能用普通的文件操作函数去读写它。挂载代码很简短import machine, os sd machine.SDCard(slot2, sck18, mosi23, miso19, cs5) os.mount(sd, /sd) os.listdir(/sd)这里slot2在 ESP32 上通常对应 SPI 模式。执行完os.mount之后如果卡没问题os.listdir(/sd)就能列出卡里的文件。如果报错OSError: [Errno 19] ENODEV说明卡没被识别八成是接线问题或者卡格式不对。2.2 文件读写的基本操作与路径管理挂载成功之后读写文件就跟在电脑上操作一样自然。写文件用open(/sd/test.txt, w)读文件用open(/sd/test.txt, r)。这里有个坑要注意MicroPython 的文件系统路径是以挂载点为根目录的所以你不能写/test.txt必须写/sd/test.txt否则会写到 ESP32 内部的 Flash 文件系统里去。我一般会先建一个目录结构比如/sd/data/放采集数据/sd/log/放日志文件。建目录用os.mkdir(/sd/data)判断目录是否存在用os.stat配合异常捕获。下面是一个完整的写入和读取示例# 写入 with open(/sd/data/sensor.csv, a) as f: f.write(2025-01-01 10:00:00,25.6,60.2\n) # 读取 with open(/sd/data/sensor.csv, r) as f: for line in f: print(line.strip())用with语句的好处是文件会自动关闭不用手动调close()避免因为忘记关闭导致数据没写进去。我早期就犯过这个错写完数据直接断电结果文件里空空如也后来养成用with的习惯就再也没丢过数据。2.3 追加模式与缓冲区刷新的时机数据记录场景最常用的是追加模式a每次写入都往文件末尾加不会覆盖之前的内容。但这里有个关键点MicroPython 的文件写入默认是带缓冲的也就是说你调了write()之后数据可能还在内存缓冲区里并没有真正落到 SD 卡上。如果这时候突然断电缓冲区里的数据就丢了。解决办法有两个一是每次写完调f.flush()强制刷新二是用os.sync()同步整个文件系统。我一般在对数据完整性要求高的场景下每写一行就 flush 一次虽然会稍微降低写入速度但能保证断电时最多只丢最后一行。实测下来flush 一次的开销大概在几毫秒级别对于秒级采集频率完全无感。with open(/sd/data/sensor.csv, a) as f: f.write(2025-01-01 10:00:00,25.6,60.2\n) f.flush() os.sync()注意频繁调用os.sync()会加速 SD 卡磨损因为每次同步都会触发实际的写操作。如果你的采集频率很高比如每秒多次建议攒够一定行数再统一 flush在数据安全和卡寿命之间找个平衡。3. Arduino 框架下的 SD 卡读写实战3.1 库的选择与初始化流程Arduino 环境下操作 SD 卡核心库就是内置的SD.h和SPI.h。SD.h封装了 FAT16/FAT32 文件系统的操作你不需要关心底层扇区怎么读写直接调SD.begin()、SD.open()这些函数就行。初始化的标准流程是这样的#include SPI.h #include SD.h #define SD_CS 5 void setup() { Serial.begin(115200); if (!SD.begin(SD_CS)) { Serial.println(SD card mount failed!); return; } Serial.println(SD card mounted.); }SD.begin()的参数是片选引脚号ESP32 的默认 SPI 引脚会自动使用。如果返回false说明挂载失败。失败原因通常有三个接线错了、卡没格式化成 FAT32、卡座供电不足。我建议先用电脑把卡格式化成 FAT32分配单元大小选 4096 字节兼容性最好。3.2 写入 CSV 数据与读取校验Arduino 里写文件用SD.open(path, FILE_WRITE)注意FILE_WRITE是追加模式不是覆盖。如果你想覆盖得先SD.remove(path)删掉旧文件再创建。这个设计跟 MicroPython 的w模式不太一样新手容易搞混。下面是一个典型的 CSV 数据记录代码File dataFile SD.open(/data/sensor.csv, FILE_WRITE); if (dataFile) { dataFile.print(2025-01-01 10:00:00); dataFile.print(,); dataFile.print(25.6); dataFile.print(,); dataFile.println(60.2); dataFile.close(); }每次写完必须调close()否则数据不会真正写入卡里。这一点比 MicroPython 的with语句要麻烦一些但养成习惯就好。读取的时候用SD.open(path)不带第二个参数默认就是只读模式然后逐行readStringUntil(\n)就能把内容读出来。3.3 性能优化减少 open/close 次数SD 卡的 open 和 close 操作其实挺耗时的每次都要读写文件系统的元数据。如果你在循环里每次都 open 再 close写入速度会慢得让你怀疑人生。我实测过每写一行就 open/close 一次写入 1000 行花了将近 8 秒改成打开一次、连续写 1000 行再关闭只用了不到 1 秒。所以正确的做法是在采集循环开始前 open 一次循环里只 write循环结束后再 close。如果采集是持续不断的那就每隔一段时间比如每 100 行close 再重新 open既能保证数据落盘又不至于频繁操作文件系统。File dataFile SD.open(/data/sensor.csv, FILE_WRITE); for (int i 0; i 1000; i) { dataFile.print(millis()); dataFile.print(,); dataFile.println(analogRead(34)); if (i % 100 0) { dataFile.flush(); } } dataFile.close();flush()在 Arduino 的File类里也有作用跟 MicroPython 的 flush 一样把缓冲区数据推到卡上。每 100 行 flush 一次既控制了数据丢失风险又不会太影响写入速度。4. 那些让我折腾半天的坑和排查思路4.1 卡识别不了从供电到格式的完整排查链路SD 卡挂载失败是最常见的问题排查起来要有条理不能瞎试。我的排查顺序是这样的第一步查供电。用万用表量卡座 VCC 和 GND 之间的电压必须在 3.2V 到 3.4V 之间。如果低于 3.0VESP32 的 3.3V 输出可能带不动需要外接稳压或者加电容。第二步查接线。重点检查 MISO 和 MOSI 有没有接反这两个接反了现象是“能识别卡但读写全是乱码”。CS 引脚有没有接对代码里的片选号和实际接线是否一致。第三步查卡格式。把卡插到电脑上确认是 FAT32 格式。如果是 exFAT 或者 NTFSESP32 的库认不出来。大于 32GB 的卡默认可能是 exFAT需要用工具格式化成 FAT32。第四步换卡测试。有些杂牌卡兼容性差换一张品牌卡试试。我手里有一张老卡在电脑上好好的在 ESP32 上就是挂载失败换卡之后立刻正常。第五步降速试试。SPI 时钟频率太高也会导致通信失败。MicroPython 里可以在SDCard初始化时传freq参数Arduino 里可以调SPI.setFrequency()。从 20MHz 降到 10MHz 甚至 4MHz 试试很多时候降速就能解决。4.2 写入数据丢失缓冲区与断电时机的博弈数据写进去了但读出来是空的或者少了几行这个问题我遇到过好几次。根本原因就是前面说的缓冲区没刷新。除了 flush 之外还有一个隐藏问题文件关闭顺序。如果你先关了 SD 卡的文件系统再关文件数据也会丢。正确的顺序是先 close 文件再 unmount 文件系统。另外ESP32 在深度睡眠或者复位时如果文件还没关闭数据同样会丢。所以我在做低功耗采集项目时每次唤醒采集完数据写完立刻 close然后再进入睡眠。虽然这样会稍微增加功耗但数据安全第一。4.3 长时间运行的稳定性问题SD 卡连续写入几个小时甚至几天可能会出现写入速度变慢、偶尔写入失败的情况。这通常是因为卡内部的垃圾回收机制在起作用。闪存芯片的擦除块是有限的频繁写入会导致某些块提前老化。缓解办法有几个一是不要频繁创建和删除小文件尽量往一个大文件里追加二是定期把数据转存到其他地方然后重新格式化 SD 卡三是选用高耐久度的卡比如标有“High Endurance”的监控级卡。我在一个气象站项目里用普通卡跑了三个月就出现坏块换成监控卡之后跑了一年多还没出问题。5. 从数据记录仪到文件服务器SD 卡的进阶玩法5.1 用时间戳命名文件实现按天归档做长期数据采集把所有数据塞进一个文件不是好主意文件太大会导致打开和读取变慢。更好的做法是按天或者按小时创建新文件文件名里带上时间戳。ESP32 可以通过 NTP 获取网络时间然后格式化成20250101.csv这样的文件名。import ntptime, time ntptime.settime() t time.localtime() filename /sd/data/{:04d}{:02d}{:02d}.csv.format(t[0], t[1], t[2])这样每天零点之后自动切换到新文件单个文件大小可控查找历史数据也方便。我一般还会在文件头写一行列名方便后续用 Excel 或 Python 分析。5.2 通过 WiFi 把 SD 卡变成简易文件服务器ESP32 自带 WiFi配合 SD 卡可以做一个极简的文件服务器。思路是起一个 HTTP 服务收到请求后从 SD 卡读文件内容返回。MicroPython 里可以用socket模块手写一个简单的 HTTP 响应Arduino 里可以用WebServer库。这个玩法的实用价值在于设备部署在现场你不用拆机拔卡连上 WiFi 就能下载数据。我做过一个版本访问http://esp32-ip/list列出所有文件访问http://esp32-ip/download?file20250101.csv下载指定文件。代码量不大但省去了大量现场维护的麻烦。5.3 与传感器结合打造离线数据采集终端把 SD 卡和传感器结合起来就是一个完整的离线数据采集终端。BME280 测温湿度气压SD 卡存数据锂电池供电外壳一装往野外一放隔一段时间去收一次数据就行。这种方案成本低、部署快适合农业监测、环境记录、设备振动监测等场景。我在做这类项目时会在代码里加一个 LED 指示灯正常采集时慢闪SD 卡写入失败时快闪这样现场不用连电脑就能判断设备状态。另外还会加一个按键长按可以安全弹出 SD 卡避免直接拔卡导致数据损坏。6. 关于 SD 卡选型和寿命的一些个人经验选卡这件事我的原则是容量够用就行速度不用追高耐久度最重要。ESP32 的 SPI 接口速度有限再快的卡也跑不出高速读写所以没必要买 UHS-II 或者 V90 这种高端卡。Class 10 的普通卡完全够用容量 8GB 到 32GB 性价比最高。品牌方面我倾向于选闪迪、三星、金士顿这些大厂的产品杂牌卡虽然便宜但主控和闪存颗粒质量参差不齐长期写入容易出问题。另外要注意避免买到扩容卡那种卡标称 64GB 实际只有 8GB写满之后会覆盖旧数据非常坑。关于寿命SD 卡的闪存颗粒有擦写次数限制TLC 颗粒大概在 1000 到 3000 次 P/E 循环。如果你每天写入 1GB 数据一张 32GB 的卡理论寿命大概在几年左右。但实际使用中文件系统的元数据区会被频繁擦写往往比数据区先坏。所以定期备份数据、定期更换卡是长期运行项目必须考虑的事情。最后分享一个我常用的测试方法新卡到手之后先用dd命令或者专门的测试工具做一次全盘写入和校验确认没有坏块再投入使用。这个步骤花不了多少时间但能避免很多后期莫名其妙的故障。