SPI NOR 首次启动失败 · 样机排查手册
编制日期 2026-09-21 | 版本号 Rev 1.0
XTX 芯天下 · FAE 样机 bring-up 技术文档 | 面向第一次上电 / 手焊 / 飞线 / 转接板场景 | 2026-09 版
命令码、寄存器位、dummy cycle 数值、BootROM 取址行为一律以器件 datasheet 与主控平台手册为准;本文只讲机制与定位方法,不代替 datasheet。
- SPI NOR 没有统一的强制标准:状态寄存器数量与位定义、QE 位位置、4 字节地址的进入/退出方式、dummy cycle 默认值、QPI 进出命令都由各厂自定,同名位在不同厂、甚至同厂不同系列上含义都可能不同。
- 本文出现的命令码(如
0x9F读 ID、0x03普通读、0x0B快读、0x6B/0xEBQuad 快读)均为常见写法示例,不是通用常量,必须逐颗核对该料号的正式 datasheet。 - BootROM 侧的取址 offset、读取模式、dummy 数由平台 BootROM 固化,以主控厂商的 TRM / BootROM 手册与 SDK 源码为准,不以烧录器默认值为准。
- 凡本文标注「以手册为准」的条目,现场必须打开对应料号与平台的正式文档复核后再动手。
一句话结论:样机上 SPI NOR 起不来,九成不是芯片坏了,而是「烧进去的字节」与「BootROM 读它的方式」在地址长度、读命令、dummy cycle、QE 状态这四件事上没对齐。定位的正确顺序是:先看串口 LOG 停在哪一句,把它映射到启动链路的某一段,再进对应根因 —— 而不是一上来就重烧、换片、改板。
这份文档解决什么
解决的是样机第一次上电:串口无输出、停在 BootROM、或跑起来后崩这一类问题的定位与修复。全文按「链路 → 定段 → 决策树 → 分成因 → 速查 → 清单」组织:第 1 章把看不见的 BootROM 流程拆成 5 段,第 2 章用串口 LOG 停点把死点定位到段,第 3 章给出决策树,第 4 章是六类根因的原理 + 实操 + 验证,第 5 章是可直接翻查的速查表,第 6 章是勾选清单与结案模板。
区分侧重:本文讲样机首次 bring-up(飞线、手焊、转接板、第一次上电、最小系统);产线批量烧录后的不开机问题属于量产篇,两者根因分布不同,不要混用结论。
1. 启动链路:BootROM 到底做了什么
SPI NOR 是极少数能让 BootROM 直接取指执行的启动介质 —— 这句话既是它最大的优点,也是它最难查的地方:BootROM 固化在芯片内部,你看不见它发了什么命令,只能从串口 LOG 停在哪一句,反推它死在哪一段。本章先把这条看不见的链路拆成 5 段。
1.1 五段链路的标准动作
把 BootROM 从 SPI NOR 取代码的过程抽象成 5 段。不同平台的细节(取址 offset、是否校验签名、是否切 QPI)以各自 TRM/BootROM 手册为准,但「先供电、再认器件、再找头、再搬代码、最后交棒」这个骨架是通用的。
| 段 | BootROM 在做什么 | 这一段的关键变量 | 这一段失败的典型后果 |
|---|---|---|---|
| 第 1 段 | 电源与复位:等待 VCC/VCCQ 稳定、释放复位、拉高 CS#、把 SPI 控制器配成默认模式 | 实测电压、复位释放时刻、上电等待时间(以 datasheet 为准) | 完全无任何串口输出,电流可能异常 |
| 第 2 段 | 识别器件:发读 ID 命令(常见为 0x9F)读 JEDEC ID,与内置器件表比对 | CS#/CLK/DI/DO 波形、IO 电压域、SPI 模式(CPOL/CPHA) | 无输出,或报 unknown device / no flash |
| 第 3 段 | 定位镜像头:从 BootROM 约定的固定 offset 读回若干字节,检查 magic / header / IVT | 镜像起始烧录地址、header 是否真的烧进去、字节序 | 有 banner,随后报 no valid header / bad magic |
| 第 4 段 | 搬运代码:用约定的读命令(常见 0x03/0x0B/0x6B)连续读出 SPL 到内部 SRAM | 读命令选择、QE 位状态、地址长度、dummy 数、时钟频率 | 报 read fail / timeout,或搬出来的代码是错的 |
| 第 5 段 | 交棒执行:跳到 SPL 入口,后续由 SPL 负责初始化 DDR、加载下一级 | 数据完整性、高频下的 dummy 边界、3B/4B 地址模式 | SPL 打印正常但加载下一级时 CRC 错、随机崩 |
1.2 为什么「分段」比「猜根因」快
样机阶段最常见的低效行为,是把「不开机」当成一件事,然后按顺序试:重烧一遍、换个芯片、换个烧录器、改个频率。问题在于这四件事对应完全不同的段,试错不会收敛。
分段定位的价值在于:串口 LOG 的停点是一个免费的断点。BootROM 每往前走一步都会留下痕迹,痕迹停在哪一步,就说明上一步通过了、这一步没通过。把范围从「整块板」缩小到「某一段的某一个变量」,后面的排查才有方向。
BootROM 是固化代码,你看不到源码,也改不了它。它能做什么、按什么 offset 取、用几条线读、dummy 给几拍,全部由主控厂商在芯片出厂时定死,只能通过 TRM / BootROM 手册与 SDK 里的 SPL 源码间接推断。
因此本文所有涉及 BootROM 具体行为的条目,结论都是「去查平台手册与 SDK 源码确认」,而不是给一个放之四海皆准的数值。FAE 现场最忌讳把 A 平台的 BootROM 行为套到 B 平台上。
2. 分段定位:串口 LOG 停在哪,就死在哪一段
本章给一套可以直接照做的分段定位动作。核心只有一句话:先定段,再定因;定段之前不重烧、不换片、不改板。
2.1 三段法:把现象归到三个大段里
| 大类 | 串口表现 | 死在哪几段 | 立刻要取的三项证据 |
|---|---|---|---|
| A 类 | 完全无输出,串口全程静默,连 BootROM banner 都没有 | 第 1 段 / 第 2 段 | ① VCC 与 VCCQ 实测值;② 复位脚在上电后的电平轨迹;③ CS# 与 CLK 有没有波形 |
| B 类 | 有 BootROM banner,但随后报 no valid header / bad magic / read fail / 卡死 | 第 3 段 / 第 4 段 | ① 完整 LOG 原文(含报错码);② 主控侧读回的头 256 字节;③ 烧录器侧同一区间的 dump |
| C 类 | SPL 打印正常,加载 U-Boot / 内核时 CRC 错、随机崩,或只在特定温度/电压下崩 | 第 5 段 | ① 出错地址与期望值的比对;② 当前 SPI 时钟频率与 dummy 配置;③ 器件容量与地址模式 |
2.2 取证动作:主控侧读回 vs 烧录器侧读回
分段定下来之后,第一件事不是改配置,而是取证据。最有用的一条证据是「同一个地址区间,两套系统读出来的字节是不是一样的」。
# ① 主控侧(U-Boot 命令行)读回头 256 字节,打印出来
sf probe 0
sf read 0x42000000 0x0 0x100
md.b 0x42000000 0x100
# ② 烧录器侧:把同一颗片子(或同批同一颗)的 0x0 起 256 字节读回存成 bin
# (以烧录器软件操作为准,注意选「读器件」而不是「读缓冲区」)
# ③ 主机上做二进制比对
cmp target_readback_0x0_256.bin host_readback_0x0_256.bin
# 或:md5sum 两边文件比对
三条判读规则:
- 两边一致、且等于镜像文件头:数据没问题,去查读取语义(读命令 / QE / dummy / 地址长度)。
- 两边一致、但不等于镜像文件头:烧录这一步就错了(offset、文件选错、或者烧的是另一个工程),回去查烧录工程。
- 两边不一致:这是最有价值的一类 —— 说明「烧录器读得对」与「主控读得对」不是一回事,问题就在读取语义或电气条件上,优先查电压域、时序、QE 与 dummy。
① 比对区间必须包含 BootROM 真正取址的那一段,而不是只比 0x0 起几个字节 —— 取址 offset 以平台手册为准。
② 主控侧读回必须在 BootROM 之后的稳定状态做(比如停在 U-Boot 命令行),此时 SPI 控制器已经被 SPL 重新配置过,读回来的字节可能与 BootROM 阶段读的不同 —— 这正是要抓的差异,不要当成噪声忽略。
③ 烧录器侧读回的供电与板上供电不同。如果板上 VCCQ 是 1.8 V 而烧录器给的是 3.3 V,两边的读写裕量本来就不一样,比对结果要标注电压条件。
2.3 逻辑分析仪:看不见 BootROM 时,用抓包代替
如果串口完全无输出(A 类),或者平台根本没有可用的 BootROM LOG,就用逻辑分析仪直接抓 SPI 总线。看三件事:有没有命令、第一个命令是什么、命令序列停在哪一条。
- 探点接 CS#、CLK、DI(IO0)、DO(IO1);Quad 模式还要接 IO2、IO3。地线尽量短,接在主控侧 GND。
- 采样率至少是 SPI 时钟的 8~10 倍;用 CS# 下降沿触发,单次捕获。
- 上电,抓完整一段。解码时先按 1-1-1(单线)解,能解出
0x9F说明链路通。 - 如果解出来是
0x9F之后紧跟一串读命令然后停住 → 死在第 3/4 段;如果连0x9F都没有 → 死在第 1/2 段。 - 把 BootROM 阶段实际发出的读命令码记下来,与 SPL/内核里配的读命令比对 —— 两边不一致就是第 4 类根因的直接证据。
① 禁止反复重烧。每重烧一次就多一个变量(擦除是否干净、状态寄存器是否被改写、offset 是否被改动),LOG 停点也会跟着变,问题更难复现。
② 禁止在通电状态下用烙铁补焊 CS# / CLK / IO 飞线。烙铁接地不良会直接打穿器件 IO,也会把主控的 SPI 控制器一起烧掉,故障范围瞬间扩大。
③ 禁止把烧录器的 Verify PASS 当成板上可用的依据。它只证明「烧录器自己按自己的规则写对、读对」,不证明目标 SoC 按它的规则也读得出来。
④ 禁止对状态寄存器做试探性写入。SRP / LB 一类非易失位一旦写入不可撤销,误写只能换片;写之前一定先读回当前值并对照 datasheet 逐位确认。
3. 决策树:从上电现象到根因类目
把第 2 章的三段定位固化成一张图:从「上电」开始,两个判据、五条出口,每条出口直接指向一个根因类目和该段的首查动作。现场按图走完一遍,通常 10 分钟内就能把范围从「整块板」缩到「一个变量」。
3.1 走树时的三条纪律
- 每条分支都要留下物证。走完树不能只说「我查了 QE」,要留下 QE 位的读回值、读命令的抓包记录、比对文件的 md5。
- 一次只改一个变量。从「1-1-1 + 0x03 慢读」往上加配置,每加一项就重新上电验证一次。同时改三个变量,就算通了也不知道是哪一个生效的。
- 走到叶节点还没解决,回到第 1 段重新走。实践中相当一部分「查遍了配置都没问题」的case,最后根因在第一段 —— 电压在带载瞬间跌落、复位释放太早。
如果故障是偶发的(十次起不来一两次),决策树依然适用,但每一次上电都要记录 LOG,连续记录 50~100 次,统计停点分布。停点集中在同一段 → 按该段根因查;停点分散在多段 → 优先怀疑第 1 段(电源/复位)与样机专属的飞线问题。
4. 分成因排查:六类根因的原理、实操与验证
六个根因按样机首次 bring-up 的出现概率排序。每一条都按原理 → 实操 → 验证三段给,「验证」是判断是否真的解决了的标准,不能省。
4.1 根因 1:镜像起始 offset 与 BootROM 期望不符
原理。BootROM 不会去「找」镜像,它只会去一个固定的地址(具体 offset 以平台手册为准)读回若干字节,然后按约定的格式检查 magic / header / IVT。镜像文件内部的布局(header 在文件偏移 0、SPL 在偏移 N)是链接脚本定的,而文件被烧到 Flash 的哪个地址是烧录工程定的 —— 这两件事如果对不上,BootROM 取址点读到的就不是 header,而是 payload 中间的某个字节。
样机阶段高发的三种错位:
- 镜像文件头直接烧在 Flash 0x0,但 BootROM 从非零 offset 取(或反之)。
- 镜像原本需要前置一段 header/IVT,但烧进去的是「裸 bin」,header 没生成或没拼进去。
- 工程沿用了另一个项目/另一颗料号的烧录配置,起始地址参数没跟着改。
实操。三步走:
- 从 SDK 构建产物里找到最终烧录的那个 bin(注意不是 SPL 单独编译出来的 elf/bin),用十六进制工具看它偏移 0 起是什么 —— 是不是平台要求的 header 结构。
- 打开烧录工程,确认「烧录起始地址」这一项等于平台要求的 offset,而不是默认的 0x0 或上一个项目的值。
- 按 2.2 节做交叉验证:主控侧读回取址点起 256 字节,与镜像文件 0x0 起 256 字节比对。
验证。比对完全一致,且重新上电后 BootROM 不再报 no valid header,进入第 4 段(搬运)或第 5 段(执行)—— 这才算 offset 这一项通过。
4.2 根因 2:QE 位与 BootROM 读取模式不匹配
原理。Quad 读(1-1-4 / 1-4-4)需要器件把 IO2、IO3 也用作数据线,这个能力由 QE(Quad Enable)位控制。QE 未置位时,IO2/IO3 处于高阻或被内部上拉,主控按 Quad 时序去采样这两条线,读回来的永远是固定电平(常见为全 1 或全 0)。结果就是:头几个字节的 magic 就已经不对,BootROM 直接判 header 无效 —— 现象与「镜像 offset 错」几乎一模一样,这是最容易被误判的地方。
三个必须知道的事实:
- QE 位的物理位置各厂不同:在状态寄存器 1、状态寄存器 2、还是专用配置寄存器,取决于料号,以 datasheet 为准。
- QE 有易失与非易失两种实现:有的器件写进去断电保持,有的器件断电/复位后回到默认。样机上「烧录器设了 QE 但板上没生效」,多半是后者。
- BootROM 阶段的读取模式是固化的:它不会因为器件 QE 是 0 就自动降级成单线读(具体行为以平台手册为准,不要假设)。
实操。
- 先确认 BootROM 阶段用的是几条线:查平台手册,或者用逻辑分析仪抓 BootROM 的读命令码 (
0x6B/0xEB一类是 Quad,0x0B是 1-1-1 快读,0x03是普通读)。 - 读回器件状态寄存器里 QE 所在的那个 bit(位置以 datasheet 为准),注意要在板上、在上电稳定之后读,而不是看烧录器报告。
- 如果 BootROM 用 Quad 而 QE = 0:要么在 SPL 里改成单线读(若平台允许),要么在烧录时把非易失 QE 写进去并确认掉电保持;两条路都要看平台是否支持。
验证。板上读回 QE = 期望值,逻辑分析仪上看到 IO2/IO3 有真实翻转的数据波形,而非常电平;重新上电不再报 header 错。
4.3 根因 3:dummy cycle 与工作频率不匹配
原理。快读命令(0x0B 及各类 Quad 快读)在发完地址之后,需要插入若干个 dummy cycle(空时钟周期)让器件准备数据,然后才开始输出。主控发几个 dummy 是主控侧配置的,器件需要几个 dummy 是器件在给定频率下决定的 —— 两者不一致,读回来的数据就会整体错位。错位在字节层面看不出来(都是合法字节),只有校验和会失败。
为什么它「最难查」:
- 错位是整体的、恒定的,不是随机的坏数据 —— 所以看起来像「镜像错了」,很多人在这里绕很久。
- 需要的 dummy 数随频率变化:同一个器件在低频下需要的 dummy 数,在高频下可能就不够。样机换了晶振、或者 SPL 里把 SPI 时钟提上去之后突然起不来,基本都是这一类。
- 温度会移动这个边界:常温能起来、高温起不来(或反过来),是 dummy 裕量不足的典型特征。
实操。
- 先把 SPI 时钟降到最低(或在 SPL 里配成最低分频),用
0x03普通读(无 dummy)跑通一遍 —— 这是基线。 - 逐步提高频率,每档都做一次「读回已知内容并比对」的测试,记录开始出错的频率点。
- 把主控侧的 dummy 配置改到器件 datasheet 在该频率下要求的数值,重测该频率点。
- 在最容易出错的频率点上做高低温拉偏,确认裕量。
验证。在目标工作频率 + 目标温度范围(至少包含常温与最高温)内,连续 100 次上电启动,读回比对 100% 一致,才算通过。
4.4 根因 4:4 字节地址模式切换不一致
原理。传统 3 字节地址只能寻址到 16 MB。容量更大的器件需要切到 4 字节地址模式,或者用分段/扩展地址寄存器的办法访问高地址。关键点在于:这个切换是一个「状态」,而 BootROM、SPL、应用三方的代码对这个状态的理解可能不一致。
典型故障链:BootROM 用 3B 模式读低地址,SPL 起来之后切到 4B 模式去读高地址的 U-Boot,但器件上电默认是 3B、切换命令的时机或命令码与器件不匹配;或者反过来,SPL 认为已经是 4B 而器件实际还在 3B,高地址被截断回卷到低地址 —— 低地址的 SPL 能起来,高地址的 kernel 却是另一个地址的内容。
实操。
- 确认器件容量是否超过 3 字节地址寻址上限;超过就必须规划地址模式。
- 查 datasheet 确认该料号进入/退出 4 字节地址模式的命令,以及上电默认是 3B 还是 4B。
- 在 SDK 源码里搜索地址模式切换相关代码,确认 BootROM → SPL → 应用这条链上每一步切换之后,下一步的假设与之匹配。
- 用逻辑分析仪看地址字节数是 3 个还是 4 个,与实际访问的地址范围对照。
验证。在最高地址段写一组已知 pattern,断电重启后从主控侧读回,确认读到的就是写进去的内容(而不是低地址回卷过来的内容)。
4.5 根因 5:烧录后未做主控侧交叉验证
原理。烧录器的 verify 是一个闭环自查:它按自己的时序、自己的电压、自己的读命令把刚写进去的字节读回来比对。这个闭环里完全不包含目标主控。板上的差异可能来自:供电电压不同、VCCQ 不同、走线带来的信号完整性差异、主控 SPI 控制器的读命令与 dummy 配置不同、主控侧有额外的上拉/下拉。
实操。把交叉验证做成样机 bring-up 的固定动作,见 2.2 节。补充两个容易漏的检查:
- 烧录时器件是否真的处于可写状态:状态寄存器里的块保护位(BP 一类)如果锁住了目标区间,写操作会被静默忽略,而有些烧录器的 verify 读的是缓存而非器件 —— 这种情况下「verify 通过」是完全假的。
- 烧录器的供电电压与板上是否一致:3.3 V 烧、1.8 V 用,读的裕量完全不同。样机上如果转接板/座子有自己的供电,一定要确认电压域。
验证。主控侧读回结果与镜像文件二进制一致,且连续多次上电都能稳定读到。
4.6 根因 6:样机专属 —— 飞线、转接板、手焊
原理。这一类根因只会在样机阶段出现,量产板不会出现,因此最容易被忽略:FAE 往往先怀疑配置和镜像,查了一圈才回头看板。飞线把 SPI 信号引到板外,引入了量产板上不存在的寄生参数与噪声耦合。
| 样机特有做法 | 引入的问题 | 排查/规避动作 |
|---|---|---|
| 飞线过长(>10 cm) | CLK 边沿变缓、建立保持裕量丢失;高频下直接读错 | 尽量缩短;CLK 串联阻尼电阻;先降频验证再升频 |
| CLK 与 DI/DO 并行长距离走线 | 串扰,DO 上出现与 CLK 相关的毛刺 | 信号之间插地线;或把 CLK 单独走、远离数据线 |
| 转接板 / 烧录座没有地回流路径 | 回流面积大,信号完整性差,Quad 模式下更严重 | 转接板上铺地、多打地过孔;用尽量短的排线 |
| 手工焊接虚焊 / 连锡 / 助焊剂残留 | 接触电阻大、漏电,表现为「偶尔能起来」 | 显微镜下复检;清洗助焊剂;必要时重新拖焊 |
| 上拉/下拉电阻焊错位置或阻值不对 | CS# 上电期间不确定、IO 电平被拉偏 | 按原理图逐颗核对;特别注意 CS# 上拉与 WP#/HOLD# 的处理 |
| 多颗器件共用一套飞线(没做好片选隔离) | 多主多从冲突,总线上出现两个驱动源 | 确认未选中的器件处于高阻;必要时只焊一颗做最小系统 |
当怀疑是样机板级问题时,按这个顺序砍变量:
① 只焊一颗 SPI NOR,去掉板上所有其它外设;② 用最短的线、直连而不是飞线(必要时做一块最小转接板);③ 强制最低频率 + 单线 0x03 读;④ 烧最小可启动镜像(只要 SPL 能打印即可,不要带内核)。
四步做完能起来,再逐项把变量加回来,每加一项上电验证一次。这个方法的价值不在于「修好」,而在于把「板级 / 配置 / 镜像」三类问题干净地分开。
矩阵的用法:先按最上面两行把系统调到「一定能起来」的保守档(1-1-1 + 0x03 慢读),确认链路、供电、镜像都没问题;然后按图下半部分的顺序,一次加一个变量往上走。任何一档加上去之后起不来,根因就锁死在刚加的那个变量上。