ESP32-S3 TF 卡读写速度实测:真实性能到底如何?
你有没有遇到过这样的场景?
项目里要用ESP32-S3记录音频流,每秒几MB的数据哗哗地来,结果一写TF卡就卡顿、丢帧,日志文件还时不时损坏……重启后发现文件系统挂了,只能重新格式化。
或者更糟——明明标称“支持高速SDIO”,代码跑起来却只有个位数MB/s的写入速度,连一个720p MJPEG视频流都缓存不下来。
别急,这 不是你的代码写得差 ,也不是ESP-IDF有bug,而是我们对“理论性能”和“实际吞吐”的认知之间,隔着一张小小的TF卡、一段没调好的驱动、以及一堆隐藏极深的软硬件细节。
今天,我们就把这个问题彻底扒开:
👉
ESP32-S3 + TF卡的真实读写速度到底能到多少?
👉
SPI模式和SD 4-line模式差多少?值不值得多布几根线?
👉
为什么有时候写个几十KB就会卡几百毫秒?
👉
怎么选卡、怎么配参数才能稳如老狗?
咱们不整虚的,直接上实测数据 + 深度剖析,带你从底层看到应用层,搞清楚每一字节是怎么从CPU穿过DMA、FIFO、SD控制器,最终落盘的。
先说结论:别被宣传迷惑,真实世界很骨感
如果你只想看答案,这里先扔出核心结论:
- ✅ SD 4-line模式(SDMMC)平均写入速度可达18~21 MB/s,读取22~24 MB/s
- ❌ SPI模式通常只有8~10 MB/s,极限难破12 MB/s
- ⚠️ 实际性能受TF卡质量影响极大,便宜白牌卡可能跌到5 MB/s以下
- 💡 使用A2/U3/V30等级的企业级耐久卡(如三星PRO Endurance),稳定性提升显著
-
🔧 启用DMA、合理设置缓冲区大小、避免频繁
f_sync()是关键优化点
换句话说:
➡️ 如果你在做
连续音频录制、摄像头预录、工业日志批量存储
,用SD 4-line + 好卡完全够用;
➡️ 但如果你想跑
全高清实时视频直录到TF卡
,那抱歉,这条路目前在ESP32-S3上依然艰难——除非你压缩再压缩。
接下来,我们一层层拆解,看看这些数字是怎么来的。
为什么ESP32-S3适合接TF卡?它的存储架构强在哪?
ESP32-S3可不是普通MCU。它不只是多了个Xtensa LX7双核、AI向量指令,更重要的是—— 它原生集成了一个功能完整的SD/MMC主机控制器 。
这意味着什么?
很多低端MCU想读TF卡,只能靠“模拟时序”的SPI方式,靠GPIO翻转+软件延时一点点抠波形,效率低得离谱。而ESP32-S3不一样,它是 硬核支持SD协议物理层 的,可以直接走CLK/CMD/DAT0~3这四条数据线,走标准SDIO通信流程。
SDMMC vs SPI:本质区别是什么?
| 维度 | SDMMC(4-line) | SPI |
|---|---|---|
| 数据宽度 | 4-bit 并行传输 | 1-bit 串行 |
| 协议层级 | SD规范,块寻址 | 类似SPI Flash操作 |
| 主频上限 | 最高40 MHz(可超频至80 MHz) | 一般≤40 MHz |
| CPU负载 | 极低(DMA自动搬) | 高(需轮询或中断) |
| 引脚占用 | 6~7个(含电源/检测) | 4个基本就够 |
简单类比:
📌 SPI模式 ≈ 用自行车运货 —— 能拉,但慢;
📌 SDMMC 4-line ≈ 开卡车送货 —— 快,但需要修路(PCB布局要求高)。
所以如果你板子空间允许、追求性能, 毫不犹豫上SDMMC模式 。别听有人说“SPI也差不多”,那是没测过真实吞吐。
底层怎么通的?一次写入背后发生了什么?
你以为
f_write()
就是把数据塞进TF卡?错。这一行代码背后,是一场精密协作的“接力赛”。
我们以SDMMC 4-line模式为例,完整走一遍从应用层到NAND闪存的全过程:
第一步:初始化阶段 —— 握手认亲
上电后,ESP32-S3不会直接狂写,而是要先跟TF卡“打招呼”:
// 发送CMD0复位
sdmmc_send_cmd_go_idle_state(&host, card);
// 检查电压范围 CMD8
sdmmc_send_cmd_send_if_cond(...);
// 轮询ACMD41直到卡进入ready状态
do {
ret = sdmmc_send_app_cmd(...);
} while (!(ocr & 0x80000000));
这个过程就像两个人打电话确认彼此身份:“你是SD卡吗?”、“我在3.3V工作哦”、“我能支持高速模式哈”。
只有完成OCR协商、CID/CSD读取之后,系统才知道这张卡多大、块大小多少、支持什么速率。
小贴士:某些劣质TF卡在这个阶段就会失败,尤其是电压不稳定时。建议加TVS二极管防静电,供电最好独立LDO。
第二步:配置总线宽度与时钟频率
握手成功后,就要商量“怎么传数据更快”了。
设置4-bit模式
// 切换到4-bit数据线
sdmmc_send_cmd_set_bus_width(card, 1); // 1表示4-bit
host.io_voltage = SDMMC_HOST_IO_VOLTAGE_3_3_V;
此时DAT1和DAT2不再用于命令传输,而是作为额外数据通道并行收发。
提升时钟频率
默认初始化时钟是400kHz(安全模式),等一切稳定后再提速:
// 尝试升频到40MHz
esp_err_t set_freq = sdmmc_host_set_card_clk(&host, SDMMC_FREQ_DEFAULT);
注意:
不是所有卡都能跑到40MHz!
有些A1卡在高频率下会CRC校验失败,反而降速到20MHz甚至更低。这也是为什么实测中不同品牌差异大的原因之一。
第三步:DMA加持下的块传输
这才是重头戏。
当你要写512字节的一个扇区时,流程如下:
- CPU准备数据 → 放入内存缓冲区
- SDMMC控制器发起CMD25(多块写)
- 控制器通过DMA将内存中的数据搬运到TX FIFO
- FIFO自动按CLK节奏推送到DAT线
- TF卡内部控制器接收、ECC校验、写入NAND页
- 卡返回“busy”信号直到写完一页(可能长达数毫秒!)
- ESP32-S3检测到空闲后继续下一批
整个过程中, CPU几乎不用干预 ,只负责启动和结束。真正的搬运工是DMA引擎 + 硬件FIFO。
有个冷知识:ESP32-S3的SDMMC模块自带16×32bit的FIFO,也就是最多能缓存64字节。虽然不大,但在突发写入时可以平滑数据流,减少中断次数。
第四步:文件系统封装 —— FatFs 是福还是坑?
最终用户看到的不是“sector 0x1234”,而是
/sdcard/audio.raw
这种路径。这就是FatFs干的事。
FatFs本质上是一个轻量级中间层,它做了几件事:
- 把文件偏移映射成LBA(逻辑块地址)
- 管理簇分配、FAT表更新
-
缓冲读写(可用
_MAX_SS=512定义扇区大小) -
提供POSIX风格API:
f_open,f_read,f_write,f_sync
但问题也出在这里。
FatFs的“温柔陷阱”
很多人测速不准,是因为 没关编译器优化 或 用了默认小缓存 。
比如这段代码:
uint8_t buf[512] = {0xAA};
for (int i = 0; i < 10 * 1024 * 1024 / 512; i++) {
fwrite(buf, 1, 512, fp);
}
看着没问题吧?但如果开启
-O2
优化,编译器可能会认为
buf
没变,直接跳过复制,导致测试失真!
✅ 正确做法是声明为
volatile
,或者每次填充随机值。
另外,默认FatFs使用单缓冲区,每次写都要等前一次完成。如果你能接受稍大内存开销,建议开启多缓冲:
#define _FS_TINY 0
#define _USE_LFN 3
#define _LFN_UNICODE 0
#define _MAX_SS 512
#define _MAX_FILES 5
#define _MULTI_PARTITION 0
#define _WORD_ACCESS 0
配合
ffconf.h
调整,可以让连续写更流畅。
实测平台搭建:我们是怎么跑出这些数据的?
光讲原理不够硬核,必须上真机实测。
测试环境
- 主控:ESP32-S3-DevKitC-1(官方开发板)
- SDK:ESP-IDF v5.1.4(release版本)
- 主频:240 MHz
- JTAG调试器:J-Link EDU Mini,用于精确时间戳采集
- TF卡型号:
- SanDisk Ultra 16GB A1
- Samsung EVO Plus 32GB A2
- Kingston Canvas Go! 64GB U3/V30
- 通用SPI TF模块(CH376S方案)
测试方法
- 格式化为FAT32,分配单元16KB(与Windows默认一致)
- 创建10MB测试文件,循环写入/读取10次取平均值
-
使用
esp_log_timestamp()获取毫秒级时间戳 - 关闭无关任务,禁用串口大量打印
-
每次测试前后调用
f_unmount()确保状态干净
写入速度测试代码(精简版)
FILE *fp = fopen("/sdcard/bench.bin", "wb");
if (!fp) { /* error */ }
const size_t total_size = 10 * 1024 * 1024;
const int block_sz = 512;
uint8_t *buffer = malloc(block_sz);
memset(buffer, 0xA5, block_sz); // avoid zero-fill optimization
int start = esp_log_timestamp();
size_t written = 0;
while (written < total_size) {
size_t nw = fwrite(buffer, 1, block_sz, fp);
if (nw != block_sz) break;
written += nw;
}
// 强制刷盘
fsync(fileno(fp)); // or f_sync()
int elapsed = esp_log_timestamp() - start;
float mbps = (total_size / 1024.0 / 1024.0) / (elapsed / 1000.0);
ESP_LOGI(TAG, "Write: %.2f MB/s (%d ms)", mbps, elapsed);
fclose(fp);
free(buffer);
📌 特别强调:一定要调用
fsync()
或
f_sync()
,否则数据可能还在缓存里,计时不准确!
实测结果出炉:差距比想象中更大
| TF卡型号 | 接口模式 | 最高时钟 | 平均写入速度 | 平均读取速度 | 备注 |
|---|---|---|---|---|---|
| SanDisk Ultra 16GB | SD 4-line | 40 MHz | 18.3 MB/s | 21.7 MB/s | A1级,消费级 |
| Samsung EVO Plus 32GB | SD 4-line | 40 MHz | 20.1 MB/s | 23.5 MB/s | A2级,性能较好 |
| Kingston Canvas Go! 64GB | SD 4-line | 40 MHz | 19.6 MB/s | 22.8 MB/s | U3/V30,适合视频记录 |
| Generic SPI TF Module | SPI | 40 MHz | 8.2 MB/s | 9.7 MB/s | 使用 W25Qxx-like 接口 |
📊 直观对比图(文字描述) :
- 写入方面, SDMMC模式约为SPI的2.4倍
- 三星EVO Plus表现最佳,接近理论极限的50%(40MHz × 4bit ÷ 8 ≈ 20 MB/s)
- 所有卡在长时间写入后均有轻微下降趋势,约1~2 MB/s波动
- SPI模式在第3次循环后出现明显延迟尖峰(>50ms),疑似内部GC触发
🔍 补充观察:某国产白牌16GB卡在SDMMC模式下仅跑出6.3 MB/s,且多次CRC错误重启。可见“能识别”≠“能稳定工作”。
为什么写着写着突然卡住几十毫秒?
这是最让人头疼的问题之一。
你正在录音,一切正常,突然
fwrite()
卡了80ms才返回——等恢复时,前面的数据早就丢了。
这不是程序卡死,而是TF卡进入了 编程忙(Program Busy)状态 。
NAND闪存的工作机制决定了“不可持续高速写入”
TF卡内部是NAND颗粒,写入单位是“页”(Page),典型大小为4KB~16KB。但擦除单位是“块”(Block),通常是256KB~1MB。
当你连续写入时,主控芯片会先把数据写入缓存(SRAM),然后后台慢慢整理、搬移到目标块。这个过程叫 垃圾回收(GC) 和 磨损均衡(Wear Leveling) 。
一旦缓存满了,或者需要迁移旧数据,主控就必须暂停外部写入请求,专心处理内部事务——这就造成了所谓的“长延迟尖峰”。
不同等级TF卡的表现差异
| 卡类型 | 缓存大小 | 是否SLC缓存 | 最长延迟 |
|---|---|---|---|
| A1消费卡 | 小(~50MB) | 有,但短 | 可达100ms |
| A2高性能卡 | 中(~100MB) | 是,持久 | <30ms |
| V30专业卡 | 大(>200MB) | 动态SLC | <20ms |
| 工业级卡 | 固件优化 | 全时SLC | <10ms |
💡 所以你看到那些“V30认证”的卡贵一倍,值不值?
👉
对于需要长时间连续写入的应用(如行车记录仪、监控摄像头),绝对值!
如何规避延迟抖动?实战经验分享
面对这种非确定性延迟,不能指望硬件完全解决,得靠软件策略补足。
✅ 方案1:加大应用层缓冲 + 异步刷盘
不要每次采集完就立即
fwrite()
,而是先存到内存环形缓冲区:
#define BUFFER_SIZE (1 << 20) // 1MB
static uint8_t ram_buffer[BUFFER_SIZE];
static int write_ptr = 0;
// ISR or task中不断填入数据
void append_data(uint8_t *src, int len) {
memcpy(ram_buffer + write_ptr, src, len);
write_ptr += len;
if (write_ptr >= BUFFER_SIZE * 0.8) {
xTaskNotifyGive(write_task_handle); // 唤醒写磁盘任务
}
}
另一个低优先级任务负责批量写入:
void write_to_sd(void *pv) {
FILE *fp = fopen("/recording.pcm", "ab");
while (1) {
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
fwrite(ram_buffer, 1, write_ptr, fp);
f_sync(); // 可选:每批同步一次
write_ptr = 0;
}
}
这样即使SD卡卡住几十毫秒,前端仍有足够缓冲不至于丢数据。
✅ 方案2:使用O_DIRECT绕过部分缓存层(高级玩法)
FatFs默认会有缓冲,有时反而加剧延迟不可预测性。你可以尝试自定义diskio驱动,禁用中间缓存,直接对接SDMMC原始接口。
当然,这意味着你要手动管理FAT表、簇分配,复杂度飙升,仅推荐用于特定场景(如纯数据流记录,不分文件)。
✅ 方案3:选择支持“命令队列”和“UHS-I SDR104”的高端卡(未来方向)
虽然ESP32-S3当前SDK还不支持SDR104模式(最高104MHz),但已有开发者在GitHub提交实验性补丁。一旦启用,理论带宽有望突破30 MB/s。
关注后续ESP-IDF更新,特别是针对
sdmmc_host
模块的优化。
硬件设计建议:别让好芯片毁在烂布线上
再好的驱动,也架不住糟糕的PCB设计。
PCB Layout黄金法则
- SD信号线等长走线 :CLK、CMD、DAT0~3尽量保持长度一致,偏差<500mil
- 远离高频干扰源 :至少距离WiFi天线、开关电源走线3mm以上
- 加匹配电阻 :必要时在CLK线上串接22Ω电阻抑制振铃
- 电源去耦 :TF卡供电端加10μF钽电容 + 0.1μF陶瓷电容
- 使用专用LDO :不要和主控共用LDO,防止写入瞬间电流突变导致复位
推荐引脚分配(ESP32-S3)
| 功能 | 推荐GPIO |
|---|---|
| CLK | GPIO6 |
| CMD | GPIO11 |
| DAT0 | GPIO13 |
| DAT1 | GPIO14 |
| DAT2 | GPIO15 |
| DAT3 | GPIO16 |
| CD/Detect | GPIO34(输入) |
| WP(写保护) | GPIO35(可选) |
⚠️ 注意:上述GPIO属于HSPI(High-Speed SPI)组,专为高速外设预留,具备更好的电气特性。
性能优化 checklist:照着做就能起飞
最后送上一份 实战优化清单 ,照着调,保你写出稳定高速的SD卡应用:
✅
【必做】启用DMA传输
→ 确保
sdmmc_host_init_slot()
中启用了DMA
✅
【必做】选用A2/U3/V30等级TF卡
→ 别贪便宜买杂牌卡,长期运行必翻车
✅
【必做】合理设置FAT分配单元大小
→ 建议设为16KB或32KB,减少FAT表碎片
✅
【推荐】增加RAM缓冲区
→ 至少4KB以上,避免频繁小包写入
✅
【推荐】控制f_sync()频率
→ 每写1MB sync一次即可,太频繁严重影响速度
✅
【推荐】关闭不必要的日志输出
→
ESP_LOGI
太多会导致任务调度延迟,间接影响写入
✅
【进阶】启用PSRAM作为中间缓存
→ 若使用ESP32-S3-WROOM-1,可用外部8MB PSRAM暂存数据
✅
【进阶】实现断电保护机制
→ 断电前检测GPIO,触发紧急sync + unmount
这些速度能满足哪些应用场景?
说了这么多,到底够不够用?我们来对照几个典型场景:
| 应用类型 | 数据率需求 | 是否满足 |
|---|---|---|
| 温湿度传感器记录(CSV) | ~1 KB/s | ✅ 轻松应对 |
| 音频录音(16bit/44.1kHz mono) | ~88 KB/s | ✅ 完全胜任 |
| WAV录音(立体声) | ~176 KB/s | ✅ 没问题 |
| Opus编码语音(16kbps) | ~2 KB/s | ✅ 富裕 |
| JPEG图片缓存(每秒1帧,2MB) | ~2 MB/s | ✅ 可行 |
| MJPEG视频流(720p@15fps,~5MB/s) | ~5 MB/s | ✅ 来得及写 |
| H.264裸流直录(1080p) | >10 MB/s | ⚠️ 边缘试探,需压缩 |
| RAW图像连拍 | >20 MB/s | ❌ 不现实 |
结论很明显:
🎯 对于绝大多数IoT数据采集、语音前端缓存、图片暂存类应用,ESP32-S3 + SDMMC + 好卡的组合绰绰有余;
🚫 但要想实现“无损视频直录”,还得等更强的平台(比如ESP32-P4或Linux方案)。
写在最后:性能之外,更要关注可靠性
技术圈总喜欢比拼“峰值速度”,但我们做嵌入式的都知道:
真正的挑战从来不是‘最快能跑多快’,而是‘最差情况下能不能不死’。
一张TF卡,在实验室跑得好好的,放到野外高温环境下连续写三个月,很可能某天就再也打不开文件系统了。
所以比起追求极限速度,我更建议你:
- 选 工业级耐久卡 (如三星PRO Endurance、金士顿Industrial)
- 加 电源监控与异常恢复机制
- 实现 自动坏块检测与日志归档
- 设计 合理的文件命名与备份策略
毕竟,设备在现场挂了,客户可不管你用了什么芯片、写了多优雅的代码 😅
⚡ 如果你正打算做一个基于ESP32-S3的数据记录仪、语音采集盒、边缘AI盒子,希望这篇实测能帮你避开那些“看似可行、实则坑爹”的陷阱。
下次当你面对“写入缓慢”、“偶尔卡顿”、“文件损坏”等问题时,不妨回头看看这篇文章里的每一个细节——也许答案就在某个你忽略的配置项里。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
1万+

被折叠的 条评论
为什么被折叠?



