4、U-Boot编译与烧录:获取U-Boot源码、配置与编译(make am335x_evm_defconfig)、烧录到SD卡/SPI Flash、U-Boot常用命令。
好,咱们接着往下走。上一章我们把SD卡启动环境搭好了,现在该让U-Boot真正跑起来了。这一节内容很实在,就是教你如何从源码拿到一个能用的U-Boot,然后把它塞进板子里。
我个人习惯把U-Boot比作「硬件的BIOS」,但它比BIOS灵活得多。你想想看,一个嵌入式系统从上电到运行Linux,中间全靠它撑着。所以,搞懂U-Boot的编译和烧录,是每个嵌入式Linux工程师的必修课。
4.1 获取U-Boot源码
U-Boot的源码托管在多个地方,最权威的当然是官方仓库。不过,TI的处理器通常有自己维护的分支,里面包含了对特定外设和时钟的优化。
官方主线仓库:
git clone https://source.denx.de/u-boot/u-boot.git
TI官方维护分支(推荐):
git clone git://git.ti.com/ti-u-boot/ti-u-boot.git
cd ti-u-boot
git checkout ti2023.04 # 或者你需要的版本标签
我个人建议用TI维护的分支。为什么?因为主线U-Boot虽然新,但有时候对AM335x的某些外设支持不够完善。TI的分支经过了他们的硬件验证,踩坑的概率小很多。我记得有一次项目赶进度,用了主线U-Boot,结果网卡死活不通,折腾了两天,换成TI分支后直接一次过。
4.2 配置与编译
拿到源码后,第一步不是直接make,而是配置。U-Boot的配置系统跟Linux内核很像,都是基于Kconfig的。说白了,就是通过一个配置文件告诉编译器:「我的板子有哪些外设?内存多大?从哪启动?」
配置命令:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- am335x_evm_defconfig
这里解释一下几个关键点:
ARCH=arm:指定架构为ARM。虽然AM335x是ARM Cortex-A8,但U-Boot里统一用arm。CROSS_COMPILE=arm-linux-gnueabihf-:交叉编译工具链的前缀。注意后面的横杠不能少,它告诉make工具链的名字是arm-linux-gnueabihf-gcc。am335x_evm_defconfig:这是TI官方为AM335x评估板提供的默认配置。如果你用的是BeagleBone Black,也可以用am335x_boneblack_defconfig。
执行完配置后,你可以用make menuconfig来微调。比如你想关闭某个不用的驱动,或者调整启动延时,都可以在这里改。不过第一次玩的话,直接用默认配置就行。
编译命令:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
-j4表示用4个线程并行编译。如果你的电脑CPU核心多,可以适当加大这个数字。编译完成后,会在当前目录生成几个关键文件:
| 文件名 | 说明 |
|---|---|
MLO |
第一级引导加载器。AM335x的ROM会先加载它。 |
u-boot.img |
U-Boot的主镜像。MLO加载完后再加载它。 |
u-boot.bin |
裸的U-Boot二进制文件,一般不用这个。 |
arm-linux-gnueabihf-gcc: not found,说明交叉编译工具链没装或者没加到PATH里。赶紧回去看上一章,把工具链装好。
4.3 烧录到SD卡
编译出MLO和u-boot.img后,接下来就是烧录。SD卡是AM335x最常用的启动介质,也是调试阶段的首选。
步骤一:准备SD卡
先把SD卡插到电脑上,用lsblk或fdisk -l找到它的设备名。假设是/dev/sdb(注意,千万别搞错,搞错了你的硬盘就没了)。
步骤二:分区(如果还没分过)
AM335x的ROM要求SD卡第一个分区必须是FAT32格式,用来放MLO和u-boot.img。第二个分区可以是ext4,用来放内核和设备树。
sudo fdisk /dev/sdb
# 在fdisk里:删除所有分区,新建一个主分区,大小设为100M,类型选c(FAT32)
# 再建一个主分区,用剩余空间,类型默认(Linux)
# 写盘退出
sudo mkfs.vfat -F 32 /dev/sdb1
sudo mkfs.ext4 /dev/sdb2
步骤三:烧录MLO和u-boot.img
这里有个坑,我刚开始做的时候踩过。MLO必须放在FAT32分区的特定位置,而且文件名必须是小写的MLO。
sudo mount /dev/sdb1 /mnt
sudo cp MLO /mnt/MLO
sudo cp u-boot.img /mnt/u-boot.img
sync
sudo umount /mnt
嗯,这里要注意:sync命令很重要。它确保数据真正写入了SD卡,而不是还在缓存里。我曾经因为忘了sync,拔卡后MLO文件损坏,板子死活起不来。
MLO文件。如果找不到,它会尝试SPI Flash。所以,如果你SD卡没插好或者MLO没放对位置,板子会从SPI Flash启动(如果里面有U-Boot的话)。
4.4 烧录到SPI Flash
SD卡适合开发阶段,但产品量产时,一般会把U-Boot烧到SPI Flash里。这样板子一上电就能启动,不需要插SD卡。
前提: 你得先有一个能运行的U-Boot环境(比如从SD卡启动)。然后在U-Boot命令行里操作。
步骤一:把U-Boot镜像加载到内存
假设你从SD卡或网络把u-boot.bin(注意,这里用.bin,不是.img)放到了内存地址0x82000000。
# 从SD卡加载
fatload mmc 0:1 0x82000000 u-boot.bin
# 或者从网络加载(需要先配好IP)
tftp 0x82000000 u-boot.bin
步骤二:擦除并写入SPI Flash
# 擦除SPI Flash,从0地址开始,擦除512KB(0x80000字节)
sf probe
sf erase 0x0 0x80000
# 把内存里的数据写入SPI Flash
sf write 0x82000000 0x0 0x80000
这里要注意,SPI Flash的擦除是按扇区来的,不同型号的Flash扇区大小可能不一样。我遇到过一款Flash,扇区是64KB,结果我按128KB擦除,直接报错。所以,sf erase的大小最好是扇区大小的整数倍。
sf info命令查看。它会显示Flash的型号、大小和扇区信息。
4.5 U-Boot常用命令
U-Boot启动后,你会看到一个=> 提示符。这就是它的命令行。下面是我工作中最常用的一些命令:
| 命令 | 功能 | 示例 |
|---|---|---|
printenv |
打印所有环境变量 | printenv bootargs |
setenv |
设置环境变量 | setenv bootdelay 3 |
saveenv |
保存环境变量到存储介质 | saveenv |
fatload |
从FAT分区加载文件到内存 | fatload mmc 0:1 0x82000000 zImage |
ext4load |
从ext4分区加载文件到内存 | ext4load mmc 0:2 0x82000000 zImage |
tftp |
通过网络TFTP加载文件 | tftp 0x82000000 zImage |
bootm |
启动内存中的内核镜像 | bootm 0x82000000 |
sf probe |
初始化SPI Flash | sf probe |
sf read/write |
读写SPI Flash | sf read 0x82000000 0x0 0x80000 |
mmc dev |
切换MMC设备 | mmc dev 0(切换到第一个MMC) |
我个人最常用的组合是:
# 从SD卡加载内核和设备树,然后启动
fatload mmc 0:1 0x82000000 zImage
fatload mmc 0:1 0x88000000 am335x-evm.dtb
bootm 0x82000000 - 0x88000000
这里bootm后面的三个参数分别是:内核地址、ramdisk地址(没有就用-跳过)、设备树地址。
bootm后内核卡住了。查了半天,原来是设备树地址和内核地址重叠了。AM335x的内存起始地址是0x80000000,内核一般加载到0x82000000,设备树放到0x88000000,这样就不会冲突。
好了,这一节的内容就到这里。你学会了如何获取U-Boot源码、配置编译、烧录到SD卡和SPI Flash,还掌握了一些常用命令。下一节,我们会用这些知识,真正把Linux内核跑起来。