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。我会这样做:
- 在
board/vendor/下创建myboard/目录 - 复制一份相近板子的
Kconfig、Makefile、myboard.c - 在
include/configs/下创建myboard.h - 在
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的编译流程其实很清晰。我把它总结成四步:
- 配置:
make myboard_defconfig—— 生成.config - 菜单调整:
make menuconfig—— 按需裁剪功能 - 编译:
make -j4—— 生成u-boot.bin - 打包:
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移植的核心脉络串起来了。从源码结构到板级配置,再到编译烧录,每一步都有对应的文件和工具。
4.6 实战要点总结
最后,我把移植过程中最容易踩的坑列出来:
- 时钟配置不对:串口乱码、DDR初始化失败。先确认SoC的PLL和分频系数。
- DDR参数错误:U-Boot能跑但内核启动崩溃。用厂商提供的DDR初始化代码或工具。
- 环境变量存储位置冲突:擦除环境变量时把内核镜像也擦了。仔细算偏移量。
- 编译工具链版本不匹配:链接报错或运行时异常。用U-Boot文档推荐的GCC版本。
我的经验:第一次移植时,先别急着加各种功能。目标只有一个——让串口输出“U-Boot 2024.01”这几个字。串口通了,后面的事就好办了。