简介:一套针对STM32F446微控制器的QSPI接口与SPI NAND闪存通信工程,面向嵌入式开发者及物联网设备存储场景,帮助解决QSPI外设配置和NAND驱动开发中的常见问题。工程通过四线SPI(QSPI)实现NAND设备的初始化、擦除、写入、读取与编程,并覆盖时钟配置、引脚复用、命令序列构建、坏块检测与ECC校验等关键环节,实测可适配多款SPI NAND芯片。RAR压缩包共274个文件,以C源文件(103个)、H头文件(127个)和汇编启动文件(24个)为主,另有STM32CubeMX工程、链接脚本及调试配置文件,整体仅1.89MB,结构清晰便于查看。已有1137人学习下载。借助该工程,开发者能快速掌握STM32F4 QSPI外设配置流程和SPI NAND驱动框架,了解从底层寄存器操作到应用层调用的完整思路;工程可直接导入调试,便于二次开发和移植,对需要快速搭建QSPI NAND存储原型的工程师尤其实用,是嵌入式存储开发的有用参考。
1. 项目整体思路:为什么选QSPI接口接NAND
1.1 需求解析与应用场景
最近接手一个数据记录类项目,主控用的STM32F7系列,需要外挂一块大容量非易失存储,用来存ADC采样值、运行日志和少量配置文件。最初我第一反应是上SD卡,但仔细盘了一下需求:设备要长期连续运行,SD卡的插拔和文件系统损坏风险让人不踏实,而且体积、功耗、成本都有硬约束。于是我把目光转向了QSPI接口的NAND Flash——这就是“STM32F_QSPI_NAND”这个项目名字的由来。
先说清楚这套方案能解决什么问题:STM32F系列内部Flash一般就1MB到2MB,存代码可以,存数据完全不够;NOR Flash容量大一点的Winbond W25Q256也就32MB,再往上型号少、价格贵、性价比低。QSPI接口的NAND芯片(比如W25N01G、GD5F1GQ系列)单颗能做到128MB甚至1Gbit以上,价格比同容量NOR便宜不少,而且STM32F7/F4/H7系列原生带QSPI外设,硬件连线只需要6根线(CLK、CS、IO0~IO3),不占并口也不占SDIO,对PCB布局特别友好。
适合谁来参考?如果你正在做数据采集记录仪、GUI图形界面字库/图片存储、OTA升级备份区、或者任何需要“大容量+低成本+引脚少”存储方案的嵌入式项目,这篇内容可以直接抄作业。我自己从调研到跑通大概花了两天,踩了不少坑,包括QE位没置导致读回全0xFF、页写超时、坏块没扫导致文件越写越乱等等,下面全部摊开讲。
1.2 NAND与NOR、DRAM、HBM的本质区别
很多新手会混淆NAND和NOR,甚至把DRAM、HBM也扯进来。这里用最直白的话给它们排排坐:
- DRAM是易失性内存,断电数据就没了,主要拿来做运行时的栈和堆,速度极快,但容量再大也跟你存文件的诉求无关。
- HBM是DRAM的一种高带宽堆叠封装方案,用在AI加速卡和高端GPU上,跟嵌入式小系统八竿子打不着,唯一关系就是它也带电存储不了数据。
- NOR Flash和NAND Flash都是非易失的,断电数据保留。NOR的特点是支持字节寻址,可以像内存一样XIP直接执行代码,适合存Bootloader和小配置,但容量做大成本高、擦写慢。
- NAND的特点是按页读写、按块擦除,单bit成本低、容量可以做得很大,但坏块概率高、读写需要专门的协议配合。
QSPI接口接的NAND,本质上是把NAND的并行接口封装成了SPI/QSPI串行协议,管脚少了大半,但外部MCU还是得按NAND的逻辑去操作:读一页数据、写一页数据、擦一块数据、检查状态寄存器。我用W25N01G(1Gbit,即128MB)做主芯片,页大小2048字节加64字节备用区,块大小64页,也就是128KB每块,这些参数后面编程全用得上。
另外提一嘴SD NAND,这是最近比较火的方案,本质是把NAND Flash和一个简单的控制器封装在一起,外壳像一颗芯片,接口走SDIO,好处是MCU端不需要自己写坏块管理和擦写均衡,但缺点也很明显:比裸NAND贵、SDIO时序在低速MCU上反而费劲、而且掉电异常时文件系统锁死的case还是得自己处理。我的观点是:如果你不想碰FTL(Flash Translation Layer)这些脏活累活,又有一定成本预算,SD NAND可以考虑;如果追求极致成本和可控性,觉得“自己搞坏块管理也没什么大不了”,那裸NAND+QSPI才是你的路。
1.3 QSPI协议怎么选:单线、双线还是四线
QSPI外设支持几种命令模式,最核心的区分是地址线/数据线的宽度。常规SPI是单线双向,CLK一个时钟传1bit;QSPI可以在命令阶段选1-1-1、1-1-4、1-4-4以及4-4-4等模式,数字含义依次是“命令阶段线数-地址阶段线数-数据阶段线数”。
实际用的时候,读操作我会优先用1-1-4甚至1-4-4,因为NAND读数据是大头,比如读一页2KB,用1-1-1要传2万个时钟,用1-4-4只需要5000个时钟,速度快4倍。写操作同样用1-1-4来提升Payload传输效率,但命令和地址阶段保持单线,因为NAND命令码大多是固定的8位,做成4线反而增加解析复杂度,收益很低。
初始化阶段最关键的寄存器叫做QE(Quad Enable),不置这个位,四线模式根本发不出去,芯片直接忽略你IO2/IO3上的并行数据。不同厂牌的置位方式不一样,Winbond的W25N01G是通过Status Register 2的bit1来开,GD5F1GQ也是类似的寄存区,但也有厂家喜欢通过Read Feature/Write Feature命令来配置。所以拿到一颗新芯片,我建议第一步先读JEDEC ID,然后翻手册找到QE对应的寄存器位,专门写一个readModifyWrite流程去置位,不要照抄别人的初始化函数。
2. 硬件连接与芯片选型:手把手从原理图说起
2.1 引脚定义与最小接线
STM32F7系列QSPI外设映射到IO的引脚大致是:
- CLK:PA3或PB2
- CS:PB6或PG6(不同封装有差异)
- IO0:PB1或PD11
- IO1:PB0或PD12
- IO2:PA7或PE7
- IO3:PA6或PE8
具体以你自己的型号数据手册为准,但原则是:QSPI引脚不能用默认的复用功能,必须在CubeMX里手动配置成Alternate Function,并且留意IO输出速度,建议设为Very High,因为四线模式时钟跑到50MHz以上时,沿口太钝会直接导致采样bit错误。我习惯在CLK上串一个22Ω的电阻,减少振铃,CS和IO线就不用串了,省几个物料成本。
还有一个很多人忽略的点:NAND芯片的WP(Write Protect)引脚和HOLD引脚。QSPI封装里通常把WP映射到IO2、HOLD映射到IO3,如果你用四线模式,这俩引脚不能再承担保护功能,必须通过寄存器配置成数据线。如果是两个板子分离设计,建议硬件上给WP加一个上拉到VCC,防止飞线时悬空误触发写保护;HOLD同理,但四线模式下它已经是数据线了,不能拉死,否则数据传不出去。
2.2 国产芯片平替怎么选
热搜词里有人问“国产便宜的NAND Flash芯片有推荐的吗”,这个问题我太有发言权了。目前市面上能找到且资料齐全的国产QSPI NAND大概有这几类:
| 厂商 | 系列 | 容量 | 特色 |
|---|---|---|---|
| 兆易创新 | GD5F1GQ4 | 1Gbit | JEDEC标准命令,和W25N01G命令兼容度较高 |
| 东芯半导体 | DS35Q1GA | 1Gbit | 有专门的工业级版本,-40到85度 |
| 华邦 | W25N01G | 1Gbit | 原厂资料最全,很多开源的起始模板 |
| 台湾力积电转售 | 各种1Gbit裸片 | 1Gbit | 需走贸易商,不推荐小批量玩 |
选型时第一要求不是容量,而是命令集兼容性。你最好选和W25N01G命令完全兼容的芯片,这样网上老外写的驱动直接能用,省得自己从零翻寄存器手册。我实测过GD5F1GQ4,主体命令和W25N01G一致,但Feature寄存器的具体地址有细微差别,得看它的Unique ID和状态寄存器的偏移量。另一个坑是坏块标记位置,Winbond默认标记在Block的第1页Spare区第1个字节,如果国产芯片标记位置不一样,要记得改扫描逻辑。
如果预算不够但又想开发省事,退而求其次有两个选择:一是继续用NOR Flash但砍容量(比如W25Q64,8MB),对很多日志存储场景其实够用;二是用SD NAND,但成本从裸片翻一倍以上。我自己对数据类存储比较敏感,坚决不用二手拆机片,这种片子坏块可能已经堆到出厂标记区域外,手写坏块表根本兜不住。
2.3 上电时序与去耦电容的讲究
QSPI NAND芯片上电后需要等待tVSL(一般1ms左右)才能接受第一个命令,太快发指令芯片会不回ACK,表现出来就是读ID超时。STM32上电后外部晶振起振也要时间,所以驱动里我习惯在QSPI初始化后不急着发命令,先hazDelay(2ms)再Reset一下芯片。不要把这2ms当浪费,我遇到过有块板子元件参数离散比较大,1ms不够导致偶发ID读取失败,加了2ms后就稳了。
电源去耦方面,NAND在擦除和编程时电流尖峰比读取大得多,我会在芯片VCC引脚旁边放一个4.7uF陶瓷电容加一个0.1uF高频电容并联,并靠近引脚放置。很多“偶发写失败”的case都是电源纹波在擦除瞬间崩了,不是Flash本身的锅。
3. 软件实现:QSPI命令序列与NAND状态机
3.1 初始化流程与关键寄存器配置
STM32CubeMX里打开QSPI外设,参数大致这样配:
- ClockPrescaler,根据系统时钟算出实际QSPI时钟,我一般控制在50MHz左右
- FifoThreshold设成4字节以上,方便中断/轮询读取
- MemoryMode选IndirectRead/Write,不启用Memory Mapped(因为NAND不支持XIP)
- ChipSelectHighTime设为1个时钟周期以上,防止连续命令间隔太短
初始化代码框架如下:
QSPI_HandleTypeDef hqspi; hqspi.Instance = QUADSPI; hqspi.Init.ClockPrescaler = 2; // 假设系统时钟216MHz, 得108MHz,再配合Dummy Cycle hqspi.Init.FifoThreshold = 4; hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCLK; hqspi.Init.FlashSize = POSITION_VAL(0x8000000) - 1; // 128MB hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_1_CYCLE; hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0; hqspi.Init.FlashID = QSPI_FLASH_ID_1; hqspi.Init.MemoryMapped = QSPI_MEMORY_MAPPED_DISABLE; HAL_QSPI_Init(&hqspi);我踩过的一个典型坑是SampleShifting参数。默认HalfClock在某些板子上没问题,但在长走线或者CLK质量差时,会出现高位数据采样不稳定,表现为读回来的数据局部错位。这时候把SampleShifting改成QuarterClock或者调整ClockPrescaler降低频率,往往就好了。这个参数看起来不起眼,但它直接影响高速模式下的稳定性,不要偷懒不调。
3.2 NAND核心命令集梳理
QSPI NAND的命令集和并行NAND神似,只是封装成SPI格式。用W25N01G举例,最核心的几条命令:
| 命令 | 编码 | 说明 |
|---|---|---|
| Reset | 0xFF | 软复位,重置内部状态机 |
| Read JEDEC ID | 0x9F | 返回厂家ID和设备ID,调试第一利器 |
| Read Feature | 0x0F | 读指定Feature寄存器,比如状态寄存器 |
| Write Feature | 0x1F | 写指定Feature寄存器,比如配置QE、四线模式 |
| Read Status | 0x05 | 读状态寄存器1,bit0是Busy,bit6是WEL |
| Page Data Read | 0x13 | 把指定页数据加载到芯片内部缓存 |
| Read from Cache | 0x03/0x6B/0xEB | 从缓存读出数据,可配合四线模式 |
| Program Load | 0x02/0x32/0x12 | 把数据写入芯片内部缓存 |
| Program Execute | 0x10 | 把缓存数据真正写入指定页 |
| Block Erase | 0xD8 | 擦除指定块 |
| Get Feature | 0x06 | 读取特定状态(部分芯片用) |
理解这套命令的核心在于:NAND写一页数据不是一个命令能搞定的,它分成了“先Load到缓存”和“再Execute到阵列”两步。这有点像一个流水线:你先把货物放到传送带(Program Load),等传送带开始运转后,整个页面数据一次性压入存储阵列(Program Execute)。好处是MCU可以把2KB数据通过QSPI快速扔到缓存,然后不等芯片忙完就去干别的,通过轮询Status寄存器判断是否完成。
3.3 地址计算与4字节地址模式
NAND按块、页两级寻址。W25N01G一页2048字节+64备用字节,一块64页,总共1024块。所以地址需要16位页地址和2位块地址高位来拼,总共24位块地址+16位页地址+16位列地址。QSPI命令里要把这些地址拆成3字节发出去。
比如要读第100块的第30页,偏移量0:
- 块号100,块内页号30,线性页号 = 100 * 64 + 30 = 6430
- 页地址 = 6430(需要16位)
- 列地址 = 0(2字节)
- 命令序列:0x13 + 列地址高8位 + 列地址低8位 + 页地址高8位 + 页地址低8位
这里有坑:不同厂家对列地址是否需要发送的处理不一样,W25N01G在0x13命令后要发2字节列地址,哪怕你只想读页头也要发。发错字节数的话,芯片内部地址指针会错乱,回来的是Full或者错位数据。我在调试期间有整整半天被这个问题折磨,最后是拿逻辑分析仪抓了时序,对比官方驱动才发现的。
容量大于128MB的芯片(比如256MB、512MB)会引入4字节地址模式,也就是地址从3字节变4字节。这时候需要对所有访问命令统一切换地址宽度,不能这边读ID用4字节、那边读页数据用3字节,状态机会直接乱掉。我建议驱动层抽象一个nand_set_address_width()函数,初始化时根据芯片容量自动设置,所有命令统一走这个封装。
3.4 页读、页写、块擦除的时序与代码示范
页读的核心时序是:先发0x13加载页到缓存,轮询Status等缓存就绪,再发0x03/0x6B把缓存数据读出来。代码大致长这样:
int qspi_nand_page_read(uint32_t block, uint32_t page, uint8_t *buf, uint16_t offset, uint16_t len) { uint8_t cmd[5]; uint32_t linear_page = block * 64 + page; cmd[0] = 0x13; cmd[1] = (offset >> 8) & 0xFF; cmd[2] = offset & 0xFF; cmd[3] = (linear_page >> 8) & 0xFF; cmd[4] = linear_page & 0xFF; qspi_send_cmd(cmd, 5); if (qspi_nand_wait_busy(1000) != 0) return -1; return qspi_fast_read(0x03, linear_page, buf, offset, len); }页写要区分“普通写”和“带OTP/内部数据更新”的写,驱动里至少要有两个函数:
int qspi_nand_page_program(uint32_t block, uint32_t page, uint8_t *buf, uint16_t offset, uint16_t len) { uint8_t cmd[5]; uint32_t linear_page = block * 64 + page; qspi_send_cmd_0x02_load(buf, offset, len); cmd[0] = 0x10; cmd[1] = (offset >> 8) & 0xFF; cmd[2] = offset & 0xFF; cmd[3] = (linear_page >> 8) & 0xFF; cmd[4] = linear_page & 0xFF; qspi_send_cmd(cmd, 5); return qspi_nand_wait_busy(1000); }块擦除也一样,只是命令从0x10变成了0xD8,并且地址只有块地址,没有列地址和页地址。擦除前一定要确保目标块没有有效数据,或者通过坏块表确认这是可擦除的块,否则数据就真真切切没了。
3.5 状态寄存器轮询的正确姿势
NAND执行Program和Erase时,内部状态寄存器bit0是Busy标志,为1代表忙,为0代表空闲。轮询函数我习惯写成这样:
int qspi_nand_wait_busy(uint32_t timeout_ms) { uint8_t status = 0; do { qspi_send_cmd_1byte(0x05); qspi_read_data_1byte(&status); if ((status & 0x01) == 0) return 0; delay_ms(1); } while (--timeout_ms); return -1; }这里有个细节:读Status寄存器时用0x05单线命令还是0x0F Feature命令,取决于你的芯片。W25N01G推荐直接读Status Register,有些芯片只支持Read Feature Register(地址设为0xC0)来取状态。不要想当然,先看手册里的状态寄存器映射表再写轮询函数。
另一个坑是:擦除大块时,内部操作时间可能达到5ms到20ms,我的轮询超时设成1000ms,是因为有些国产芯片在极端温度下擦除速度会放缓。你要是设成100ms,冬天户外设备分分钟报写失败。
4. 痛点处理:坏块管理、ECC与掉电保护
4.1 NAND为什么天天要提坏块
NAND Flash和NOR Flash最大的差异就在于坏块。NOR出厂几乎没有坏块,NAND则是“出厂就可能带坏块”,而且随着P/E次数增加还会持续产生新坏块。这意味着你直接把它当成一个大EEPROM去用,第二周可能就会出现“这页写成功了但读是乱的”这种诡异现象。
我最早犯的错误就是把NAND当作普通Flash连续写,结果日志数据越积越多后,某天突然发现历史记录里出现大段乱码。查了半天最后定位到坏块。从那以后我再也不敢跳过坏块扫描这一步了。工业级NAND一般保证最少1K次P/E,消费级稍差,但坏块管理做得好不好决定的是系统的长期可靠性,不是单颗芯片的体质。
4.2 坏块扫描与动态重映射
NAND出厂坏块会在芯片某个特定区域打上标记,一般是块第1页的Spare区前几个字节非0xFF。所以上电初始化时要做一次全盘扫描,把每一个块的第一个有效页读出来检查标记。扫描方式是:逐块读该块第0页的Spare区(64字节中的前8字节),如果出现非0xFF,就把这个块加入坏块表,同时跳过一个“保留块”作为备用。
我自己维护了一张内存中的坏块表,大小1024bit,也就是128字节,足够覆盖1Gbit芯片的所有块。每次写操作之前先查表,如果目标块坏了,就映射到保留块。实现上是分两层:底层NAND驱动只负责“按块读写”,上层做坏块替换时,需要知道“这个块对应哪个物理块”。这个逻辑很像文件系统的FTL层,但简化版够用就行。
注意:别把坏块表和坏块标记本身放在同芯片上,否则芯片彻底挂了就麻了。我的做法是把坏块表冗余存两份,一份在Flash最前面的专用块,一份在最后面的专用块,启动时比较两份表,不一致就用0xFF那块的漂移算法修复,或者直接重新扫描。
4.3 ECC选硬件还是软件
NAND的数据翻转问题比坏块还要隐蔽。即使块没坏,电荷泄漏也可能导致个别bit从1变0或者从0变1。这就是ECC(纠错码)存在的意义。STM32F7的QSPI外设不带硬件ECC,所以你有两个选择:一是用支持内嵌ECC的NAND芯片,比如W25N01G内部有1-bit ECC引擎,读数据时它会自动纠正,并通过状态寄存器置位告诉你“这页有错但已经被纠正了”;二是软件层面使用汉明码或Reed-Solomon。
我自己的实践是:如果存储的是日志、录音、图片这类“坏几个bit无伤大雅”的数据,1-bit硬件ECC就够了,只依赖芯片内部的引擎,程序里不额外做校验。如果是关键配置参数、固件升级包,就必须用软件层双备份+CRC校验,甚至做两份冗余镜像,主镜像校验失败自动切换备份。这是成本最低也最可靠的做法。
4.4 意外掉电怎么兜底
NAND最冤枉的死法就是编程过程中掉电:缓存里的数据可能写了一部分,阵列还没写完,下次上电读这页就变成半旧半新。为了兜底,我在设计里加了几个基本功:
- 写数据前先写入“魔法数+长度+CRC”的头,写完再更新头里的状态位为“完成”。
- 程序启动时扫描日志区,发现头状态不是“完成”的那条记录,直接丢弃尾部,从上一个完整记录开始写。
- 关键参数采用A/B双区交替写,每次写完切换激活序号,上电时比较两个区的激活序号,取大者。
这套思路不复杂,但能避免“掉电一次丢半年日志”的惨剧。顺便说一句,有些SD NAND方案会内置电源管理,掉电异常时自动处理,如果你不想自己写这些逻辑,那SD NAND确实能帮你省掉很多麻烦,只要接受成本翻倍。
5. 实际调试中的常见问题与排查实录
5.1 读ID失败、全0xFF、全0x00的排查清单
实际调板子时,遇到最多的问题就是读ID不对。这里给一份排查优先级清单:
| 现象 | 可能性 | 检查手段 |
|---|---|---|
| 读ID超时 | 时钟没起来、CS极性反、芯片供电异常 | 示波器抓CLK和CS,确认上电时序 |
| 读回全0xFF | QE位没置、芯片处于写保护状态、四线模式没生效 | 用1-1-1模式读ID,再读Feature Register确认寄存器值 |
| 读回全0x00 | 数据线虚焊、IO配置错误(PA6/PA7被其他外设占用) | 换GPIO,断掉其他外设时钟,量引脚电平 |
| 读ID数值对不上 | 芯片命令集不同、Dummy Cycle配置错误 | 查手册确认JEDEC ID顺序是2字节还是4字节 |
我亲眼见过有同事把W25N01G当成W25Q256驱动,JEDEC ID返回0xEFAA,但当时他已经固化了“0xEF4018”的期望值,于是死活不认芯片,后来改掉ID检查逻辑马上就好了。调试QSPI外设,一定要先“裸读ID”,不要一上来就跑文件系统,否则排查范围太大。
5.2 页写超时与内部状态未就绪
页写超时也是高频故障,但八成不是芯片坏了,而是命令序列问题。比如你发送Program Load的时候列地址长度发错,芯片内部状态机会一直停留在等待状态,后面轮询Busy位永远为1。解决办法是每次遇到超时,先发一个Reset命令(0xFF)把芯片拉回默认状态,再重新走流程。这个做法不是正规的恢复流程,但在调试阶段特别好用,能快速定位是“芯片忙”还是“状态机卡死”。
另一个注意点是:NAND写操作前需要确保WEL(Write Enable Latch)状态位的值正确。如果上次操作后芯片没有正确退出写保护状态,Program Execute命令会被直接拒掉。我在每次门函数前都会顺手读一次Status Register,确认WEL为1,再继续往下走。
5.3 兼容性差异:GD32H7、国产NAND与魔百盒的启示
有热搜词提到GD32H7的QSPI,我也顺带测过GD32H7系列(H7主打高性能,类似STM32H7)。GD32H7的QSPI寄存器布局跟ST不完全一致,但CubeMX的初始化流程是通的。最大的坑是GD32H7的QSPI时钟源配置在APB2还是AHB上,搞错了时钟频率,读ID永远是乱码。通用建议:拿到新平台第一步先翻RCC时钟树图,把QSPI的输入时钟确认清楚,再调分频系数。
还有热搜词里的“魔百盒CM201-2、M8268、NAND、Hi3798MV300固件”,这虽然是电视盒子方向,但原理上有共通之处:那些盒子的固件存储在NAND里,靠BootROM加载引导程序,再按分区表引导内核和文件系统。本质上和我们在STM32上规划Bootloader区、数据区、日志区是一致的,只是它们有专门的分区表和镜像工具。如果你做STM32 NAND存储,也可以借鉴这种“分区+引导镜像”的思路,把Bootloader存在NOR里,把应用和数据放在NAND里,上电先跑NOR里的Bootloader,再初始化QSPI NAND加载主程序。
5.4 从“能用”到“可靠”:一些小经验补充
最后整理几条我从这个项目里得出的经验,未必在课本上能看到:
- QSPI NAND的驱动代码一定要抽象成分层结构:底层管命令时序,中层管坏块和地址映射,上层才管读写数据和日志。别图省事把所有逻辑写在一个文件里,后面改一个参数能让你崩溃。
- 调试阶段在初始化函数里保留充足的打印,尤其是打印JEDEC ID、状态寄存器、Feature寄存器、坏块表统计。我至今保留着这套log,上线运行后偶尔导出log分析健康状况。
- 好记性不如烂笔头,QSPI NAND的数据手册建议打印出来,把命令表、地址结构、时序参数用荧光笔标出来,比看PDF效率高十倍。
这套方案我已经跑了小半年,产品也小批量出货了,目前没有收到一起存储相关的故障反馈。当初选QSPI NAND是我拍板的,从设计到现在,我依然觉得这是STM32F系列做大容量存储最均衡的方案——成本可控、接线简单、性能够用,只要把坏块管理、ECC、掉电保护这几板斧做好,它比SD卡更让人放心。如果你也在权衡存储方案,希望这篇能帮你少踩几个坑。
本文还有配套的精品资源,点击获取