
1. 从零开始为什么我们需要与uboot打交道如果你刚刚接触嵌入式开发尤其是基于ARM、PowerPC、RISC-V等架构的SoC片上系统那么“uboot”这个名字很快就会成为你开发日志里的高频词。它不像Linux内核那样名声在外也不像应用程序那样直接与用户交互但它却是整个系统启动过程中最关键、最底层的一环。你可以把它想象成电脑主板上的BIOS但功能更强大、更开放也更“折腾人”。简单来说uboot全称Das U-Boot是一个开源的、跨平台的引导加载程序Bootloader。它的核心任务就两个第一初始化最基础的硬件比如CPU、内存、时钟、存储控制器第二从存储介质如eMMC、NAND Flash、SD卡、SPI NOR Flash上加载操作系统内核如Linux并跳转执行。没有它你的RK3568、i.MX6ULL、Zynq或者全志R818开发板就是一块无法启动的“砖头”。那么为什么我们需要“烧写”和“使用”它呢原因有三。首先硬件初始化。芯片上电复位后CPU处于一个非常原始的状态它不知道内存有多大、时钟频率是多少、从哪里可以读取代码。uboot的第一段代码通常是SPLSecondary Program Loader就是用汇编和C语言写的“开机自检”程序负责把这些最基本的硬件配置好为后续更复杂的代码运行搭建舞台。其次环境适配与灵活性。不同的板子内存型号、Flash型号比如AM29LV040B这种老式NOR Flash、网络PHY都可能不同。uboot通过源码编译时的配置和运行时的环境变量提供了极高的灵活性让同一套代码能适配成千上万种硬件组合。最后开发与维护的入口。在开发阶段我们经常需要通过uboot的命令行来测试硬件、更新内核、修复系统甚至进行裸机程序调试。一个功能健全、使用顺畅的uboot是高效开发的基石。因此无论是为一块新板子移植uboot还是修复现有板子的启动问题亦或是进行二次开发增加新功能“烧写”和“使用”uboot都是嵌入式工程师必须掌握的硬核技能。这个过程充满了细节从理解SPL和uboot proper的区别到处理不同Flash的烧写协议再到配置复杂的编译环境每一步都可能藏着“坑”。接下来我将结合常见的平台如NXP i.MX6ULL、Xilinx Zynq、Rockchip RK3568等拆解uboot烧写与使用的完整链条。2. 源码获取与编译环境搭建一切始于源头在动手烧写之前我们得先有uboot的“原材料”——源代码并准备好“厨房”——交叉编译工具链。这一步的规范性直接决定了后续所有操作的成败。2.1 获取uboot源码的几种途径uboot的官方源码托管在Denx的Git服务器上但国内访问可能较慢。更常用的方式是获取芯片原厂或开发板厂商提供的适配版本。官方主线仓库适合学习、研究最新特性或为非常小众的板子做移植。你可以通过git克隆git clone git://git.denx.de/u-boot.git。但主线代码可能不包含特定芯片的所有驱动需要自己动手集成。芯片厂商的Git仓库这是最推荐的方式。比如NXP通常在其官方SDK如Linux for i.MX发布包中提供或有一个独立的imx_uboot仓库。Rockchip在GitHub上有rockchip-linux/u-boot仓库为RK系列芯片做了大量适配。XilinxZynq系列的uboot通常包含在Xilinx的PetaLinux工具链或GitHub的Xilinx/u-boot-xlnx仓库中。全志社区维护的armbian/build或linux-sunxi社区会有相关移植。 使用厂商版本的优点是驱动完善、配置预设好能大大降低启动门槛。例如搜索“rk3568 emmc烧写”时你遇到的问题很可能在Rockchip提供的uboot源码的README或doc/目录下就有答案。开发板供应商的SDK购买开发板时附带的资料光盘或下载链接里面的uboot源码往往是开箱即用的配置文件和设备树都针对该板卡优化好了。注意务必记录下你使用的uboot源码的Git提交哈希值commit id。当出现问题时这个信息是回溯和对比的关键避免因为源码版本不同导致的诡异问题。2.2 交叉编译工具链的选择与安装我们的开发主机通常是x86 Linux无法直接编译生成ARM或PowerPC架构的可执行文件因此需要交叉编译工具链。对于ARM架构如i.MX6ULL, RK3568, R818最通用的是gcc-arm-none-eabi用于裸机/SPL和gcc-linaro-arm-linux-gnueabihf用于uboot proper和内核。更简单的方法是使用芯片厂商提供的工具链如NXP的gcc-arm-none-eabi-...和gcc-linaro-arm-linux-gnueabihf-...这些工具链往往针对其芯片的特定指令集如Thumb-2做过优化。对于Xilinx ZynqARM Cortex-A9使用Xilinx Vitis或PetaLinux自带的工具链例如arm-linux-gnueabihf-确保与Xilinx的硬件IP核驱动兼容。对于PowerPC架构如PowerPC440需要如powerpc-eabi-或powerpc-linux-gnuspe-这样的工具链。安装后需要将工具链的路径添加到系统的PATH环境变量中并在编译时通过CROSS_COMPILE变量指定前缀。例如export CROSS_COMPILEarm-linux-gnueabihf- export ARCHarm实操心得不要在多个项目中使用系统全局的工具链建议使用buildroot或Yocto为每个项目构建独立的工具链或者至少在shell脚本中局部定义CROSS_COMPILE避免环境污染。2.3 配置与编译为你的板子生成专属镜像获取源码和工具链后进入uboot源码根目录。选择正确的配置文件uboot的板级配置在configs/目录下文件名通常遵循芯片名_板子名_defconfig的格式。例如i.MX6ULL EVK板mx6ull_14x14_evk_defconfigRK3568 EVB板rockchip_rk3568_evb_defconfigZynq ZedBoardzynq_zed_defconfig如果你是为自己的定制板卡编译通常需要找一个最接近的配置文件复制并修改。使用make defconfig_name来应用配置。make mx6ull_14x14_evk_defconfig进行细节配置可选如果需要调整内存大小、网络PHY地址、启动参数等可以执行make menuconfig进行图形化配置。但对于初次烧写使用默认defconfig通常即可。执行编译直接运行make。如果一切顺利将在目录下生成多个重要文件u-boot.bin 原始的、可执行的uboot二进制文件。u-boot.img 在某些平台如Rockchip这是包含了特定头部信息的uboot镜像用于直接烧写。u-boot.srec Motorola S-Record格式可用于某些编程器。spl/u-boot-spl.bin SPL的二进制文件。对于需要SPL的芯片如i.MX6ULL这个文件需要被烧写到启动设备的特定位置。u-boot.dtb 设备树二进制文件Blob描述了板级的硬件信息。踩坑记录编译时最常见的错误是工具链路径不对或版本不兼容。错误信息可能晦涩难懂如“unrecognized command line option ‘-marcharmv7-a’”等。首先检查CROSS_COMPILE前缀是否正确其次确认工具链是否支持目标芯片的ARM架构版本如ARMv7-A。使用厂商推荐的工具链版本是最稳妥的。3. 深入核心SPL与uboot proper的职责与烧写布局在烧写之前必须理解uboot的启动阶段。对于现代SoC尤其是从外部非执行介质如NAND、eMMC启动时通常采用两级引导SPL (Secondary Program Loader)和uboot proper (主uboot)。3.1 SPL轻量级的硬件初始化器当SoC上电后内部的ROM代码固化在芯片里无法修改会从预定的启动设备由启动引脚决定如SD卡、eMMC、SPI NOR的最起始扇区加载一小段代码到内部SRAM中执行。这段代码就是SPL。为什么需要SPL芯片内部的SRAM容量非常有限可能只有几十KB到几百KB不足以容纳完整的uboot可能有几百KB甚至上MB。因此需要一个极其精简的“引导引导程序”它的唯一任务就是用这有限的空间初始化足够多的硬件尤其是DDR内存控制器以便将体积更大的主uboot从存储设备搬运到DDR内存中运行。SPL的功能边界SPL通常只包含最必要的驱动时钟、串口用于调试、DDR初始化、存储设备如eMMC/SD的基础读写驱动。它不包含网络、USB、图形等复杂驱动。它的代码尺寸受到严格限制编译时需要特别配置CONFIG_SPL_BUILD。输出文件编译后生成的spl/u-boot-spl.bin就是它。对于像i.MX6ULL这样的芯片我们可能需要使用厂商提供的工具如imx-mkimage给SPL.bin加上一个特殊的头部IVT、DCD等生成SPL或u-boot-spl.imx文件这个头部包含了ROM代码加载SPL所需的信息和DDR初始化参数。3.2 uboot proper功能齐全的引导管理器当SPL将DDR初始化好并把主uboot镜像从存储设备加载到DDR的指定地址后就会跳转到DDR中去执行主uboot。核心职责完成更全面的硬件初始化初始化网络ETH、USB、显示等外设。加载设备树DTB从存储设备或编译时内置的设备树信息获取板级硬件描述。提供交互式命令行这就是我们常说的uboot命令行可以执行printenv,setenv,saveenv,tftp,mmc read/write,bootm等命令。加载并启动内核根据环境变量如bootcmd的设定从网络或本地存储加载内核镜像zImage和设备树并传递启动参数bootargs最后跳转到内核入口点。3.3 存储设备上的烧写布局理解SPL和uboot proper的关系后就知道它们必须被烧写到存储设备的特定位置。这个布局由芯片的ROM代码和SoC设计决定。以一个典型的eMMC/SD卡启动为例以i.MX6ULL/RK3568常见配置偏移量 (扇区)大小内容说明扇区 01KBMBR/GPT分区表。某些平台ROM代码要求从LBA0开始因此SPL不能覆盖这里。扇区 1 - 扇区 X~64-128KBSPLROM代码固定从这里加载。X取决于SPL的实际大小需对齐到扇区。扇区 X1 - 扇区 Y~512KB-1MBuboot proper紧接着SPL存放。地址在SPL的代码中是硬编码或通过配置定义的。扇区 Y1 - ...可变环境变量存储uboot.env的区域。通常预留128KB。后续扇区内核、设备树、根文件系统等属于Linux系统分区。对于SPI NOR Flash布局类似但地址是线性的如SPL在0x0uboot在0x40000环境变量在0x80000。关键点烧写时你必须知道你的SoC的ROM代码从存储设备的哪个偏移量开始加载SPL。这个信息在芯片的参考手册Reference Manual的“系统启动”章节中有明确规定。例如i.MX6ULL从SD卡的1KB偏移即第2个扇区开始加载而一些Rockchip芯片可能要求SPL必须位于扇区64。烧错位置板子就无法启动。4. 实战烧写多种平台与工具链详解有了镜像理解了布局现在进入实战烧写环节。根据板子所处的状态全新的、能进入uboot命令行的、变砖的我们采用不同的烧写方法。4.1 通过uboot命令行自身更新最常用这是最安全、最常用的方法前提是你的板子当前能正常启动到uboot命令行。核心原理利用uboot已有的驱动如网络、USB、MMC将新的uboot镜像从外部设备TFTP服务器、U盘加载到DDR内存中然后使用uboot的写命令mmc write,sf write将其写入存储设备的正确位置。以更新i.MX6ULL的eMMC中的uboot为例准备新镜像将编译好的u-boot.imx已加头文件放到TFTP服务器目录下。启动板子中断进入uboot在串口倒计时结束前按任意键。配置网络如果尚未配置 setenv ipaddr 192.168.1.100 # 板子IP setenv serverip 192.168.1.10 # TFTP服务器IP setenv ethaddr 00:11:22:33:44:55 # MAC地址 saveenv通过TFTP加载镜像到DDR tftp 0x80800000 u-boot.imx这里0x80800000是DDR中的一个空闲地址u-boot.imx是文件名。命令执行后会显示加载的字节数。计算并执行烧写这是最关键的一步需要知道烧写到eMMC的哪个位置。首先确定eMMC的设备号输入mmc list查看。假设eMMC是mmc 1。其次确定烧写偏移对于i.MX6ULLSPL需要烧写到eMMC的扇区2因为前1KB是MBR1KB/512B2个扇区。u-boot.imx文件包含了SPL和uboot proper。计算块数先查看加载的镜像大小。假设tftp显示加载了bytes573440(560KB)。块大小block size通常是512字节。块数 573440 / 512 1120个块。执行烧写 mmc dev 1 # 切换到eMMC设备 mmc write 0x80800000 0x2 0x460解释mmc write 内存地址 设备起始块号 块数。0x2是起始块扇区20x460是1120的十六进制。重启验证reset。如果新版uboot启动说明烧写成功。注意事项mmc write命令非常危险写错地址可能覆盖分区表或内核导致系统无法启动。操作前务必再三确认起始块号和块数。对于SPI NOR Flash命令是sf probe和sf erase/write。需要先探测Flashsf probe 0:0然后擦除对应区域sf erase 0x0 0x100000擦除1MB空间最后写入sf write 0x80800000 0x0 0x80000。对于NAND Flash命令更复杂涉及坏块管理通常使用nand erase/write需要格外小心。4.2 使用厂商专用烧写工具量产与救砖当uboot完全损坏无法进入命令行时就需要借助SoC的“恢复模式”或“下载模式”通过USB或串口配合PC端工具进行烧写。NXP i.MX系列 -uuu(Universal Update Utility)uuu是一个强大的开源工具以前叫mfgtools。它利用i.MX芯片的USB OTG端口进入Serial Download ModeSDP进行烧写。将板子设置为下载模式通常通过拨码开关或测试点短接。通过USB OTG线连接板子和PC。在PC上运行命令例如uuu u-boot.imx。uuu脚本会自动将镜像烧写到正确的介质和位置。这是拯救“砖头”板的最有效方法也是量产烧写的标准流程。Rockchip RK系列 -rkdeveloptool和upgrade_tool Rockchip芯片通常通过MaskROM模式或Loader模式进行烧写。需要短接Flash的数据脚或按住特定按键上电进入MaskROM模式。安装rkdeveloptool。进入MaskROM模式后使用rkdeveloptool list查看设备。使用rkdeveloptool write 0x40 u-boot.img进行烧写0x40是SDMMC控制器下的偏移扇区数具体值需查手册。或者使用Windows下的RKDevTool图形化工具。Xilinx Zynq - Vivado/SDK 与 JTAG Zynq的启动流程复杂一些涉及FSBLFirst Stage Bootloader相当于SPL。通常使用Vivado SDK通过JTAG将FSBLuboot的合成镜像BOOT.BIN直接下载到内存运行测试或通过SDK的Program Flash功能烧写到QSPI Flash/SD卡。这也是解决“vivado添加完flash型号还烧写不了”这类问题的关键环节——确保在Vivado中正确配置了Flash型号并在SDK中生成BOOT.BIN时包含了正确的Flash驱动。全志系列 -sunxi-fel 全志芯片如R818通常支持FEL模式通过USB烧写。使用sunxi-fel工具可以初始化DRAM并烧写镜像。命令如sunxi-fel write 0x200000 u-boot.bin。实操心得救砖时串口控制台的输出信息至关重要。如果完全没输出首先检查电源、晶振、启动模式引脚。如果有少量输出后停止可能是SPL的DDR初始化失败需要检查uboot配置中的内存参数是否与板子匹配。4.3 SD卡启动与烧写开发调试利器对于支持从SD卡启动的板子绝大多数嵌入式板都支持制作一张启动SD卡是最快速的开发调试方式。准备SD卡插入读卡器在Linux下使用lsblk或fdisk -l找到其设备名如/dev/sdb。使用dd命令直接烧写这是最底层的方法直接将镜像写入SD卡的原始扇区。sudo dd ifu-boot.imx of/dev/sdb bs512 seek2 convfsyncif是输入文件of是SD卡设备bs是块大小seek2表示跳过前2个扇区1KB开始写入这正是i.MX6ULL ROM代码要求的位置。警告务必确认of参数正确否则可能清空你的硬盘使用厂商脚本很多uboot源码目录下会有make sd_fuse或类似的脚本或者有tools/目录下的专用工具如Rockchip的rkbin工具它们能更智能地处理分区和烧写。测试将SD卡插入板子设置启动模式为SD卡优先上电。如果从串口看到uboot启动信息则成功。这种方法的好处是独立于板载存储不会破坏eMMC中原有系统非常适合反复试验和调试。5. uboot命令行的高效使用与二次开发入门成功烧写并启动uboot后我们就进入了功能强大的uboot命令行。这里是硬件调试、系统维护和二次开发的主战场。5.1 必须掌握的常用命令信息查看bdinfo 打印板级信息如内存地址、大小、时钟频率等。version 显示uboot版本和编译时间。mmc list/mmc info 查看MMC/SD设备列表和详细信息。sf probe/sf info 探测和查看SPI Flash信息。nand info 查看NAND Flash信息。printenv 打印所有环境变量。这是最重要的命令之一。环境变量操作setenv var value 设置环境变量。如setenv bootargs consolettymxc0,115200。saveenv 将当前环境变量保存到持久化存储如eMMC上的环境变量分区。修改变量后必须执行此命令才会生效。editenv var 交互式编辑环境变量适合编辑长字符串。存储设备读写mmc read 内存地址 设备块号 块数/mmc write ... 读写MMC/SD。sf read/write 内存地址 flash偏移 长度 读写SPI NOR Flash。fatload mmc dev:part 内存地址 文件名 从FAT分区加载文件到内存。例如从SD卡第一个分区加载内核fatload mmc 0:1 0x82000000 zImage。ext4load mmc dev:part 内存地址 文件路径 从ext4分区加载文件。网络操作tftp 内存地址 服务器文件名 通过TFTP协议下载文件。网络调试的利器。ping 服务器IP 测试网络连通性。启动与测试bootm 内核地址 - 设备树地址 启动内核。例如bootm 0x82000000 - 0x83000000。go 地址 跳转到指定地址执行裸机程序用于测试。boot 执行bootcmd环境变量中定义的自动启动命令。5.2 环境变量uboot的“大脑”环境变量是uboot灵活性的核心。bootcmd定义了上电自动执行的命令序列bootargs则是传递给Linux内核的启动参数。一个典型的bootcmd可能是setenv bootcmd mmc dev 0; fatload mmc 0:1 0x82000000 zImage; fatload mmc 0:1 0x83000000 dtb; bootz 0x82000000 - 0x83000000意思是切换到SD卡0从第一个分区加载内核镜像到内存0x82000000加载设备树到0x83000000然后用bootz命令启动。bootargs则告诉内核控制台在哪里、根文件系统在哪里setenv bootargs consolettymxc0,115200 root/dev/mmcblk0p2 rootwait rw调试技巧在开发阶段可以设置bootcmd为run netboot网络启动或run usbboot而将bootargs设置得尽可能精简先确保内核能启动起来。使用setenv临时修改用saveenv保存最终配置。5.3 uboot二次开发入门当默认的uboot功能不满足需求时就需要进行二次开发。这通常包括添加新命令在cmd/目录下参考现有命令如cmd/mmc.c创建新文件实现do_xxx函数并在Kconfig和Makefile中添加编译选项。这是扩展uboot功能最直接的方式。驱动新硬件例如为一块新的以太网PHY芯片添加驱动。需要在drivers/net/phy/下添加驱动文件并在板级配置文件中启用相应的CONFIG_PHY_xxx。适配新板卡这是最常见的二次开发。步骤包括在board/vendor/下复制一个最接近的板子目录重命名。修改Kconfig、Makefile和板级头文件如include/configs/myboard.h定义内存大小、环境变量存储位置、默认配置等。修改或创建对应的设备树源文件.dts正确描述所有硬件外设。在configs/下创建新的defconfig文件。编译测试通过串口调试输出逐步修正DDR初始化、时钟、引脚复用等配置。踩坑记录二次开发中最耗时的往往是DDR初始化参数的调试。如果SPL阶段就卡住串口没有任何输出很可能是DDR参数不对导致无法正确运行代码。此时需要仔细核对芯片数据手册和DDR芯片的数据手册。使用厂商提供的配置工具如NXP的DDR Stress Test工具生成初始化序列。尝试降低DDR频率或放宽时序参数进行测试。借助JTAG调试器进行单步调试这是最有效但成本较高的手段。6. 高级话题uboot与boot的关系及源码深度探索经常有初学者混淆uboot和“boot”。简单来说“boot”是一个广义的概念指系统从加电到操作系统运行的整个过程。而uboot是这个过程中的一个具体实现是bootloader的一种。在PC领域这个角色是BIOS或UEFI在嵌入式领域除了uboot还有如RedBoot、Barebox、Coreboot等多种bootloader。uboot因其强大的功能、广泛的硬件支持和活跃的社区成为了嵌入式Linux领域事实上的标准。要真正驾驭uboot离不开对其源码的深度探索。当遇到“uboot 读写am29lv040b”这类具体驱动问题或者想理解“powerpc440 uboot命令”的实现时直接阅读源码是最佳途径。如何高效阅读uboot源码抓住主线从arch/arch/lib/crt0.S如arch/arm/lib/crt0.S开始这是CPU上电后执行的第一个C语言环境的入口。然后跟踪board_init_f()和board_init_r()这是uboot初始化的两个主要阶段。理解关键目录arch/ 与CPU架构相关的代码如ARM、PowerPC、RISC-V。这里包含了最底层的启动代码、中断向量表、CPU初始化。board/ 板级相关的代码每个板子一个子目录包含板级初始化和设备树。common/ 通用功能如命令行解析、环境变量处理、启动逻辑。drivers/ 所有外设驱动如MMC、NET、USB、SPI、I2C等。要解决驱动问题就来这里找。include/ 头文件集合特别是include/configs/下的板级配置头文件。cmd/ 所有uboot命令的实现。善用搜索在源码根目录使用grep -r am29lv040b可以快速找到与这个Flash芯片相关的驱动文件很可能在drivers/mtd/spi/或drivers/mtd/jedec_flash.c中。通过阅读相关代码你就能知道uboot是否支持该芯片以及如何配置CONFIG_SPI_FLASH_AM29LV040B这样的宏来启用它。参考现有板子当为一块新板子做移植时在board/和configs/下找一个硬件最相似的现有板子作为参考是最高效的方法。复制其代码然后对比数据手册逐一修改差异点。关于“spl uboot”在源码中SPL的代码与主uboot是共享的通过CONFIG_SPL_BUILD宏进行条件编译。在SPL构建时这个宏被定义因此只会编译那些被标记为SPL_的驱动和功能从而保证代码精简。在阅读驱动代码时你会经常看到#ifdef CONFIG_SPL_BUILD这样的条件编译块。uboot的世界庞大而深邃从烧写使用到源码移植每一步都是理论与实践的结合。它没有图形界面所有的交互都通过命令行完成这要求开发者必须清晰理解硬件的工作原理和软件的执行流程。然而正是这种“赤裸裸”的控制赋予了开发者最大的灵活性和力量。当你第一次通过自己编译和烧写的uboot成功引导起一个Linux系统时那种对硬件和软件栈的掌控感是嵌入式开发中最纯粹的乐趣之一。记住多查手册Reference Manual、多读源码、善用串口打印信息你就能解决uboot道路上遇到的大部分挑战。