1. 项目概述:嵌入式系统启动的基石
每次给一块新的嵌入式板卡上电,看着串口终端里一行行启动日志滚动出来,最终进入熟悉的命令行或者应用界面,这个过程背后其实是一场由硬件和固件精密配合的“交响乐”。而这场演出的总指挥,就是固化在芯片内部只读存储器(ROM)中的那一段代码,我们通常称之为BootROM或ROM代码。它的任务简单而艰巨:在一片“混沌”的硬件世界里,建立起最基本的秩序,找到我们存放在存储介质里的“乐谱”(也就是我们的应用程序镜像),并把它加载到正确的位置,然后交棒执行。
最近在为一个基于TI OMAP平台的老项目进行启动优化,不得不再次深入研读那份厚重的技术参考手册(TRM)。手册里关于ROM代码初始化的章节,尤其是从FAT文件系统解析到配置头(Configuration Header, CH)处理,再到最终镜像执行的完整链条,其设计之精巧,细节之丰富,让我这个老嵌入式开发者也时常有新的收获。这不仅仅是芯片厂商提供的一个黑盒流程,更是理解整个系统从“无”到“有”的关键。掌握它,意味着你不仅能解决“板子为什么不启动”这类基础问题,更能进行深度的性能调优和定制化引导。今天,我就结合手册内容和这些年的实操经验,把这套启动“内功”拆解清楚,希望能帮你少走些弯路。
2. ROM代码引导流程全景解析
2.1 引导流程的宏观阶段划分
ROM代码的执行并非一蹴而就,而是一个环环相扣的严谨过程。我们可以将其宏观上划分为几个清晰的阶段,理解每个阶段的目标,是后续深入细节的基础。
第一阶段:硬件自检与最小化初始化。芯片上电或复位后,ROM代码首先从固定的复位向量地址开始执行。这个阶段的核心任务是让芯片自己“活过来”。代码会初始化最核心的CPU状态(例如设置ARM处理器为Supervisor模式、禁用中断)、配置必要的锁相环(PLL)以产生基础时钟信号(例如为调试串口UART和USB接口提供48MHz或96MHz的时钟),并初始化用于早期调试和状态输出的硬件模块(如UART)。这个阶段对内存控制器(如SDRC)、复杂外设(如GPMC)都还保持默认或未配置状态,因为系统可能还没有可用的内存空间。
第二阶段:引导设备枚举与探测。系统有了“心跳”之后,ROM代码需要知道从哪里加载我们的应用程序。芯片设计时通常会定义一套引导顺序(Boot Order)。以我手头的OMAP3530为例,其默认顺序可能是:XIP Memory -> NAND Flash -> MMC/SD -> UART -> USB。ROM代码会按照这个顺序,依次尝试初始化每一种可能的引导设备接口。例如,轮到检查MMC/SD卡时,它会以低速模式(如400kHz)初始化MMC/SD控制器,尝试读取卡的类型、容量等信息。如果某个设备不存在或初始化失败,则记录状态(通常通过内部的追踪向量Tracing Vector)后,迅速切换到下一个设备。这个过程就像一个人依次尝试用钥匙开锁,直到找到匹配的那一把。
第三阶段:引导介质解析与镜像定位。成功识别出引导设备后,ROM代码需要在该存储介质上找到具体的引导文件。对于像NAND、MMC/SD这样的块设备,文件系统(如FAT)解析就发生在这个阶段。ROM代码会读取存储设备第一个扇区的主引导记录(MBR)或引导扇区,获取文件系统参数(如每扇区字节数、每簇扇区数BPB_SecPerClus),然后遍历FAT表和根目录,寻找预定义文件名(如MLO,u-boot.bin)的文件。对于XIP(Execute In Place)存储器如NOR Flash,则可能直接从固定偏移地址开始执行代码,无需复杂的文件解析。
第四阶段:镜像加载、配置与跳转执行。找到目标文件后,ROM代码会将其内容从存储设备读取到指定的内存地址。这个阶段的一个关键点是处理可选的配置头。如果镜像前部存在有效的CH,ROM代码会解析其中的参数,并据此覆盖默认的硬件配置,例如将系统时钟提升到更高频率,或者精细调整SDRAM的时序参数以提升稳定性。最后,ROM代码将CPU的程序计数器(PC)跳转到镜像的入口地址,并将一个包含引导设备类型、配置状态等信息的引导参数结构体指针通过寄存器(如ARM的R0)传递给我们的应用程序,完成权力的交接。
2.2 引导设备类型及其特性
不同的引导设备对应着不同的硬件接口和协议,ROM代码需要适配每一种情况。理解它们的差异有助于我们在设计硬件和准备镜像时做出正确选择。
XIP存储器(如NOR Flash):这是最简单的引导方式。CPU可以直接从NOR Flash的地址空间取指执行,无需将代码拷贝到RAM。ROM代码只需配置好对应的内存控制器(如GPMC)的时序,然后直接跳转到NOR Flash的映射地址(如0x08000000)开始执行。它的优点是启动速度快,代码可直接运行;缺点是NOR Flash成本高、容量小。在OMAP中,XIP引导的镜像格式最为简单,通常就是一个纯二进制代码,前面可以带一个可选的配置头。
NAND Flash:这是嵌入式领域最常用的低成本、大容量非易失存储方案。但NAND访问需要复杂的控制器(如ELM)进行纠错(ECC),且不能直接执行(非XIP)。ROM代码中的NAND驱动会初始化NAND控制器,尝试从NAND的前几个块(Block 0, 1, 2...)中读取引导加载程序。它需要处理坏块管理和ECC校验。手册中的追踪向量(Tracing Vector)有专门位指示“从Block 0/1/2/3尝试NAND引导”和“检测到MLC NAND”,这对于调试NAND启动失败极为有用。
MMC/SD卡:通过高速MMC/SDIO主机控制器(MMCHS)引导。ROM代码先将控制器置于识别模式(400kHz),确认卡存在后,切换到数据传输模式(默认19.2MHz)。之后,它像访问磁盘一样,读取卡上的FAT文件系统来定位引导文件。这种方式非常灵活,便于通过读卡器更新程序,是开发和调试阶段的常用选择。
串行外设引导(UART/USB):当以上存储设备均不可用时,ROM代码会进入外设引导模式。例如,在UART引导时,ROM代码会等待主机通过串口发送特定的引导消息和镜像数据。这种方式通常用于工厂烧录或深度恢复,速度较慢,但不需要依赖板上存储介质。
DiskOnChip (DOC):这是一种较老的集成闪存解决方案,手册中将其视为一种特殊的“类NOR”设备。ROM代码会以标准异步16位复用模式初始化GPMC来连接DOC,并使用其专有的M-System驱动进行识别和扇区访问。如果识别失败,则返回错误码。
实操心得:设备探测顺序的陷阱芯片的引导顺序通常是硬件绑定的,但有些平台可以通过上拉/下拉特定的启动引脚(Boot Pin)或读取熔丝(eFuse)设置来改变。在硬件设计时,务必确认这些引脚的电平状态是否符合你的预期。我曾遇到一个案例,板子总是试图从空的NAND启动,耗时数秒后才轮询到SD卡,导致启动缓慢。最终发现是启动引脚的上拉电阻未焊接,导致芯片误读了配置。最稳妥的方法是,在原理图设计和PCB检查时,将启动引脚的网络高亮,并对照数据手册反复确认。
3. FAT文件系统解析:ROM代码如何找到你的引导文件
3.1 FAT表解析的核心机制
当ROM代码确定从MMC/SD或支持FAT的存储设备引导时,它便化身为一个极简的文件系统阅读器。其首要任务是解读FAT(File Allocation Table),这是FAT文件系统用于记录文件簇链的核心数据结构。
FAT文件系统将存储空间划分为一个个簇,每个簇包含若干个扇区(由BPB_SecPerClus定义)。文件的内容就存放在这些簇中,特别是大型文件,会占用多个簇。FAT表就像一个“簇号地图”,每个表项对应一个簇,表项的值指明了该簇的下一个簇号,从而将文件占用的所有簇串成一条链。手册中的表26-40清晰地定义了不同FAT类型下,表项值的含义:
000h(FAT12) /0000h(FAT16) /?0000000h(FAT32):表示这是一个空闲簇,可以被新文件使用。002h-FEFh/0002h-FFEFh/00000002h-FFFFFEFh:表示这是一个已使用的簇,其值就是文件下一个簇的簇号。通过这个“指针”,ROM代码可以像遍历链表一样,读取文件的全部内容。FF7h/FFF7h/FFFFFF7h:标记为坏簇,ROM代码在读取时会跳过,这对于处理有缺陷的SD卡很重要。FF8h-FFFh/FFF8h-FFFFh/FFFFFF8h-FFFFFFFh:这是文件结束标志,表示这是文件占用的最后一个簇。
这里有一个关键细节:簇0和簇1的FAT表项是保留不用的,它们必须被设置为特定的值(如FAT16是FFF8h和FFFFh)。ROM代码在初始化FAT访问时,会检查这些值作为文件系统有效性的一个初步判断。
对于FAT32,还需要注意其表项实际只使用低28位(bits [27:0]),高4位必须保留为0。ROM代码在读取时会屏蔽高4位,我们在准备镜像时也无需关心它们。
3.2 根目录遍历与文件查找流程
知道了FAT的用法,下一步就是找到文件的“入口点”——根目录。ROM代码首先定位根目录的起始位置。
- 对于FAT12/FAT16,根目录位于固定的扇区,大小固定,直接读取即可。
- 对于FAT32,根目录本身也是一个簇链,其起始簇号记录在引导扇区的BPB_RootClus字段中。手册明确指出:“When accessing the root directory for FAT32, the ROM code starts from the root directory cluster entry and follows the linked chain to retrieve the clusters.” 这意味着ROM代码会像读取普通文件一样,通过FAT表遍历根目录簇链。
在根目录的每一个条目(通常是32字节)中,ROM代码查找文件名、扩展名、属性、起始簇号和高低文件大小等信息。当找到与预设引导文件名(例如MLO)匹配的条目时,它就获取到了该文件的起始簇号。
3.3 簇链遍历与数据加载
拿到起始簇号后,ROM代码就开始了实际的加载工作。它遵循以下步骤:
- 根据起始簇号,计算该簇对应的第一个扇区的物理地址。公式类似于:
数据区起始扇区 + (簇号 - 2) * 每簇扇区数。这里“减2”是因为簇号从2开始编号。 - 读取该簇的所有扇区数据到内部缓冲区。手册提到:“the ROM code buffers each FAT entry corresponding to the file in a sector way.” 这意味着它按扇区进行缓冲管理。
- 读取当前簇在FAT表中的对应表项,获取下一个簇的簇号。
- 如果下一个簇号是文件结束标志,则加载完成;否则,跳回步骤1,继续读取下一个簇。
- 将所有读取到的数据,按照镜像格式的约定(见下文),拷贝到指定的内存地址。
这个过程确保了文件在磁盘上可以碎片化存储,但能被连续地加载到内存中。
注意事项:FAT文件系统的格式化参数ROM代码对FAT文件系统的支持是有局限的。它通常只支持FAT12/16/32的基本格式,而不支持exFAT或NTFS。更关键的是,扇区大小(Sector Size)和每簇扇区数(Sectors Per Cluster)必须在其支持范围内。常见的兼容设置是:扇区大小为512字节,每簇扇区数为1(即一个簇一个扇区)或更大但为2的幂次方。使用不支持的参数格式化SD卡,是导致“找不到引导文件”的常见原因。我建议在Linux下使用
mkfs.vfat -F 32 -S 512 -s 1 /dev/sdX1命令进行格式化,以确保最大兼容性。
4. 配置头详解:定制化启动的钥匙
4.1 配置头的存在意义与结构总览
ROM代码的默认配置是“通用”且“保守”的,它要保证在未知的硬件环境下也能勉强启动。但为了发挥硬件的最佳性能,我们必须能够覆盖这些默认设置。这就是配置头存在的意义。它是一个可选的数据结构,放置在引导镜像的头部,允许我们在镜像被加载前,就由ROM代码完成对系统关键硬件的定制化配置。
根据手册,CH只支持从存储器类型设备引导时使用(如XIP, NAND, MMC/SD),而不支持UART/USB等外设引导。整个CH(包括TOC和所有配置项)必须能容纳在一个512字节的扇区内。
CH的结构非常清晰,采用了一个目录表加数据段的设计:
- TOC:位于CH的最开始,是一个由多个TOC项组成的数组,每个项指向一个具体的配置段(如CHSETTINGS, CHRAM)。TOC以一个偏移量为
0xFFFFFFFF的项作为结束标记。 - 配置段:每个TOC项指向一个具体的配置数据块,包含了要写入特定硬件寄存器的值。
每个TOC项的结构(表26-41)是固定的20字节:
Start(4字节):从TOC起始地址到本配置段实际数据的偏移量。Size(4字节):本配置段的大小。Reserved(12字节):保留字段,必须为0。Filename(12字节):以\0结尾的配置段名称字符串,如CHSETTINGS\0。
ROM代码通过检查第一个TOC项的Filename是否为已知字符串(如“CHSETTINGS”)来识别CH的存在。然后遍历TOC,依次处理每个有效的配置段。
4.2 CHSETTINGS:时钟系统配置
时钟是系统的脉搏,错误的时钟配置轻则导致外设工作不稳定,重则系统根本无法运行。CHSETTINGS段就是用来覆盖ROM代码默认的时钟配置的。
ROM代码默认会初始化一个基本的时钟树,例如为MPU、外设等提供基础时钟。但默认频率往往不是最优性能点。通过CHSETTINGS,我们可以配置锁相环(PLL)的倍频/分频参数、时钟源选择等,从而在启动早期就将系统时钟提升到目标频率。
其结构(表26-42)包含:
Section Key:固定为0xC0C0C0C1,用于验证段有效性。Valid:非0则启用本段配置。Version:版本号,0x0001。Clocking settings:一个时钟配置结构体,其字段与手册中“Software Booting Configuration”章节描述的完全一致,包含了各个PLL(MPU, CORE, PER等)的控制寄存器值。
一个重要限制:手册明确指出,用于引导的外设(如UART、USB)的时钟(96MHz和48MHz)不能在CH中修改,因为CH处理时这些外设可能正在被使用。这意味着你的定制时钟配置必须保证这些外设的时钟源是稳定的。
4.3 CHRAM:SDRAM/DDR SDRAM控制器配置
这是最常用也最关键的配置段。ROM代码默认不配置SDRC(SDRAM控制器),因为它无法预知板上焊接的是哪种型号、什么时序的SDRAM芯片。如果我们不提供CHRAM,那么ROM代码在将镜像加载到SDRAM后,跳转过去执行时,SDRAM控制器可能还处于未初始化状态,导致第一条指令就无法读取,系统挂死。
CHRAM段(表26-43)包含了SDRC所需的所有关键寄存器值:
SDRC_MCFG_x:内存配置寄存器,设置内存类型(SDRAM, Mobile DDR等)、数据总线宽度、CAS延迟等。SDRC_MR_x/SDRC_EMR1_x/EMR2_x/EMR3_x:模式寄存器,用于发送SDRAM初始化序列中的MRS命令。SDRC_ACTIM_CTRLA_x/ACTIM_CTRLB_x:时序控制寄存器,定义了tRAS,tRP,tRCD,tRC等关键的行、列访问时序参数。SDRC_RFRCTRL_x:刷新控制寄存器,设置刷新频率。
这些值必须严格匹配你所使用的SDRAM芯片的数据手册。通常,芯片厂商的SDK或参考板设计会提供一份正确的配置值,我们可以以此为起点进行微调。
4.4 CHFLASH与CHMMCSD:外设接口调优
CHFLASH:用于配置通用内存控制器。如果你使用NOR Flash、异步SRAM或FPGA通过GPMC接口连接,可能需要调整访问时序。ROM代码默认配置GPMC为异步模式、使能等待引脚、使用片选0、基地址
0x08000000。如果你的设备时序要求不同(如需要更长的建立/保持时间),或者使用了其他片选,就需要通过CHFLASH来覆盖默认设置。其结构(表26-44)包含了GPMC相关的各类时序和控制寄存器值。CHMMCSD:用于配置MMC/SD控制器。ROM代码默认以400kHz进行卡识别,以19.2MHz进行数据传输。对于支持更高速度的SD卡(如High-Speed模式),我们可以通过此段将数据传输时钟提高到更高频率(如25MHz或更高),从而显著加快从SD卡加载镜像的速度。其结构(表26-45)相对简单,主要包含
SYSCTRL寄存器值和Bus width(总线宽度,可配置1/4/8位)。
手册对CHMMCSD有一个特别说明:如果该段所有字段都被设置为0xFFFFFFFF,那么无论Valid位为何值,ROM代码都会认为此段未执行,并在传递给镜像的引导参数中标记此段无效。这提供了一种“条件忽略”的机制。
实操心得:如何生成正确的配置头数据?手动计算这些十六进制寄存器值极其繁琐且容易出错。标准的做法是:
- 借助参考代码:TI的SDK(如Processor SDK)中,对于每个评估板,都会有一个类似
board/evmXX/board_mem.c的文件,里面定义了get_sdram_config()这样的函数,返回一个struct sdram_config。这个结构体里的值就是经过验证的、针对该板SDRAM的配置。- 使用配置工具:一些芯片厂商提供图形化的引脚复用和时序配置工具(如TI的SysConfig),可以可视化地配置时钟树、DDR时序等,并直接生成C结构体或头文件。
- 从U-Boot中提取:成熟的Bootloader如U-Boot在初始化阶段一定会正确配置SDRAM。你可以通过调试器在U-Boot初始化SDRAM后,读取相关SDRC寄存器的值,将其整理成
CHRAM段的数据。- 验证与调试:最直接的验证方法是,将生成的CH集成到你的X-Loader(如MLO)中,上电观察系统是否能在预期频率下稳定运行。如果启动失败,首先检查
CHRAM的时序参数是否过于激进,可以尝试放宽(增大)tRAS,tRP等参数后再试。
5. 镜像格式与执行交接
5.1 通用设备镜像格式解析
找到文件并加载到内存后,ROM代码需要知道如何执行它。这由镜像格式决定。手册中定义了两种主要的镜像格式,适用于不同类型的引导设备。
1. 非XIP存储器引导镜像格式(用于NAND, MMC/SD等):这种镜像需要被“搬运”到RAM中执行,因此它的开头有一个简单的镜像头。
Size(4字节,偏移0x00):整个镜像(包括头部)需要被拷贝到内存的总字节数。Destination(4字节,偏移0x04):镜像在内存中的目标起始地址。这通常是SDRAM的某个地址(如0x80000000)。Image(变长,偏移0x08):紧接着头部的就是实际的二进制代码和数据。
ROM代码的工作是:读取头部的Size和Destination,然后将从偏移0x08开始的Size-8个字节的数据,拷贝到Destination指向的内存地址。
2. XIP存储器引导镜像格式(用于NOR Flash等):这种镜像直接在存储器中执行,无需拷贝。因此其格式最为简单:
Image(变长,偏移0x00):直接从存储器的映射地址开始就是可执行代码。在代码之前,可以放置一个可选的配置头(CH)。如果存在CH,ROM代码会先处理CH,然后再跳转到CH之后的代码处执行。
外设引导镜像格式(UART/USB):这种镜像没有任何头部,主机发送过来的数据流直接被ROM代码视为纯二进制代码,并加载到固定的内存地址执行。
5.2 执行交接与引导参数传递
当镜像被正确加载(或就位)后,ROM代码的最后一个动作是执行一条分支指令,跳转到镜像的入口点。对于非XIP镜像,入口点是Destination地址;对于XIP镜像,入口点是CH之后(或无CH时镜像开头)的地址。
一个至关重要的细节是:ROM代码通过ARM的R0寄存器,向我们的镜像传递了一个指向booting parameters structure(引导参数结构体)的指针。这个结构体(表26-47)是镜像了解“我是谁,我从哪里来”的关键信息来源,它包含了:
Current booting device:当前引导设备代码(0x01: XIP, 0x02: NAND, 0x06: MMC/SD1等)。你的镜像可以根据这个信息决定后续初始化哪些外设,或者从哪里加载下一阶段镜像(如内核)。CH flags:一个位图,指示哪些配置头段被成功解析和应用了(bit0: CHSETTINGS, bit1: CHRAM等)。你的镜像可以检查这些标志,确认硬件(如SDRAM)是否已按CH配置完成初始化。Device descriptor pointer:指向当前引导设备描述符结构的指针。通过这个指针,可以调用ROM代码中保留的设备驱动函数(如继续从该设备读取数据),这在实现多阶段引导时非常有用。
一个典型的引导加载程序(如U-Boot的SPL)的入口汇编代码,第一件事往往就是保存R0的值,然后在C语言初始化代码中,将其解析为引导参数结构体,并据此做出决策。
5.3 追踪功能:启动过程的“黑匣子”
手册中提到的Tracing Vector是一个极其强大的调试工具。它是一个位于RAM中的64位(两个32位字)的位向量,ROM代码在执行流程的关键节点会设置相应的位。例如:
- Bit 3: Booting started(引导开始)
- Bit 10: CH found(找到配置头)
- Bit 11: Image header correct(镜像头正确)
- Bit 34: NAND(从NAND引导)
- Bit 38: MMC/SD1(从MMC/SD1引导)
- Bit 48: MLC NAND image was detected(检测到MLC NAND镜像)
当系统启动失败,串口没有任何输出时,我们可以通过JTAG调试器连接芯片,在ROM代码执行后、我们的镜像运行前,读取这个追踪向量的值。通过查看哪些位被置位,我们可以精确判断启动流程在哪个环节失败了(例如,Bit 3置位但Bit 11未置位,说明引导开始了但镜像头校验失败)。这相当于给启动过程安装了一个“黑匣子”。
6. 唤醒引导与调试配置
6.1 从深度睡眠中唤醒:上下文恢复
在一些低功耗场景中,设备会进入深度睡眠状态(如CORE OFF),此时除了唤醒域(WKUP domain)外的所有配置都会丢失。当被唤醒时,系统需要恢复到睡眠前的状态,而不是进行一次冷启动。ROM代码支持这种唤醒引导。
其核心机制依赖于一块叫做CONTROL_SAVE_RESTORE_MEM的特定内存区域(位于SCM,地址0x48002910,共240字节)。在系统进入深度睡眠前,用户软件有责任将需要恢复的硬件寄存器值保存到这个区域中,主要包括两部分:
- PRCM块:保存时钟和电源管理相关的寄存器值(表26-50)。
- SDRC块:保存SDRAM控制器的配置寄存器值(表26-51)��
CONTROL_SAVE_RESTORE_MEM的头部有几个关键指针(表26-49):
Public Restore Pointer:这是必须由用户设置的字段。它指向一个用户自定义的恢复函数地址。SDRC Block Offset/PRCM Block Offset:指向上述保存的SDRC和PRCM配置数据在内存块内的偏移量。
唤醒流程如下:
- 唤醒事件触发,芯片复位(但属于唤醒复位)。
- ROM代码运行,检测到是唤醒复位。
- ROM代码根据
SDRC Block Offset和PRCM Block Offset,自动从CONTROL_SAVE_RESTORE_MEM中恢复SDRC和PRCM的寄存器配置。这一步恢复了最基本的时钟和内存环境。 - ROM代码跳转到
Public Restore Pointer指向的用户函数。 - 用户函数执行更复杂的上下文恢复,例如恢复其他外设状态、内核状态等,最后跳转到主应用程序继续执行。
这要求我们的系统在进入深度睡眠前,必须正确地组织并填充CONTROL_SAVE_RESTORE_MEM结构,否则唤醒后将无法恢复。
6.2 通过JTAG进行早期调试
当系统完全无法启动,甚至串口都没有输出时,JTAG调试是最后的救命稻草。手册第26.6节详细描述了调试配置。
关键点在于初始扫描链配置。芯片上电时,EMU0和EMU1引脚的状态决定了JTAG TAP(测试访问端口)控制器的初始连接方式。手册推荐在POR时将这两个引脚上拉,使能TAP router-only mode。在这种模式下,TAP路由器是TDI和TDO之间唯一的TAP,其指令寄存器(IR)长度为6位。
调试器(如Lauterbach Trace32, DS-5等)在连接时,需要先通过一系列特定的JTAG扫描序列,将ARM CoreSight DAP(调试访问端口)等内部TAP控制器添加到扫描链中。手册中给出了一段类似伪代码的序列,描述了如何设置前导/后导计数、发送特定指令和数据来配置TAP路由器,最终将DAP接入。这个过程通常由调试器的脚本或自动检测功能完成,但了解其原理有助于在连接失败时进行排查。
一个重要的警告:在启动调试器之前,必须确保包含调试逻辑的电源域(DEBUG domain)已被激活。方法是,在拉高nTRST信号后,至少向TCK引脚施加10个时钟脉冲。此外,为了防止JTAG_TMS引脚在EMU电源域关闭时浮空,建议在外部添加一个10kΩ的上拉电阻。
一旦DAP被成功接入扫描链,调试器就可以通过DAP的地址空间(如表26-53所示,例如Cortex-A8模块在0x54011000,ETM在0x54010000)直接访问整个系统内存空间,无需CPU介入。这允许我们在ROM代码运行期间设置断点、查看和修改内存,是分析启动失败原因的终极手段。
常见问题与排查技巧实录在实际开发中,ROM代码初始化阶段的问题五花八门,但又有规律可循。下面是我总结的一些典型问题及排查思路,可以做成一个速查表:
现象 可能原因 排查步骤 上电后毫无反应,调试器也无法连接 1. 电源异常。
2. 时钟未起振。
3. 启动引脚配置错误,芯片进入非预期模式。
4. JTAG连接问题。1. 测量核心电压、IO电压是否稳定且在容差范围内。
2. 测量主晶振是否有波形,幅度频率是否正确。
3. 用万用表测量BOOT[0:N]引脚的电平,与原理图和数据手册的启动顺序表对比。
4. 检查JTAG线序、nTRST、EMU0/1的上拉,确保TCK上有脉冲。串口有部分乱码或输出后停止 1. 串口波特率不匹配(ROM代码默认波特率通常是115200或更低)。
2. SDRAM未初始化或配置错误,代码在尝试使用SDRAM时挂死。
3. 配置头(CH)中的时钟配置导致系统不稳定。1. 尝试不同的波特率(9600, 115200, 38400等)。
2.重点检查CHRAM段。确认时序参数与SDRAM芯片手册完全匹配,可尝试放宽时序。若无CH,则镜像必须自己初始化SDRAM。
3. 暂时移除或禁用CHSETTINGS,使用默认时钟测试。提示“Loading…”后卡住或重启 1. 引导设备初始化失败(如SD卡识别不了)。
2. FAT文件系统无法解析。
3. 找不到引导文件(如MLO)。
4. 镜像头损坏或格式错误。1. 确认SD卡电压、引脚连接。尝试更换SD卡,并用兼容参数重新格式化(FAT32, 512字节扇区)。
2. 检查SD卡是否包含有效的MBR/FAT引导扇区。
3. 确认引导文件名是否正确,且位于根目录。
4. 使用二进制工具检查镜像文件头部的Size和Destination字段值是否合理(如未溢出)。能加载但跳转后立即跑飞 1. Destination地址非法或未初始化(如指向了未初始化的SDRAM)。
2. 镜像入口点代码错误(如第一条指令就是无效指令)。
3. 中断向量表设置错误。1. 确认 Destination地址位于已初始化的内存范围内(如SDRAM有效区域)。
2. 用反汇编工具查看镜像开头几条指令是否合法。
3. 检查镜像是否设置了正确的中断向量表(例如,复位向量是否指向有效的C入口函数)。使用CH后系统不稳定 1. CHRAM时序过于激进,SDRAM访问出错。
2.CHSETTINGS时钟配置超频,导致系统锁相环失锁或时序违例。
3. CH数据本身损坏或TOC格式错误。1. 使用更保守的SDRAM时序参数。
2. 逐步提高时钟频率,测试稳定性。检查PLL锁定状态寄存器。
3. 计算CH数据CRC或使用校验和,确保数据在存储和加载过程中未出错。检查TOC结束项是否为0xFFFFFFFF。独家避坑技巧:
- 制作一个“万能”调试SD卡:准备一张SD卡,除了放置正确的
MLO和u-boot.img,还可以在FAT分区里放几个不同版本的镜像(如带CH和不带CH的),甚至一个简单的内存测试程序(二进制格式)。通过修改文件名,可以快速切换引导镜像进行测试。- 利用ROM代码的追踪向量:如果芯片支持且你有JTAG调试器,在系统复位后、任何用户代码运行前, halt住内核,直接读取RAM中追踪向量的地址(具体地址需查芯片手册),其值能告诉你ROM代码执行到了哪一步。
- CH的渐进式验证:不要一次性启用所有CH段。先只启用
CHRAM,确保SDRAM能工作;然后再加入CHSETTINGS进行超频;最后再考虑CHFLASH等。这样可以隔离问题。- 仔细核对字节序和位域:CH中的数据是直接写入寄存器的原始值。务必确认你的生成工具和芯片的字节序(通常是小端)一致,并且寄存器中每个位域的值都填对了位置。一个常见的错误是将位域的位置搞错,例如某个配置位在寄存器的[5:3],却误填到了[7:5]。