ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SD NAND嵌入式存储选型与跨平台复用设计实战

SD NAND嵌入式存储选型与跨平台复用设计实战 做过几个数据记录类设备之后我对存存储选型这件事的体会特别深很多产品迭代几天就能搞定主控更换但存储方案往往是最后被拖后腿的部分。最近一年多我手头几个项目都在用米客方德的SD NAND做存储最直接的感受是它不单单是把“TF卡”贴在了PCB上而是从硬件到软件整个存储子系统都能在不同主控、不同容量、不同产品形态之间“平移”。这篇文章我不打算念规格书就讲实际画板子、调驱动、跑量产时总结的干货为什么选SD NAND、Pin-to-Pin兼容设计怎么拆、软硬件适配的要点以及几个值得写进checklist的坑。做嵌入式数据存储的工程师十有八九都纠结过这几个方案TF卡座便宜灵活但接触不良、插拔损耗、振动松脱量产返修率够喝一壶eMMC性能和稳定性都不错可协议复杂对普通MCU不算友好SPI NOR Flash简单到头容量撑到64MB就差不多了日志一多根本不够用。SD NAND作为“贴片式TF卡”把NAND Flash裸片和SD控制器封在一起对外暴露标准SD接口主控侧SDIO甚至SPI都能读写容量又能从128MB覆盖到数GB确实是个非常务实的折中方案。1. 为什么跨平台复用要先解决“存储方案”这个问题1.1 存储往往是产品迭代中最容易被绑死的部分很多做硬件的朋友有一个固定思维主控换了意味着PCB重画、驱动重写、外设重调存储自然也要重新来过。但恰恰存储是最不应该被绑死的模块。问题的根源在于不同主控的SDIO控制器寄存器完全不同文件系统实现千差万别总线宽度、时钟频率策略也各有各的脾气。如果你选的存储方案跟主控深度绑定比如某些私有接口的Flash芯片换主控基本等于存储设计推倒重来。解决跨平台复用问题的第一步是选一个“协议层中立”的存储介质。SD协议本身就是为通用性设计的几乎所有MCU和SoC都原生支持SD/SDIO接口。正因为这个底层协议的中立性SD NAND才有资格当跨平台复用设计的那块“公共地基”。1.2 SD NAND和其他常见方案的边界划分我习惯把候选存储方案放在一张表里对比边界会非常清楚存储方案接口成本容量段控制器/管理可靠性典型短板SPI NOR Flash低常为16~64MB无需自主管理高容量小、写速慢分立NAND自研FTL高大需要自研取决于软件开发量极大eMMC中高大内置高协议复杂、MCU门槛高TF卡座SD卡低大卡内置中接触故障、空间浪费、可插拔风险SD NAND低128MB~数GB内置高不可插拔无法用户自更换从这个对比能看出来SD NAND有点“一专多能”的味道容量比NOR大得多接口却和NOR一样简单可靠性接近eMMC但协议复杂度远低于eMMC。MCU项目拿它当大容量NOR用Linux项目拿它当不可插拔的启动盘两种场景都顺理成章。1.3 米客方德SD NAND在产品层面做了什么我接触到的米客方德SD NAND产品线核心思路是把标准SD协议做成一个成体系的系列。容量覆盖从128MB到数GB支持SD 3.0规范同时兼容SPI模式封装以LGA-8为主不同容量段的Pin-to-Pin一致性做得很好内置坏块管理、ECC纠错和磨损均衡写入和数据完整性由控制器兜底部分型号支持工业级温度范围适合户外设备和工控场景。对工程师而言最省心的地方在于“容量可伸缩、引脚不用动、驱动照搬”。今天打样用512MB量产发现日志增长太快需要2GBBOM里换个料号就行原理图、PCB封装、软件配置全部不用动。当然具体型号的电压、速度等级和封装引脚排列还是以对应数据手册为准我这里讲的是产品线的通用设计思路。2. Pin-to-Pin兼容设计的拆解同一块板子怎么“一版通吃”2.1 Pin-to-Pin兼容到底在兼容哪些要素Pin-to-Pin兼容不是芯片引脚数量一样就完事稍微较真一点至少要满足三个层次封装尺寸一致焊盘、丝印、元件高度一致替换之后不用改PCB封装库引脚功能一致每个引脚对应的信号完全一致CLK、CMD、DAT0-DAT3、VDD、VSS顺序不能乱电气参数兼容供电电压、IO电平、上电时序要求一致替换之后系统行为不变这三个要素缺一个BOM切换时就得动板子或者动软件。米客方德的产品线在这一点上做得比较“强迫症”不同容量、不同型号保持同一封装体系换容量就等于改BOM里一个料号。硬件工程师画一份原理图、一套PCB能吃下整个容量梯度这对产品线规划来说价值极大。2.2 从TF卡座到贴片SD NAND的引脚映射逻辑标准SD/TF卡座的引脚定义是DAT2、DAT3/CS、CMD/DI、VDD、CLK/SCLK、VSS、DAT0/DO、DAT1。贴片式SD NAND基本沿用这套定义。所以哪怕你之前画的是卡座改成SD NAND时原理图符号和网络标签几乎可以照搬只是把连接器换成了芯片封装。这个映射逻辑背后最关键的一点是主控侧看到的永远是同一个SD总线。硬件设计只要保证CLK、CMD、DAT0-DAT3连到正确的引脚软件驱动就完全不需要区分“下面是个卡座还是一颗裸芯片”。这也意味着之前为TF卡座写的驱动代码、文件系统代码在SD NAND上可以直接复用迁移成本低到可以忽略。2.3 兼容设计做得好的板子长什么样我做过最有意思的一块板子是这样的同一份PCB一个BOM焊SD NAND另一个BOM装TF卡座网络名和接口完全共用。客户需要插卡导出数据就装卡座成本敏感或稳定性优先的量产版本就贴SD NAND。硬件改版为零软件用一个编译宏切换初始化时优先检测SD NAND检测不到再等卡座插入。这个模式对产品线特别友好打样调试阶段用卡座换卡方便量产阶段用贴片抗振防尘。前提是原理图设计阶段就要预留两种器件的焊盘位置并且让两种物料的引脚一一对应。这种前期“来回迁就”的布局功夫后期能省出大量的返工时间。跨平台复用还要看到另一层SD NAND不像eMMC那样依赖厂商私有初始化序列也不需要针对某款主控定制IP。同一个“存储子系统”可以横跨MCU平台、RTOS平台、Linux平台。硬件上只剩3.3V电源和一组SD总线软件上遵循SD协议写一遍到哪个平台都能快速迁移这就是跨平台复用的底层逻辑。3. 硬件设计实操要点一张能过审、能量产的原理图与PCB3.1 电源设计别让VDD成为脆弱的一环SD NAND的标称工作电压通常是3.3V。很多人容易忽略读操作电流一般十几到几十mA写和擦除时会有更明显的瞬态电流脉冲。如果供电网络在别的大负载同时工作的时候出现电压跌落轻则命令响应超时重则写入数据损坏。设计时至少要满足这几点VDD就近放0.1uF陶瓷电容再配一个10uF左右的大电容如果板上还有WiFi/BT模块这类耗电大户SD NAND电源尽量从LDO单独拉或者至少单独走线去耦电容尽量靠近芯片引脚过孔直接下到电源平面不要绕路上电顺序也要注意。如果主控先启动SD NAND后上电主控发命令时芯片还没就绪首次初始化就会失败。最好让VDD随系统一起起来稳定至少1ms再跑初始化。实在做不到软件上做失败重试也能兜底但不如硬件设计一步到位干净。3.2 SDIO总线布线与上拉配置SD总线信号不复杂就CLK、CMD、DAT0-DAT3这几根布线规则其实不难但经常有人踩坑CMD和DAT0-DAT3各加一只10kΩ上拉到3.3VCLK不用上拉走线长度尽量短CLK优先级最高其次CMD最后数据线四根数据线组内等长控制在几十mil差距以内CLK与数据组整体不要差太多走线要避免跨越分割的地平面如果非跨不可也要保证有完整参考平面如果是主控侧驱动长走线或多负载加一个22Ω串阻可以减少反射单板短走线不加也没问题。布局上把SD NAND尽量靠近主控永远比依赖等长和端接靠谱得多。我见过一块板子主控和SD NAND分居两侧六根信号线穿越整个板子并且跨了一片电源区测试时速度一上50MHz就随机出错。后来把芯片挪到主控旁边问题当场消失。信号完整性这个事物理距离是最粗暴有效的解决方法。3.3 兼容卡座的布局细节如果要做SD NAND与TF卡座二选一的兼容布局Layout阶段要提前想清楚卡座有金属外壳和机械定位贴片芯片没有两者外形差异显著。建议把卡座机械位置和芯片焊盘画在同一个区域丝印上明确标出两种物料的贴装方向和占位。生产时钢网和贴片机按BOM切换选SD NAND时卡座焊盘空着不影响电气。高度问题也别忽略卡座占了结构高度SD NAND贴片不到1mm。如果产品壳体内部空间紧张两种物料的兼容位置最好放在PCB边缘方便将来用户插拔卡如果确定只用SD NAND放在哪里都无所谓。这个细节要和结构工程师提前打好招呼否则后期又得改。3.4 电平适配与1.8V陷阱现在不少主控支持SD 3.0的电压切换流程从3.3V降到1.8V目的是省电。但如果选的SD NAND只支持3.3V主控一旦执行切换总线电平降到1.8V之后通信就废了。常见表现是配置完高速模式然后读写直接卡死。解决方案要根据平台来Linux平台设备树里加no-1-8-v属性禁用1.8V切换MCU裸机不要调用任何SD卡电压切换相关步骤如果确实需要低功耗模式选明确支持1.8V/3.3V双电压的SD NAND型号并且以数据手册的电气参数为准实测过一块Linux板子开机偶尔检测不到SD NAND查dmesg发现是内核尝试切1.8V失败后重新枚举出现概率性失败。在DTS里加上no-1-8-v之后连续重启三百多次没有失败记录。这种坑真就是排查很久、改起来一行。4. 软件适配层把存储驱动写成“可搬运”的能力4.1 SD初始化流程的通用骨架不管在哪个平台SD NAND的初始化都遵循标准SD协议流程是固定的上电等电源稳定CLK先跑低速400kHz左右发送至少74个时钟周期CMD0让卡进入IDLE模式CMD8配0x1AA区分SD 2.0及以上规格CMD55ACMD41带HCS位协商工作电压并等待卡上电完成CMD2读CIDCMD3读RCACMD7选中卡需要4位总线时发CMD55ACMD6可选CMD6切换高速模式CLK提升到25MHz或50MHz开始正常读写因为这个流程是所有平台通用的我建议在底层驱动里把SD协议实现成独立的一层对外只暴露sd_init()、sd_read_sector()、sd_write_sector()这类接口。以后哪个平台需要接SD NAND直接把这一层代码搬过去只改最底层的寄存器配置就行。伪代码大概是这样的int sd_init(void) { sd_set_clock(400 * 1000); // 低速时钟 sd_send_empty_clocks(80); // 上电确认时钟 if (sd_cmd0() ! SD_IDLE) return -1; // 进入IDLE if (sd_cmd8(0x1AA) SD_OK) { // SD 2.0 分支 if (sd_acmd41(1 30) ! SD_OK) return -2; // HCS置位 } else { return -3; // 只支持SD 1.x或异常 } sd_get_cid(); sd_get_rca(); sd_set_bus_width(SD_BUS_WIDTH_4); // ACMD6 sd_set_high_speed(); // 可选CMD6 sd_set_clock(50 * 1000 * 1000); // 切高速 return 0; }注意ACMD41通常要轮询多次直到OCR里的busy位清零因为卡上电完成需要时间。只发一次就判定失败在慢启动的电源环境下容易误报。4.2 MCU裸机平台FatFS加SDIO/SPI的移植实践以STM32为例用SDMMC外设时CubeMX生成的驱动已经包含了基本的SD初始化流程你要做的主要是确认总线宽度和时钟分频然后把底层函数接到FatFS上。FatFS需要你实现这几个函数DSTATUS disk_status(BYTE pdrv); DSTATUS disk_initialize(BYTE pdrv); DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count); DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count); DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff);其中disk_ioctl里的GET_SECTOR_COUNT尤其重要要把从CSD里解析出来的总扇区数返回给文件系统否则挂载时容量识别错误。GET_SECTOR_SIZE返回512GET_BLOCK_SIZE拿不准就返回1。这里有一个很实际的坑SD NAND出厂状态不一定带文件系统。有的批次是白片直接f_mount会返回FR_NO_FILESYSTEM。所以量产时建议写一个产线测试程序上电后先识别卡用f_mkfs格式化一遍再做一轮读写校验保证用户手里的板子存储是ready状态。国产MCU平台比如GD32、AT32、瑞萨RA这些基本都有SDIO外设和兼容SDK。移植思路完全一样把供应商寄存器接口层换掉协议层和文件系统层原样不动。这就是“跨平台复用”在代码层面的直接价值。4.3 Linux平台设备树、块设备与文件系统Linux下SD/MMC子系统已经非常成熟SD NAND插上、识别、创建mmcblk设备都是内核自动完成的你要管的主要是设备树配置和文件系统策略。一个典型节点配置mmc1 { pinctrl-names default; pinctrl-0 mmc1_clk_pins mmc1_cmd_pins mmc1_dat_pins; bus-width 4; max-frequency 50000000; cap-sd-highspeed; no-1-8-v; non-removable; status okay; };non-removable这个属性非常关键。SD NAND是焊在板上的内核如果不加这个属性会把它当可移动设备处理持续监听移除事件。一旦总线上有个毛刺触发了remove存储直接“掉线”。加上之后内核完全不走热插拔逻辑系统稳定很多。文件系统选择上只存数据的话ext4或FAT都行。但如果是拿SD NAND跑系统根文件系统就要注意写入寿命。我的建议是日志目录用tmpfs或者overlayfs周期性写入的缓存攒够一定量再落盘。SD NAND内部有磨损均衡但日志型写模式依然会让某些物理块先老化别把耐磨性想得太理想。4.4 RTOS平台RT-Thread与ESP-IDF的适配RT-Thread的SDIO驱动框架可以自动识别SD NAND挂载后走DFS文件系统。做法是使能SDIO设备驱动配置总线宽度为4位再把设备挂成elm文件系统。需要注意RT-Thread默认的dfs_elm依赖512字节扇区SD NAND通常满足不用额外处理。ESP-IDF环境用esp_vfs_fat_sdmmc接口很直接。配置sdmmc_host_t时把slot设对card_detect和card_int直接设为NULL因为SD NAND没有这两个信号线。有个小经验max_freq先用25MHz验证读写稳定再提到50MHz确认布线质量支持再上。RTOS平台移植速度快但小坑不少。比如FreeRTOS下如果主控的SDIO中断优先级设得比系统心跳还低长时间高负载读写会出现偶发超时。把中断优先级提到合适的范围是一个很实用的经验很多偶发问题都是这样治好的。5. 从“点亮”到“量产”我遇到的坑和排查实录5.1 初始化失败排查清单初始化失败常见现象是CMD0无响应、ACMD41超时、读CID失败。按优先级去查供电示波器看VDD确认稳定在3.3V且纹波不大IO电平也得匹配时钟初始化阶段必须是400kHz左右不能上来就50MHz上拉CMD和DAT0-DAT3有没有接10k上拉CLK悬空可以CMD/DAT悬空不行虚焊LGA封装焊盘小回流焊之后仔细检查焊点连锡和少锡都常见时序确认发CMD0前已经给了足够的时钟周期我亲手查过一起“十块板子三块识别不了”的问题最后发现是钢网开孔偏小导致SD NAND底部焊盘锡量不足。补焊之后全部恢复。这种属于工艺问题软件再怎么调优也救不回来。5.2 速度上不去的三个常见原因读写速度不如预期通常逃不出这三种情况第一种总线只有1位。有些主控默认1位模式没执行ACMD6切换到4位速度只有4位模式的四分之一。检查SDIO控制器的总线宽度寄存器别想当然以为默认就是4位。第二种没切高速模式。一直跑在Default Speed的12.5MHz没发CMD6切到高速就急着拉高CLK。初始化成功后先切高速再提高时钟频率。第三种小文件写入导致的文件系统开销。每次写4KBFAT表频繁更新吞吐自然低。日志场景要调整写策略用大块缓冲、定时落盘或者做成环形文件。排查时建议先做裸设备读写测试绕开文件系统。直接在SD块设备上连续读写记录吞吐再对比带文件系统的吞吐差值就是文件系统的开销。很多人一上来就怪芯片其实是没区分这两层。5.3 数据完整性与异常掉电嵌入式产品经常直接断电。SD方案掉电时如果正好在写FAT表或者擦除块轻则丢最新数据重则文件系统损坏。SD NAND内置控制器有掉电保护逻辑但不同批次表现不同不要把安全性全押在芯片上。我自己的习惯重要数据双备份主记录区加副记录区启动时对比校验每次写文件后调f_sync落盘日志类数据攒一批写一次Linux挂载加sync选项MCU下用FatFS时按场景调整FF_FS_TINY可靠性要求极高的产品优先选明确带紧急掉电保护Power Loss Protection的型号或者加一个大电容撑几百毫秒让控制器完成内部元数据更新这算不上奢侈数据丢一次客户对整机的信任就少一分。5.4 量产品质与批次一致性SD NAND内部的Flash颗粒可能随批次变化虽然对外行为一致但固件版本、Flash ID会有差异。这个不是缺陷但直接关系到采购和生产采购时要求明确批次兼容性说明产线上电后读取并记录CID、CSD、固件版本留档至少做一遍全容量写读校验成本不高挡住早期不良如果有OTA升级需求注意写入放大频繁整片擦写会加速磨损差分包或增量写入更友好我量产时写了个简单测试程序上电初始化读取最后一个扇区签名比对再顺序写满一半容量读回。花费不大但在出厂前能挡掉一大半存储相关的不良剩下的配合老化抽检再筛。5.5 不要热插拔最后必须多提一句SD NAND是贴片器件不是可插拔卡。软件上最好把所有卡检测、热插拔回调全局关掉。Linux加non-removableMCU不初始化卡检测引脚否则总线上偶尔一个毛刺触发一次移除事件系统就莫名其妙“丢盘”了。这个坑只在现场偶发但难查得很所以设计阶段就要避免。实际上写这篇东西最想传达的不是某颗芯片有多强而是一种设计习惯选型时先把“复用”想清楚。存储是最容易被复用的模块又是最容易因为选型不当被绑死在一个平台的模块。SD NAND这种协议中立、封装统一、软硬件适配成本低的方案天然适合当跨平台复用的基石。我个人的习惯是新项目画原理图之前先问自己三个问题这个板子以后会不会换主控存储容量会不会升级用户需不需要插卡把这三个问题落到硬件设计上就是SD NAND与卡座兼容布局、同封装容量梯度、软件层接口抽象。回答完这三个问题存储部分基本就踏实了。如果以后有机会我想再整理一篇SD NAND在OTA升级和断电续传场景下的分区设计与实测数据那个话题里的坑更多。这篇先分享到这里希望对正在选型和做方案的同行有些实际帮助。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进