嵌入式系统启动流程深度解析:从ROM代码到Bootloader加载
2026/7/22 20:11:45 网站建设 项目流程

1. 嵌入式系统启动流程全景解析

每次给一块全新的嵌入式板卡上电,看着串口终端里一行行启动日志滚动出来,最终进入熟悉的命令行或者应用程序界面,这个过程看似理所当然,背后却是一套精密而复杂的“开机自检”流程在默默工作。这套流程的起点,就是固化在芯片内部只读存储器(ROM)中的那一段代码,我们称之为ROM代码或BootROM。它就像是设备的“出厂预设本能”,在CPU复位后第一个获得执行权,负责唤醒沉睡的硬件,并找到存储在外部的“操作系统说明书”——也就是引导加载程序(Bootloader),将其搬运到内存中执行。

对于嵌入式开发者,尤其是做底层驱动和系统移植的工程师来说,深入理解ROM代码的启动流程,尤其是它对NAND Flash、eMMC、SD卡这些非易失性存储设备的初始化与检测过程,是解决各种“板子不启动”、“镜像烧录失败”问题的关键。这不仅仅是理论,更是实战中排查问题的地图。今天,我们就以TI(德州仪器)某款处理器的ROM代码为例,拆解从芯片上电到成功加载第一段外部代码的完整旅程,重点聚焦在NAND Flash和MMC/SD卡的设备检测与初始化上。你会发现,那些看似神秘的“启动失败”,背后无非是时序不对、参数不匹配或者坏块没处理好。

2. ROM代码的使命与启动设备选择逻辑

2.1 ROM代码的核心职责与工作环境

ROM代码是芯片设计阶段就烧录进去的,无法修改。它的首要任务是在一个“一无所有”的环境里,为后续软件的运行搭建起最基础的舞台。这个环境有多原始呢?CPU刚复位,内部RAM可能还未初始化,所有外设都处于复位或未知状态,更没有文件系统、驱动这些概念。ROM代码就是在这样的“蛮荒之地”开始工作的。

它的核心职责可以概括为三点:

  1. 最小化硬件初始化:配置最基础的系统时钟、必要的电源管理域,以及初始化用于与外部存储通信的控制器,比如通用内存控制器(GPMC)用于NOR/NAND,MMC/SD控制器用于存储卡。它不会初始化所有外设,只做启动所必需的那部分。
  2. 枚举与检测启动设备:按照预设的顺序(通常由芯片的启动模式引脚SYSBOOT[15:0]的电平决定)去尝试与各种可能的启动设备通信。常见的顺序可能是:XIP NOR Flash -> NAND Flash -> MMC/SD -> UART -> Ethernet等。一旦在某个设备上找到了有效的启动镜像,流程就会继续,否则尝试下一个。
  3. 加载并验证初始软件:从检测成功的设备中,读取预先约定格式的镜像文件(例如TI的.bin文件,包含大小、校验和、加载地址等信息),将其拷贝到指定的内存地址(通常是内部RAM或已初始化的外部DDR),并进行简单的校验(如校验和),最后跳转到该地址执行。

注意:ROM代码加载的通常不是完整的操作系统,而是一个被称为“第一阶段引导加载程序”的小程序,比如U-Boot的SPL(Secondary Program Loader)或MLO文件。这个程序会完成更复杂的硬件初始化(如DDR),然后加载更大的第二段引导程序或直接加载内核。

2.2 启动模式引脚:告诉ROM代码从哪里开始找

芯片如何知道该先去检查哪个设备呢?答案就在SYSBOOT引脚上。这些引脚在上电复位时的电平状态,会被硬件锁存到一个特定的寄存器中,ROM代码读取这个寄存器来决定启动顺序。

以NAND启动为例,芯片手册中通常会有一个类似下表的定义:

SYSBOOT[n]引脚名称描述
[14:13]CS0MUX[1:0]必须设置为00b,表示非复用设备(对于NAND)。
[12]CS0BW必须设置为0如果使用8位NAND;必须设置为1如果使用16位NAND。
[15]CS0WAIT对于NAND启动,必须设置为0

为什么这些引脚配置如此重要?

  • CS0MUX[1:0]:这告诉GPMC控制器,连接到CS0片选信号的设备是哪种地址/数据线复用模式。NAND Flash是典型的非复用设备(地址和数据通过不同的周期传输),所以设为00
  • CS0BW:这是硬件设计的关键。它直接决定了GPMC控制器在初始化时,是以8位还是16位宽度去尝试与NAND通信。如果你板子上焊的是16位NAND,但这个引脚被错误地上拉或下拉成了0,ROM代码就会以8位模式去读ID,很可能读不到正确的设备ID,导致检测失败。这个错误非常隐蔽,因为软件无法在ROM阶段更改这个配置。
  • CS0WAIT:NAND Flash操作(如擦除、编程)需要时间,会通过R/B#(Ready/Busy)引脚告知控制器。这个引脚通常连接到GPMC的WAIT信号。但在ROM代码最初的设备检测阶段,可能采用延时等待或轮询状态寄存器的方式,所以这里通常设为0(不使用WAIT功能),或者根据具体硬件设计决定。

实操心得:在画原理图时,务必根据你选用的NAND Flash型号(8位或16位),正确配置SYSBOOT[12]引脚的上拉或下拉电阻。这是硬件层面的“硬编码”,一旦出错,软件无法补救。

3. NAND Flash设备检测与初始化的深度剖析

NAND Flash因其高容量、低成本的优势,成为嵌入式系统大容量存储的首选。但它并非“即插即用”的设备,其初始化过程比NOR Flash复杂得多,主要挑战在于:接口时序复杂、存在坏块、需要ECC纠错、以及设备参数(页大小、块大小)不统一。

3.1 GPMC控制器初始化:为通信铺平道路

ROM代码要操作NAND,首先需要初始化与它连接的桥梁——GPMC控制器。GPMC是一个非常灵活的接口,可以模拟多种异步存储器的时序。对于NAND,ROM代码会将其配置为异步模式,并设置一套相对保守、兼容性较好的初始时序参数。

根据参考文档中的时序图(Figure 4-11)和参数表(Table 4-11),我们可以理解这些参数的意义。假设GPMC时钟为55MHz(周期约18.18ns),ROM代码可能会设置如下时序(单位:时钟周期):

参数描述值(时钟周期)计算出的时间(ns)
twr写周期周期30545.4
trd读周期周期30545.4
tCEon片选(CE)有效时间00
tOEonCE有效到输出使能(OE)有效的时间7127.3
tWEonCE有效到写使能(WE)有效的时间590.9
trddataCE有效到数据锁存的时间21381.8
tOEoffCE有效到OE无效的时间24436.4
tWEoffCE有效到WE无效的时间22400.0

为什么是这些值?这些是初始化的安全值,确保能与市面上大多数速度不高的NAND Flash进行最基本的通信(如发复位命令、读ID)。它们比NAND Flash数据手册中要求的最小值要大得多,以保证可靠性。在后续的Bootloader(如U-Boot)中,我们会根据读取到的NAND型号,查询其数据手册,配置一套更精确、性能更优的时序参数。

关键点:ROM代码在初始化GPMC时,会释放地址总线,因为NAND使用复用的I/O口来传输命令、地址和数据,而不是独立的地址线。数据总线宽度初始设为8位,如果后续检测到是16位设备,会再动态调整。

3.2 设备检测与参数获取:ONFI与备选方案

这是NAND启动最核心也最容易出错的环节。ROM代码需要知道面对的NAND是“谁”(制造商、容量)以及“什么样”(页大小、块大小、OOB大小、ECC要求)。它通过两级探测机制来实现。

第一步:尝试ONFI标准识别ONFI(Open NAND Flash Interface)是一个标准化组织,定义了NAND Flash的通用接口和识别方法。支持ONFI的NAND会在特定地址返回一个签名。

  1. 复位与等待:发送复位命令FFh,并等待最多250ms让NAND完成内部初始化,轮询其状态直到就绪。
  2. 发送ONFI Read ID命令:发送命令90h,地址20h。这是ONFI标准规定的“读ID”命令地址。
  3. 检查签名:读取4字节数据。如果它们是4Fh 4Eh 46h 49h(即“ONFI”的ASCII码),则表明这是一个ONFI设备。
  4. 读取参数页:发送命令ECh,读取完整的参数页(通常256字节)。从这个参数页中,可以解析出页大小(如2048或4096字节)、OOB/备用区大小(如64或128字节)、每块的页数、所需的地址周期数等关键信息。这些信息是准确访问NAND的基础。

第二步:回退到旧式ID表查询如果ONFI识别失败(旧款或不兼容的NAND),ROM代码会回退到传统方法。

  1. 再次复位
  2. 发送标准Read ID命令:发送命令90h,地址00h。这是绝大多数NAND都支持的标准命令。
  3. 查询内置ID表:读取返回的ID字节流。通常第二个字节是“设备ID”。ROM代码内部维护了一个庞大的设备ID对照表(如参考文档中的Table 4-13),通过匹配这个ID来查找对应的设备参数(容量、总线宽度、页大小)。例如,读到设备ID为DAh,查表可知这是一个2Gb容量、8位总线、页大小为2048字节的NAND。

第三步:特殊模式——从I2C EEPROM读取几何参数对于一些非常规或自定义的NAND模块,其几何参数可能无法通过上述两种方式获得。ROM代码支持一种特殊的NANDI2C启动模式。在此模式下,它会尝试从一个I2C EEPROM(从地址50h)的特定偏移(80h开始)读取7字节的NAND几何信息。这7个字节定义了页大小、块大小、总线宽度、ECC类型等。这为定制化硬件提供了灵活性。

常见问题与排查

  • 问题:板子从NAND启动失败,串口无任何输出。
  • 排查思路
    1. 检查硬件:确认SYSBOOT引脚配置是否正确(特别是总线宽度)。测量NAND的电源、复位信号是否正常。
    2. 检查ID读取:如果可能,在ROM代码运行前,用示波器或逻辑分析仪抓取GPMC总线在发送90h 00h命令时的波形,看NAND是否有数据返回。没有返回,可能是硬件连接、上拉电阻或时序问题。
    3. 核对ID表:将读取到的设备ID与芯片手册中的ROM支持列表对比。如果你的NAND不在列表中,ROM代码将无法识别,这是最直接的“不支持”错误。
    4. 时序问题:虽然ROM代码使用保守时序,但在某些高速或特定型号NAND上仍可能失败。这需要结合具体芯片数据手册分析。

3.3 坏块管理与ECC纠错:保障数据可靠性

NAND Flash由于物理特性,出厂时就会存在一些“坏块”,并且在生命周期中还会产生新的坏块。ROM代码在寻找启动镜像前,必须避开它们。

坏块检测方法: ROM代码通常只检查前几个块(例如前4个块),因为Bootloader镜像就存储在这里。它通过读取每个块**第一页和第二页的OOB(Out-Of-Band)备用区的第一个字节(8位设备)或字(16位设备)**来判断。

  • 好块:该位置的数据为FFh(8位)或FFFFh(16位)。
  • 坏块:该位置的数据不是FFh/FFFFh。这个标记是由NAND生产商或烧录工具在出厂/编程时写好的。 ROM代码会跳过被标记为坏块的区域,只在好块中搜索启动镜像。

ECC纠错——数据的守护者: NAND Flash在读写过程中可能发生位翻转。ROM代码使用硬件ECC(纠错码)引擎(如BCH算法)来检测和纠正这些错误。

  • ECC计算与存储:当数据被写入NAND的一个页时,GPMC控制器会为每512字节的扇区计算出一段ECC校验码,并写入该页对应的OOB区域中。
  • ECC校验与纠错:当从NAND读取数据时,GPMC会再次计算读取数据的ECC校验码,并与OOB中存储的原始ECC码进行比较。
    • 如果一致,数据无误。
    • 如果不一致,且错误位数在ECC纠错能力之内(如BCH8可纠8位/扇区),则ELM(Error Location Module)等硬件模块会尝试纠正错误,返回正确数据。
    • 如果错误位数超出纠错能力,则扇区读取失败,ROM代码会认为该扇区损坏。
  • ECC类型选择:对于不同容量和类型的NAND,所需的ECC强度不同。ROM代码会根据设备ID(特别是第4个ID字节的某些位)自动选择使用BCH8还是更强的BCH16纠错。

实操心得

  1. 烧录工具至关重要:用于烧写Bootloader到NAND的工具(如flash_erase,nandwrite)必须支持跳过坏块,并且能正确计算和写入ECC信息。如果工具不写ECC,或者ECC格式与ROM代码预期不符,即使数据烧进去了,ROM代码读取时ECC校验也会失败。
  2. 镜像位置:确保你的Bootloader镜像烧录在NAND的起始好块中。通常烧录工具会自动处理,但你需要知道镜像实际被写到了哪个物理块,这在调试时很有用。
  3. OOB布局:不同页大小(2KB vs 4KB)的NAND,其OOB区域内ECC数据的存放偏移是不同的(参考Figure 4-15, 4-16)。ROM代码和你的烧录工具、后续的Linux内核驱动,必须使用一致的OOB布局,否则ECC数据对不上,必然导致读取失败。

4. MMC/SD卡启动与FAT文件系统解析

相较于NAND,从MMC/SD(包括eMMC)卡启动是另一种常见方式,尤其在开发阶段非常方便,因为可以通过读卡器在PC上直接准备镜像。

4.1 设备检测与初始化:区分MMC与SD

ROM代码通过MMC/SD控制器(如MMC1接口)与设备通信。初始化流程如下:

  1. 电压设置:首先尝试用标准高电压(3.0V)与设备通信。
  2. 发送CMD0:使设备进入空闲状态。
  3. 鉴别设备类型
    • 发送CMD1(发送操作条件)。如果设备响应,则识别为MMC设备。
    • 如果不响应,则发送CMD55(应用特定命令)后跟ACMD41(发送操作条件)。如果设备响应,则识别为SD卡(包括SDHC/SDXC)。
    • 如果两者均无响应,则认为该接口上无设备,启动失败。
  4. 获取RCA:为检测到的设备分配一个相对卡地址(RCA),用于后续通信。

4.2 两种启动模式:Raw模式 vs FAT文件系统模式

ROM代码支持从MMC/SD设备以两种方式加载镜像:

1. Raw模式(原始扇区模式)在这种模式下,ROM代码将存储设备视为一个原始的、按扇区寻址的块设备。它会在固定的几个扇区偏移量(例如0, 128KB, 256KB, 384KB)处寻找一个特殊的结构——TOC(Table of Contents)和配置头(CH),以确认镜像的存在并获取加载信息。

  • 优点:简单直接,无需解析文件系统,常用于eMMC等嵌入式存储。
  • 限制:镜像必须放置在精确的扇区地址,且大小通常受限(如不超过128KB),否则会覆盖下一个可能的镜像位置。

2. FAT文件系统模式这是更常用的方式,尤其对于可插拔的SD卡。ROM代码内置了一个轻量级的FAT12/16/32文件系统解析器。

  • 镜像文件:ROM代码会在存储设备上寻找一个名为MLO的特定文件(对于TI平台)。这个文件就是第一阶段引导加载程序。
  • 支持的结构
    • 无MBR(软盘模式):整个卡就是一个FAT分区。
    • 有MBR(硬盘模式):卡的第一个扇区是主引导记录(MBR),其中包含分区表。ROM代码会查找一个活动的、主分区类型为FAT12/16/32的分区。
  • 流程
    1. 读取第一个扇区,检查末尾签名是否为0xAA55,判断是否存在MBR。
    2. 如果存在MBR,遍历其4个分区表项,找到活动且类型为FAT的主分区。
    3. 读取该分区的第一个扇区(DBR,DOS Boot Record),解析BPB(BIOS Parameter Block)信息,确认是有效的FAT分区,并计算出FAT表、根目录区的位置。
    4. 读取根目录区,查找名为MLO的文件条目(只支持8.3短文件名格式)。
    5. 找到文件后,从目录条目中获取文件的起始簇号。
    6. 读取FAT表,将文件所占用的所有簇号链缓存到内存中的一个“FAT缓冲区”。这样,ROM代码在后续读取文件数据时,就可以通过这个映射表,直接由文件内的逻辑扇区号转换到存储设备上的物理扇区号(LBA),而无需每次都解析FAT。

4.3 FAT文件系统解析的关键步骤

ROM代码中的FAT解析器是精简而高效的,它只关心如何找到MLO文件。以下是几个关键检查点,也是制作可启动SD卡时容易出错的地方:

  1. BPB有效性检查:ROM代码会验证DBR中的关键字段,例如:

    • BPB_BytsPerSec:必须是512(ROM代码通常固定处理512字节扇区)。
    • BPB_SecPerClus:必须是2的幂次方(如1,2,4,8...)。
    • BPB_RsvdSecCnt:保留扇区数,FAT32通常为32。
    • BPB_NumFATs:FAT表副本数,通常为2。
    • BPB_RootEntCnt:FAT12/16的根目录条目数,FAT32此处为0。
    • 分区大小计算必须自洽。
  2. 计算FAT类型:ROM代码通过计算数据区的簇数来确定FAT类型:

    • 簇数 < 4085 -> FAT12
    • 4085 ≤ 簇数 < 65525 -> FAT16
    • 簇数 ≥ 65525 -> FAT32 这个计算必须与BS_FilSysType字段(如“FAT32”)匹配,但ROM代码更相信计算出来的结果。
  3. 查找MLO文件:在根目录区(FAT12/16)或由BPB_RootClus指定的簇链(FAT32)中,线性搜索目录项。目录项的文件名字段必须是“MLO”(大写,后跟空格填充),属性为普通文件(非卷标、目录、隐藏、系统)。

制作可启动SD卡的避坑指南

  • 格式化工具:在Windows下,不要使用默认的“快速格式化”,建议使用diskpartcleancreate partition primary+format fs=fat32 quick命令,或使用第三方工具如rufusWin32DiskImager。在Linux下,使用fdisk分区后,用mkfs.vfat -F 32格式化。
  • 文件命名:确保文件系统上的文件名就是大写的MLO。在Linux下复制后,可以用dosfsck -v检查。避免使用长文件名。
  • 分区与活动标志:如果使用MBR分区,确保目标分区是主分区,类型是0x0B0x0C(FAT32),并且使用fdiskdiskpart将其设置为**活动(active)**分区。
  • 文件放置:直接将MLO文件拷贝到分区的根目录,不要放在任何子文件夹里。
  • 大小写敏感:FAT文件系统本身不区分大小写,但ROM代码的查找函数可能是大小写敏感的,严格按照MLO来匹配,所以使用大写最保险。

5. 启动失败常见问题与高级调试技巧

当你的板子无法启动,串口一片寂静时,不要慌张。我们可以通过一些方法和思路,层层递进地定位问题。

5.1 问题分类与初步定位

  1. 完全无输出(死寂)

    • 最可能:ROM代码本身未运行。检查电源、复位电路、时钟、启动模式引脚电平。
    • 其次:ROM代码运行了,但在初始化最基本硬件(如PLL、时钟)时就失败了。这需要结合芯片的勘误表和硬件设计检查。
    • 可能:ROM代码尝试从默认启动设备失败,且没有切换到下一个设备或没有触发任何错误指示(如点亮LED)。检查你的目标启动设备是否在支持列表中,硬件连接是否可靠。
  2. 有部分输出后停止

    • 如果芯片支持,ROM代码可能会通过某个UART端口输出简单的调试信息或错误码(例如,通过UART0输出字符C8等,具体需查芯片手册)。捕获并解析这些字符是黄金手段。
    • 输出可能表明它卡在了某个阶段,如“NAND ID read failed”或“MMC init failed”。
  3. 输出乱码或错误信息

    • UART配置错误:Bootloader和ROM代码的UART波特率、数据位、停止位设置不一致。
    • DDR初始化失败:如果ROM代码负责初始化DDR,但参数配置错误,可能导致向DDR拷贝数据或执行时崩溃。

5.2 基于ROM代码流程的专项排查

针对NAND启动失败:

  • “Device not found”
    • 查硬件:确认SYSBOOT引脚,特别是总线宽度。用万用表量电压。
    • 查连接:检查NAND芯片的CE#WE#RE#ALECLER/B#WP#等控制信号线是否连接正确,上拉电阻是否合适。
    • 测波形:用示波器在复位后抓CLEALEWE#RE#I/O线。你应该能看到ROM代码发送复位命令FFh和读ID命令90h 00h的波形。如果命令波形正确但无数据返回,问题在NAND侧(电源、复位、芯片损坏)。如果命令波形都没有,问题在SoC或GPMC配置侧。
  • “Bad block found” / “ECC error”
    • 确认烧录:你使用的烧录工具和命令是否正确?是否支持坏块管理?是否写入了正确的ECC?
    • 检查OOB:用编程器或能直接读NAND原始数据的工具,读出前几个块的页数据和OOB区,手动检查坏块标记和ECC数据是否正确。
    • 更换存储位置:尝试将Bootloader镜像烧录到NAND更靠后的位置(避开前几个物理块),并在Bootloader中修改加载地址。这可以排除前几个块恰好是坏块或物理损坏的情况。

针对MMC/SD启动失败:

  • “No MMC card present”
    • 检查卡槽的电源、检测脚(CD)、命令和数据线的连接。
    • 尝试更换一张不同的卡(品牌、容量)。
    • 确认ROM代码支持的卡类型(是否支持SDHC/SDXC?)。
  • “FAT fs error” / “MLO not found”
    • 确认文件系统:将SD卡插入电脑,确认分区是主分区、活动、FAT32格式。
    • 确认文件名:在磁盘根目录查看,文件名必须是大写MLO,没有隐藏的扩展名。
    • 使用二进制查看工具:如hexdumpWinHex,直接查看SD卡的第一个扇区(MBR)和FAT分区的第一个扇区(DBR),手动验证BPB字段和0xAA55签名。
    • 简化测试:尝试使用Raw模式,用dd命令将Bootloader镜像直接写入SD卡的固定偏移扇区(如sudo dd if=MLO of=/dev/sdX bs=512 seek=2048,偏移量需查手册),并配置为Raw模式启动,以绕过FAT文件系统解析的复杂性。

5.3 利用硬件调试器进行深度追踪

如果以上软件方法都无法定位,就需要祭出终极武器——硬件调试器(如JTAG/SWD)。

  1. 连接调试器:在板子上电前连接好调试探头。
  2. 暂停CPU:上电后立即暂停CPU(或设置复位后立即暂停)。
  3. 查看ROM代码:虽然ROM代码本身不可读,但你可以:
    • 单步执行:从复位向量地址开始单步,观察程序流。这需要芯片厂商提供ROM代码的符号文件或大致流程图(通常不提供),难度极高。
    • 设置数据观察点:在关键地址设断点。例如,在GPMC的数据寄存器地址设写断点,观察ROM代码是否在尝试发送NAND命令。在内部RAM的加载地址设读/写断点,观察ROM代码是否成功将数据加载到了内存。
    • 查看寄存器:检查GPMC、MMC控制器的��置寄存器,看其是否被ROM代码正确初始化。检查SYSBOOT状态寄存器,确认启动模式是否如预期。
    • 查看内存:在ROM代码执行完毕后(如果可能),查看它应该加载了Bootloader的内部RAM区域。如果那里全是0x000xFF,说明加载失败。如果数据看起来像有效的代码(有可识别的指令头),说明加载成功但执行失败。

调试ROM代码启动问题是对工程师硬件知识、软件理解和调试耐心的综合考验。最有效的方法永远是预防:在硬件设计阶段就严格参考芯片厂商的推荐原理图;在软件层面,使用经过验证的烧录工具和方法;在遇到问题时,遵循从简单到复杂、从硬件到软件的排查顺序,善用一切可用的日志和调试工具。理解本文剖析的每一个步骤,就等于拥有了解决大部分启动问题的路线图。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询