十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SD卡深度解析:从硬件原理到MicroPython驱动与文件系统挂载

SD卡深度解析:从硬件原理到MicroPython驱动与文件系统挂载 作为一个常年跟嵌入式存储打交道的开发者我太清楚 SD 卡这个看似不起眼的小东西里藏着多少坑了。从最初在 STM32 上用 SPI 模式驱动到后来用 MicroPython 做快速原型验证再到被文件系统莫名其妙损坏搞得焦头烂额这一路踩过的雷、试过的错足够写出一篇诚意满满的深度解析了。这篇就把 SD 卡从硬件物理结构、通信协议、电路设计一直到 MicroPython 驱动实现和文件系统挂载一层层剖开来讲清楚。1. 刨析底层SD 卡的硬件结构与“内部世界观”在动手写驱动之前你得先搞明白 SD 卡这个指甲盖大小的东西里面到底装了什么。很多人以为它就是一个简单闪存芯片其实不然——它是一台微型计算机只是长了一张存储卡的脸。1.1 闪存介质与磨损均衡的“黑盒”逻辑从物理层面看SD卡的存储介质确实是 NAND Flash但这颗 NAND Flash 并不是你直接通过引脚就能访问的。卡内藏着一颗专用控制芯片这颗芯片承担了三大任务坏块管理、磨损均衡Wear Leveling和 ECC 纠错。这就意味着你写的往 SD 卡写数据并不是每次都会落在同样的物理地址上控制器会根据内部算法动态迁移数据块位置。这个特性解释了至少两件事一是在做数据采集或日志记录时我们不需要自己实现 LBA 级别的擦写均衡卡内控制器已经处理了这些恶心的逻辑二是为什么有些廉价 SD 卡在小文件持续写入时会突然掉速甚至卡死因为低端控制器没有任何优化策略垃圾回收直接导致瞬间极慢。加粗提示在关键应用中尽量选工业级或带有固定写入寿命标识的卡。从内部对外部的编码逻辑来看SD卡向主机暴露的是 LBALogic Block Address逻辑块地址寻址空间每个扇区通常为 512 字节。你发出的读取命令是读 LBA #1000控制器内部再把 LBA 映射到物理页。这一点和机械硬盘的 CHS 寻址被 LBA 替代的演进有点像本质上都是把存储细节封装成统一访问接口。1.2 寄存器体系决定驱动写法CID、CSD、OCR 是关键SD卡内部有几组重要的寄存器几乎所有初始化流程和状态查询都依赖它们OCR 寄存器Operation Conditions Register32 位宽用来说明卡的电压范围支持。Bit 15 表示 3.3VBit 21 表示 2.7V-3.6V 范围支持还包含CCS位Card Capacity Status用于判断是 SDSC 还是 SDHC/SDXC。CID 寄存器Card Identification Register128 位包含制造商 ID、OEM ID、产品名、生产日期、序列号等。这是卡的“身份证”读取它可以在产品中实现防伪识别或绑定授权。CSD 寄存器Card Specific Data Register128 位描述了卡的最大传输速率、读写块大小、传输模式、容量等具体参数。初始化时解析 CSD 可以拿到精确的存储容量。SCR 寄存器SD Configuration Register通过CMD51读取用于明确卡支持的命令集是不是支持 SD Spec 更高版本如 UHS-I等。驱动开发时你至少要在初始化阶段正确读出 CID、CSD 和 OCR否则后续的读写操作几乎必挂。我在做 MicroPython 驱动调试时经常输出这三组寄存器的 Hex 值用于定位问题。如果 OCR 的电压范围位没有置上 3.3V 支持而你又在 3.3V 下工作那初始化就会一直卡在ACMD41。1.3 存储容量分类SDSC、SDHC 与 SDXC 的差异SD卡按容量被划分为几个家族SDSC标准容量最大 2GB、SDHC高容量2GB-32GB、SDXC扩展容量32GB-2TB以及 SDUC超扩展容量2TB-128TB。从驱动开发角度最核心的差异是寻址方式SDSC 使用字节寻址BYTE AddressingSDHC/SDXC 使用块寻址Block Addressing按 512 字节扇区。CMD 参数数据读写的命令参数在 SDSC 是字节地址而在 SDHC/SDXC 是扇区地址。初始化握手ACMD41参数中的HCSHost Capacity Support位必须根据主机能力置位卡会在 OCR 的CCS位返回自己的类型。在 MicroPython 中sdcard.py模块已经自动处理了这些差异。但如果你要写底层 C 驱动或者像我一样做过 FPGA 直接驱动 SD 卡的项目就必须自己对CMD8和ACMD41的返回做分支。简单说支持 SDHC 的现代卡在 SPI 模式下也需要把ACMD41的 HCS 位置 1。2. 通信协议拆解SPI 模式与 SD 模式的选择艺术SD卡支持两种访问协议原生 SD 总线模式和 SPI 模式。在嵌入式 MCU 上SPI 模式因其引脚占用少、时序简单而大受欢迎而追求高速场景如摄像头图像存储时SDIO 四线模式则能发挥更大带宽。2.1 SD 总线模式SDIO速度天花板与引脚开销原生 SD 模式频率上限在 UHS-II 规范下达 312MB/s较常见的高性能卡在普通高速模式High Speed Mode下可达 50MB/s。它通过 CMD 线、CLK 和四条 DAT 线并联传输数据允许一次传输多块数据并支持 CRC 校验与中断机制。但这种高性能换来的代价是接线复杂至少需要 CLK、CMD、DAT0-DAT3 六根线且要求 MCU 有原生 SDIO 外设。假如你的 MCU 没有 SDIO 控制器想用 GPIO 模拟会非常痛苦——时序要求严格加上 CRC7/CRC16 计算CPU 开销大几乎是得不偿失。从驱动实现来看SD 模式初始化有繁琐的上电时序要求CMD0 → CMD8 → ACMD41 → CMD2 → CMD3 → CMD7每一条命令都有响应而且流程中还有 CRC 校验要求。这也是为什么很多开发者宁可用 SPI 模式的原因。2.2 SPI 模式嵌入式开发的“全民之选”SPI 模式把 SD 卡从高速总线设备“降格”为慢速 SPI 从设备牺牲性能换易用性。它只占用 MISO、MOSI、SCLK、CS 四根线兼容绝大多 MCU时序也宽松许多。SPI 模式的关键点在于初始化协议转换上电后主机必须在至少 74 个时钟周期内把 SCLK 拉高等待 SD 卡内部逻辑准备好。然后发送CMD0参数 0x00000000让卡进入 SPI 模式。命令帧格式每条命令固定 6 字节1 字节命令号 4 字节参数 1 字节 CRC7end bit。在 SPI 模式下只有CMD0和CMD8需要合法 CRC其余命令 CRC 可填任意值因为 SPI 模式不检查。响应时间SPI 模式下卡通常在 0-8 个时钟周期内给出响应字节R1。如果超 64 个时钟无响应基本上就是硬件连接或初始化时序问题。劣势也很明显SPI 时钟一般不能超过 25MHz大部分卡在 SPI 模式只保证 25MHz所以顺序读性能通常被限制在 1-2MB/s 左右。如果只是存日志、配置文件或离线采集数据完全够用但你要是跑音视频录播就得乖乖切 SDIO 了。这一条是踩坑经验也是性价比决策在项目立项时就得想清楚。2.3 SPI 模式的软件栈要怎么做才稳我用 MicroPython 写驱动时其实背后就是 SPI 模式的协议栈。核心流程可以总结成主机MCU初始化 SPI 外设工作频率先降到 400kHz 以下兼容旧卡。发送至少 74 个时钟脉冲唤醒 SD 卡。循环发送CMD0直到收到0x01空闲状态标志。发送CMD8参数0x000001AA检测卡是否支持 SDHC/SDXC 电压窗口。发送ACMD41参数带 HCS 位 0x40000000轮询直到响应为0x00卡退出空闲状态。读取CMD58读 OCR确认识别到的卡类型。设置 SPI 时钟到高速通常 10-20MHz。通过CMD17/CMD18单块/多块读、CMD24/CMD25单块/多块写进行数据读写。这些步骤缺少哪一步都不行。我第一次写 C 驱动的时候跳过了CMD8的链路结果在 SDHC 卡上全盘失灵后来对照协议栈逐条排查才找出这个问题。3. 深入电路设计看懂 SD 卡原理图的几个关键点网上经常看到有人问“sd卡原理图怎么画”其实掌握了几个核心原则原理图比想象中简单。但简单归简单细节坑也不少。一个典型的 MicroSD 卡座原理图包含供电、信号、检测三大部分。3.1 供电设计3.3V 与去耦电容的位置没你想的那么随意SD 卡有两种工作电压3.3V 和 1.8VUHS-I 模式。绝大多数嵌入式场景只用 3.3V。电路上需要给 VDD 引脚通常卡座的第 4 引脚提供一个干净稳定的 3.3V。我习惯在每个卡座的 VDD 引脚旁边放两个去耦电容一个 10uF 的钽电容或电解电容应对写入时的大电流浪涌一个 100nF 的陶瓷电容滤除高频噪声并且这两个电容要尽量靠近卡座引脚而不是随意放在 PCB 的某个角落。加粗提示如果把 100nF 电容放远超过 3mm那它对高频噪声的滤波能力会明显衰减高频噪声轻则导致读写错误重则卡直接无响应。另外要注意部分 MicroSD 卡座的 VDD 与 VSS 引脚有两个位置内部其实是连通的。画原理图时务必把所有 VSS 引脚接实到地不要有浮空或单点接地否则可能出现信号完整性问题。3.2 信号线上拉电阻一个容易被忽略的稳定器SD 总线模式下所有的 CMD 和 DAT 信号线在卡内部都有弱上拉但稳定性往往不够。大部分参考设计都会在 CMD、CLK、DAT0-DAT3 线上外接 10kΩ有的用 47kΩ上拉电阻到 3.3V。在 SPI 模式下MISO、MOSI、SCLK 这三个信号如果外接上拉可以在 MCU 引脚处于 High-Z 状态时保证电平确定防止毛刺触发误操作。一个常见问题就是初始化时卡没响应检查后发现 MISO 线上拉电阻虚焊导致 MCU 读到的电平一直不稳定。注意CLK 信号线通常不建议外接上拉电阻因为 CLK 由主机主动驱动外部上拉可能造成上升沿变缓或反射反而干扰高速时钟。3.3 电平转换3.3V 与 1.8V 系统的桥接如果你的 MCU 是 3.3V 系统SD 卡也是 3.3V直连就行。如果 MCU 是 5V 逻辑比如老一些的 8051 或者某些工业级 FPGA或者你要支持 1.8V UHS-I 信号就需要电平转换芯片如 TXS0108E、SN74AVC4T245。有一个高频翻车点很多人以为 SPI 的 MISO 方向是从卡到 MCU那 MCU 的 5V 耐压是不是就没事了其实 MOSI、SCLK 和 CS 都是 MCU 输出到卡的方向5V 逻辑电平直接怼到 3.3V 供电的 SD 卡信号引脚上会造成长期过压损坏轻则导致初始化和读写异常重则直接烧坏卡控制器。所以如果 MCU 是 5V 逻辑老老实实加电平转换不要图省事。3.4 卡检测开关一个低成本高回报的 IO绝大多数 MicroSD 卡座自带一个卡检测开关Card Detect Switch通常是一根弹片引脚。当卡插入时引脚状态翻转拔出时恢复。把这一对引脚接到 MCU 的 GPIO 上配合外部上拉或下拉可以在固件里实时检测卡是否存在。这个开关的价值在于用户交互当系统需要安全移除存储时可以直接提示“请勿拔卡”如果模块 24 小时在线监测还能在卡被误拔时立刻停止写入避免文件系统损坏。我见过不少产品把这一功能直接省略了后来售后被“拔卡导致配置丢失”的事故搞到崩溃。强烈建议保留这个开关成本不到一毛钱收益极大。4. MicroPython 驱动实现从零手写 SD 卡类用 MicroPython 做 SD 卡驱动最大的好处是省心官方文档已经给出了一个精简版的sdcard.py类封装了 SPI 初始化、读扇区、写扇区等核心方法。但封装归封装你要是不懂背后原理出了问题一样抓瞎。这一节就带你把sdcard.py的逻辑一行一行拆了看然后再做点定制优化。4.1 官方 sdcard.py 的核心逻辑拆解MicroPython 官方仓库中的sdcard.py是一个约 200 行的类核心方法包括__init__初始化 SPI 和 CS 引脚调用_init_card()完成进入 SPI 模式、SD 卡身份确认、扇区寻址配置。_init_card内部实现 CMD0、CMD8、ACMD41、CMD58 的发送与响应检查。readinto读取任意起止扇区到指定缓冲区逐扇区执行 CMD17 并解析数据 token。write解析数据为扇区列表逐扇区执行 CMD24并等待卡写入完成的响应。deinit释放引脚资源。驱动核心原理不复杂但它有几处需要特别留意代码块MicroPython sdcard.py 中_init_card的关键段落def _init_card(self, spi): # 让卡进入 SPI 模式 for _ in range(10): spi.write(b\xff) # 发送 CMD0让卡进入 Idle 状态 if not self._cmd(spi, 0, 0, 0x95) 0x01: raise OSError(no response to CMD0) # 发送 CMD8用于区分 SDSC 还是 SDHC/SDXC if not self._cmd(spi, 8, 0x1AA, 0x87) 0x01: raise OSError(no response to CMD8)这里有两个细节_cmd方法内部的响应读取并非只读一次而是有一个超时循环CMD0的参数为 0CRC 却必须填 0x95因为这是 SPI 模式下为数不多需要合法 CRC 的命令。4.2 健壮性改造成等待时间、超时重试与 CR C校验官方的sdcard.py核心逻辑没问题但实际项目里有许多地方需要增强。我总结三个我自己改进过的点卡初始化超时与重试官方代码只做一次CMD0请求如果卡没准备好就直接抛 OSError。在实际产品中卡座接触不良或上电瞬间电压不稳都可能造成一次性失败。我习惯在_init_card里加入最多 3 次重试并在每次重试前重新拉低 CS 引脚再释放强制复位 SPI 状态机。写入超时优化write方法在发送 CMD24 后需要等待卡返回0x05数据接受并等待 BUSY 信号。很多卡在大容量写完一页后内部刷写要 50ms 甚至 150ms。官方代码里是固定延时我改成轮询等待 BUSY 释放这样可以在保证可靠性的同时提升持续写入速度。数据 CRC 校验SPI 模式的数据块包含 CRC16 尾部但官方驱动并不检查。在某些 EMC 干扰较强的环境比如电机启动、继电器吸合数据可能在传输过程中出问题。进阶做法是在readinto时自己额外校验 CRC。但这个功能以软件实现代价较大折中方案是配合文件系统做 CRC 校验比如 FATFS 带的_FS_READONLY模式和哈希摘要根据实际可靠性要求选择是否做。加粗提示如果是做产品级固件不要直接拿官方样例上线。至少要加上初始化超时重试、写入 BUSY 轮询、CS 信号时序控制这三个修改能让稳定性上一个台阶。4.3 自定义驱动框架让读写接口与 FatFS 对齐如果只是存日志直接用官方代码也没问题。但如果要把 SD 卡接到现有存储框架中比如作为 FatFS 的底层块设备那我建议做一层薄薄的抽象class SDCardBlockDevice: def __init__(self, spi, cs): self._sdcard SDCard(spi, cs) def read_blocks(self, block_num, buf): return self._sdcard.readinto(buf, block_num) def write_blocks(self, block_num, buf): return self._sdcard.write(buf, block_num)这样做的意义在于未来的 FatFS 或 LittleFS 调用只需要面向扇区级接口不关心底层是 SD 卡还是 SPI NOR Flash。封装完成后文件系统的 mount 操作就变得非常简洁。4.4 关于 MicroPython 固件的选择要不要支持 USB Host热搜里的“支持 usb host 的 micropython 固件”其实指向一个实用场景用 MCU 直接读 U 盘或者接读卡器再配合 SD 卡做数据中转。MicroPython 官方仓库有部分板卡支持 USB Host如 STM32 的 Pyboard 的 OTG但大多数串口板卡不具备此功能。如果板子恰好支持 USB Host且 MicroPython 固件包含了usbhost或pyb.usb_host库那么操作 U 盘存储的方式与 SD 卡一致——都是块设备接口再挂载 FatFS。但这套方案硬件门槛较高而且 USB Host 栈占用 RAM 较多我在资源紧张的板子上就不勉强了。更稳妥的思路是直接用 SD 卡做主力存储U 盘场景留给带 Linux 的处理器去做。5. 文件系统挂载FatFS 与 os 层绑定的实操细节驱动层搞定后接下来的重头戏就是把文件系统搬上来。MicroPython 通过 VFSVirtual File System层抽象了文件系统接口所以你会看到import os和os.mount()是标准入口。5.1 os.mount 挂载流程与 block device 要求在 MicroPython 中挂载 SD 卡几乎是三行代码的事import os, machine sd machine.SDCard(spimachine.SPI(1), csmachine.Pin(10)) os.mount(sd, /sd)但这背后其实要求sd 对象必须实现readblocks、writeblocks、ioctl三个方法。ioctl里要正确返回块数量、块大小MicroPython 的 FatFS 默认按 512 字节块处理。os.mount的第二个参数必须是存在的目录挂载点通常约定为/sd或/mmc。挂载前不需要手动创建目录内核会自动创建。挂载后通过os.listdir(/sd)可以正常操作文件和目录这就是 VFS 层的便利之处。如果挂载失败大概率是块设备的ioctl返回的块数或块大小不对。排查技巧是在ioctl里打印_BLKCOUNT和_BLKSIZE的返回值确认与实际的扇区数、扇区大小完全一致。我做过一次蠢事——返回的块数是 0结果 FatFS 罢工好一阵。5.2 格式化SD 卡拔插后“文件系统损坏”的真相很多人遇到“明明把卡格式化成了 FAT32怎么挂载还是报错”的问题。真相往往不是文件系统炸了而是分区表的问题。如果用 Windows 系统格式化 SD 卡通常会建一个 MBR 分区表并在第一个分区里放 FAT32。这时 SD 卡在逻辑上是一个“可移动磁盘”块设备起始于 LBA 0但文件系统的引导扇区却在 LBA 2048 处等分区的起点。FatFS 在f_mount时如果没有指定分区号如0:通常扫描整个设备尝试找到可用的 FAT 卷这没问题。但是 MicroPython 的 VFS 层有些版本处理不了带 MBR 的卡只认从 LBA 0 开始的“超级软盘模式”FAT 卷。解决办法有两个用 SD 卡官方向导SD Card Formatter格式化它会默认创建不带 MBR 分区的完整 FAT 卷。在代码里解析 MBR 分区表的第一个分区把起始扇区偏移交给 FatFS。但 MicroPython 默认 VFS 不开放这个入口所以最好的方案就是格式化时候选对工具。加粗提示如果你的 MicroPython 板卡挂载 Windows 格式化的 SD 卡失败先别怀疑驱动用 SD Card Formatter 重新格式化成 FAT32 再试90% 会解。5.3 FatFS 与掉电保护异常断电后文件系统为何“炸”SD 卡在写入过程中的掉电会导致 FAT 表更新不完整目录项丢失严重时整个卷无法挂载。这在日志记录和远程数据采集中尤其常见。FatFS 自带写保护策略但对掉电来说最有效的办法是若不想额外引入掉电检测电路尽量把数据攒在内存里每 10 秒或 50 条记录批量写入一次减少写频率。最好在写入后强制调用os.sync()MicroPython FS 层支持把缓存数据刷进物理扇区。更稳妥的方案是用双分区轮流写A/B 切换保证一个分区损坏时可以从另一个分区恢复。做野外部署的项目光这一条就能避免很多远程维护噩梦。我在某个湖泊水质监测项目中就吃过掉电的亏后来加上批量写入和 sync故障率降了一大截。5.4 排查技巧sd卡没锁但是写保护别急着乱试热搜里“sd卡没锁但是写保护”是个经典问题。SD 卡的写保护在标准上是靠拨动卡侧面的机械锁片实现的——其实锁片并不会改变卡内部的任何状态只是卡座上的检测开关会感应到位移从而通知主机禁止写入。因此如果卡没锁但还是提示写保护通常有两个原因卡座的检测开关接触不良卡座弹片氧化或变形导致 MCU 检测到锁定的状态。用万用表测一下卡座 CD 引脚的电压逻辑或者换张卡试试。文件系统被标记为只读FAT 卷标头有只读标志或卷已损坏。此时读写测试会失败但 SCSI/Block 层并不会直接报“写保护”而是底层返回错误。如果是 MicroPython 环境插卡后挂载失败或写文件报 OSError可以先排除硬件层面再用读卡器在电脑上看看文件系统是否正常。我之前就遇到过一张工业级卡卡本身没锁但卡座检测引脚因为助焊剂污染造成虚短查了我足足两天。6. 高级场景与避坑实录从 FPGA 到系统镜像的实战经验6.1 FPGA 直接读取 SD 卡的常见误区热搜里“fpga读取sd卡bmg”显然指 FPGA 读取 SD 卡中的 BMP 图像文件。FPGA 读 SD 卡的思路通常有两种实现 SD 协议控制器 FatFS 软核在 FPGA 里用 Verilog 写 SPI 主机再集成一个 FatFS 的 C 工程通过软核如 MicroBlaze 或 RISC-V解析文件系统。性能好但开发量大。直接读取原始扇区数据如果 BMP 文件放在固定位置比如 LBA 1000FPGA 直接发 CMD17 把扇区读取出来再转并行数据不经过文件系统这种方式简单粗暴但灵活性差。很多人直接把 BMP 文件在电脑上拖入卡里然后让 FPGA 按固定 LBA 读结果读出来的全是乱码。原因很简单FAT32 文件系统把 BMP 文件存储在零散的簇里并不是连续的 LBA。解决思路要么用 Linux 下的dd把 BMP 写到裸分区跳过文件系统要么做一个通用的 FatFS 解析模块不要抱侥幸心理。6.2 64G SD 卡系统镜像 img 烧录与 FatFS 的容量局限32GB 以上 SD 卡通常是 SDXC 格式出厂为 exFAT 文件系统。MicroPython 的 FatFS 默认对 exFAT 支持度不高官方需要打开_USE_EXFAT宏才支持而且很多 MCU 内存偏小挂载 64G 卡不一定高效。如果想把系统镜像 img 烧到 64G 卡有两种路径在电脑上用dd ifimage.img of/dev/sdX bs4M直接整卡写入写入后卡上可能只有一个很小的 FAT 分区剩余的未分配空间不生效需要额外用分区工具扩容。如果镜像里已经是 FatFS确保构建 FatFS 时开启_USE_EXFAT和_MAX_SS支持 4096 字节扇区部分大 SDXC 卡物理扇区是 4K。实际踩坑中镜像写入 64G 卡后挂载失败十有八九是分区表没有按照 4K 对齐或者 FatFS 的_MAX_SS没有设置成 4096。顺带一提很大一部分采用小容量 4K page 的 FatFS 代码对 4K 物理扇区的卡支持不完美所以优先选物理扇区 512B 的卡更稳妥。6.3 性能测试你的 SD 卡瓶颈到底在哪很多写日志的项目跑着跑着发现数据记录出现断点怀疑是 SD 卡太慢。问题可能出在 SPI 时钟上在 STM32F103 上SPI 最大 18MHz加上协议开销顺序写速率大约 300-500KB/s如果开启 FatFS 且频繁小文件写入速率可能掉到几十 KB/s。MicroPython 环境下由于解释器开销和 Python 层的逐字节发送性能更差。实测用官方sdcard.py加 SPI 时钟 20MHz 的顺序写大概能达到 200-400KB/s再高就非常吃力。如果瓶颈无法接受可以切 SDIO 模式让 STM32H7 这类带 SDMMC 外设的 MCU 跑到 20-40MB/s。但代价是 SDIO 的驱动复杂度显著增加。做产品选型时要明确需求上限再来定方案。6.4 自编小工具推荐SDL 测试与 H2testw 的配合最后聊聊“sd卡测试app”。我在入手新卡或者排查故障时通常用 H2testw 做全卡写入校验这能测出扩容卡和坏块。再用 SD Insight 或类似的工具查 CID 信息确认卡的制造商和型号是否真实。在嵌入式现场手边没电脑时也可以写一个简单的 MicroPython 测试脚本对每个扇区做 write/read 回环校验定位坏块。如果需要批量测试我习惯在 PC 上用 F3Fight Flash Fraud跑一遍快速随机读写再用 Flash Drive Tester 抽查小文件性能。这套组合基本能覆盖 90% 的存储卡质量场景。7. 一些行内人不会告诉你的事做 SD 卡开发这么多年最后分享几条纯粹的个人体会。第一SD 卡的“标称速度”在嵌入式场景里参考价值有限。连续读 90MB/s 的高性能卡在小文件随机写入时可能被一张普通 class 10 卡甩开几条街。选型时关键是看你实际负载是顺序读写还是随机读写。第二MicroPython 的效率其实比很多人想象中要够用。在 168MHz M4 上把 SPI 时钟提到 30MHz用官方驱动挂载 FatFS顺序读速率能逼近 1MB/s 左右。对于绝大多数传感器记录、配置存储和语音播报场景这种性能已经绰绰有余了。第三文件系统相关的问题先怀疑 SD 卡的格式化方式再怀疑驱动。我见过太多人花了大半天调 SPI 时序最后发现是 Windows 格式化惹的祸。临时抱佛脚换 SD Card Formatter 重新格式化一切恢复正常。第四任何 SD 卡驱动做正式产品前都要做一次持续写的压力测试。不要只测半小时就上线至少跑 24 小时满负荷写入期间拔出再插拔观察 FAT 表是否损坏、写入速度是否骤降、卡是否出现错误状态。这部分测试能过滤掉 90% 的潜在翻车点。SD 卡看似简单但每个细节都值得较真。希望这篇能把原理、驱动和文件系统串起来助你少走些弯路。
返回列表