
去年给一个小批量设备做数据记录功能时被TF卡座坑惨了振动环境下卡座接触不良日志写到一半掉卡现场返修了好几台。后来换用了MK SD NAND情况一下子干净了。手头正好有正点原子的STM32F103ZET6精英板我顺手把这块“贴片式SD卡”跑通了整个过程其实可以压缩成三步焊接接线、配置工程、读写验证。这篇文章就把这套流程完整记录下来给打算用SD NAND做存储方案的你一个可以直接抄作业的参考。MK SD NAND本质上是把SD控制器和NAND Flash封装在一颗芯片里对外接口完全兼容SD协议所以STM32的SDIO外设可以直接驱动它上层再配上FatFS就能像操作普通文件一样读写。相比TF卡它不用卡座、不怕振动、适合机器贴片量产相比eMMC它只需要SDIO或SPI接口不用额外买eMMC控制器STM32全系基本都能带动。这篇文章适合三类人一是正点原子用户想扩展存储空间二是做产品原型需要可靠存储方案三是准备把TF卡方案升级成贴片式存储的嵌入式工程师。1. 为什么是MK SD NAND三种存储方案的取舍1.1 MK SD NAND到底是什么很多人第一次听到“SD NAND”会误以为是某种特殊Flash其实把它的名字拆开看就清楚了SD是指接口协议NAND是指底层存储介质合起来就是一颗自带SD控制器的NAND Flash芯片。它和TF卡最大的区别在于形态。TF卡是把SD控制器和NAND颗粒做成一张卡片靠卡座的金手指接触导电SD NAND则是把同样的东西封装成一个LGA-8的贴片IC直接焊在PCB上。这带来的直接好处就是没有接触不良的问题没有卡座弹簧疲劳的问题也没有运输途中卡片脱落的问题。我是在一次做设备振动摸底测试时意识到这个差距的。TF卡在静态环境跑得好好的一上振动台就偶发SD_Init失败后来查来查去就是卡座触点瞬间断开导致的。换成SD NAND之后同样的振动测试跑了三天读写全程零故障。从那以后但凡要交付到现场的设备我基本都用SD NAND方案。1.2 关键对比TF卡、eMMC与MK SD NAND为了说清楚选型逻辑我把三类方案放在一起对比过直接看表格。对比项TF卡 卡座eMMCMK SD NAND接口协议SDIO / SPIeMMC专用接口SDIO / SPISTM32驱动难度低高需要eMMC控制器或时序模拟低等同驱动SD卡焊接要求无需焊接BGA封装一般要回流焊LGA-8封装手工烙铁可焊机械可靠性差触点接触好贴片固定好贴片固定长期供货稳定性卡商经常换方案稳定稳定单颗成本低但卡座结构件拉高成本中高中典型容量1GB~512GB4GB~256GB1GB~16GB从表格能看出SD NAND和eMMC在可靠性上属于同一档次但SD NAND的驱动门槛低得多。STM32F103这类M3内核芯片本身没有eMMC控制器如果硬上eMMC需要额外扩展方案不划算。而SD NAND直接吃SDIO或SPI正点原子精英板的例程里本身就带了SDIO驱动和FatFS几乎是无缝对接。1.3 选型注意事项MK SD NAND具体型号时要注意两点。第一是容量。当前市面上常见的是1GB、4GB、8GB对嵌入式应用来说4GB是比较平衡的选择存日志、存图片素材、存固件升级包都够用。如果容量需求超过16GBSD NAND的性价比优势会减弱这种情况不如考虑其他方案。第二是引脚定义。不同品牌、不同系列的SD NAND引脚排列不完全一样焊接前一定去官网下对应型号的datasheet确认。以我用的MK系列4GB型号为例LGA-8封装的引脚功能大致是VCC、GND、CMD、CLK、DAT0、DAT1、DAT2、DAT3其中DAT3/CS和CMD/DI在SPI模式下还承担片选和数据输入输出。这个布局和SD卡协议是对应的好在正点原子精英板上的SDIO引脚资源刚好能覆盖。2. 硬件连接让芯片“长”在精英板上2.1 先看懂精英板的TF卡座原理图正点原子精英板的板载TF卡座如果手上没有原理图可以直接看开发板TF卡座附近丝印。卡座引脚通常引出6路信号SDIO_CK、SDIO_CMD、SDIO_D0、SDIO_D1、SDIO_D2、SDIO_D3外加VCC和GND。这些信号对应到STM32F103ZET6的具体引脚是信号STM32引脚SDIO_CKPC12SDIO_CMDPD2SDIO_D0PC8SDIO_D1PC9SDIO_D2PC10SDIO_D3PC11卡检测CDPA8这就是为什么用MK SD NAND替换TF卡如此顺滑卡座上的信号线和SD NAND的引脚是一一对应的SDIO 4位模式正好全部用上。只要把SD NAND各引脚飞线到卡座对应焊盘硬件层面就等于“插了一张永远不掉的TF卡”。2.2 三种接线方式的推荐与实操我试下来有几种接线方案按推荐程度排序方式一、飞线到TF卡座焊盘这是最省事、也最接近量产形态的做法。把MK SD NAND放在靠近TF卡座的位置用杜邦线或者漆包线把芯片引脚逐根连到卡座焊盘。注意飞线尽量等长、间距合理信号线不要贴着长距离并行走避免干扰。这种方式下正点原子的SDIO例程几乎不用改只处理CD检测逻辑即可。方式二、通过转接板直接插到卡座如果只是快速验证可以买现成的SD NAND转TF卡转接板把芯片焊在转接板上然后像TF卡一样插进卡座。优点是零改动、随时拔插缺点是转接板增加了接触点只适合测试不适合交付。方式三、SPI模式接到任意IO口如果不想占用SDIO外设可以用SPI模式驱动SD NAND。这要求芯片支持SPI协议接线只需要VCC、GND、SCLK、MOSI、MISO、CS六根线但FatFS底层驱动要换成SPI版本速度也远不如SDIO 4位模式。一般只在SDIO被其他功能占用的场景才考虑。我在精英板上最终用的是方式一飞线焊接后用一个塑料支架固定芯片散热和抗振都更好。2.3 上拉电阻、去耦电容与CD检测的处理硬件连接里有三个容易踩的细节我单独拿出来说。第一是上拉电阻。SD卡协议要求在CMD线和DAT0~DAT3线上有上拉电阻到VCC正点原子精英板原理图里TF卡座部分已经加了相关上拉所以直接接卡座焊盘不需要额外处理。但如果你是走SPI模式接到其他IO或者自己画板子千万别省这4个上拉电阻我见过不少初始化失败就是数据线上没上拉导致的。第二是电源去耦。MK SD NAND在读写瞬间电流变化明显我习惯在芯片VCC和GND之间靠近放一个100nF陶瓷电容条件允许再并一个10μF钽电容。这个动作在开发板上不太明显但到了产品PCB上能显著降低写入时偶发失败的概率。第三是CD检测引脚的处理这是正点原子例程最容易卡住的地方。正点原子的SD卡实验代码通常会先读PA8通过卡座的CD引脚判断有没有插入TF卡没有卡就直接报“SD卡错误”。MK SD NAND是焊在板子上的卡座孔位里没有实体卡CD引脚状态永远是无卡于是例程会一直停在“未检测到卡”。解决办法有两种任选其一把PA8引脚用一个跳线直接接到GND模拟“卡已插入”的信号在代码里把检测逻辑屏蔽或者直接把判断表达式强制为真。我图省事是直接把PA8焊了一根线到GND这样正点原子例程原封不动就能继续跑连代码都不用改。3. 软件配置CubeMX加上FatFS伪代码与关键逻辑3.1 用CubeMX快速生成SDIOFatFS工程正点原子精英板是STM32F103ZET6CubeMX配置不算复杂按下面几步走打开STM32CubeMX新建工程选择芯片STM32F103ZET6在Pinout视图里配置RCC选择外部晶振Crystal/Ceramic Resonator时钟树配置为72MHz主频SDIO勾选SD 4 bits Wide busDMA添加SDIO_RX和SDIO_TX的DMA通道方向分别是PeripheralToMemory和MemoryToPeripheral模式选NormalUSART1异步模式115200波特率Middleware → FATFS选择SDIO接口建议打开长文件名支持代码页按自己需求选437或936Project Manager里生成MDK-ARM V5工程。这里有一个关键参数SDIO的Clock Div。初始化时SD协议要求时钟不超过400kHzCubeMX的HAL库初始化阶段会自动用较低频率识别卡之后才切到用户设定的Clock Div。我习惯把Clock Div设为4对应9MHz左右的数据时钟在精英板上跑得很稳。如果想保守一点可以调到8甚至16速度会降但兼容性更好。3.2 应用层代码挂载、格式化、读写一个文件CubeMX生成的FatFS底层驱动会通过sd_diskio.c把磁盘操作和HAL库的SDIO驱动对接好应用层只需要在main.c里写逻辑。完整流程是挂载文件系统、判断是否需要格式化、创建文件、写入内容、读回验证。以下是一段经过验证的测试代码串口会打印每一步的返回值。#include ff.h #include string.h #include stdio.h FATFS fs; FIL file; UINT bw, br; char write_buf[] Hello MK SD NAND!\r\n; char read_buf[64]; FRESULT res; void SD_NAND_Test(void) { res f_mount(fs, , 1); printf(f_mount %d\r\n, res); if (res ! FR_OK) { /* 如果返回 FR_NO_FILESYSTEM说明芯片没有文件系统需要格式化 */ if (res FR_NO_FILESYSTEM) { printf(No filesystem, formatting...\r\n); f_mount(NULL, , 0); res f_mkfs(, 0, 0, 0); printf(f_mkfs %d\r\n, res); res f_mount(fs, , 1); printf(f_mount again %d\r\n, res); } if (res ! FR_OK) return; } res f_open(file, test.txt, FA_CREATE_ALWAYS | FA_WRITE); if (res ! FR_OK) { printf(f_open write error %d\r\n, res); return; } f_write(file, write_buf, strlen(write_buf), bw); f_close(file); printf(wrote %d bytes\r\n, bw); res f_open(file, test.txt, FA_READ); if (res ! FR_OK) { printf(f_open read error %d\r\n, res); return; } memset(read_buf, 0, sizeof(read_buf)); f_read(file, read_buf, sizeof(read_buf), br); f_close(file); printf(read %d bytes: %s\r\n, br, read_buf); f_mount(NULL, , 0); }注意这段代码里f_mkfs(, 0, 0, 0)的参数格式在不同FatFS版本里略有差异老版本常用f_mkfs(, 0, 0)新版是带了一个对齐配置参数。编译报错的话查一下头文件里的函数原型按对应版本调整即可。3.3 串口验证与实测效果串口重定向走USART1需要在代码里重写fputcint fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }另外记得在Keil工程里勾选MicroLIB否则printf重定向容易出问题。我在精英板上实测挂载、格式化、写文件、读文件全程不到一秒串口输出如下f_mount 3 No filesystem, formatting... f_mkfs 0 f_mount again 0 wrote 21 bytes read 21 bytes: Hello MK SD NAND!第一次运行时返回3对应FR_NO_FILESYSTEM格式化后再次挂载返回0说明文件系统正常。这个流程跑通了后续任何文件读写操作都只是换文件名和路径的问题。如果要验证掉电保存效果可以在写文件后调用f_sync(file)或者在关闭文件后断电重启重新挂载并检查文件内容。我实测多次断电重启数据都完整保留这也是SD NAND相对RAM缓存方案最核心的卖点。4. 踩坑记录从“no target found”到写入异常4.1 第一步可能挡路的调试器报错很多人在配置完工程后第一件事不是下载SD NAND代码而是发现Keil里下载程序直接报错。最常见的错误是error: no stm32 target found! if your product embeds debug authentication, pl...看到这行提示先别慌和SD NAND没有关系通常是调试器没连上芯片。排查顺序如下检查ST-Link与板子的SWD接线SWDIO、SWCLK、GND三根线必须对应有些便宜的ST-Link和杜邦线接触不稳定重新插拔几次确认板子已经供电精英板USB线连接后要看到电源指示灯亮在Keil的Options → Debug → Settings里查看是否能识别到设备ID如果显示No target检查一下Debug里选择的调试器类型是否和实际一致降低SWD时钟频率把Settings里默认的4MHz降到400kHz时序余量大更容易连上如果芯片之前烧过读保护程序先用STM32CubeProgrammer选择Connect Under Reset方式连接解除读保护后再回到Keil。从我的经验看80%的“no target found”都是接线松动或者没选对调试器剩下20%才是芯片读保护。因为这块和存储芯片完全无关很多人会绕圈子排查所以单独提醒一下。4.2 文件系统挂载不上、格式化失败怎么破文件系统挂载失败是接触SD NAND后最普遍的软件问题具体表现是f_mount返回FR_NO_FILESYSTEM或者直接卡死在SDIO初始化。从底层往上逐项排查按这个顺序供电电压。用万用表量芯片VCC和GND之间电压必须在3.3V左右。如果焊完后电压被拉低到3V以下大概率是焊接短路或者电源走线有问题焊接质量。LGA-8封装引脚在芯片底部手工焊接最容易出现连锡和虚焊。我用放大镜检查每对相邻引脚再用万用表蜂鸣档确认相邻引脚不导通、对应引脚和飞线另一端导通时钟频率。把Clock Div从4改大到16或32降到4MHz以下再试只要能初始化就说明是速率余量问题上拉电阻。如果方案里省掉了CMD和DAT线上的上拉初始化卡在CMD响应超时一点不奇怪CD检测干扰。如果你用的正点原子例程记得PA8要接地或屏蔽检测逻辑否则初始化流程直接就不往下走。如果f_mount提示没有文件系统第一次使用SD NAND非常正常。记住先卸载再格式化否则格式化可能返回错误。上面代码里已经做了这个处理。4.3 数据写错、速度慢、掉电丢文件的真凶读写正常之后还会遇到几类隐蔽的异常我展开说。第一个是缓冲区对齐问题。STM32F103的SDIO走DMA时对内存地址有对齐要求如果用局部数组当读写缓冲很可能因为栈上地址没对齐导致DMA传输出错表现出来就是写入几百个字节后偶尔读回几个错数据。解决办法是定义全局缓冲区或者加__attribute__((aligned(4)))声明__attribute__((aligned(4))) char read_buf[64];第二个是掉电丢文件。很多人测试时写完文件直接断电再次上电发现文件大小不对或内容丢了一半。这不是SD NAND坏了而是文件系统的写缓存还没刷到Flash里。FatFS在写操作后需要f_sync(file)或者f_close(file)才会真正落盘测试掉电保存时记得先调同步函数。第三个是写入速度异常。SD NAND的写入速度天然低于读取速度如果感觉慢得离谱检查是不是在SPI模式下跑。SPI模式字节位宽只有1位和SDIO 4位宽差别很大同样容量的写入耗时能差好几倍。如果必须用高速模式确认DMA配的是Normal模式并且HAL_SD_WriteBlocks_DMA完成后等待的中断回调没有被其他中断挤掉。第四个是文件系统损坏。频繁断电或格式化过程中异常掉电偶尔会导致分区表损坏现象是插到电脑上提示需要格式化。遇到这种情况直接在程序里重新f_mkfs即可不过这会清空全部数据。所以我的习惯是产品代码里单独做一个隐藏的“格式化入口”只有维修模式才触发避免误操作。我个人在实际操作中的体会是MK SD NAND这类芯片最大的价值是把“存储模块”变成了“存储焊点”省掉卡座、外壳开孔、结构固定这些乱七八糟的事产品BOM做起来清爽多了。最后再分享一个焊接小技巧LGA封装底部是金属焊盘手工放芯片时容易被助焊剂表面张力带偏我的办法是先给PCB焊盘镀一层薄锡涂助焊剂然后芯片放上去热风枪先低温吹到锡融化芯片会在表面张力作用下自动“归正”到焊盘中心这个技巧对LGA和QFN都很管用。整个项目跑通之后后面再换到其它STM32系列只是改一下引脚和CubeMX配置的问题存储这块代码基本可以跟着走。