只读分享解决方案文档原理方案设计📅 2026-09-21🔖 V1.0✅ 长期有效
🔧解决方案&应用市场分析 -> 原理方案设计 -> SPI_NAND_Flash_软件设计规范

SPI_NAND_Flash_软件设计规范

生成时间 2026-09-21 11:02:20 长期有效

SPI NAND Flash 软件设计规范

编制日期 2026-09-21

面向 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.1XT26G11D 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 → 06h10h 落阵列少发 10h 数据就丢了
状态寄存器05h 读 SR0Fh + C0h 读状态05h 轮询永远读不到 OIP
坏块出厂即可能有坏块,须建 BBT不做坏块管理必然丢数据
ECC片内 8bit/528B,须读 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
SectorECC 粒度,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(如 A0HC0H),指 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 = 1WP# 引脚为低时,A0H 的所有可写位都无法再改动——这是防软件误操作的硬件锁,一旦生效只能靠断电或拉高 WP# 解除。
  • 向被保护区域发擦除命令 → 返回 E_FAIL(状态字节 04h);发编程命令 → 返回 P_FAIL08h)。

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;
}
📥 芯参谋客户端下载