
用户给的这个标题一看就是给刚入坑的创客、电子爱好者准备的。ESP32这个芯片在这个圈子里火了很多年了价格便宜、双核、自带WiFi和蓝牙玩法极多。但真正上手做过数据记录、离线缓存、音频播放这类项目之后你会发现一个痛点它内部能用的Flash存储空间太有限了跑个Web应用、存个日志、记录传感器数据很快就捉襟见肘。这时候接一张SD卡就是最常规、也最实用的解法。这篇文章就以“零基础”为起点把ESP32读写SD卡这件事从头到尾拆开讲清楚。不光是连线、装库、抄一段代码完事我尽量把为什么这么接、为什么这么写、踩坑踩在哪都说透让你看完之后能自己在项目里用起来而不是只跑通一个demo。1. 先想清楚为什么ESP32要外接SD卡1.1 自带存储不够用SD卡是最理性的扩展方案ESP32芯片系列的存储容量说句实话做复杂项目不太够看。以最常见的ESP32 DevKit为例Flash一般也就4MB到16MB扣掉固件、OTA分区、文件系统分区之后真正能给用户随意存东西的“自由空间”是非常有限的。你在项目里一旦开始存大日志、存配置历史、存图片、存音频素材这点空间很快就会见底。而SD卡的优势非常明显容量大几GB到几十GB随便选、便宜、通用、电脑上直接能读写。你不需要额外搞一套复杂的存储协议也不需要画一块大容量Flash的PCB。对于做原型验证、做小批量设备的场景SD卡是最踏实的方案。这里我要强调一下“为什么不是EEPROM、为什么不是Flash”这个事儿。很多新手会问ESP32不是有EEPROM吗我用Preferences库存东西不行吗行但你去看一下Preferences和EEPROM可用的空间是按KB算的而SD卡是按GB算的。一个是放配置参数、校准系数这种小数据的一个是放日志、文件、媒体资源的两者定位完全不同。SD卡在读写速度和容量上碾压代价就是多一块硬件、多一点连线仅此而已。1.2 ESP32读写SD卡的三种可行路线我推荐哪一种新手在网上搜“ESP32 SD卡”大概率会搜到几种不同说法容易懵。我帮你把路线理清楚路线A使用SD库走SPI方式。这是最经典、最常用、资料最多的方式。ESP32通过SPI协议与SD卡模块通信接线简单只需要四根数据线几乎兼容市面上所有SD卡模块。这条路线是最适合零基础的我下面会重点讲它。路线B使用SD_MMC库走SDIO方式。这个方式走的是SD卡的原生SDIO协议速度比SPI快不少但只能用在特定引脚上而且和某些硬件组合容易出兼容性问题。新手如果用SD_MMC接普通SPI模块的卡座还可能因为线序和电平问题折腾很久。路线C外挂Flash芯片如W25Q64。这不是SD卡而是另一种思路。优点是体积小、可以直接焊PCB上缺点是你得在驱动芯片上下很大的功夫代码复杂度明显高于“SD库一行初始化”。所以我的建议非常明确零基础起步就用路线ASPI方式接SD卡模块先把流程跑通将来确实有高速需求再研究SDIO。这篇文章的实操部分也都是基于SPI方式的。2. 硬件准备与连线零基础也能一次接通2.1 要哪些硬件怎么选不容易踩坑准备硬件这步其实不复杂但你选件的时候有几个细节值得留意我吃过亏专门说给你。你需要四样东西ESP32开发板一块、SD卡模块一个、SD卡或TF卡一张、杜邦线若干。ESP32开发板就不用多说了市面上的DevKit都行注意看引脚丝印是否清晰即可。SD卡模块我建议直接买常见的“TF卡/ MicroSD卡 SPI模块”它通常自带一个卡座和几个电阻电容使用很方便。模块上的电压转换芯片最好是AMS1117-3.3可以保证卡的供电更稳定。我遇到过的坑是有的模块做工粗糙卡座弹片接触不良插卡后系统偶尔识别不到当时排查了很久最后换了一个模块就好了所以卡座质量和模块做工尽量别选太次的。SD卡本身的选型也是有讲究的。买正规品牌的卡容量别贪大。8GB、16GB的Class 10卡足够绝大多数项目用了。市面上的“扩容卡”“山寨卡”绝对不要碰那种卡看着容量很大实际一写入就报错零基础根本没法判断是卡的问题还是代码的问题。尽量在开发之前用电脑把卡格式化成FAT32格式因为ESP32官方SD库对FAT32支持最成熟稳定。还有一个小建议如果你用的是不带插座的纯SPI模块而是自己搭电路那么SD卡的3.3V供电和信号线都得注意电平匹配ESP32是3.3V逻辑的这一点比老款Arduino5V逻辑省心很多一般不需要额外加电平转换电路但绝对不要把任何5V信号直接灌进ESP32引脚。2.2 接线图与供电细节这几根线别接错SPI方式接线很简单但ESP32的引脚不是随便选的。虽然SD库允许自由映射SPI引脚但新手最容易出的问题就是把CS、SCK、MOSI、MISO接错了位置。我建议你按我下面的默认接线来一次成功概率最高。SD卡模块引脚ESP32引脚说明CS (片选)GPIO5这个引脚用来选中SD卡ESP32的默认CS引脚就是5SCK (时钟)GPIO18SPI时钟线MOSI (主出从入)GPIO23主机发送数据给SD卡MISO (主入从出)GPIO19SD卡发送数据给主机VCC3.3V模块供电别接5V除非你的模块明确标注支持5V带稳压GNDGND共地这个组合实际上就是ESP32默认的VSPI接口。SCK、MISO、MOSI分别对应18、19、23这在ESP32 Arduino核心的源码里是有默认定义的。你如果非要用其他引脚做主接线也不是不行但必须在代码里用SPI.begin(SCK, MISO, MOSI, CS)重新指定不指定的结果就是“卡初始化失败”。这里插三个经验都是血和泪换的VCC最好不要接开发板的5V输出即使你的模块上有AMS1117稳压也不行。因为有些模块上根本没有稳压芯片直接接5V会烧卡或者烧模块。最稳妥的做法VCC接3.3V。所有线尽量短尤其是SCK时钟线。杜邦线别拖太长超过20cm在高速传输下容易出偶发读写错误。我实测下来10cm左右的线是最稳的。SD卡要插到位。SD卡模块上有个小弹片插卡时能感觉到“咔哒”一下才说明到位。没插到位时代码报错的表现和卡坏了几乎一模一样。3. 开发环境与基础代码让SD卡“跑”起来3.1 Arduino IDE下的ESP32环境配置代码之前先把环境准备好。Arduino IDE是最适合零基础的工具没有之一。我默认你已经装了Arduino IDE如果没有先去官网下载一个版本选2.x即可。启动Arduino IDE后第一步添加ESP32开发板支持。打开“文件 - 首选项”在“附加开发板管理器网址”里加入官方支持链接见官方文档即可然后在“工具 - 开发板 - 开发板管理器”中搜索“esp32”安装“esp32 by Espressif Systems”。这个过程会下载一些工具链文件耐心等一会儿就行。安装完成之后在“工具 - 开发板”里选择你手头的板子。如果你不确定自己买的是哪种一般选“ESP32 Dev Module”都不会错其他选项如Flash大小、Partition Scheme保持默认即可新手不用动。接下来SD库不需要额外安装在Arduino IDE里搜索“SD”就能找到Espressif官方提供的SD库装上它就行。看到这里你可能会问“SD库自带那我为什么还看到很多教程让人装‘SD卡库’”其实是因为ESP32有自己移植好的SD库和Arduino官方的SD库在API上基本一致直接用Arduino IDE库管理器里的SD库就可以不用特意去GitHub手动下载。3.2 第一段能用的读写代码初始化、写文件、读文件环境装好后直接上代码。下面这段代码是“最小可用版本”包含了初始化、写文件、读文件三个核心动作。你把它粘到Arduino IDE里编译上传然后打开串口监视器波特率115200就能看到结果。#include SPI.h #include SD.h // SPI引脚定义与前面接线表对应 #define SD_CS 5 #define SPI_SCK 18 #define SPI_MISO 19 #define SPI_MOSI 23 void setup() { Serial.begin(115200); delay(1000); // 初始化SPI参数顺序SCK, MISO, MOSI注意不是MOSI在前 SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI); // 初始化SD卡传入CS引脚号 if (!SD.begin(SD_CS)) { Serial.println(SD Card Mount Failed); return; } Serial.println(SD Card Mount Success); // 写一个文件 writeFile(/hello.txt, Hello from ESP32 SD Card!); Serial.println(Write Done); // 读回这个文件 String content readFile(/hello.txt); Serial.print(Read Content: ); Serial.println(content); } void loop() { // 空循环演示用 } // 写文件如果文件不存在则创建存在则覆盖 void writeFile(const char* path, const char* message) { File file SD.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(); } // 读文件一次性读取整个文件内容为字符串 String readFile(const char* path) { File file SD.open(path); if (!file) { Serial.println(Failed to open file for reading); return ; } String content ; while (file.available()) { content char(file.read()); } file.close(); return content; }代码本身不长但有几个点需要理解一下不然你后面改代码容易糊涂。SPI.begin(SCK, MISO, MOSI)这行参数顺序是SCK、MISO、MOSI很多新手会下意识写成MOSI、MISO一错就会导致“卡能初始化但写入只有前几个字节”。这个函数本身也不报错是最坑的。SD.begin(CS)传入的是片选引脚。SD库内部会通过这个引脚向SD卡发送“选通”信号所以CS必须和实际接线一致。FILE_WRITE模式的含义是“打开文件如果存在则从末尾追加还是覆盖”实际上在SD库中FILE_WRITE对于已存在的文件是覆盖模式不是追加后面的内容会从文件开头重写。如果你希望是追加模式在Open时用FILE_APPEND这是另一行你要去翻SD库的源码或者头文件不同版本可能支持情况不同。最稳的方式打开用FILE_WRITE然后file.seek(EOF)到文件末尾再写。file.close()非常关键。写完文件之后如果不close数据可能只停留在缓冲里断电后文件就是损坏的或者干脆没有这个文件。这个是新手最容易踩的坑切记。3.3 追加写入和中文文件名这两个小点很实用基础版本跑通之后你可能马上就想做“每隔几秒往文件里追加一行数据”的功能。我直接说追加的正确写法以及几个细节。先说追加。用上面的writeFile函数向已有文件写数据它会从头覆盖这肯定不行。正确的追加写法是在打开文件后用seek定位到文件末尾void appendFile(const char* path, const char* message) { File file SD.open(path, FILE_WRITE); if (!file) { Serial.println(Failed to open file for appending); return; } file.seek(EOF); // 移到文件末尾 if (file.print(message)) { Serial.println(Message appended); } else { Serial.println(Append failed); } file.close(); }你可能会想FILE_WRITE明明文档上写的是“写”为什么加了seek(EOF)就变成追加了因为底层文件系统是FAT32文件打开时记录的是文件起始位置只要把当前位置指针放到EOF再写入的内容自然就接在文件末尾。道理不复杂但新手看不一定想得到。再说中文文件名。ESP32的SD库在FAT32下支持中文文件名吗答案是如果用Arduino核心自带的SD库默认并不友好。我实测下来直接用它打开中文路径很容易失败因为底层对中文文件名的编码支持不够完善。如果你非要存中文文件名可以考虑用英文文件名加时间戳来命名比如20250815_120101.csv。或者自己写一套“内部文件名映射”把英文名存SD卡中文名写文件头部作为描述。我不建议零基础去折腾SD库的编码配置性价比太低。项目里有中文信息的存文件内容里完全够用。4. 进阶可靠的数据记录与常见问题排查4.1 数据记录场景的代码结构跳过了demo阶段你多半要拿SD卡来做点正事最常见的就是接传感器、周期记录数据。这种场景下代码必须考虑稳定性不能像demo那样裸奔。我给你一个参考结构每次启动时打开CSV文件先写入一行表头如果文件不存在之后在定时中断或循环里读取传感器数据拼成一行写入文件并且定期比如每10条或每分钟关闭一次再重新打开避免长时间不关闭导致数据丢失。void logToSD(float temp, float humi) { File file SD.open(/sensor_log.csv, FILE_WRITE); if (!file) { Serial.println(Failed to open log file); return; } file.seek(EOF); file.print(millis()); file.print(,); file.print(temp, 2); file.print(,); file.println(humi, 2); file.close(); // 每次写完立即关闭保证数据落盘 }我见过不少同学图省事在setup里打开一个文件然后一直不关loop里一直写。这个方案有两个隐患一是如果单片机意外重启最后一段数据大概率没了二是文件描述符在底层可能被占用影响其他操作比如同时要读配置不便于排查问题。所以“写完即关”虽然牺牲一点速度换来的是稳定在数据记录场景里非常值得。此外还要注意一点频繁写入SD卡SD卡的寿命会消耗得比想象中快。对于连续记录频率很高的项目更稳的做法是先在内存里攒一批数据比如每凑够32条或者512字节才写一次减少卡的写次数。这个属于进阶技巧但对数据记录的设备来说非常重要。4.2 我把常见问题整理成了一张速查表在几十个初学者群里帮人排查过问题之后我总结出一个结论90%的SD卡问题集中在硬件接触和初始化的几个常见点上。我直接以表的形式给你方便你遇到问题对着查。错误现象可能原因解决办法SD Card Mount Failed1. CS引脚用错 2. SD卡没插到位 3. 模块供电不稳定 4. SPI线序接反1. 检查CS是否接GPIO5 2. 重新插卡 3. VCC接3.3V并换根短杜邦线 4. 按文中表格重新核对初始化成功但写入后文件打不开1. 文件路径有误 2. 文件名超过FAT32命名规范 3. 文件没close1. 用/开头 2. 文件名不超过8.3标准或参考长文件名规则 3. 写完一定要close写入中文乱码编码和系统查看工具不一致文件内容尽量用ASCII/UTF-8查看工具用支持UTF-8的编辑器断电后上次数据丢失文件没有close缓冲区未落盘每批次写完立即close或定期close一次偶尔读写出错、不稳定1. 供电不足 2. 线太长 3. 卡速太慢/兼容性差1. 用独立3.3V供电 2. 缩短杜邦线 3. 换一张Class 10品牌卡格式化之后还是不能用格式化成了exFAT/NTFSSD库不支持exFAT部分版本有实验支持请格式化为FAT32这里面最隐蔽的是“格式化格式”。很多新买来的大容量TF卡默认是exFAT插到ESP32上就会初始化失败。拿到卡的第一件事先放到电脑上右键格式化为FAT32再插到模块里这个问题能帮后续省掉大量的无用功。还有一点ESP32的SD库对热插拔支持很差。不要在一段程序运行过程中不断插拔SD卡容易把文件系统搞挂。要插拔就先断电或者复位。这个习惯建议从入门就养成。4.3 几个让你少走弯路的经验最后一节我不讲大而全的东西了就分享一些“没人提醒你可能要整晚才能发现”的小经验。第一关于CS默认引脚的坑。市面上很多教程会把CS接到GPIO4或者GPIO15但那是某些早期库的默认设置。现在的Arduino核心中如果你不指定CS参数部分版本会默认用GPIO5来初始化。如果你正好用了GPIO4就必须在SD.begin()里写明不要靠“默认值”去猜。我建议干脆所有项目都用GPIO5减少混乱。第二关于调试时串口监视器里看不到打印信息。很多时候代码明明跑起来了但串口什么都没输出八成是因为波特率没对上。这段代码里所有Serial.print走的是115200你的串口监视器右下角也要选择115200。另一个容易被忽略的问题是ESP32上电后GPIO6到GPIO11是内部Flash的SPI引脚绝对不能用来接SD卡。接线时如果手一滑接到了这些引脚上板子可能直接启动失败。第三关于CSD卡和WiFi同时用ESP32魅力的很大一部分是WiFi。但要注意SPI和WiFi同时工作时如果走的是SPI速度可能会互相拖累特别是在传输大量数据时。如果你的项目既要连WiFi又要写SD卡尽量把SD卡的写入操作放在不影响WiFi业务的空隙进行或者用定时器批量写入而不是每收一包数据就写一次。实测下来这种“批量写入”策略不仅让SD卡更稳定WiFi掉包率也会明显下降。第四关于掉电处理。如果你的设备可能随时断电比如电池供电或插拔供电那在正式数据记录场景里我强烈建议在写文件之后立即close同时考虑在写重要文件时写一个“临时文件重命名”的方案。简单说先写到data.tmp写完后再把它重命名为data.csv。这样即使中途断电最多损失一个不完整的tmp文件正式文件的结构不会损坏。这个做法在PC文件系统上很常见在嵌入式上一样好用只是很多人没想到。结尾想说的话我个人在实际操作中最大的体会是SD卡本身不是什么高大上的技术但它能让你从“内存吃紧、数据没处放”的局促里彻底解放出来。很多项目比如环境监测站、离线打卡机、简易摄像头加上一张SD卡之后整个设备的能力就上了一个台阶而且改造工作量并不大。读一读这篇文章把连线接好把第一段代码跑通之后你就会有更多精力去关注数据本身而不是天天为存储发愁。最后再多嘴一句一旦卡能正常初始化了就别频繁换不同的卡和模块了找到一套稳定组合老老实实用下去稳定性比折腾新硬件重要得多。