
1. 为什么给ESP32配一张SD卡是性价比最高的存储扩展方案搞ESP32开发的朋友迟早都会撞上一堵墙芯片自带的Flash存储实在太小了。以常见的ESP32-WROOM-32模组为例片内SPI Flash通常只有4MB扣掉Bootloader、分区表、应用程序本身占用的空间真正能拿来存用户数据的可能连1MB都不到。你随便跑一个数据采集项目传感器每秒写一条记录几天下来就把空间撑爆了。更别提音频录制、图片存储、日志长期归档这类需求片内Flash根本扛不住。这时候SD卡就是最务实的解法。一张8GB的MicroSD卡几十块钱容量是片内Flash的两千倍而且随时可以拔下来插到电脑上直接读数据不需要任何额外的上位机工具。对于零基础的朋友来说ESP32加SD卡这个组合硬件成本低、接线简单、代码量少是入门嵌入式存储的最佳切入点。这篇文章面向的是完全没有SD卡使用经验的ESP32初学者当然有Arduino或MicroPython基础更好。我会从硬件接线讲起把SPI通信的底层逻辑用大白话解释清楚然后分别给出Arduino和MicroPython两套完整可跑的代码最后把实际调试中踩过的坑一个一个摊开来讲。看完你就能让自己的ESP32拥有海量存储空间想存什么存什么。2. 硬件准备与SPI通信原理拆解2.1 需要哪些硬件先列一下清单这些东西加起来不到五十块钱ESP32开发板一块ESP32-WROOM-32、ESP32-S3、ESP32-C3都行本文以最常见的ESP32-WROOM-32为例MicroSD卡模块一个市面上常见的那种带电平转换芯片的模块通常标着“MicroSD Card Adapter”MicroSD卡一张建议用Class 10以上速度等级的容量4GB到32GB都可以格式化成FAT32杜邦线若干母对母或公对母看你的开发板和模块的引脚形式面包板一块可选但强烈建议用接线更稳这里要特别说一下SD卡模块的选择。市面上有两种一种是纯SD卡座没有电平转换和稳压电路工作电压是3.3V可以直接和ESP32连另一种是带AMS1117稳压芯片和电平转换的模块支持3.3V和5V输入。我建议买后者虽然多花几块钱但它的电平转换电路能保护ESP32的GPIO不被意外电压损坏而且模块上通常自带去耦电容信号质量更稳定。注意ESP32的GPIO工作电压是3.3V绝对不能直接接5V信号。如果你手头只有5V的SD卡模块一定要确认模块上有电平转换芯片再使用。2.2 SPI通信到底是怎么回事SPI全称是Serial Peripheral Interface翻译过来叫串行外设接口。你可以把它想象成一条单向的高速公路数据只能沿着一个方向流动但速度非常快。SPI通信需要四根线SCKSerial Clock时钟线由主机ESP32产生像节拍器一样控制数据传输的节奏MOSIMaster Out Slave In主机输出、从机输入ESP32通过这根线把数据发给SD卡MISOMaster In Slave Out主机输入、从机输出SD卡通过这根线把数据回传给ESP32CSChip Select片选线ESP32通过拉低这根线来“点名”SD卡告诉它“现在轮到你了”SPI的通信过程就像老师ESP32提问、学生SD卡回答。老师先通过SCK打拍子然后通过MOSI把问题一个字一个字地说出来学生通过MISO把答案一个字一个字地传回来。CS线就是老师点名的那根手指指到谁谁才开口。SPI有四种工作模式由时钟极性CPOL和时钟相位CPHA组合而成。SD卡在SPI模式下固定使用Mode 0也就是CPOL0、CPHA0。这意味着时钟空闲时为低电平数据在时钟的上升沿被采样。这个细节你不需要死记但调试时如果通信失败检查一下SPI模式是否正确是排查方向之一。2.3 ESP32的SPI引脚怎么分配ESP32有两个硬件SPI控制器SPI0和SPI1是内部使用的SPI2也叫HSPI和SPI3也叫VSPI可以给用户自由使用。我们通常用VSPI它的默认引脚是信号ESP32默认引脚说明SCKGPIO 18时钟线MISOGPIO 19数据输入MOSIGPIO 23数据输出CSGPIO 5片选可自定义当然你也可以自定义引脚ESP32的GPIO矩阵允许你把SPI信号映射到几乎任意引脚上。但为了简单起见零基础阶段直接用默认引脚最省事。接线的时候把SD卡模块的对应引脚和ESP32连起来就行SD模块的SCK接ESP32的GPIO 18SD模块的MISO接ESP32的GPIO 19SD模块的MOSI接ESP32的GPIO 23SD模块的CS接ESP32的GPIO 5SD模块的VCC接ESP32的3.3VSD模块的GND接ESP32的GND提示有些SD卡模块的引脚标注是CLK、DO、DI、CS分别对应SCK、MISO、MOSI、CS不要接错了。3. Arduino环境下读写SD卡的完整实操3.1 环境搭建与库安装Arduino IDE从1.6.5版本开始就内置了SD库不需要额外安装。但前提是你得先装好ESP32的开发板支持。打开Arduino IDE依次点击“文件”→“首选项”在“附加开发板管理器网址”里填入ESP32的板管理器地址然后在“工具”→“开发板”→“开发板管理器”里搜索“esp32”并安装。装好之后在“工具”→“开发板”里选择“ESP32 Dev Module”端口选对就可以开始写代码了。3.2 最简SD卡初始化与文件写入先来看一段最基础的代码功能是初始化SD卡、创建一个文件、写入一行文字、然后读出来验证#include SPI.h #include SD.h #define SD_CS 5 void setup() { Serial.begin(115200); delay(1000); Serial.println(正在初始化SD卡...); if (!SD.begin(SD_CS)) { Serial.println(SD卡初始化失败请检查接线和卡是否插好。); return; } Serial.println(SD卡初始化成功); Serial.print(卡容量: ); Serial.print(SD.cardSize() / (1024 * 1024)); Serial.println( MB); // 写入文件 File myFile SD.open(/test.txt, FILE_WRITE); if (myFile) { Serial.println(正在写入test.txt...); myFile.println(Hello ESP32 SD Card!); myFile.println(这是第二行测试数据。); myFile.close(); Serial.println(写入完成。); } else { Serial.println(打开文件失败); } // 读取文件 myFile SD.open(/test.txt); if (myFile) { Serial.println(test.txt的内容如下); while (myFile.available()) { Serial.write(myFile.read()); } myFile.close(); } else { Serial.println(读取文件失败); } } void loop() { // 这里什么都不做 }这段代码的逻辑很直白SD.begin()负责初始化参数是CS引脚号SD.open()用来打开文件第二个参数FILE_WRITE表示以写入模式打开如果文件不存在就创建myFile.println()写入内容myFile.close()关闭文件这一步很关键不关文件数据可能不会真正写入卡里。3.3 数据追加与目录操作实际项目中我们通常需要往同一个文件里不断追加数据而不是每次覆盖。这时候用FILE_APPEND模式File logFile SD.open(/datalog.csv, FILE_APPEND); if (logFile) { logFile.print(millis()); logFile.print(,); logFile.print(temperature); logFile.print(,); logFile.println(humidity); logFile.close(); }这段代码模拟了一个数据记录场景每采集一次温湿度就追加一行CSV格式的数据。用FILE_APPEND模式打开文件时写入指针会自动移到文件末尾不会覆盖已有内容。SD库还支持目录操作比如创建目录、列出目录内容// 创建目录 SD.mkdir(/logs); // 列出根目录内容 File root SD.open(/); File entry root.openNextFile(); while (entry) { Serial.print(entry.name()); Serial.print(\t); Serial.println(entry.size()); entry root.openNextFile(); }3.4 性能优化调整SPI时钟频率SD库默认的SPI时钟频率是较低的为了兼容性考虑。如果你用的是质量好的Class 10卡可以手动提高频率来加快读写速度if (!SD.begin(SD_CS, SPI, 40000000)) { // 40MHz Serial.println(高速初始化失败尝试默认速度...); if (!SD.begin(SD_CS)) { Serial.println(SD卡初始化失败); return; } }实测下来把频率从默认的几MHz提到20MHz到40MHz写入速度能有明显提升。但要注意频率越高对布线质量要求越高杜邦线太长或者接触不良的话高速下容易出错。我一般先用默认频率调通再逐步往上加找到一个稳定的最高值。4. MicroPython环境下读写SD卡的完整实操4.1 固件准备与驱动加载MicroPython的SD卡支持需要固件里包含sdcard模块。官方的ESP32固件从1.13版本开始就内置了这个模块所以只要你刷的是比较新的固件直接就能用。刷固件的方法这里不展开用esptool或者Thonny一键烧录都行。接线方式和Arduino完全一样还是那四根SPI线加电源。4.2 初始化与基本读写MicroPython的代码更简洁import machine import sdcard import os # 配置SPI spi machine.SPI(2, sckmachine.Pin(18), mosimachine.Pin(23), misomachine.Pin(19)) sd sdcard.SDCard(spi, machine.Pin(5)) # 挂载文件系统 os.mount(sd, /sd) print(SD卡挂载成功) print(目录内容:, os.listdir(/sd)) # 写入文件 with open(/sd/test.txt, w) as f: f.write(Hello from MicroPython!\n) f.write(第二行数据。\n) # 读取文件 with open(/sd/test.txt, r) as f: print(f.read())这里有几个关键点machine.SPI(2, ...)里的2表示使用VSPI控制器sdcard.SDCard()创建SD卡对象os.mount()把SD卡挂载到虚拟文件系统的/sd路径下。挂载之后你就可以像操作普通文件一样用open()、read()、write()了。4.3 追加模式与文件管理MicroPython里追加内容用a模式with open(/sd/datalog.csv, a) as f: f.write({},{},{}\n.format(time.ticks_ms(), temp, humi))文件管理操作也很直观# 查看文件大小 stat os.stat(/sd/datalog.csv) print(文件大小:, stat[6], 字节) # 删除文件 os.remove(/sd/old.txt) # 创建目录 os.mkdir(/sd/backup) # 重命名 os.rename(/sd/a.txt, /sd/b.txt)4.4 卸载与重新挂载在MicroPython里如果你要拔卡或者重新插卡最好先卸载文件系统os.umount(/sd) # 此时可以安全拔卡 # 重新插卡后再次挂载 os.mount(sd, /sd)不卸载直接拔卡的话如果正好有数据在写入缓冲区里没落盘数据就会丢失严重的话还会导致文件系统损坏。5. 常见问题与排查技巧实录5.1 SD卡初始化失败这是最常见的问题现象是串口打印“初始化失败”或者MicroPython报错。排查顺序如下排查项可能原因解决方法接线杜邦线松动或接错逐根检查重点确认MISO和MOSI没接反供电3.3V供电不足换一个3.3V引脚或用外部稳压供电卡格式不是FAT32格式在电脑上格式化为FAT32分配单元大小选32KB卡容量超过32GB换32GB及以下的卡大容量卡可能需要exFATCS引脚引脚号写错确认代码里的CS引脚号和实际接线一致模块电平转换芯片损坏换一个模块试试我遇到过最坑的一次是杜邦线内部断了外表看不出来万用表量通断才发现。所以接线问题不要只看表面该量就量。5.2 写入数据但电脑上读不到有时候ESP32这边显示写入成功但把卡插到电脑上发现文件是空的或者内容不全。原因通常是文件没有正确关闭。SD卡的数据是先写到缓冲区再批量写入卡的。如果你写完不调close()缓冲区里的数据可能还没落盘。注意每次写完文件一定要调close()或者用with语句自动关闭。在Arduino里就是myFile.close()在MicroPython里用with open(...)最省心。另一个原因是热插拔。运行中拔卡再插卡文件系统可能没有重新挂载后续写入都写到了内存缓冲区里根本没到卡上。5.3 读写速度慢如果你觉得SD卡读写速度不理想可以从这几个方面优化提高SPI时钟频率前面讲过了从默认提到20MHz到40MHz减少小文件频繁打开关闭批量写入后再统一关闭用二进制模式读写比文本模式快避免在循环里频繁调用SD.open()和close()可以保持文件打开定期flush实测数据一张Class 10的16GB卡在ESP32上以20MHz SPI频率写入速度大约在200KB/s到500KB/s之间。写入1MB的数据大概需要2到5秒。这个速度对于日志记录、传感器数据采集完全够用但如果你想录高采样率的音频可能就有点吃力了。5.4 文件系统损坏的修复如果卡在电脑上提示“需要格式化”或者“文件系统损坏”先别急着格式化试试用电脑自带的磁盘修复工具。Windows下可以右键盘符→属性→工具→检查或者用命令行chkdsk /f X:。Mac下用“磁盘工具”的“急救”功能。很多时候只是文件分配表出了小问题修复一下数据还能救回来。预防文件系统损坏的最好办法就是每次断电前确保文件已关闭长时间运行的项目定期调用flush()把缓冲区数据落盘。6. 进阶玩法与项目扩展思路6.1 用SD卡做数据记录仪把ESP32和一个温湿度传感器比如DHT22或BME280结合起来每隔几分钟往SD卡里写一条记录就是一个完整的数据记录仪。CSV格式的数据可以直接用Excel打开画图表。这个项目麻雀虽小五脏俱全涵盖了传感器读取、定时任务、文件操作三个核心技能点。6.2 用SD卡存网页文件做小型服务器ESP32可以跑Web服务器把HTML、CSS、JS文件放在SD卡里ESP32只负责读取文件并通过HTTP返回给浏览器。这样你就能做一个不依赖外部网络的本地控制页面手机连上ESP32的热点就能访问。SD卡在这里充当了静态资源仓库的角色比把网页内容硬编码在代码里优雅得多。6.3 用SD卡做OTA固件备份在做OTA升级之前先把当前固件备份到SD卡里。万一升级失败变砖还能通过串口从SD卡里恢复。这个操作需要读取Flash内容并写入SD卡涉及到底层分区操作适合有一定基础之后再来折腾。6.4 多文件系统共存ESP32的Flash上可以同时存在SPIFFS/LittleFS和SD卡两个文件系统。SPIFFS存配置文件和关键数据SD卡存大容量日志和媒体文件。两者通过不同的挂载点区分代码里用不同的路径前缀访问就行。这种组合方式在实际产品中很常见兼顾了可靠性和容量。7. 几个让我印象深刻的踩坑经历第一个坑是关于SD卡格式化的。我一开始用了一张从旧手机里拔出来的64GB卡怎么都初始化不了。后来查资料才知道SD库对exFAT格式支持不好必须格成FAT32。而Windows自带的格式化工具对64GB以上的卡只给exFAT选项得用第三方工具才能格成FAT32。换了一张32GB的卡问题立刻解决。第二个坑是供电。我用面包板给SD卡模块供电同时面包板上还插了其他几个模块结果SD卡时好时坏。用万用表一量3.3V电压在SD卡工作时会跌到3.0V以下。后来单独给SD卡模块拉了一根供电线问题消失。ESP32的3.3V输出能力有限多个外设同时工作时一定要算一下总电流。第三个坑是文件名的坑。在Arduino的SD库里文件名如果以“/”开头表示绝对路径不以“/”开头表示相对于当前目录。我一开始写SD.open(test.txt)文件创建在了根目录下但后来写SD.open(/test.txt)又找不到文件折腾了半天才发现是路径写法不一致导致的。统一用绝对路径最省心。第四个坑是MicroPython的SPI引脚映射。我一开始用machine.SPI(1, ...)怎么都不工作。后来查文档才知道SPI(1)在ESP32上对应的是内部Flash使用的控制器用户不能用。必须用SPI(2)也就是VSPI。这个坑在Arduino里不存在因为Arduino的SPI库默认就用VSPI。8. 关于SD卡选型的一点个人建议如果你打算长期跑数据记录项目我建议买工业级的MicroSD卡虽然贵一点但耐高温、耐频繁写入寿命比消费级卡长很多。消费级卡用久了容易出现坏块导致文件系统损坏。另外容量不要贪大8GB到16GB对于大多数ESP32项目来说绰绰有余太大的卡反而可能遇到格式兼容问题。速度等级方面Class 10是底线U1或U3更好。但要注意ESP32的SPI接口本身速度有限再快的卡也发挥不出全部性能所以没必要追求顶级速度卡均衡考虑性价比就行。最后说一个细节SD卡模块上的CS引脚有些模块标注的是“CS”有些是“SS”意思是一样的。接线时如果模块上没有明确标注可以看芯片手册或者用万用表量一下哪个引脚连着卡座的片选脚。这个确认工作花不了两分钟但能省掉后面半小时的排查时间。