4. Bootloader移植基础:U-Boot源码结构、板级配置、编译与烧录

Bootloader是嵌入式系统上电后运行的第一段代码。说白了,它就是个“引路人”——初始化硬件、加载内核、跳转到操作系统。U-Boot是目前最主流的Bootloader,几乎支持所有主流架构。

我个人习惯把U-Boot移植分成三步:看懂源码结构、配好板级文件、搞定编译烧录。这三步走通了,板子就能跑起来。

4.1 U-Boot源码结构:先摸清家底

我第一次接触U-Boot时,面对几千个文件确实有点懵。但后来发现,核心目录就那么几个。你想想看,一个成熟的Bootloader,无非就是“架构相关代码 + 板级代码 + 通用驱动 + 工具脚本”这四块。

目录/文件 作用 我关注的点
arch/ CPU架构相关代码(arm、riscv、x86等) 启动流程、异常向量表、MMU设置都在这里
board/ 板级初始化、外设配置 移植时改得最多的目录
include/configs/ 板级配置头文件 裁剪功能、调整内存大小全靠它
drivers/ 串口、网卡、Flash、MMC等驱动 遇到新外设时来这里找驱动
common/ 命令解析、环境变量、HUSH shell 加自定义命令时改这里
tools/ mkimage、fdt工具等 打包内核、生成镜像时用

嗯,这里要注意:arch/arm/mach-xxx/ 和 board/vendor/board_name/ 是移植时最常打交道的两个目录。一个管CPU,一个管板子。

4.2 板级配置:从零开始配一块新板

U-Boot的配置体系,说白了就是“Kconfig + 头文件”双轨制。新版U-Boot用Kconfig做菜单配置,但板级参数还是靠头文件。

4.2.1 创建板级目录

假设我们要给一块叫“myboard”的板子做移植,CPU是ARM Cortex-A7。我会这样做:

  1. 在 board/vendor/ 下创建 myboard/ 目录
  2. 复制一份相近板子的 Kconfig、Makefile、myboard.c
  3. 在 include/configs/ 下创建 myboard.h
  4. 在 arch/arm/mach-xxx/Kconfig 中添加 TARGET_MYBOARD 选项

核心思路:找一块官方已经支持的、硬件配置相近的板子作为模板。我一般选同一家SoC厂商的EVB板,改起来最快。

4.2.2 配置头文件详解

include/configs/myboard.h 是板级配置的核心。我习惯把它分成几个区段:

/* 内存配置 */
#define CONFIG_SYS_SDRAM_BASE       0x80000000
#define CONFIG_SYS_SDRAM_SIZE       0x20000000  /* 512MB */

/* 串口配置 */
#define CONFIG_CONS_INDEX           1
#define CONFIG_BAUDRATE             115200

/* 启动参数 */
#define CONFIG_BOOTCOMMAND \
    "sf read 0x80800000 0x100000 0x800000; bootm 0x80800000"

/* 环境变量存储 */
#define CONFIG_ENV_IS_IN_SPI_FLASH
#define CONFIG_ENV_OFFSET           0x100000
#define CONFIG_ENV_SIZE             0x20000

/* 裁剪:不需要的功能就关掉 */
#undef CONFIG_CMD_NET
#undef CONFIG_CMD_USB

我曾经在一个项目里忘了配 CONFIG_SYS_SDRAM_SIZE,结果U-Boot只认了64MB内存,内核启动到一半就崩了。查了两天才发现是这里的问题。

避坑指南:配置头文件里的宏定义,有些是“定义即启用”,有些是“值决定行为”。一定要看注释或文档确认语义,别想当然。

4.3 编译流程:从源码到镜像

U-Boot的编译流程其实很清晰。我把它总结成四步:

  1. 配置:make myboard_defconfig —— 生成 .config
  2. 菜单调整:make menuconfig —— 按需裁剪功能
  3. 编译:make -j4 —— 生成 u-boot.bin
  4. 打包:make u-boot.imx 或 mkimage —— 生成带头部信息的烧录镜像

为什么会有第四步?因为很多SoC要求Bootloader镜像前面加一个“头部”,包含加载地址、校验和、签名等信息。比如i.MX系列需要 .imx 格式,Rockchip需要 idblock 格式。

我的习惯:第一次编译时加 V=1 参数(make V=1),能看到完整的编译命令。出错了方便定位是哪个文件的问题。

4.4 烧录方法:把镜像写进Flash

烧录方式取决于板子的硬件设计。我遇到过三种主流方式:

烧录方式 适用场景 工具 注意事项
SD卡启动 开发调试阶段 dd、win32diskimager 注意扇区偏移,别把分区表覆盖了
串口XMODEM 无网口、无SD卡 SecureCRT、minicom 速度慢,适合小镜像
JTAG/SWD 裸板、Flash为空 OpenOCD、JLink 需要调试器硬件,但最可靠
网络TFTP 有网口、U-Boot已跑起来 tftp命令 开发中最常用,更新快

我个人最常用的是“SD卡启动 + TFTP更新”组合。第一次用SD卡把U-Boot烧进去,之后通过网口TFTP下载新版本,省得反复插拔SD卡。

4.5 知识体系总览

下面这张图把U-Boot移植的核心脉络串起来了。从源码结构到板级配置,再到编译烧录,每一步都有对应的文件和工具。

U-Boot移植核心流程 1. 源码结构 2. 板级配置 3. 编译烧录 arch/ board/ drivers/ include/configs/ common/ tools/ Kconfig + 头文件 内存/串口/时钟 环境变量/启动命令 defconfig → .config make → u-boot.bin mkimage → 烧录镜像 找模板板子 改头文件+Kconfig 烧录验证 最终产出:u-boot.bin / u-boot.imx / u-boot-spl.bin 串口输出 → 进入U-Boot命令行 提示:先跑通串口输出再调其他

4.6 实战要点总结

最后,我把移植过程中最容易踩的坑列出来:

  • 时钟配置不对:串口乱码、DDR初始化失败。先确认SoC的PLL和分频系数。
  • DDR参数错误:U-Boot能跑但内核启动崩溃。用厂商提供的DDR初始化代码或工具。
  • 环境变量存储位置冲突:擦除环境变量时把内核镜像也擦了。仔细算偏移量。
  • 编译工具链版本不匹配:链接报错或运行时异常。用U-Boot文档推荐的GCC版本。

我的经验:第一次移植时,先别急着加各种功能。目标只有一个——让串口输出“U-Boot 2024.01”这几个字。串口通了,后面的事就好办了。