
最近手头一个项目需要给设备加一块掉电不丢的数据存储TF 卡槽的位置因为设备震动老是接触不良跑着跑着文件系统就挂了。我把目光放到了 MK SD NAND 上在正点原子精英板STM32F103ZET6上从驱动到文件系统整个流程跑通包括格式化、读写校验全部走了一遍。真正动手做下来其实就三步硬件转接、SDIO 驱动对接、FatFs 挂载。这篇文章把每一步怎么走、哪些坑必须绕开都写清楚适合所有想在 STM32 项目里用贴片式存储卡替代 TF 卡的开发者。1. 先搞清楚 MK SD NAND 是什么为什么值得折腾1.1 贴片式 SD 卡和 SPI NAND、TF 卡有啥区别MK SD NAND 本质上是把一颗标准 SD 控制器和一颗 NAND Flash 封装在一起外面做成 LGA-8 封装8 个金属焊盘直接贴片焊接。对外接口走标准 SD 2.0 协议支持 SDIO 和 SPI 两种工作模式。从主控视角看它就是一张“焊在板子上的 SD 卡”你不需要关心 NAND 的坏块管理、ECC 校验、磨损均衡这些全部由芯片内部的 FTL 固件处理。这跟 SPI NAND 有本质区别。裸 NAND 只是存储颗粒主控要自己做坏块扫描、ECC 纠错、逻辑块映射工作量很大而且一旦掉电处理不好文件系统分分钟损坏。SD NAND 把这些全都包了主控只需要像操作 SD 卡一样发命令读写扇区就行开发成本低很多。跟 TF 卡相比SD NAND 最大的优势是焊接固定没有接触不良的问题。设备在震动环境下TF 卡的金属弹片会氧化、卡座会松动这是很多工业产品用 TF 卡存储数据出故障的根源。SD NAND 焊死在主板上数据线短抗干扰强上电时序也更稳定。1.2 为什么在精英板上用 SDIO 而不是 SPIMK SD NAND 支持 SPI 模式但我在正点原子精英板上强烈建议用 SDIO。原因有三点速度差距明显。STM32F103 的 SPI 最高 18MHzSD NAND 在 SPI 模式下吞吐量被卡死而 SDIO 走 4 位总线时钟 24MHz 时理论带宽接近 12MB/s实际跑文件系统差距是数量级的。例程成本近乎为零。正点原子精英板例程里自带 SDIO 驱动的 SD 卡实验SDIO 引脚已经接到板载 SD 卡座底层驱动、FatFs 文件系统全都给你弄好了。SD NAND 协议和 SD 卡一致理论上就是无缝替换。CRC 校验由硬件完成。SDIO 控制器硬件生成和校验 CRC比 SPI 模式下用软件模拟 SD 协议状态机可靠得多排查问题也省心。有人会问正点原子精英板上板载了 W25Q64 SPI Flash我直接用 SPI Flash 不就行了也行但 SPI Flash 是裸 Flash你要么用文件系统组件做磨损均衡要么自己管理扇区容量也比 SD NAND 小。SD NAND 在容量和易用性上更适合当“数据盘”用。2. 硬件准备与接线动烙铁前先想清楚这三件事2.1 需要的物料清单我这次用的是 MK81104Gbit 也就是 512MB 容量实际项目里用 8Gbit 的 MK8210 也完全一样驱动流程没有区别。物料如下MK SD NAND 芯片一颗注意买正品这芯片有翻新和打磨片到手先核对丝印。LGA-8 转接板用来把贴片封装引出来。常见有两种一种是转 TF 卡形状的 PCB 转接板焊好后可以插卡座另一种是转 2.54mm 排针的小板方便飞线调试。正点原子精英板一块STM32F103ZET6 版本。杜邦线若干烙铁、助焊剂、万用表。调试阶段我建议先用排针转接板飞线因为方便用示波器抓命令线波形。等验证通过、确定批量方案之后再考虑做成 TF 卡形状转接板插卡座或者直接在量产的 PCB 主板上预留 SD NAND 焊盘。2.2 正点原子精英板上的 SDIO 引脚分配正点原子精英板的板载 SD 卡座接在 STM32F103ZET6 的 SDIO 外设上引脚分配如下表SD NAND 引脚SDIO 功能精英板 MCU 引脚1 (DAT2/RSV)DAT2PC102 (DAT3/CS)DAT3PC113 (CMD/DI)CMDPD24 (VDD)VCC 3.3V3V35 (CLK)CLKPC126 (VSS)GNDGND7 (DAT0/DO)DAT0PC88 (DAT1/RSV)DAT1PC9注意不同批次的 SD NAND 引脚排列可能有差异接线前一定要对着你手头芯片的 datasheet 核对不要照抄网上任何人的脚位图包括我这篇的。这里有一个特别容易翻车的点DAT3/CS 引脚在 SDIO 模式下必须通过上拉电阻拉到 3.3V推荐 10kΩ。DAT3 在 SD 协议里承担“卡在线检测”的功能上电时如果没有被拉高主控会认为卡不存在初始化直接失败。STM32 内部虽然有上拉但内部上拉阻值偏大飞线状态下不可靠我干脆在转接板上焊了一个 10kΩ 电阻一劳永逸。2.3 两种连接方式的操作要点方式一排针转接板飞线。把 SD NAND 焊在 LGA-8 转 DIP 小板上然后用杜邦线连到精英板的 SDIO 引脚。注意杜邦线总长度控制在 10cm 以内走线不要跟电机驱动线、220V 交流线绑在一起。SDIO 时钟频率高长线会造成信号反射和数据错乱。方式二TF 卡形转接板插卡座。把 SD NAND 焊到 TF 卡形状的 PCB 转接板上插到精英板的 SD 卡座里。这里有个兼容性问题精英板的卡座是标准 SD 卡座而 TF 卡形转接板是 micro SD 尺寸需要再加一个 TF 转 SD 卡套。调试时不推荐这种方式因为两层转接会让厚度增加插拔不顺而且接触不良反而掩盖了 SD NAND 本身的稳定性优势。我试过一次初始化失败率明显比飞线高后面果断改回飞线调程序。焊接 LGA-8 封装时温度控制在 300℃ 到 350℃先给焊盘上锡放上芯片后用热风枪均匀加热看到锡珠融化下沉就停止。焊完后必须用万用表量一遍相邻引脚之间有没有短路特别是 VCC 和 GND 之间。这步不能省芯片烧了事小把精英板也带走了就亏大了。3. 三步实操SDIO 驱动、文件系统与读写验证3.1 第一步先把 SDIO 底层配置成 400kHz 低速SD 协议要求上电初始化阶段时钟必须低于 400kHz这是几乎所有 SD NAND 驱动问题的第一道坎。很多人直接把时钟配成 24MHz 或者 48MHz然后初始化就卡死就是这个原因。正点原子精英板例程里初始化阶段的 SDIO 时钟分频系数已经设置好了你直接复用即可。如果是从零开始移植打开 SDIO 时钟后先配置 GPIOvoid SD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE); // PC8~PC12 对应 SDIO_D0~D3 和 SDIO_CK GPIO_InitStructure.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOC, GPIO_InitStructure); // PD2 对应 SDIO_CMD GPIO_InitStructure.GPIO_Pin GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOD, GPIO_InitStructure); }GPIO 配好之后初始化 SDIO 外设。STM32F103 的 SDIO 外设时钟源是 PCLK2通常为 72MHz初始化阶段的时钟分频要设置得足够大确保 SDIO_CK 低于 400kHz。正点原子例程中的SDIO_INIT_CLK_DIV就是这个目的具体值你可以根据自己工程的时钟配置现场算。初始化阶段总线宽度固定为 1 位即只使用 DAT0。设置完 SDIO 外设后给 SDIO 上电然后发送至少 74 个时钟周期。这 74 个时钟的作用是让 SD NAND 内部的电源管理电路稳定下来完成上电复位。程序里可以循环写 SDIO 的时钟控制寄存器来产生脉冲也可以直接延时但最稳妥的还是显式发时钟。提示SD NAND 上电后比普通 SD 卡需要更长的稳定时间。我在代码里 SD_Init 最前面加了 100ms 延时实测初始化成功率明显提高。如果是刚焊接完的芯片第一次上电建议延时拉长到 300ms。3.2 第二步让 SD NAND 正确响应 CMD0/CMD8/ACMD41底层时钟准备好之后按 SD 协议标准命令序列初始化。我把关键步骤整理成表格照着这个排查又快又清楚步骤命令参数期望响应说明1CMD00x00000000R1 0x01复位到 Idle StateCRC 固定为 0x952CMD80x000001AAR7 0x000001AA确认支持 SD 2.0工作电压 2.7~3.6V3CMD55 ACMD41HCS 1R1 0x00循环发送直到卡退出 Busy 状态4CMD580OCR 寄存器判断是 SDHC 还是 SDSC5CMD9RCA 0CSD 寄存器读取卡容量和参数6CMD55 ACMD60x02R1 0x00切换 4 位总线模式7CMD16512R1 0x00设置块长度为 512 字节SDSC 必需这里有几个细节必须注意第一CMD0 的 CRC 是固定值 0x95不能省略。SDIO 模式下硬件会计算 CRC但如果你的驱动是软件控制命令发送CRC 字节要手动填对否则 SD NAND 直接忽略这条命令。第二CMD8 的响应要严格校验。SD NAND 是 SD 2.0 设备CMD8 应该返回 0x000001AA如果返回别的值说明芯片可能处于异常状态先断电重新上电再来。注意如果返回 0x5A 之类则可能是老版本 SDSC 卡不响应 CMD8这种情况就需要走 CMD1 或者不带 HCS 的 ACMD41但 MK 的 SD NAND 我还没遇到过不响应 CMD8 的情况。第三ACMD41 的 HCS 位必须置 1。HCS1 告诉卡“主控支持 SDHC”SD NAND 内部才会切换到块寻址模式扇区地址从 0 开始编号。如果 HCS 没置位卡可能用字节寻址后面读写扇区的参数完全不对表现就是能初始化但读全 0xFF。// ACMD41 循环等待卡就绪 for (i 0; i 0xFFFF; i) { SD_SendCmd(CMD55, 0, 0, 0); result SD_SendCmd(ACMD41, 0x40000000, 0, 0); // HCS 1 if (result 0) break; // 卡就绪 }初始化完成后把 SDIO 时钟从 400kHz 提高到 24MHz。正点原子例程中这一步是重新设置 SDIO 的 CLKCR 寄存器分频系数。为什么要 24MHz 而不是 48MHz因为 SD 2.0 规范明确规定高速模式时钟上限为 50MHz但 STM32F103 的 SDIO 在 48MHz 下走线长了波形就难看了24MHz 是稳定性和速度的折中。3.3 第三步挂载 FatFs 并完成读写校验SD NAND 的扇区读写函数就绪后文件系统部分就水到渠成了。正点原子例程里已经把diskio.c和ff.c都移植好了你只需要确认底层函数对接正确。核心是这四个DSTATUS disk_initialize(BYTE pdrv); // 调用 SD_Init() DSTATUS disk_status(BYTE pdrv); // 返回 SD 卡状态 DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count); // 调用 SD_ReadDisk() DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count); // 调用 SD_WriteDisk()这里最容易踩坑的是扇区号类型。正点原子旧版例程里SD_ReadDisk的第二个参数是u32 sector但 FatFs 新版本使用LBA_t类型在ffconf.h中配置为FS_LBA64后是 64 位。移植时把接口之间的类型转清楚否则容量超过 2GB 的 SD NAND 读写地址会溢出。MK8110 只有 512MB暂时碰不到但代码规范一点后面换大容量省事。挂载文件系统的关键流程FATFS fs; FIL fp; UINT bw, br; u8 write_buf[512] Hello MK SD NAND!; u8 read_buf[512]; // 挂载到盘符 0 FRESULT res f_mount(fs, 0:, 1); if (res FR_NO_FILESYSTEM) { // SD NAND 出厂一般是空白的先格式化 f_mount(NULL, 0:, 0); res f_mkfs(0:, 0, 0, 0); res f_mount(fs, 0:, 1); } if (res FR_OK) { // 写文件 res f_open(fp, 0:test.txt, FA_CREATE_ALWAYS | FA_WRITE); if (res FR_OK) { f_write(fp, write_buf, sizeof(write_buf), bw); f_close(fp); } // 读回校验 res f_open(fp, 0:test.txt, FA_READ); if (res FR_OK) { f_read(fp, read_buf, sizeof(read_buf), br); f_close(fp); if (br bw memcmp(write_buf, read_buf, sizeof(write_buf)) 0) { printf(SD NAND write/read test PASS\r\n); } } }注意格式化之前不能挂着文件系统否则f_mkfs会返回 FR_MKFS_ABORTED。这就是代码里先f_mount(NULL, 0:, 0)再格式化、格式化完再重新挂载的原因。这个坑我最早也踩过老是格式化失败后来检查例程才发现顺序错了。写完文件后建议调用f_sync(fp)或者等f_close后再认为数据落盘。SD NAND 虽然有 FTL 和掉电保护但 FatFs 在写入时存在缓存直接断电不关闭文件缓存里的数据就丢了严重时可能损坏文件系统元数据。4. 踩坑实录从初始化超时到 no target found 的处理4.1 SD 初始化卡死的三个典型原因初始化卡死是 SD NAND 最常见的故障日志里表现为卡在SD_Init()里出不来或者循环等待响应超时。按概率排序三个原因占了九成一是上电时序问题。SD NAND 上电后需要时间完成内部电源稳定和固件加载如果你在系统复位后立刻发 CMD0芯片还没准备好自然没有响应。解决办法是延时 100ms 以上再开始初始化实测最稳。二是初始化时钟太快。SD 规范要求 400kHz 以下但很多人不重视直接在 24MHz 下发 CMD0。此时 SD NAND 内部逻辑跟不上CMD0 根本没有响应。用示波器抓 CLK 引脚就能看到时钟频率超标。三是 DAT3 上拉缺失。前面提过 DAT3/CS 必须上拉如果这个引脚悬空SD NAND 不会进入正常待机状态ACMD41 永远返回 Busy。检查方法很简单上电后量一下 PC11 电压应该在 3.3V 附近如果接近 0V就是上拉没接好。4.2 读写扇区数据错乱的排查能初始化成功但读写的数据偶尔错位、拷贝大文件时校验失败这个问题的排查路径从信号完整性入手。我遇到过一次很典型的情况飞线接到精英板初始化正常写 512 字节也正常但连续写 1MB 数据时读回来有大约 2% 的扇区数据是乱的。先用排除法把 SDIO 时钟从 24MHz 降到 12MHz再跑一次压力测试数据全部正确。这就说明是飞线过长或者信号质量不够导致的。我把杜邦线剪短到 8cm 以内并且保证每根信号线旁边都有一根地线跟随把时钟频率调回 24MHz问题消失。还有一个容易被忽视的坑是供电。SD NAND 写入时瞬时电流会比读取时大如果在面包板上用杜邦线从精英板的外部 3.3V 引脚取电线阻和接触电阻会造成 VCC 跌落。用万用表测写入瞬间的 VCC 电压如果低于 3.0V就要改用粗短线直接焊接到电源引脚或者加一个 10μF 电容在 SD NAND 的 VCC 和 GND 之间。MK 官方手册也建议在电源引脚附近放去耦电容这一步对长期稳定性很有帮助。4.3 文件系统挂载不了、格式化失败怎么办f_mount返回FR_NO_FILESYSTEM是好消息说明底层通信没问题只是 SD NAND 出厂不带文件系统。按 3.3 节的顺序格式化就能解决。麻烦的是f_mkfs返回FR_MKFS_ABORTED。这个错误在 SD NAND 上最常见的原因有两个一个是格式化前没有卸载文件系统导致 FatFs 认为盘被占用。解决方法是先f_mount(NULL, 0:, 0)卸载。另一个是disk_ioctl没有实现GET_SECTOR_COUNT。f_mkfs需要知道磁盘总扇区数才能规划 FAT 表如果disk_ioctl函数里只处理了GET_SECTOR_SIZE和GET_BLOCK_SIZE没有处理GET_SECTOR_COUNTf_mkfs就会直接放弃。检查十分简单在disk_ioctl里补上case GET_SECTOR_COUNT: *(LBA_t *)buff SD_GetSectorCount(); // 返回总扇区数 return RES_OK;还有一个很低级但很容易犯的错格式化参数全传 0 时FatFs 会根据磁盘容量自动选择 FAT 类型。512MB 的 SD NAND 会格式化成 FAT321GB 的也 FAT32这都没问题。但如果ffconf.h里_FS_READONLY被改为 1f_mkfs根本不会被编译进去直接报 undefined symbol。如果是这种情况先把只读宏改回来。4.4 调试器报 no target found 的处理办法调试过程中可能会遇到一个经典报错error: no stm32 target found! if your product embeds debug authentication, please check your debug authentication settings。这个报错的意思是 Keil/调试器在 SWD 接口上找不到 STM32 芯片常见原因有五个第一SWD 接线错误。SWDIO 接 PA13SWCLK 接 PA14GND 必须共地。很多初学者只接两根信号线不接地调试器无法建立参考电平就容易报 no target found。第二目标板没有供电。SWD 接口本身不带供电功能调试器不能给板子供电。精英板要用 USB 线供电确认电源灯亮再点调试。第三调试器固件版本不兼容。如果调试器是 ST-Link固件太旧可能识别不了新版本的 Keil。打开 ST-Link Utility 或者 Keil 的固件升级工具更新一下固件。第四芯片被置入低功耗模式或 SWD 引脚被复用。这是最阴间的情况你之前跑过一个程序把 PA13/PA14 配置成了普通 GPIO并且关闭了 SWD 调试功能。程序烧进去后第二次下载就再也连不上了。解决办法是按住精英板的复位键在 Keil 里点下载等下载进度条出现再松开复位键也就是“connect under reset”方式。如果还不行用串口 ISP 擦除整个 Flash再重新下载。第五BOOT0 影响。精英板 BOOT0 默认拉低从 Flash 启动不会影响 SWD。但如果 BOOT0 被跳线拉到高电平芯片上电进入 ISP 模式此时 SWD 调试同样会找不到目标。检查一下跳线帽位置。这个报错跟 SD NAND 本身没有关系但它在调试 SD NAND 驱动时出现频率极高因为我在调驱动时经常要反复烧录、断电、复位任何一次接线松动或者调试器固件问题都会跳出来。4.5 SD NAND 使用注意事项速查表检查项正确做法踩坑后果DAT3/CS 上拉10kΩ 上拉到 3.3V初始化失败ACMD41 永远 Busy初始化时钟低于 400kHzCMD0 无响应上电延时100ms 以上初始化超时写入后关闭文件f_close / f_sync掉电丢数据文件系统损坏CMD8 校验确认返回 0xAA误判 SDSC容量计算错误电源去耦10μF 电容靠近 VCC写入时掉电死机飞线长度10cm 以内地线跟随数据错乱CRC 失败5. 这次调完我最大的几个体会这次在正点原子精英板上跑通 MK SD NAND整个过程让我对嵌入式存储选型有了更深的体感。最明显的一点是SD NAND 协议兼容性做得确实好正点原子自带的 SD 卡例程几乎不需要改动就能跑起来这说明芯片内部控制器对标准 SD 协议的遵循度很高。但“几乎不用改”不等于“不用看原理”初始化时序、上拉电阻、电源去耦这些底层细节该注意的一个都不能少。还有一个小技巧值得分享如果你手头没有示波器调试 SD NAND 初始化时可以把 SDIO 命令线上串一个 1kΩ 电阻然后接一个 LED 到 GND。发送 CMD0 时命令线会有电平跳变LED 会闪一下。通过 LED 的闪烁情况能判断命令线上到底有没有脉冲信号很多时候能快速区分“主控没发命令”和“SD NAND 没响应”这两种截然不同的故障。这方法虽然土但关键时刻比瞎猜效率高多了。最后说一下后续扩展方向。这套驱动验证通过后我在量产项目里直接在 PCB 上预留了 SD NAND 的焊盘省掉了整个卡座和相关结构件设备体积小了存储可靠性也上去了。如果你的产品有频繁掉电、震动、高低温这些恶劣工况SD NAND 确实是一个值得认真考虑的存储方案。写这篇文章之前我在网上也翻了不少资料发现很多人纠结 MK SD NAND 和 SPI NAND 怎么选。我的观点是如果主控资源紧张、追求低成本SPI NAND 配上成熟的文件系统组件也能用但如果你想要最短的开发周期、最稳的工程实现SD NAND 加 SDIO 这套组合几乎是无脑选。毕竟把时间花在业务逻辑上而不是天天处理坏块和 ECC才是做产品该有的样子。