U-Boot移植实战:从BootROM、DDR初始化到设备树适配的关键路径

发布时间:2026/10/4 2:26:54
U-Boot移植实战:从BootROM、DDR初始化到设备树适配的关键路径 说实话嵌入式开发这个行当里能把 uboot 移植跑通的不少但能把“移植”这件事真正讲清楚的教程不多。很多人一拿到新板子就急着把源码放进去编译结果要么卡在启动介质引导头要么 DDR 初始化过不去要么串口根本没输出折腾一个礼拜还不知道问题出在哪。这篇文章不打算按“第几步做什么”来流水账式讲解而是把 uboot 移植前必须建立的硬件认知、配置系统逻辑、DTS 适配重点、调试三板斧和常见报错定位串起来讲一遍。适合刚入门的 Linux 驱动开发、BSP 工程师以及手里正好握着一块新板子需要快速 bring-up 的朋友。读完之后你至少能回答一个问题uboot 移植到底是在移什么。1. 拿到板子先看三样东西BootROM、DDR、串口1.1 上电那一刻BootROM 比 U-Boot 先跑很多人理解 uboot 移植以为就是从 uboot 源码开始。实际上 SoC 上电后芯片内部固化的 BootROM 会先执行它根据熔丝位、启动引脚或 OTP 配置决定从 SD、eMMC、NAND、NOR 还是 UART 去读取引导代码。BootROM 这一段代码是不可改的它只负责“把第一段引导代码搬进 SRAM然后跳过去执行”。于是问题就来了SRAM 容量非常有限几十 KB 到几百 KB 不等U-Boot 主镜像动辄几百 KB 甚至上 MB根本塞不进去。所以现代 SoC 普遍采用两级引导设计——SPLSecondary Program Loader先被 BootROM 加载进 SRAMSPL 负责完成最基本的初始化特别是 DDR 初始化然后把完整的 U-Boot 从存储介质搬到 DDR 里再跳转执行。有些平台甚至在 SPL 之前还有一级 TPL像瑞芯微的 RK3288/RK3399 就是 TPL SPL 的典型。移植者最常犯的错误就是只盯着 U-Boot 主镜像改忽略了 SPL 的编译产物、启动头格式和大小限制。比如很多 Cortex-A7/A9 平台的 SRAM 只有几十 KBSPL 一旦编译出来超过这个容量BootROM 只会读一半现象就是板子“死了”串口没有任何输出。这时候你调三天 uboot 配置都没用先把 SPL 的体积压下来再说。我在实际项目里遇到过 SPL 超了 4KB启动时随机失败最后就是把一些无关驱动从 SPL 配置里摘掉才稳定。1.2 DDR 参数几乎所有移植难点都在这张表上要说 uboot 移植里最磨人的部分DDR 初始化排第二没人敢排第一。DDR 控制器的时序参数包括 tRCD、tRP、tRFC、tFAW、tRAS、tWR 等等这些值由 DDR 颗粒的规格、工作频率和 PCB 走线长度共同决定。厂商的参考设计里通常会给出一个能跑通的 DDR3/DDR4 初始化序列有的是 DCD 表i.MX 系列有的是专门的 ddrbinRockchip有的干脆就是一套固定的寄存器操作代码。移植时最稳的做法是先照抄原厂参考设计的初始化参数烧进去能起来以后再通过 DDR 压力测试工具逐步收紧时序换性能。不要一上来就追求高频那是给自己挖坑。举个例子我之前在某 Cortex-A7 平台上调 DDR频率从 400MT/s 改成 533MT/s 后启动卡在 U-Boot log 的第二行怎么查都查不出原因。后来用仿真器去看 DDR 控制器的 Vref 校准寄存器才发现是校准参数没跟着频率一起调训练出来的参考电压完全偏了。这类问题靠肉眼 log 是看不出来的只能靠工具一层层扒寄存器。还有一点容易被忽略DDR 电源的上电时序。SoC 的 DDR 控制器手册里通常会有一张上下电时序图要求 VDD1、VDD2、VDDQ 按顺序上电间隔多少毫秒都有规定。如果板子的电源管理芯片配置不对DDR 颗粒的初始化训练就会间歇性失败。很多板子“昨天还能起来今天就不行了”大概率不是软件问题而是电源时序在临界状态。1.3 串口不通一切免谈U-Boot 移植调试的第一依赖不是网口不是 JTAG而是串口。BootROM 阶段通常不需要串口但 SPL 一旦跑起来第一行 log 就是从那一个 UART 出来的。串口不通你面对的就是一块“哑巴板子”所有调试手段都要打折。拿到新板子做 uboot 移植第一个目标就是让串口输出“U-Boot SPL ...”。这时候要确认三件事UART 引脚有没有被其他外设占用对应 pinmux 寄存器有没有在早期代码里配好串口时钟源频率和波特率分频是否匹配。很多板子“不启动”的真相只是串口号绑错了或者复用配置被人改了。我见过一个案子硬件工程师把 UART3 调试口和 UART2 的流控引脚做了 swap结果在设备树里查了半天最后拿万用表量引脚电平才发现的。所以说移植 U-Boot 之前先把原理图上的调试串口引脚、主控 pinmux 表那一页翻熟能不踩的坑先别踩。2. 配置系统不是玄学board 目录、defconfig 与 Kconfig 的三层关系2.1 新建板级目录的正确姿势而不是盲目复制改名U-Boot 的代码组织不是按“一块板子一个分支”来管理的而是按 vendor / board 两层结构。比如你要给一块新的 Cortex-A7 平台做支持会在 board 目录下新建一个厂商名目录再在其中建一个板子名目录。里面通常有几个固定文件Kconfig定义TARGET_BOARDNAME这个 Kconfig 符号作为整个编译系统的入口MAINTAINERS写维护者信息提交上游时必须有Makefile组织这个板子目录下的目标文件比如 board.oboard.c实现board_init、board_late_init、board_mmc_init等板级回调一股脑复制别的板子目录然后全局改名的做法我不是很推荐。因为 Kconfig 符号依赖、头文件 include 路径、defconfig 里CONFIG_SYS_CONFIG_NAME的指向都是联动的改漏一处编译直接报错或者编译过了但链接出来的镜像根本不是给这个板子用的。更隐蔽的问题是复制过来的 board.c 里可能有一堆原板卡特有的初始化代码比如某路 GPIO 的电平设置、某个 PMIC 的 I2C 配置这些在你的板子上根本不存在遗留下来就是隐患。正确做法是先建一个最小目录只放 Kconfig、MAINTAINERS、Makefile 和空的 board.c等编译通路完全打通之后再对照原厂 BSP 逐步往 board.c 里补初始化逻辑。2.2 defconfig 里最该盯住的几行U-Boot 的 defconfig 文件在 configs/ 目录下命名一般是厂商_板子_defconfig。文件内容看起来是一堆CONFIG_XXXy但真正决定平台走向的是这么几行CONFIG_SYS_SOCarmv7 CONFIG_TARGET_XXXXy CONFIG_DEFAULT_DEVICE_TREE厂商-板子CONFIG_SYS_SOC决定了编译哪个 arch 目录下的 CPU 相关代码CONFIG_TARGET_XXXX对应 board 目录里的 Kconfig 符号CONFIG_DEFAULT_DEVICE_TREE则绑定了 arch/arm/dts 下的同名 dts 文件。对于 U-Boot 2018 之后的版本这个机制尤其重要因为设备树已经是 U-Boot 自身驱动模型的一部分不再只是“给内核用的附件”。我在帮朋友把 itop4412 这类较老平台从 U-Boot 2015 升级到 2017 版本时最大的工作量不是改 board.c而是重新拼接 defconfig 和处理 DTS。老版本里很多平台配置靠configs/xxx.h里的宏定义新版里逐步迁移到了 Kconfig 和 defconfig。你如果拿着一份老代码直接 make编译能过但生成的行为会和原来完全不一样。2.3 menuconfig 改配置的边界以及 savedefconfig 的使用习惯很多新手喜欢在 make menuconfig 里勾选各种配置其实 menuconfig 只是把 Kconfig 的选择逻辑图形化真正决定“哪些源码文件被编译进去”的是 defconfig 与各级 Kconfig 合并后生成的.config文件。menuconfig 里改完配置直接 diff.config你会发现增量远比你预期的大因为make defconfig会根据默认值填充所有未显式指定的选项。我自己的习惯是先在 menuconfig 里做实验性改动验证通过后执行 make savedefconfig让它生成一份最小化的 defconfig再替换 configs/ 目录下的原文件。这样提交到版本管理里的配置是干净可读的同事接手时也知道你到底动了哪些开关。永远不要把编译机里的.config直接提交那个文件里有大量从默认值继承来的冗余项review 的时候根本看不出来差别。还有一点需要特别提醒SPL 和 U-Boot 主镜像共用一套源码但配置可能是分离的。很多平台通过CONFIG_SPL_xxx前缀的配置项来单独控制 SPL 里包含哪些代码。调试 DDR 阶段SPL 里一定要开足调试信息的输出比如CONFIG_SPL_BANNER、CONFIG_SPL_PRINTF否则真的会瞎猜。3. 设备树、时钟与串口移植期最容易翻车的三个节点3.1 U-Boot 里的 DTS 到底管什么从 U-Boot 2018 开始DTS 在 U-Boot 里的角色越来越重。U-Boot 的驱动模型DM通过解析板级 DTB 来匹配设备节点、绑定驱动、填充资源。简单说U-Boot 自己是带着一张“硬件地图”在跑不再是早年那种全靠board.c里写死寄存器的做法。所以在移植 uboot 时arch/arm/dts/目录下的 dts 和 dtsi 文件几乎是必改的。常见需要关注的节点包括串口节点uart...要确认 reg 地址范围、clocks 属性、pinctrl 绑定内存节点memory...告诉 U-Boot DDR 的物理地址和大小很多板卡的 DDR 信息甚至要在 dts 里显式指定GPIO 控制器节点U-Boot 里的 GPIO 操作依赖它chosen 节点常用来覆盖 bootargs设置 stdout-path一个常见的误区是只把 DTS 当作“给内核的文件”随便放了个厂商默认 dts 就不管了。结果 U-Boot 自己在初始化串口时找不到匹配节点或者 GPIO 控制错位编译没问题但行为完全不对。从 U-Boot 2018 之后的移植项目我会建议优先把 dts 作为第一层排查重点而不是一头扎进 board.c。3.2 时钟树先全盘照抄再逐项验证时钟树配置错误的表现非常隐蔽。串口波特率不对、DDR 频率异常、网卡 PHY 时钟起不来、SD 卡识别超时背后往往是同一个根因某个 PLL 的分频参数写错了。我的建议是在移植起步阶段时钟节点里先给一个“最保守”的配置所有外设分频设到最低所有 PLL 用芯片参考手册上最通用的推荐值确认整板能起来之后再逐步往上调。因为时钟树一旦改错U-Boot 可能在第一条 log 都打不出来之前就死掉了你根本不知道是时钟的锅还是串口的锅。还有一个小细节串口波特率是否准确取决于外设时钟频率和 UART 分频器。用 115200 波特率做调试如果时钟源差个百分之几短 log 看不太出来但一旦有大量数据交互就会出现乱码或丢字节。遇到“偶尔有输出、偶尔没输出”的情况先用逻辑分析仪或者频率计测一下 TX 引脚的实际波特率有时候比翻代码快得多。3.3 chosen 节点与 bootargs 的关系U-Boot 的 dts 里chosen 节点是一个特殊的“运行时”节点它常用来覆盖内核启动参数。移植中常见的“U-Boot 起来了内核也有 log但 console 完全不工作”的问题很多都和stdout-path、bootargs没有配对有关。比如内核早期输出依赖chosen/stdout-path指向的串口别名如果 dts 里aliases节点没有定义serial0或者chosen里写的是stdout-path serial0:115200n8但 serial0 实际对应到别的串口那内核的早期 log 就会打到别处去。调试这种问题不要在 U-Boot 阶段改一堆代码先把printenv bootargs和 dts 里的 stdout-path 对齐通常能省半天时间。4. 调试三板斧串口、网络、仿真器轮着上4.1 如何用串口中断和快捷键稳定停在 U-BootU-Boot 默认的bootdelay时间内如果你在串口终端按任意键启动流程会被打断进入命令行。这个机制听着简单但在某些平台上特别是那类“TTL 串口调试口”的板子实际操作是有讲究的。比如海思 hi3798m100 那类机顶盒方案板子上电后 BootROM 留给串口中断的窗口非常短需要在掉电重启瞬间掐准时间连按快捷键才能稳定停在 U-Boot。很多人用 TTL 转 USB 小板连上去按回车没反应不是线的问题是时机没掐准。我的做法是先把串口终端软件的流控全部关闭波特率设成和 U-Boot 一致然后在脚本里做一个自动循环发送 0x0d 0x0a回车换行再反复给板子上电。板子只要能跑十次里八次能停在命令行。停在 U-Boot 之后做的第一件事永远是printenv把环境变量完整存一份到本地文件。这一步是后续一切调试的“基准点”。4.2 TFTP 下载与 bootm / booti 的正确配合开发调试阶段把内核和 dtb 通过 TFTP 下载到 DDR 再启动是效率最高的方式。具体链路是板子通过网线连到开发机U-Boot 里配置好ipaddr、serverip然后执行setenv ipaddr 192.168.1.100 setenv serverip 192.168.1.10 ping 192.168.1.10 tftpboot 0x42000000 zImage tftpboot 0x43000000 board.dtb bootz 0x42000000 - 0x43000000这里有几个细节容易踩坑。第一TFTP 下载地址要避开 U-Boot 镜像本身所在的内存区间否则下载过程会把自己覆盖掉。第二32 位内核用bootz或bootm64 位内核用booti用错了会报Wrong Image Format。第三bootm后面三个参数分别是内核地址、ramdisk 地址、fdt 地址中间那个参数不需要时可以填-很多人不知道这个占位符的用法导致 fdt 传不进去。还有一个经验tftpboot 下载时如果反复超时先别急着查网络驱动ping一下开发机再mii看一下 PHY 的 link 状态。很多时候是网线或交换机问题不是 uboot 的问题。4.3 用 md / mm 直接读寄存器判断 DDR 是否起来DDR 初始化是否成功最直接的验证方式不是看 log因为 log 本身就依赖 DDR而是对内存地址做写读回。在 U-Boot 命令行下执行mw 0x40000000 0xa5a5a5a5 16 md 0x40000000 16如果读出来的值不是 0xa5a5a5a5说明 DDR 控制器的训练或时序参数有问题。如果直接报错或死机那基本可以断定 DDR 初始化根本没有完成。这个方法在 SPL 阶段也可以用只是需要提前在代码里留一个调试入口。我调试 DDR 参数时几乎每改一组参数就跑一次这个测试确认稳定了才会做下一步。4.4 仿真器/JTAG 作为最后的底牌串口和网络都不太管用的时候比如 DDR 压根没初始化、SPL 卡死在开头就需要上仿真器了。JTAG/SWD 调试器可以直接接管 CPU查看寄存器、内存、PC 指针。通过仿真器看 PC 停在哪个地址基本能判断是卡在时钟配置、DDR 训练还是 pinmux 设置。这个方法适合有一定硬件调试基础的工程师初期学会看几个关键寄存器就够了不需要精通。5. 从 “Bad CRC” 到 “No working FDT” 的常见报错复盘5.1 Bad CRC of Environment环境变量区被破坏U-Boot 启动时打印Bad CRC of Environment是特别常见的问题含义是环境变量存储区里保存的数据校验失败。出现这个报错板子不会死U-Boot 会使用编译时内置的默认环境变量继续跑。但它通常说明一件事CONFIG_ENV_OFFSET和CONFIG_ENV_SIZE这两个配置跟你板子上实际的 Flash 布局对不上。比如你的 SPI NOR Flash 里U-Boot 镜像占了 0 ~ 0x100000环境变量区原本应放在 0x100000 之后但配置里写的是 0x200000恰好那里存的是 kernel 镜像的开头。U-Boot 启动时把 0x200000 处的数据读出来当环境变量解析发现校验不对于是报 Bad CRC。这种问题在移植阶段几乎人人都会遇到一次解决办法不是简单执行 saveenv而是先把环境变量区的偏移和大小正确设置再 saveenv 写入合法的数据。5.2 No working FDT设备树没跟上内核启动内核时报No working FDT意思是 U-Boot 需要把设备树地址传给内核但这个地址上解析不到合法的设备树结构。常见原因有三个一是 tftpboot 加载 dtb 时下载失败二是指定了fdt_addr但那个地址上根本没有数据三是 bootm/booti 命令的第三个参数没写对。排查套路是先 tftpboot 加载 dtb 到固定地址然后用fdt addr检查该地址上的设备树是否可解析tftpboot 0x43000000 board.dtb fdt addr 0x43000000如果这里不报错再执行 bootm/booti 并显式指定 fdt 地址。建议把fdt_file、fdt_addr环境变量在移植阶段就配好别依赖自动探测减少变量。还有就是确认 dtb 是用同一份 dts 编出来的版本不匹配也容易出奇怪问题。5.3 小容量 Flash 平台的体积焦虑wr703n 刷 uboot 带来的启示WR703N 这类小路由器Flash 容量就 4MB 左右刷第三方 uboot 是社区里玩得很热的项目。它带出的普适问题是整个 U-Boot 镜像必须控制在 64KB 以内才能在一个小的 SPI NOR 分区里放下。为了容量很多人会裁剪掉一批用不到的命令、板级支持、文件系统支持。这个思路对任何 NOR Flash 启动的板子都有参考价值移除不需要的网络协议栈驱动裁剪掉不用的命令集比如把 bootm、bootz 之外的命令全部禁用压缩 SPL关掉 SPL 里不必要的驱动手动指定CONFIG_SYS_MAXARGS、CONFIG_SYS_CBSIZE等缓冲区参数腾出少量可执行镜像空间实际裁剪时要注意U-Boot 的代码之间有不少隐藏依赖减过头会导致链接失败或者功能残缺。我的建议是每次裁剪后都实际编译、烧录、验证一遍串口和网络不要批量裁完一起测否则出了错根本定位不了是哪一项配置引起的。5.4 卡死在“无串口输出”阶段时的排查顺序凡是遇到板子一点输出都没有的情况按照下面的顺序排查通常能省下大量时间先用万用表确认串口 TX 引脚有电平变化排除线序和电平问题检查板子的电源轨是否全部正常特别是 DDR、SoC 内核电压确认 BootROM 的启动介质选择引脚有没有被拉错用逻辑分析仪看 BootROM 有没有在 CS 信号上尝试读取启动介质最后才怀疑 U-Boot 代码本身先烧一份已知能跑的原厂固件做对照我见过太多人一碰到“板子没输出”就冲进源码里改代码其实前四步里就能解决八成问题。硬件上的问题靠逻辑分析仪软件上的问题靠串口和仿真器这个顺序千万别颠倒。移植 U-Boot 从来不是一锤子买卖。我的个人习惯是先让串口有输出再让 DDR 稳定然后让网络通最后才去碰 Flash 和 saveenv。每一步之间都留一个可回退点把原厂固件完整备份、把 printenv 输出存档。做到这些哪怕中途翻车也能几分钟内回到之前的进度。U-Boot 跑通之后下一个环节就是 kernel 和根文件系统Zynq 这类平台上顺手还要把 busybox 的移植一起纳入计划又是一片新的调试天地不过那是后话了先把这块板子的 uboot 稳稳跑起来再说。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询