资讯动态

嵌入式系统启动流程全解析:从复位向量到main函数

发布时间:2026/10/3 15:15:22 来源:尧图企业网站定制
相信不少做嵌入式的朋友都有过这种经历拿到一块全新开发板照着例程把固件下载进去按一下复位键串口里该打印的东西都正常出来程序跑得稳稳当当。但要是哪天这块板子“起不来了”你插上调试器看着PC停在某个莫名奇妙的地址翻遍网络也找不到答案这时候你才会意识到——原来自己一直不太清楚从按下电源键到main函数之间芯片里到底发生了什么。这条“上电之后到用户代码之前”的路就是我们常说的嵌入式系统启动流程。整个链路通常由三个角色接力完成芯片出厂固化的ROM Code、负责搬运和装配的Bootloader、以及每个工程里那段不起眼的启动代码。它们各自承担什么任务、彼此怎么衔接、出了问题会表现出什么症状是每个嵌入式开发者迟早要补的一课。这篇内容适合刚接触嵌入式系统的新手也适合正在调试启动问题、准备做IAP/OTA升级、或者想深入理解U-Boot启动原理的开发者。1. 上电之后芯片到底在做什么——启动流程的整体地图1.1 一段代码要跑起来最少需要哪三样东西很多人写单片机程序默认从main函数开始看世界好像代码天生就该在main里正常运行。但CPU不是这么想的。它上电之后只是个“没有剧本的演员”连第一条指令在哪里执行都不知道。要让一段代码真正跑起来硬件层面至少满足三个前提条件。首先是CPU得知道从哪个地址取指令这个地址就是复位向量或复位入口。第二个前提是栈指针要有效因为C语言里但凡有个函数调用、局部变量都得靠栈来支撑。第三个前提是内存里该初始化的数据必须初始化好全局变量要有初值未初始化的全局变量区域要清零。这三件事没有一件能靠编译器自动完成必须由代码在进入main之前亲手布置。而谁来布置就是启动流程要解决的问题。你可以把整个过程理解成一场接力赛ROM Code跑第一棒Bootloader跑第二棒启动代码跑第三棒最后把接力棒交到你写的main函数手里。1.2 ROM Code、Bootloader、启动代码的分工边界这三个名词经常混着用但它们的身份和职责差别非常大。我见过不少人在讨论“Bootloader启动链接脚本”的时候实际想的是STM32里那几百行startup汇编代码也见过有人把ROM Code和Bootloader当成一回事。先把分工盘清楚后面分析问题才不会被绕晕。ROM Code是芯片出厂时就固化在内部只读存储器里的代码通常叫Boot ROM或者固化引导程序。它不可修改容量通常只有几十KB上电后CPU首先执行的就是它。Bootloader则是可编程、可更新的一段引导程序典型代表就是U-Boot或者MCU里你自己写的IAP程序。它的职责是初始化硬件、加载下一阶段代码。启动代码往往指的就是某个具体工程里的startup文件一小段汇编程序负责把C语言的运行环境搭好。角色存在位置能否修改主要职责典型例子ROM Code芯片内部ROM出厂固化不可改初始化最小系统从启动介质加载BootloaderSTM32系统存储器程序、i.MX Boot ROMBootloaderFlash、SD卡、eMMC可更新初始化DDR等外设加载App或内核U-Boot、Barebox、IAP程序启动代码与固件一起烧录到Flash随工程修改设置栈、拷贝数据段、清零BSS、进入mainstartup_xxx.s、crt0.S1.3 三种典型启动链路从MCU到应用处理器在不同形态的嵌入式系统里这三个角色的出场方式不太一样。小单片机STM32走的是最简链路上电后硬件自动从Flash取出栈指针和复位入口启动代码做初始化然后直接进入main。整个过程里ROM Code的角色很轻只在BOOT引脚选择从系统存储器启动时才出面。Cortex-A系列的应用处理器链路则长得多BootROM读取启动介质加载SPLSPL初始化DDR之后加载主U-BootU-Boot最后加载内核。PC上那条BIOS/UEFI到GRUB再到内核的路本质上也是同一套思想。虽然表现形态不同核心逻辑始终一致用一小段“饿不死”的代码把更大的代码装配起来。理解了这个整体地图下面我们就可以逐个环节拆开看。2. ROM Code芯片出厂自带的“第一口奶”2.1 ROM Code 存在哪、为什么改不了ROM Code是芯片在流片阶段就写死在硅片上的物理上位于芯片内部的只读存储器区域。它最大的特点就是“改不了”。不是软件层面对它做了写保护是硬件设计之初就没打算让你改。这就像人一出生就会的呼吸和心跳不是后天学的也不需要你干预。为什么芯片厂商宁可牺牲灵活性也要固化这一小段代码因为总要有一段代码能处理“最原始的启动场景”。上电这一刻外部Flash可能是空的DDR还没初始化时钟可能跑在内部RC振荡器上甚至连启动介质里的数据是不是合法都还无法确认。这种环境下唯一可靠的代码就是出厂自带、不可能被破坏的那一段。很多平台上ROM Code还能驱动最基础的外设比如从UART或USB口接收数据把程序烧进Flash。像STM32的系统存储器启动模式就是这么回事BOOT引脚选择从System Memory启动后CPU执行的其实是出厂固化的一段ROM Code它检测UART或USB上有没有下载命令有就接收新固件写进Flash。这也是为什么你第一次拿到一块新芯片里面什么都没有却依然能通过串口下载程序。2.2 复位向量与第一条指令不同处理器各走各的不同处理器家族找第一条指令的方式差距很大但都绕不开“复位向量”这个概念。Cortex-M系列的做法比较温和上电后CPU会自动从地址0x00000000读取初始栈指针从0x00000004读取复位中断服务程序的地址然后跳过去执行。这就是为什么Cortex-M工程里向量表最开头两项永远是栈顶地址和Reset_Handler地址。Cortex-A系列和很多老架构则更直接复位后PC直接指向一个固定地址比如0x00000000ROM被映射到这个地址上CPU从那里取第一条指令开始执行。还有一些处理器支持多个启动入口通过芯片引脚或eFUSE配置来选择。这里需要特别提醒新手一点Cortex-M里看到的0x00000000常常不是物理ROM而是一段可重映射的地址空间。STM32上同一段地址可以映射到主Flash、系统存储器或者SRAM具体映射到哪由BOOT引脚和选项字节共同决定。如果不理解这层映射关系就会出现“我明明把程序下进去了芯片上电却没跑”的困惑。2.3 ROM Code 怎么决定从哪个设备加载ROM Code自身容量很小不可能把所有驱动都装进去。它能支配的硬件资源非常有限但有一条非常重要它能读取启动模式配置然后决定下一步从哪个设备加载Bootloader。配置方式大致分两类。一类是引脚电平配置比如开发板上几个拨码开关拨到不同位置决定从SD卡、eMMC、SPI NOR还是NAND启动。另一类是芯片内部的一次性可编程存储比如eFUSE或OTP寄存器里烧录了启动设备顺序。前者适合开发和调试阶段后者适合量产锁定。想要改变启动介质必须先让ROM Code有机会跑起来这本身就是一个启动流程问题。ROM Code从启动介质里读数据时通常不是盲目地把整块介质读一遍而是读取特定位置的数据结构。比如有的平台要求Bootloader镜像头部有固定的魔数、长度、校验值甚至要求Bootloader存储在介质上的固定偏移处。数据不合法ROM Code就不会跳过去执行而是可能回退到下一个启动介质或者进入串行下载模式。2.4 为什么ROM Code不能直接当Bootloader用要回答这个问题得先看看现代Bootloader的功能需求。一个合格的Bootloader要能初始化DDR内存控制器、配置复杂时钟树、枚举网络接口、支持USB下载、实现环境变量存储、解析FAT或ext4文件系统甚至还要有Graceful的异常处理和各种命令交互。这些功能加起来体积轻松超过几百KB有的甚至到MB级别。ROM Code的所有代码都必须在芯片出厂前写死开发商无法在芯片出厂后增删功能。如果芯片里的一个硬件勘误需要Bootloader绕过也只能等下一版流片。更何况ROM的存储空间极其宝贵芯片厂商不可能放着大容量的Flash/SD不用却把几百KB代码塞进只读存储里。所以ROM Code的定位永远是“种子”而不是“大树”。它只负责在条件最简陋时完成最小初始化把有完整能力的Bootloader拉到DDR里然后跳转过去。它存在的价值主要不在于能做什么而在于无论系统处于什么状态总有一个可靠的起点。3. Bootloader从只读世界到可编程世界的桥梁3.1 Bootloader 真正要干的活是什么Bootloader这个词直译是“引导加载程序”。从字面看它只做两件事引导下一段代码把它加载到内存里。但实际工程里Bootloader承担的远比这多。尤其要做嵌入式Linux开发时U-Boot几乎是绕不开的。你可以把Bootloader理解成一个装修队ROM Code只给你一间毛坯房Bootloader负责通水通电、铺地板刷墙直到能把家具搬进去。具体到U-Boot它的职责一般包括初始化系统时钟和DDR内存控制器保证CPU能以合适频率运行、内存可以稳定读写初始化串口输出日志让人能看到启动过程实现Flash、SD卡、网络等驱动程序为加载内核做准备读取环境变量和启动命令决定从哪个分区加载内核最后把内核镜像和设备树搬到内存设置好启动参数跳入内核。对MCU来说Bootloader通常是开发者自己写的任务要轻很多检测是否有升级请求有则接收新固件写入Flash没有则跳转去执行应用。不管是U-Boot还是IAP程序本质逻辑一样先自举再跳转。3.2 链接地址与重定位Bootloader 搬家的学问很多从单片机转向Linux开发的工程师第一次被Bootloader“卡住”的地方不是代码逻辑而是编译地址和运行地址的匹配问题。U-Boot的编译结果为什么不能像STM32那样烧到任意地址都能跑原因在于链接脚本里写死了“运行地址”。链接地址指链接器生成代码时假设代码将要运行的内存地址。如果一条跳转指令的目标地址被提前算好为0x80100000而代码实际跑在0x90F00000那这条跳转就跳到虚无缥缈的地址去了。所以Bootloader的设计通常会分阶段第一阶段放在ROM Code能加载到的SRAM里运行因为此时DDR还没初始化代码只能呆在小容量SRAM里第二阶段代码随链接地址被绑定到DDR的某个地址上运行。U-Boot的SPL和主U-Boot正是这种思路的典型实践。SPL体积小能在SRAM里跑先做基础初始化和DDR初始化然后把主U-Boot从Flash拷到DDR检查校验和最后跳转。跳转之前主U-Boot的链接地址和加载地址必须一致否则跑起来会立刻崩溃。这个“加载地址到底该写多少”的问题背后就是链接地址与运行地址是否匹配的工程问题。3.3 U-Boot 的启动链路从 SPL 到 KernelU-Boot的启动流程一直是嵌入式Linux面试高频问题也是排查启动失败绕不开的知识点。完整的U-Boot启动可以分为三个阶段ROM Code加载SPL、SPL初始化内存并加载主U-Boot、主U-Boot加载内核。第一个阶段里芯片厂把SPL单独编译成一个几十到一百多KB的小镜像ROM Code按固定格式把它读到SRAM中。SPL开始时从架构相关的汇编入口开始执行典型路径是arch/arm/cpu/armv7/start.S它在reset标签处把CPU切到SVC模式关掉中断、MMU和Cache然后跳入C语言代码。第二阶段里SPL调用board_init_f等初始化函数逐步打开UART、时钟、DDR等然后从启动介质里读取主U-Boot镜像。第三阶段里主U-Boot在DDR中运行提供完整的命令交互环境之后通过bootcmd变量里的命令加载内核到DDR指定地址设置bootargs最终跳转入内核入口。这个链路里有一个很容易被忽略的点每个阶段给下一阶段传递的信息非常少。SPL不知道主U-Boot的完整环境主U-Boot也不关心上一阶段干了什么。这就要求每个阶段都足够“自立”所有必要的状态都自己重新初始化。这也是为什么调试启动问题时文档里会建议你抓全每个阶段的串口日志只看最后阶段是看不到前面问题的。3.4 广义 BootloaderMCU 的 IAP 与 OTA聊完U-Boot再把目光转回MCU。很多工程师做产品升级时会接触IAPIn Application Programming和OTA这其实就是MCU领域里的Bootloader。STM32的IAP程序会被烧录在Flash的起始地址比如0x08000000而真正的应用App烧录在更后面的地址比如0x08020000。芯片上电后先执行IAP程序IAP程序检查是否需要升级需要就从UART、CAN或网络接收新固件写入App区域不需要就直接跳转到App入口。跳转App的过程非常考验对启动流程的理解。跳转前要关闭全局中断防止中断向量表切换过程中来了中断导致崩溃要把栈指针设置为App向量表的第一项要根据App是否启用RTOS或中断重映射来设置向量表偏移寄存器最后确认App的栈指针地址合法、复位入口地址在Flash范围内再执行跳转。我见过不少工程师在这里踩坑App程序单独下载到烧录地址能跑但通过IAP跳转过去就死机。排查下来多半是跳转前中断没关干净或者向量表偏移没有设置中断一旦触发就跑到老向量表去了。4. 启动代码C语言世界的第一级台阶4.1 为什么第一条指令必须用汇编写C语言无疑是嵌入式开发的主流但每个工程的第一段代码却几乎都是用汇编写的。这不是程序员偏爱汇编而是C语言的运行环境还没建立好时C代码根本没法跑。C代码编译后会默认依赖栈、依赖全局变量的初始值、依赖调用约定。可CPU上电那一瞬间栈指针要么是随机值要么是复位默认值全局变量所在的RAM区域全是上电随机数甚至你要调用的memset、memcpy都还没有准备好。这种情况下任何一句C代码都可能因为“栈还没设好”而崩溃。所以第一段代码必须用汇编因为它可以做C语言做不到的事直接给栈指针寄存器赋值、直接操作特殊寄存器、在没有任何函数调用的前提下顺序执行。汇编启动代码的任务也很有数设置栈指针、关闭中断、把数据段从Flash复制到RAM、清零BSS段做完了这一步C世界的大门才算打开。4.2 启动代码到底要做哪几件事不同编译器、不同芯片的启动代码略有差别但核心任务大体一致。以Cortex-M平台为例一份典型的startup_xxx.s启动代码大致做以下几件事。先定义中断向量表把栈顶地址放在向量表最开头后面依次放复位、NMI、HardFault等异常处理函数入口。然后定义复位中断服务函数Reset_Handler这是CPU上电后真正执行的第一个用户代码。Reset_Handler里先取出栈指针并写入SP然后调用SystemInit做时钟和系统初始化再调用C库的初始化函数__main由__main完成RW数据段拷贝、ZI段清零、调用C全局构造器最后才进入main。一份粗线条的启动代码看起来大约是这样Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, __initial_sp MOV SP, R0 BL SystemInit LDR R0, __main BX R0 ENDP值得一说的是这份代码里每个被IMPORT进来的符号都对应着链接脚本里定义好的值不是凭空想出来的。__initial_sp是链接脚本算出来的栈顶地址__main是C库提供的初始化入口。理解启动代码必然要同时理解链接脚本两者是配合工作的。4.3 链接脚本启动代码背后的地图很多开发者能看懂启动代码本身但一遇到链接脚本就头大。事实上启动代码里那些看起来“玄乎”的符号全部来自链接脚本。链接脚本就像一张地图告诉链接器代码段放在哪个地址、数据段放哪、栈留多少空间。一个典型的GCC链接脚本核心内容包括MEMORY命令和SECTIONS命令。MEMORY命令定义芯片里有哪些可用内存区域以及它们的起始地址和大小。SECTIONS命令则描述所有输入段如何被合并到输出文件中放在哪个地址。链接脚本里通常会定义__initial_sp、__bss_start、__bss_end这类符号。启动代码里给栈指针赋值的那个__initial_sp就是在脚本里用你预留的栈空间地址计算出来的。拷贝数据段时用到的__data_load、__data_start、__data_end也都是脚本自动生成的地址符号。没有链接脚本启动代码连栈顶在哪都不知道。所以以后写代码时如果改了内存布局或栈大小一定要同步修改链接脚本而不是闷头改启动代码。两者一个动、另一个也要动。4.4 启动代码里最常见的四类坑先说栈溢出。栈分配太大Flash不够用太小程序跑复杂逻辑就栈溢出。在Cortex-M上常见的排查手段是查看栈顶附近的关键字标记是否被改写。第二种坑是BSS段没清零现象是那些“未初始化”的全局变量上电初值奇奇怪怪判断逻辑跟着出错。这件事必须由启动代码或C库初始化去做应用代码无法替代。第三种坑是中断向量表偏移设置错误。MCU运行App时没有采用默认向量表地址IAP跳转后如果不把VTOR寄存器指向App向量表位置哪怕App本身没问题一个中断就会让程序跑飞到旧向量表。第四种坑是高段位问题SystemInit或板级初始化函数里用到了一个尚未初始化好的全局变量形成“先有鸡还是先有蛋”的死锁。调试这种问题最好把初始化代码里的全局变量依赖全部去掉寄存器操作就老老实实写寄存器。5. 三兄弟协同从复位到 main 的实战视角5.1 一条完整的时间线把前面三个角色串起来一条完整的启动时间线大概是这样的。芯片上电复位CPU从复位向量拿到第一条指令执行ROM Code。ROM Code读取启动配置从启动介质里读取Bootloader镜像做基础校验后跳转。Bootloader开始执行初始化时钟、内存和必要的接口把下一阶段代码加载到内存再次跳转。应用里的启动代码接管设置栈、完成数据段和BSS段初始化进入main。整个过程对用户代码透明但每一级跳转都有前提条件上一级必须确认下一级的代码确实位于正确地址并且内容合法。任何一级出错启动流程都会中断或者进入某个回退分支。这也是为什么排查启动问题时第一件事永远是确定“现在执行到哪一级了”而不是瞎猜。5.2 STM32 的启动过程拆解拿STM32举例这个很多人都熟悉。上电后CPU根据BOOT0/BOOT1引脚或选项字节的状态决定从主Flash、系统存储器还是SRAM启动。从主Flash启动时CPU直接把0x00000000映射到主Flash地址0x08000000读取向量表前两项跳入Reset_Handler。从系统存储器启动时CPU执行的是出厂固化的ROM Code它检测串口或USB的下载命令把新固件写入Flash。在工程层面startup_stm32f103xe.s这类启动文件承担了启动代码的职责。它定义完整向量表声明系统中断入口Reset_Handler里调用SystemInit配置时钟再调用C库初始化。结合链接脚本里的内存布局最终完成从Flash里把向量表和代码映射到0x08000000把RW数据拷贝到SRAM把ZI段清零然后跳转到main。站在这一层往下看IAP升级之所以要在App里做向量表重映射正是因为App的向量表不再位于0x08000000默认位置。理解了启动过程这些“套路”就不再是死记硬背的知识点而是顺理成章的设计。5.3 应用处理器上的启动链路拆解再往大一点的系统看应用处理器上启动链路会多出不少环节。以常见的ARM Cortex-A系列SoC为例上电后BootROM从CPU内部ROM开始执行它初始化最基础的系统然后根据启动引脚选择介质读取介质特定偏移处的Bootloader镜像。在部分平台上BootROM会检查镜像头部的一个数据结构里面记录了镜像加载地址、跳转地址、长度和校验信息。BootROM加载出来的往往是SPL或MLO这样的轻量Bootloader。SPL在SRAM中运行因为此刻DDR还不能用。SPL逐步打开时钟初始化DDR控制器确认DDR读写稳定后再从启动介质里把完整U-Boot镜像加载到DDR跳转执行。主U-Boot启动后打印大量日志如果你看到熟悉的U-Boot版本号和命令行提示符说明Bootloader阶段基本没问题了。随后U-Boot按环境变量里设置的bootcmd执行可能从网络、SD卡、eMMC里读取Linux内核和设备树放到DDR指定位置最后通过跳转把CPU控制权交给内核。内核接手后继续初始化自己的内存管理和驱动框架最终挂载根文件系统执行userspace的init进程。5.4 安全启动为什么有些 Bootloader 读不了也改不动近几年安全启动在嵌入式领域越来越普遍不少开发者会遇到“明明有Bootloader源码烧进去就是跑不起来”的困惑。原因多半是平台启用了安全启动链每一步都要求镜像带有合法签名ROM Code不再无脑加载任何数据。在安全启动体系里ROM Code内置了根密钥或者根密钥的哈希它验签第一级Bootloader镜像验不过就不执行。第一级Bootloader继续验签下一级一级一级传导下去直到内核。密钥通常能写入一次性可编程存储区域一旦烧入并封闭后门就关了。这也是为什么某些设备上的Bootloader分区在量产机上不允许被随意读取和改写。对做产品的人来说开发阶段可以把安全启动关掉方便调试量产时则一定要打开。对只想研究启动流程的开发者来说了解安全启动的原理有助于你判断问题到底出在“代码写错”还是“签名没做”上。出现启动被安全策略拦截的情况正确的路径是使用官方工具链生成自己的密钥并正确签名而不是试图绕过安全机制。加载日志里如果看到签名校验失败之类的提示基本就能定位到这一层。6. 启动失败排查现象、根因与调试工具6.1 启动失败现象与对应环节速查表做了这么多年嵌入式我最大的体会是启动类问题最怕的不是难而是没有方向。拿到一个问题先判断“卡在哪一个环节”比盲目改代码有效十倍。下面这张表是我整理的高频启动故障现象和对应排查方向。故障现象大概率所在环节首要排查点上电后芯片无任何反应电流很小电源、复位电路先量电源电压和复位时序再查调试器能否连接串口完全无输出ROM Code或Bootloader启动介质选择对不对Bootloader下载地址是否正确ROM Code阶段就反复复位启动介质、看门狗抓复位波形检查看门狗配置、启动介质镜像是否合法Bootloader打印到一半死机DDR或时钟初始化检查DDR参数、时钟树配置量时钟输出SPL能跑主U-Boot起不来加载地址和链接地址不匹配对比U-Boot的链接地址和实际加载地址U-Boot能进内核起不来内核镜像、设备树确认设备树和内核是否匹配内核启动参数是否合理下载App后没反应启动地址或向量表确认下载地址与启动地址一致检查BOOT引脚IAP跳转后死机中断向量表偏移确认VTOR寄存器指向App向量表跳转前中断已关闭这几类现象里前四类最容易被误判成“芯片坏了”。实际上很多时候只是启动介质没有选对或者镜像没有放在Bootloader期望的偏移位置。6.2 调试启动流程的几件趁手工具启动流程调试和普通应用程序调试不太一样它发生在程序入口之前很多常规调试手段失效。我调试启动阶段问题时常用的工具按优先级去排首先是带串口的调试终端其次是JTAG/SWD调试器再往后是示波器和逻辑分析仪。串口日志是启动调试最直接的信息来源。ROM Code阶段通常没有串口输出但一旦进入SPL或U-Boot日志就会非常丰富。很多平台支持设置串口打印等级能输出更底层的信息。第二件工具调试器也很关键比如用OpenOCD加J-Link连接芯片后在启动代码入口打断点单步执行观察PC指针和栈指针的变化。这能非常直观地看到“程序到底死在哪个地址”。示波器主要用于检查电源轨的上电时序、复位信号时序、外部时钟晶体是否起振。我遇到过一块板子起不来排查到最后是复位芯片的复位延时不够导致上电时序不满足芯片手册要求。这种问题你用逻辑分析仪抓一下复位引脚和电源引脚的时序关系一眼就明白。6.3 别把环境问题误判成启动问题做嵌入式开发时还有一个高频干扰项来自开发环境的报错。比如某些IDE的终端提示“进程启动失败退出代码为-1”或者“终端将被任务重用按任意键关闭”这种错误通常和芯片的启动流程毫无关系问题出在编译器路径、调试器驱动、串口被占用或者终端环境配置上。我的处理习惯是先分清楚“报错发生在编译下载阶段”还是“程序已经烧录执行之后”。前者优先排查工具链和调试器连接后者才需要动启动流程的脑筋。有一个小技巧特别管用当程序下载后没有反应时先用调试器读一下PC指针如果PC停在0xFFFFFFFF或者复位向量附近很大概率是Flash里根本没写入有效程序或者启动地址不对。这种情况下串口有没有输出都不重要最重要的是确认Flash内容是不是你想要的固件。6.4 我的启动代码调试心法最后分享几个这些年攒下来的习惯。拿到一块新板子第一件事不是急着写业务代码而是把启动流程完整走一遍阅读芯片手册里复位、启动配置、时钟树三章把启动模式引脚理清楚打开原厂提供的启动文件和链接脚本对照内存地址走一遍每个符号的含义然后烧一个最简单的点灯程序证明启动链路通、编译工具链通、烧录过程通。调试启动问题时我一般先加串口打印或者用调试器单步确定卡在哪个阶段再看那一阶段的源码。如果真的怀疑是某个硬件初始化不对就量对应引脚的波形数据用证据而不是猜测来做判断。启动代码这件事最忌讳的就是凭感觉改来改去因为它牵一发动全身任何一个环节出问题表现都可能非常相似。我个人还有一个习惯把每个平台的启动文件单独放在一个目录里备份并在文件头标注清楚它适用的芯片型号、链接地址、内存布局。真到了量产年份久远、人员更替的时候这些看起来不起眼的记录才是排查问题最可靠的依据。嵌入式系统启动流程说复杂也复杂说简单也简单关键在于把三个阶段彻底理清知道每一级在哪个地址、做什么事、验证什么。理解到这一层很多看似诡异的问题都会变得清晰起来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑