2、启动模式与介质:NOR Flash启动、NAND Flash启动、eMMC启动、SD卡启动、USB启动、UART启动
好,咱们进入第二章。这一章聊的是BootROM的“口粮”从哪来。
说白了,芯片上电后,BootROM要做的第一件事就是——找个地方把代码读进来。这个“地方”就是启动介质。不同的介质,脾气秉性完全不同。我这些年调过的板子,十有八九的启动问题都出在对介质特性的理解不够深上。
你想想看,BootROM是固化在芯片内部的只读存储器,它自己不会跑飞,但它要去“喂”自己第一口饭。这口饭从哪来?怎么嚼?嚼碎了往哪咽?这就是启动模式要解决的问题。
核心观点:启动介质的选择,决定了BootROM的初始化策略、加载速度、安全校验方式,以及整个系统的启动可靠性。选错了介质,或者没处理好介质的坑,轻则启动慢,重则变砖。
2.1 NOR Flash启动
NOR Flash,老大哥了。它的特点是支持XIP(eXecute In Place),也就是芯片可以直接在Flash上执行代码,不用先拷到RAM里。嗯,这里要注意,这个特性在BootROM场景下非常关键。
为什么?因为BootROM本身很小,它只需要把第二阶段的bootloader(比如U-Boot的SPL)从NOR Flash里读出来,然后跳过去执行。由于NOR Flash支持随机访问,BootROM可以直接通过地址总线映射到NOR Flash的存储空间,然后像读内存一样读指令。
我在项目中遇到过一件事:某款工业控制器,用的就是NOR Flash启动。当时发现系统上电后偶尔起不来,查了好久。后来发现是NOR Flash的复位时间比芯片慢了一拍。BootROM去读的时候,Flash还没准备好,读回来的全是0xFF。解决方案很简单——在BootROM里加一个超时重试机制。
个人经验:NOR Flash的读时序相对简单,但要注意它的“读操作”和“写/擦除操作”是完全两套逻辑。BootROM只负责读,所以不用管写。但如果你在调试阶段用编程器烧录NOR Flash,记得检查一下“复位后默认输出”是不是高阻态。我曾经被这个坑过,编程器烧完好好的,焊到板子上就读不出来。
2.2 NAND Flash启动
NAND Flash就不一样了。它便宜、容量大,但有个致命问题——不能XIP。而且NAND Flash天生带坏块,出厂就有,用着用着还会产生新的坏块。
BootROM要从NAND Flash启动,必须先把代码从NAND里读到芯片内部的SRAM或者DDR里,然后再跳转执行。这个“读”的过程,BootROM得自己实现NAND的控制器驱动。
你想想看,BootROM才多大?通常就几十KB。它要在这几十KB里塞下NAND控制器的初始化、时序配置、ECC校验、坏块管理……这活儿不轻松。
我记得有一次调试一个NAND启动的板子,BootROM总是加载到一半就卡死。后来用逻辑分析仪抓了NAND的引脚波形,发现是BootROM里配置的NAND时序参数跟实际颗粒不匹配。那个颗粒的tR(读操作时间)比标准值大了那么一点点,BootROM按标准值去等,结果数据还没准备好就读了。
避坑指南:我曾经因为NAND Flash的“随机读”和“页读”搞混,导致BootROM加载速度慢得离谱。NAND Flash读一个字节和读一页的开销差不了太多,所以BootROM应该按页读取,而不是一个字节一个字节地读。这个优化能让启动时间缩短一个数量级。
2.3 eMMC启动
eMMC,说白了就是把NAND Flash和一个控制器封装在一起,对外暴露一个标准接口。BootROM跟它打交道,用的是MMC/SDIO协议。
eMMC的好处是:坏块管理、ECC校验、磨损均衡,这些脏活累活eMMC自己内部搞定了。BootROM只需要发命令、读数据就行。但代价是——协议复杂。
eMMC启动有专门的“Boot Partition”模式。芯片上电后,eMMC会进入Boot模式,直接把Boot Partition里的数据通过数据线吐出来。BootROM只需要在数据线上等着收就行。这个模式不需要发命令,纯硬件流。
我个人习惯是:如果项目对启动速度要求高,优先考虑eMMC的Boot Partition模式。它比从User Partition读数据快得多,因为eMMC内部会以最高速度把数据推出来。
关键点:eMMC的Boot Partition大小通常是128KB或256KB,要确保你的bootloader能塞进去。塞不进去?那就只能用User Partition启动,但那样BootROM就得先初始化完整的MMC协议栈,代码量会大不少。
2.4 SD卡启动
SD卡启动,在开发板和单板计算机上很常见。它的协议跟eMMC同源,都是MMC协议衍生出来的。但SD卡有个特点——它是可插拔的。
这个“可插拔”带来了一个问题:BootROM怎么知道SD卡插没插?
通常芯片会有一个GPIO引脚,连接到SD卡座的卡检测开关。BootROM先读这个GPIO,如果检测到卡,再初始化SD卡控制器,发命令,读数据。如果没检测到,就跳到下一个启动介质。
我记得有一次,客户反馈说他们的设备用SD卡启动,有时候能起来有时候起不来。排查后发现是卡检测引脚的滤波电容太大,导致BootROM读GPIO的时候电平还没稳定。嗯,硬件设计上的小细节,往往就是这种问题。
建议:SD卡启动的BootROM代码里,一定要加超时处理。有些劣质SD卡初始化时间很长,甚至超过500ms。如果BootROM等不及就放弃了,那系统就启动失败了。我一般会在代码里留一个“长等待”模式,给慢卡一个机会。
2.5 USB启动
USB启动,这个就有点意思了。它不是为了量产用的,而是为了开发和调试。
BootROM通过USB跟PC上的工具通信,PC把bootloader镜像通过USB发过来,BootROM收到后写到RAM里,然后跳转执行。这样就不用反复烧录Flash了,开发效率高很多。
USB启动的难点在于:BootROM要实现USB Device协议栈。虽然不用实现完整的USB协议,但至少要实现枚举、控制传输、批量传输这几个基本功能。而且USB的时序要求很严格,BootROM得在中断或者轮询模式下及时响应USB总线事件。
我做过一个项目,BootROM的USB启动总是枚举失败。后来发现是芯片的USB PHY(物理层)初始化顺序不对。先复位PHY再配置时钟,和先配置时钟再复位PHY,结果完全不同。这个顺序在芯片手册里写得很隐晦,我是翻了应用笔记才找到的。
注意:USB启动通常需要PC端安装特定的工具软件,比如Rockchip的RKDevTool、Allwinner的PhoenixSuit。这些工具跟BootROM之间的通信协议是私有的,没有公开文档。如果你想自己写一个开源工具,得逆向分析协议,工作量不小。
2.6 UART启动
UART启动,最原始,也最可靠。它只需要三根线:TX、RX、GND。任何带UART的芯片都能用。
但UART的速度慢啊。通常115200bps,算下来每秒也就传11KB左右。一个256KB的bootloader,要传20多秒。所以UART启动只适合小镜像,或者作为最后的“救砖”手段。
BootROM的UART启动流程一般是:上电后先等一会儿,看UART RX引脚上有没有数据。如果有,就按约定的协议(比如XMODEM、YMODEM,或者自定义协议)接收数据,写到RAM里,然后跳转。
我曾经用UART启动救过一块变砖的板子。NAND Flash里的数据全乱了,NOR Flash也烧坏了,但BootROM还在。我通过UART把一个新的bootloader传进去,然后bootloader再去初始化eMMC,从eMMC里把完整的系统拉起来。嗯,那种感觉,就像给一个昏迷的人做心肺复苏。
实用技巧:UART启动的BootROM里,我建议加一个“超时自动跳过”的逻辑。如果几秒钟内没收到数据,就自动跳到下一个启动介质。这样量产的时候,不需要人工干预,板子自己会从正常的介质启动。只有需要调试的时候,才在PC端发数据触发UART启动。
2.7 启动介质对比总结
说了这么多,咱们用一张表来总结一下。这张表是我自己整理的,每次选型的时候都会拿出来看一眼。
| 启动介质 | 速度 | 可靠性 | 成本 | 适用场景 | BootROM复杂度 |
|---|---|---|---|---|---|
| NOR Flash | 中 | 高 | 高 | 小容量、快速启动、XIP需求 | 低 |
| NAND Flash | 高 | 中(需ECC) | 低 | 大容量、成本敏感 | 高 |
| eMMC | 高 | 高 | 中 | 主流嵌入式、Android设备 | 中 |
| SD卡 | 中 | 中 | 低 | 开发板、可插拔存储 | 中 |
| USB | 高 | 高 | 无额外成本 | 开发调试、量产烧录 | 高 |
| UART | 低 | 高 | 无额外成本 | 调试、救砖 | 低 |
2.8 启动介质选择的核心逻辑
最后,我用一张流程图来展示BootROM选择启动介质的核心逻辑。这张图是我自己画的,你可以把它当作一个决策树。
这张图展示的是最典型的流程。实际项目中,BootROM通常会按照一个优先级列表去尝试多个介质。比如先试eMMC,不行再试SD卡,再不行就进USB/UART等待模式。这个优先级是在芯片出厂时固化的,或者通过外部引脚配置的。
我个人习惯是:在产品设计阶段,就把所有可能用到的启动介质都列出来,然后跟芯片原厂确认BootROM支持的介质列表和优先级。有些芯片的BootROM不支持NAND启动,你非得用NAND,那就得在外部加一个小的NOR Flash或者SPI Flash来放一个“二级BootROM”,由它再去初始化NAND。嗯,这种方案虽然绕,但确实能解决问题。
好了,这一章的内容就到这里。启动介质是BootROM的“粮仓”,选对了粮仓,后面的安全回滚保护才能有基础。下一章我们会深入BootROM的启动流程,看看代码是怎么一步步跑起来的。