4. 编译与烧录:从源码到板子跑起来
好,到了真正动手的环节了。前面几章我们把 U-Boot 的启动流程、参数结构都捋了一遍,但光看不练是学不会的。这一章,我们就来走一遍完整的流程:下载源码、配交叉编译环境、配置、编译、烧录。每一步我都会把坑点指出来,这些都是我实际项目中踩过的。
本章核心流程:源码获取 → 交叉工具链安装 → defconfig 配置 → make 编译 → 烧录到目标存储介质
4.1 下载 U-Boot 源码
U-Boot 源码托管在 Denx 的 GitLab 上。我个人习惯直接从官方仓库拉取,这样能保证是最新、最干净的版本。
# 克隆主线仓库
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
# 或者下载稳定版 tarball(推荐新手用)
wget https://source.denx.de/u-boot/u-boot/-/archive/v2024.01/u-boot-v2024.01.tar.bz2
tar -xjf u-boot-v2024.01.tar.bz2
cd u-boot-v2024.01
我的建议:新手先用稳定版 tag,比如 v2024.01 或 v2023.10。主线分支可能包含未稳定化的代码,我在项目中遇到过因为用了最新主线导致编译失败的情况,排查了半天才发现是某个驱动提交有问题。
4.2 配置交叉编译环境
说白了,交叉编译就是在你的 PC(x86)上生成目标板(ARM/RISC-V/MIPS)能跑的机器码。没有交叉工具链,你编译出来的东西板子根本不认识。
4.2.1 安装交叉编译工具链
不同架构用的工具链不一样。我列几个常用的:
| 目标架构 | 工具链前缀 | 安装方式(Ubuntu/Debian) |
|---|---|---|
| ARM 32-bit | arm-linux-gnueabihf- |
sudo apt install gcc-arm-linux-gnueabihf |
| AArch64 (ARM64) | aarch64-linux-gnu- |
sudo apt install gcc-aarch64-linux-gnu |
| RISC-V 64-bit | riscv64-linux-gnu- |
sudo apt install gcc-riscv64-linux-gnu |
安装完后,验证一下:
arm-linux-gnueabihf-gcc --version
# 应该能看到版本信息,而不是 "command not found"
我曾经踩过的坑:有次我装的是 gcc-arm-none-eabi(用于裸机开发),用它来编译 U-Boot,结果链接阶段报了一堆错。U-Boot 需要的是 Linux 交叉工具链(带 linux 字样的),因为 U-Boot 依赖 glibc 的某些启动代码。记住:U-Boot 用 arm-linux-gnueabihf-,不是 arm-none-eabi-。
4.2.2 设置环境变量
我个人习惯把工具链路径加到 ~/.bashrc 里,这样每次打开终端就不用重新设了:
export CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH=arm
然后 source ~/.bashrc 使其生效。你也可以在每次 make 时临时指定:
make CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm
嗯,这里要注意:CROSS_COMPILE 后面一定要带短横 -,否则工具链找不到。
4.3 make xxx_defconfig
U-Boot 支持几百种开发板和 SoC。你不可能手动配所有选项,所以官方提供了现成的默认配置。配置文件的命名规则一般是:
<板子名称>_defconfig
比如:
qemu_arm_defconfig— QEMU 模拟的 ARM 平台raspberry_pi_3_defconfig— 树莓派 3am335x_evm_defconfig— TI AM335x 评估板
怎么找到你的板子?
ls configs/ | grep -i "你的板子关键词"
比如我手头有块 i.MX6ULL 的板子:
ls configs/ | grep -i "mx6ull"
# 输出:mx6ull_14x14_evk_defconfig
然后执行:
make mx6ull_14x14_evk_defconfig
这一步会生成 .config 文件,里面包含了所有配置项。你可以用 make menuconfig 进一步微调,比如开启某个驱动、调整启动参数等。
小技巧:如果你用的是自己做的板子,没有现成的 defconfig,可以找一个最接近的官方板子配置,然后通过 make menuconfig 修改。我在项目中就是这么干的——拿官方的 EVK 配置做底,改掉 DDR 参数和网口配置,省了不少事。
4.4 make 编译
配置完,直接 make 就行:
make -j$(nproc)
-j$(nproc) 表示用所有 CPU 核心并行编译,能快不少。编译完成后,你会看到几个关键产物:
| 文件 | 说明 |
|---|---|
u-boot.bin |
纯二进制镜像,直接烧录到偏移地址 |
u-boot.img |
带 U-Boot 头部信息的镜像,用于某些启动方式 |
u-boot-dtb.bin |
U-Boot 和设备树打包在一起的镜像 |
u-boot-spl.bin |
SPL(第一阶段引导加载器),用于支持 SPL 的 SoC |
注意:如果你的板子使用 SPL(比如 AM335x、i.MX6ULL),那么烧录时通常需要烧 u-boot-spl.bin 和 u-boot.img 两个文件,分别放到不同的偏移位置。只烧 u-boot.bin 是起不来的。我曾经在这个问题上浪费了半天,后来看了芯片手册才明白。
4.5 烧录到 SD 卡 / NAND
编译完了,怎么把镜像弄到板子上?常见的方式有两种:SD 卡烧录 和 NAND/NOR Flash 烧录。
4.5.1 烧录到 SD 卡
这是最常用的方式,尤其适合开发阶段。把 SD 卡插入电脑,先确认设备名:
lsblk
# 假设 SD 卡是 /dev/sdb
然后直接 dd 写进去:
# 对于不带 SPL 的板子
sudo dd if=u-boot.bin of=/dev/sdb bs=1k seek=1
# 对于带 SPL 的板子(比如 AM335x)
sudo dd if=u-boot-spl.bin of=/dev/sdb bs=1k seek=1
sudo dd if=u-boot.img of=/dev/sdb bs=1k seek=69
seek 参数表示跳过多少块,这个偏移量由 SoC 的启动 ROM 决定。比如 i.MX6ULL 要求 U-Boot 放在 SD 卡的第 1KB 偏移处,所以 seek=1(bs=1k 时,1 块 = 1KB)。
关键点:不同 SoC 的偏移地址不一样。烧录前一定要查芯片手册或官方文档,确认正确的偏移值。烧错位置,板子根本不会加载 U-Boot。
4.5.2 烧录到 NAND Flash
NAND 烧录稍微复杂一些,通常需要先通过某种方式(比如串口、网络)把 U-Boot 加载到内存,然后用 U-Boot 自己的命令写到 NAND:
# 在 U-Boot 命令行中
tftp 0x82000000 u-boot.bin # 从 TFTP 服务器下载到内存
nand erase 0x0 0x80000 # 擦除 NAND 前 512KB
nand write 0x82000000 0x0 0x80000 # 写入 NAND
如果板子还没 U-Boot,那就得用 JTAG/SWD 调试器 或者 SD 卡启动 先把 U-Boot 烧进去,再用 U-Boot 烧 NAND。说白了,NAND 烧录是个「先有鸡还是先有蛋」的问题,第一次烧录往往需要借助其他手段。
我的经验:开发阶段我强烈建议用 SD 卡启动。SD 卡烧录快、改起来方便,大不了换张卡重烧。NAND 烧录慢,而且擦写次数有限,不适合频繁调试。等 U-Boot 完全调通了,再考虑烧到 NAND 里做量产。
4.6 验证烧录结果
烧完后,把 SD 卡插到板子上,上电,看串口输出。如果看到类似这样的信息,说明 U-Boot 跑起来了:
U-Boot 2024.01 (Jan 15 2025 - 10:30:00 +0800)
CPU: i.MX6ULL 792MHz
Board: MYBOARD
DRAM: 512 MiB
MMC: FSL_SDHC: 0
In: serial
Out: serial
Err: serial
Net: eth0: ethernet@2188000
Hit any key to stop autoboot: 0
=>
如果串口没输出,别慌。先检查:
- 串口线接对了没?波特率设对了没?(通常是 115200)
- 烧录的偏移地址对不对?
- SD 卡格式化了没?(U-Boot 不需要文件系统,但有些板子要求 SD 卡是 MBR 分区表)
我曾经遇到的情况:有次烧完 SD 卡,板子死活没输出。折腾了两小时,最后发现是 SD 卡插槽接触不良。换个卡槽就好了。所以,硬件问题也要考虑进去,别一味怀疑软件。
好,这一章的内容就到这。从下载源码到板子跑出 U-Boot 提示符,每一步都有它的门道。你按这个流程走一遍,基本就能把 U-Boot 编译烧录这件事拿下了。