只读分享解决方案文档原理方案设计📅 2026-10-14🔖 Rev 1.1⏳ 还有3日到期
🔧解决方案&应用市场分析 -> 原理方案设计 -> 串行总线系列产品方案 -> SPI_NAND_Flash_软件设计规范

SPI_NAND_Flash_软件设计规范

同系列 · 原理方案设计
扫码打开本页
微信扫码

SPI NAND Flash 软件设计规范

编制日期 2026-10-14

面向 XT26G01D / XT26G11D(3.3V 1Gb SLC SPI NAND)的固件驱动设计约定 —— 覆盖 Feature 寄存器体系、两段式读、三段式编程、坏块管理、 片内 ECC 与 掉电数据完整性,逐条给出可落地的命令序列、轮询判据与超时预算。

文档编号SPI-NAND-SW-SPEC-001
版本 / 状态V1.0 · 正式发布
适用器件XTX XT26G01D(版本号 Rev 1.1)、XT26G11D(Rev 1.0)及同系列 3.3V 1Gb SPI NAND
参考标准XT26G01D Spec v1.1 · XT26G11D Spec v1.0 · ONFI Parameter Page 定义
配套文档SPI_NAND_Flash_电路设计指南.html(硬件 / 原理图 / 布线约束)

0. 目的与范围

本规范约束 SPI NAND 器件在嵌入式固件中的软件行为:命令序列、状态判读、 超时预算、坏块与 ECC 处理、掉电保护。凡本文与器件 datasheet 冲突,以 datasheet 为准。

  • 统一命令序列:读(两段式)、编程(三段式)、擦除(WREN + D8h)各自固化为一套标准流程。
  • 统一状态判读:只用 0Fh + C0h 轮询 OIP,并同时检查 P_FAIL / E_FAIL / ECCS。
  • 统一坏块策略:上电扫描建立坏块表(BBT),NVB = 1004,运行时增量标记。
  • 统一掉电保护:给出写窗口、提交协议与刷新阈值,明确哪些操作是原子的。
不在本规范范围:硬件原理图与 PCB 布线(见《SPI NAND Flash 电路设计指南》); 文件系统与 FTL 的算法选型;XIP 代码执行(SPI NAND 不支持线性地址映射,不应按 NOR 方式使用)。

结论:SPI NAND 是「先搬进 cache 再操作」的器件 —— 所有读都是 13h 把整页搬进 cache 再用 03h/0Bh 读出;所有写都是 02h 装数据进 cache 再用 10h 落阵列。 没有 WREN 的 10h 会被静默忽略,没有 OIP 轮询的 10h 会写出半页数据,没有 ECCS 判读的读会带回已损坏的数据。

文档使用说明

  • 本文是《SPI NAND Flash 电路设计指南》的软件配套篇:硬件篇解决「怎么接、怎么供电、怎么走线」,本文解决「驱动怎么写、协议怎么等、错误怎么救」。
  • 命令码、寄存器位、时序值均取自 XT26G01D Spec v1.1 与 XT26G11D Spec v1.0。其它容量 / 其它厂商型号存在差异,移植前必须逐条核对。
  • 文中 C 代码为协议骨架伪代码,演示流程与判断条件,非特定平台可直接编译的完整实现;spi_transfer()、now_us() 等为平台相关底层。
贯穿全文的三条铁律

① 绝不用固定延时替代 OIP 轮询——读写擦时间随温度、磨损、个体差异波动巨大; ② 每次写/擦操作前必须重新 WREN——WEL 在每次操作结束后自动清零; ③ 超时门限一律取 datasheet max 以上——typ 值只用来安排轮询节奏,不能当失败判据。

1 · 器件定位与阵列组织

SPI NAND 不是「带坏块的 NOR」。它保留了 NAND 的页/块结构与坏块约束,只是把接口换成了 SPI。软件设计的第一件事,是把「地址空间」和「ECC 边界」搞清楚。

1.1 器件定位与关键参数

2176 B
页容量
2048 B 主区 + 128 B 备用区
1024
块数量
64 页/块 · 128 KB + 8 KB
1004
最小有效块 NVB
出厂最多 20 个坏块,必须建 BBT
8 bit
片内 ECC 能力
每 528 B(512+16)纠 8 bit
360 µs
页编程 tPROG
最大 700 µs
3.5 ms
块擦除 tERS
最大 10 ms
130 µs
页读 tRD
最大 185 µs(带 ECC)
60 K
P/E endurance
10 年数据保持

数据取自 XT26G01D Spec v1.1 第 2 节 Features 与第 14.7 节 Performance Timing;XT26G11D 参数一致。

项目XT26G01DXT26G11D软件影响
容量 / 结构1Gb · 1024 Block × 64 Page × 2176 B同左驱动无需区分
JEDEC MID0Bh(XTX)0Bh(XTX)识别阈值一致
Device ID(9Fh)31h34h识别表必须同时收录
片内 ECC常开(ECC_EN 关断时状态位回默认值)可由 ECC_EN 位开关XT26G01D 不得关 ECC
封装WSON8 / LGA8 / BGA24WSON8仅影响硬件,软件无差异
手册版本Rev 1.1(2024-11)Rev 1.0(2023-12)以各自 Rev 为准

1.2 阵列组织与地址映射

SPI NAND 的地址分为两套,软件最容易在这里出错:

地址类型位宽出现在哪些命令含义
行地址(Row Address)16 bit 有效(24 bit 中高 8 位为 dummy)13h 页读、10h 编程执行、D8h 块擦除块号 + 页号:PA[15:6] = 块号(0~1023),PA[5:0] = 块内页号(0~63)
列地址(Column Address)12 bit 有效(16 bit 中高 4 位为 dummy)03h/0Bh 读 cache、02h/32h 装数据、84h 随机装数据页内字节偏移 000H~87FH,指向 cache 寄存器
行地址的高 8 位是 dummy,不是「第 9~24 位地址」

1Gb 器件只需要 16 bit 行地址(1024 块 × 64 页 = 65536 = 216)。13h / 10h / D8h 后面的 24 bit 里,最高 8 位必须为 0 且被器件忽略。把它当成「24 位地址」直接填 3 字节地址,在擦除时会擦错块。

块擦除 D8h 填的是 块首页的行地址(页号部分为 0),即 block << 6。

1 Device = 1024 Blocks128MB + 8MB = 1Gb · NVB(最小有效块)= 10041 Block = 64 Pages128KB + 8KB · 擦除的最小单位(D8h)· 块内页必须顺序编程1 Page = 2176 B2048 B 主区 + 128 B 备用区 · 读写与 cache 的最小单位Sector 0512B + 16BSector 1512B + 16BSector 2512B + 16BSector 3512B + 16BECC粒度每 sector 独立生成 8bit/528B ECC 校验码(写入 ECC 区的内容被忽略)
图 1 · 阵列组织:1Gb = 1024 Block × 64 Page × 2176 B,ECC 以 528 B 为一个扇区

1.3 页、备用区与 ECC 扇区边界

片内 ECC 以 528 字节为一个扇区(512 B 主区 + 16 B 备用区),每页 4 个扇区,另有 64 B 存放 ECC 校验码:

页内字节地址(列地址 CA[11:0],000H ~ 87FH)主区2048BMain 0000H–1FFH · 512BMain 1200H–3FFH · 512BMain 2400H–5FFH · 512BMain 3600H–7FFH · 512B备用区128BS016BS116BS216BS316B800H 为出厂坏块标记位840H–87FH · 64B片内 ECC 校验区 — 用户可读但禁写约束:一次 Program 必须以「512B 主区 + 16B 备用区」为一个 sector 整体写入只写主区不写备用区,会因 ECC 区内容不匹配而在回读时报不可纠正错误
图 2 · 页内布局:4×(512B+16B) 用户区 + 64B 片内 ECC 校验区(禁写)
840H–87FH 是禁区

这 64 字节由器件在 Program Execute 时自动写入 ECC 校验码,用户写入会被忽略(datasheet 原文:WRITEs to the ECC area are ignored)。软件若在「写整页」时把 2176 字节全填进去,前 2112 字节有效,后 64 字节被丢弃——这不会报错,但会让「我写了什么」与「实际存了什么」不一致,回读比对时误判为数据损坏。

建议:把 2176 字节的页拆成 4 个 528 字节扇区提交,第 5 段(840H~87FH)不参与传输。

1.4 与 SPI NOR 的关键差异(移植必读)

维度SPI NORSPI NAND移植后果
读发地址直接出数据先 13h 搬到 cache,再读 cache读函数必须拆成两段
写02h 直接写阵列02h 装 cache → 06h → 10h 落阵列少发 10h 数据就丢了
状态寄存器05h 读 SR0Fh + C0h 读状态用 05h 轮询永远读不到 OIP
坏块无出厂即可能有坏块,须建 BBT不做坏块管理必然丢数据
ECC无片内 8bit/512B,须读 ECCS 判读≥6 bit 即整块迁移 + 标坏(第 11 章)
局部编程可字节编程同一页最多 4 次(NOP ≤ 4),且须按 sector 对齐超次数导致编程失败
块内顺序任意块内页必须从 LSPage 到 MSPage 顺序编程随机页编程被禁止
最容易踩的一条

把 NOR 驱动改两行就拿去驱动 SPI NAND,最常见的死法是用 05h 轮询 busy:SPI NAND 根本没有 05h 读状态寄存器命令,读回来的是无效字节,表现为「轮询立刻返回成功,但数据没写进去」。

2 · 术语与缩写

本章统一术语口径,避免「页/块/sector/cache」在文档与代码里指的不是同一件事。

2.1 结构与命令术语

缩写 / 术语全称本文中的含义
Page—最小读写单位,2176 B(2048 主 + 128 备用)
Block—最小擦除单位,64 Page = 128 KB + 8 KB
Sector—ECC 粒度,512 B 主区 + 16 B 备用区 = 528 B,每页 4 个
Cache Register页缓存寄存器2176 B 的 SRAM 缓冲区;阵列与 SPI 之间所有数据都要经过它
PLProgram Load02h/32h,把数据装进 cache(不写阵列)
PEProgram Execute10h,把 cache 内容写入阵列
NOPNumber Of (partial) Programs同一页允许的局部编程次数,上限 4
OIPOperation In Progress状态位 bit0;1 = 器件忙,0 = 就绪
WELWrite Enable Latch状态位 bit1;PE / 擦除前必须先 06h 置位
P_FAILProgram Fail状态位 bit3;编程失败(含写保护区域)
E_FAILErase Fail状态位 bit2;擦除失败(含擦除锁定区域)
ECCSECC Status状态位 bit7~4;回读数据的纠错结果编码
BBMBad Block Mark出厂坏块标记,位于每块首页备用区首字节(列地址 800H),非 FFh 即坏块
NVBNumber of Valid Blocks最小有效块数,本系列为 1004(总 1024)
BBTBad Block Table软件维护的坏块表,上电扫描建立,运行时增量更新
UIDUnique ID128 bit 器件唯一 ID,附反码校验,共 16 副本
IDMInternal Data Move片内数据搬移:13h → 84h 改字节 → 06h → 10h

2.2 本文书写约定

  • 命令码用小写十六进制带后缀 h 书写(如 13h),与 datasheet 保持一致;datasheet 中出现的 13H 大写形式含义相同。
  • Feature 地址用两位十六进制 + H(如 A0H、C0H),指 Get/Set Features 命令后的那个地址字节。
  • 状态位写作 C0H[0] 形式,方括号内为 bit 位序(bit0 为最低位)。
  • 时序符号沿用 datasheet:tRD(页读)、tPROG(页编程)、tERS(块擦除)、tRST(复位恢复)。「典型值 / 最大值」成对给出时,软件超时必须按最大值预算。
  • 代码示例为 C 伪代码,省略 SPI 传输底层实现,只表达命令序列与判据。

3 · 驱动架构与初始化

SPI NAND 的驱动比 NOR 多两层:坏块映射和 ECC 决策。这两层放错位置,后面所有优化都是白做。

3.1 分层职责

层职责禁止做的事
应用层业务调用 read/write/ioctl;不感知坏块直接拼 SPI 命令、直接算行地址
存储管理层BBT 维护、磨损均衡、提交协议、ECC 刷新决策绕过 BBT 直接写物理块
命令协议层13h/03h 读、02h/06h/10h 写、D8h 擦除、0Fh 轮询省略 WREN、省略 OIP 轮询
硬件抽象层SPI 传输、CS# 时序、Quad 使能在传输中途切换时钟频率

3.2 上电初始化流程

上电后器件处于整片写保护状态(BP2/BP1/BP0 默认均为 1,对应「All」),且 cache 内容未定义。初始化必须按下列顺序执行:

1. 上电:等待 VCC 稳定tVSL ≥ 3 ms;VCC < VWI(2.5V) 时写操作被禁止2. CS# 先高后低上电后 CS# 必须经历一次高→低,器件才接受第一条命令3. 9Fh 读 ID 识别器件MID=0Bh;DID=31h(G01D) / 34h(G11D) 才算匹配4. 1Fh + A0H 解除块锁上电默认 BP2:0=111 全片保护,不解锁则一切写/擦都被拒5. 1Fh + B0H 配置工作模式QE(Quad 使能)/ ECC_EN(XT26G11D)/ HSE(顺序读高速)6. 扫描并建立坏块表 BBT逐块读首页列地址 800H,非 FFh 即坏块;NVB=10047. 完成:进入就绪态BBT 建议缓存在 RAM,并镜像到 Block0 保留区
图 3 · 上电初始化七步:最容易漏的是第 4 步——上电默认整片写保护
/* 上电初始化:返回 0 表示成功 */
int spinand_init(void)
{
    uint8_t id[2];

    delay_ms(3);                    /* tVSL: VCC(min) -> CS# low 至少 3 ms */
    spi_cs_high(); spi_cs_low();    /* 上电后必须经历一次 CS# 高->低 */

    if (spinand_read_id(id) != 0)   return -E_NODEV;   /* 9Fh */
    if (id[0] != 0x0B)              return -E_NODEV;   /* MID = XTX */
    if (id[1] != 0x31 && id[1] != 0x34)
                                    return -E_NODEV;   /* DID: G01D / G11D */

    /* 解锁:A0H 的 BP2:0 写 0;BRWD 保持 0,避免 WP# 硬件锁死 */
    if (set_feature(0xA0, 0x00) != 0) return -E_LOCK;

    /* 工作模式:B0H —— QE=1(bit0) / ECC_EN=1(bit4) / HSE=1(bit1) */
    if (set_feature(0xB0, 0x13) != 0) return -E_FEATURE;

    return bbt_scan();              /* 扫描 1024 块首页 800H,建立坏块表 */
}
初始化里最容易漏的两件事

① CS# 必须先高后低。datasheet 明确要求:上电后 CS# 必须经历一次从高到低的跳变,器件才开始接受命令。若硬件上 CS# 一直被拉低(例如 GPIO 默认输出低),第一条 9Fh 就会失败。

② 不解锁就去写,失败码是 08h 而不是「超时」。上电默认 BP[2:0]=111 全片保护,此时发编程命令返回 P_FAIL(08h),发擦除命令返回 E_FAIL(04h)。很多「SPI NAND 写不进去」的问题,根因就是这一行解锁代码没写。

3.3 器件识别:三级策略

  1. 9Fh 读 ID(2 字节):MID = 0Bh 判定为 XTX;DID = 31h(XT26G01D)或 34h(XT26G11D)。这是最快的路径,用于初始化早期筛选。
  2. 13h + 000001h 读 Parameter Page:读回 ONFI 签名 4Fh 4Eh 46h 49h("ONFI"),可拿到页大小 2048、spare 128、页/块 64、块/LUN 1024、tPROG 700 µs、tERS 10 ms、tRD 185 µs 等真实参数,并把器件型号字符串(byte 44-63,XT26G01D)读出来。
  3. CRC 校验:Parameter Page 的 byte 254/255 为 CRC,多项式 X16+X15+X2+1,初值 4F4Eh。XT26G01D 的 CRC 为 1Ch/13h。校验不过说明读到的是噪声,应重试而非继续初始化。
什么时候用 Parameter Page 而不是读 ID

如果固件要兼容多家器件的同一份驱动,应以 Parameter Page 为准:它给出真实的页/块几何与时序上限,可以避免把「1Gb 器件按 2Gb 地址空间访问」这类错误。只读 ID 的方案,等于把所有几何参数硬编码在驱动里,换料就要改代码。

4 · Feature 寄存器体系

SPI NAND 没有「状态寄存器 + 配置寄存器」的 NOR 式划分,所有配置与状态都通过 0Fh / 1Fh 加一个Feature 地址访问。理解这四个地址,就理解了整颗器件。

4.1 四张表总览

Get Features (0Fh) / Set Features (1Fh) —— 四个 Feature 地址BRWDR/Wb7Res.—b6BP2R/Wb5BP1R/Wb4BP0R/Wb3INVR/Wb2CMPR/Wb1Res.—b0A0HBlock LockOTP_PRTRb7OTP_ENR/Wb6Res.—b5ECC_ENR/Wb4CRMR/Wb3Res.—b2HSER/Wb1QER/Wb0B0HFeatureECCS3Rb7ECCS2Rb6ECCS1Rb5ECCS0Rb4P_FAILRb3E_FAILRb2WELRb1OIPRb0C0HStatusRes.—b7DS_IO1R/Wb6DS_IO0R/Wb5Res.—b4Res.—b3Res.—b2Res.—b1Res.—b0D0HDrive Strength
图 4 · Feature 寄存器四张表:A0H 块保护 / B0H 功能 / C0H 状态 / D0H 驱动强度
Feature 地址名称关键位上电默认值是否易失
A0HBlock LockBRWD / BP2 / BP1 / BP0 / INV / CMPBP2:0 = 111(全片保护)非易失(配置保持)
B0HFeatureOTP_PRT / OTP_EN / ECC_EN / CRM / HSE / QEHSE = 1,其余 0易失(OTP_PRT 除外)
C0HStatusECCS3~0 / P_FAIL / E_FAIL / WEL / OIPECCS 反映 Block0 Page0 内容只读
D0HDrive StrengthDS_IO[1:0]01 = 50% 驱动强度非易失(配置保持)

4.2 A0H —— 块保护与 BRWD 硬件锁

块保护由 CMP、INV、BP[2:0] 五个位组合决定保护区域。完整组合表见第 8 章。软件侧只需记住三条:

  • 上电默认 BP[2:0] = 111 → 全片保护;写入前必须显式解锁。
  • BRWD = 1 且 WP# 引脚为低时,A0H 的所有可写位都无法再改动——这是防软件误操作的硬件锁,一旦生效只能靠断电或拉高 WP# 解除。
  • 向被保护区域发擦除命令 → 返回 E_FAIL(状态字节 04h);发编程命令 → 返回 P_FAIL(08h)。

4.3 B0H —— 功能位(OTP / ECC / Quad / 高速)

位名称作用软件约定
bit7OTP_PRTOTP 区永久锁定;一旦置 1 不可恢复产线烧录完成后最后一步才置位
bit6OTP_EN使能访问 OTP 区 / UID / Parameter Page读 UID 与参数页前必须先置 1
bit4ECC_EN片内 ECC 开关(XT26G11D)XT26G01D 保持 1;关断后 ECCS 为无效默认值
bit3CRMContinuous Read Mode默认 0;如需连续读请联系原厂确认
bit1HSE顺序读高速模式默认 1;随机页读时建议清 0(否则 tRD 变长)
bit0QEQuad 使能使用 32h/6Bh/EBh 等 Quad 命令前必须置 1
XT26G01D 与 XT26G11D 在 ECC_EN 上的真实差异

XT26G01D(Rev 1.1):datasheet 注明「Internal ECC is always on」,即使把 ECC_EN 清 0,片内 ECC 仍在计算,只是 ECCS 状态位回落到默认值 0000b,不再反映真实纠错情况。也就是说:关掉它,你既没省下什么,还失去了唯一的错误可见性。

XT26G11D(Rev 1.0):ECC_EN 是真正的开关,清 0 后片内 ECC 停止工作。

结论:无论哪颗,固件都应保持 ECC_EN = 1。把 ECC_EN 当可调项的驱动,在 G01D 上会静默失去错误检测能力。

4.4 C0H —— 状态寄存器

状态字节的 8 个位分工明确:bit7~4 是 ECC 结果,bit3~2 是失败标志,bit1 是写使能,bit0 是忙标志。轮询只看 bit0,判成败必须看 bit3~2 与 bit7~4。

/* 读状态:0Fh + C0H + 1 字节 */
uint8_t spinand_get_status(void)
{
    uint8_t tx[2] = {0x0F, 0xC0}, rx = 0;
    spi_cs_low();
    spi_transfer(tx, 2);
    spi_read(&rx, 1);
    spi_cs_high();
    return rx;
}

#define ST_OIP     (1u << 0)   /* 1 = 忙 */
#define ST_WEL     (1u << 1)   /* 1 = 已写使能 */
#define ST_EFAIL   (1u << 2)   /* 1 = 擦除失败 */
#define ST_PFAIL   (1u << 3)   /* 1 = 编程失败 */
#define ST_ECCS_MASK (0xF0u)   /* bit7~4:ECC 状态 */
以页编程为例:命令 → 忙窗口(OIP=1)→ 0Fh 轮询 → 结果判读06h WREN10h Program Execute阵列编程进行中(OIP = 1)tPROG 典型 360 µs / 最大 700 µs0Fh0Fh0FhOIP = 0 就绪就绪后必须继续判读 C0H 的失败位与 ECC 状态P_FAIL (bit3) = 1编程失败:目标页在保护区内 / 地址越界 / OTP 区被锁E_FAIL (bit2) = 1擦除失败:目标块处于锁定区域ECCS[1:0] = 10错误位数超出 8 bit,未纠正 —— 数据已不可信ECCS[1:0] = 11错误恰达 8 bit 上限,已纠正 —— 必须安排刷新ECCS[1:0] = 00 / 01无错误或已纠正;判级与处置见第 11 章
图 5 · 状态轮询:OIP 只回答「忙不忙」,P_FAIL / E_FAIL / ECCS 才回答「成没成」

4.5 D0H —— 输出驱动强度

DS_IO[1:0]驱动强度适用场景
0025%短走线、单负载、低 EMI 要求
01(默认)50%常规应用
1075%长走线或负载较重
11100%多负载、高速 Quad 模式(120MHz)下仍不稳定时

调整驱动强度是信号完整性手段而非性能手段:Quad 模式 120MHz 读数据出错时,先用示波器看眼图,再决定降频还是加驱动。

4.6 写 Feature 的四步准则

所有 Set Features(1Fh)操作必须走完这四步,一步都不能省① 06hWrite Enable置位 WEL;不置位则 1Fh 被忽② 1FhSet Features发地址 + 数据字节;保留位必须写 ③ 0Fh轮询 OIP读 C0H 直到 OIP=0;写 F④ 0Fh回读校验再读一次该地址,确认写入值已生效超时预算:Set Features 轮询上限取 10 ms(远超实测忙时间,留足余量)违反后果速查省 ①:1Fh 静默无效,块锁没解开,后续所有写/擦返回失败(擦除锁区返回 04h,编程锁区返回 08h)
图 6 · Set Features 四步准则:06h → 1Fh → 0Fh 轮询 → 0Fh 回读校验
/* Set Features 标准实现:06h -> 1Fh -> 轮询 -> 回读校验 */
int set_feature(uint8_t addr, uint8_t data)
{
    uint32_t t0 = now_ms();

    /* ① 写使能 */
    spinand_wren();                       /* 06h */

    /* ② 下发 */
    uint8_t tx[3] = {0x1F, addr, data};
    spi_cs_low(); spi_transfer(tx, 3); spi_cs_high();

    /* ③ 轮询 OIP */
    while (spinand_get_status() & ST_OIP) {
        if (now_ms() - t0 > FEATURE_TIMEOUT_MS)   /* 建议 10 ms */
            return -E_TIMEOUT;
    }

    /* ④ 回读校验 */
    if (get_feature(addr) != data)
        return -E_VERIFY;
    return 0;
}
相关报告 · 交叉引用 1 器件全部展开收起 ▾
点开类目即可展开:具体文档 / 章节(点章直接跳转到该章)
SPI NAND
电路设计指南
Linux 驱动开发
样机问题排查
量产问题排查4
全志平台 SPI NAND NOR
章节(点章跳转,共 11 章)
海思平台 SPI NAND 量产常见失败
章节(点章跳转,共 11 章)
烧录成功但不开机 SPI NAND
章节(点章跳转,共 12 章)
瑞芯微平台 SPI NAND eMMC
章节(点章跳转,共 11 章)
系统设计指南3