第三章 Bootloader移植(U-Boot):U-Boot源码结构、板级配置、DDR初始化、网络启动

好,我们进入第三章。这一章讲的是Bootloader移植,具体说就是U-Boot。

说实话,U-Boot是嵌入式Linux系统里最容易被低估的一环。很多人觉得它就是个引导程序,随便配配就能跑。但我告诉你,U-Boot的稳定性直接决定了你整个ADAS系统能不能可靠启动。我在做域控制器项目时,就遇到过因为U-Boot DDR参数没配好,导致系统在高温下随机死机的问题。嗯,那会儿排查了整整两周。

3.1 U-Boot源码结构

先看看U-Boot的源码长什么样。你下载下来后,会发现顶层目录大概是这样:

u-boot/
├── arch/          # 架构相关代码
├── board/         # 板级支持包
├── common/        # 通用功能
├── configs/       # 板级配置文件
├── drivers/       # 驱动
├── dts/           # 设备树
├── include/       # 头文件
├── net/           # 网络协议栈
├── fs/            # 文件系统支持
├── cmd/           # 命令行命令
└── tools/         # 辅助工具

我个人习惯,拿到一个新板子,先看 board/configs/ 这两个目录。为什么?因为板级相关的核心代码都在这里。你想想看,U-Boot要支持几百种开发板,不可能每个板子都从头写。所以它用了一套很巧妙的配置机制。

具体来说,每个板子对应一个 defconfig 文件。比如我们用的TI TDA4VM,它的配置文件就在 configs/ 下叫 tda4vm_evm_defconfig。这个文件里定义了:

  • CPU架构(ARM64还是ARM32)
  • DDR大小和类型
  • 外设使能(网卡、SD卡、串口等)
  • 环境变量存储位置

核心思路:U-Boot的移植,说白了就是配好这个defconfig文件,然后实现板级初始化函数。

3.2 板级配置

板级配置是移植的第一步。我一般会先找一个参考板,比如TI的EVM板,然后在此基础上修改。

配置流程分三步:

  1. 创建板级目录:在 board/ 下新建一个目录,比如 board/mycompany/myboard/
  2. 实现板级初始化函数:主要是 board_init_f()board_init_r()
  3. 配置引脚复用:在 board.c 里设置GPIO、UART、I2C等引脚

这里有个坑,我曾经踩过。在配置引脚复用时,有个引脚被默认配置成了JTAG功能,结果导致UART无法正常工作。排查了三天才发现是引脚复用表里少配了一行。所以,我建议你拿到原理图后,先把所有关键外设的引脚复用表核对一遍。

小技巧:make menuconfig 可以图形化配置U-Boot,比直接改defconfig更直观。但最终还是要看defconfig文件,因为那是最终编译的依据。

3.3 DDR初始化

DDR初始化是U-Boot移植中最关键也最容易出问题的一步。为什么?因为DDR的时序参数一旦配错,系统要么起不来,要么运行不稳定。

DDR初始化的核心是配置 DDR控制器PHY。以TDA4VM为例,它的DDR控制器是Cadence的,配置参数包括:

参数 说明 典型值
DDR频率 DDR时钟频率 2133 MHz
CAS延迟 列地址选通延迟 15
tRCD 行地址到列地址延迟 15
tRP 行预充电时间 15
tRAS 行激活时间 35

这些参数从哪里来?从DDR芯片的数据手册里查。但光查手册还不够,你还需要根据PCB的走线长度做调整。我记得有一次,板子上的DDR走线长了2mm,导致信号时序偏移,系统在低温下频繁报ECC错误。后来在U-Boot里加了 tWR 参数的补偿才解决。

DDR初始化的代码一般在 board/ 目录下的 ddr.cspl_ddr.c 里。核心函数是:

void ddr_init(void)
{
    // 1. 配置DDR控制器寄存器
    writel(0x12345678, &ddr_ctrl->config);
    
    // 2. 配置PHY延迟
    writel(0x0F, &ddr_phy->dqs_delay);
    
    // 3. 执行DDR训练
    ddr_training();
    
    // 4. 验证DDR是否可用
    if (ddr_test() != 0) {
        printf("DDR init failed!\n");
        hang();
    }
}

警告:DDR训练必须在U-Boot的SPL阶段完成。如果跳过这一步,系统启动后可能会出现随机崩溃。我在项目中就见过有人为了省时间跳过了DDR训练,结果量产时出了问题。

3.4 网络启动

网络启动是ADAS域控制器开发中非常实用的功能。你想想看,每次改完代码都要插拔SD卡或USB,多麻烦。通过网络启动,你可以在主机上编译好内核和设备树,然后直接用TFTP下载到板子上运行。

网络启动的配置分两步:

  1. 网卡驱动移植:在 drivers/net/ 下找到对应网卡的驱动,比如 cpsw.c(TI的千兆网卡)
  2. 环境变量设置:在U-Boot命令行里设置IP、服务器IP、文件名等

典型的网络启动环境变量配置如下:

setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.10
setenv netmask 255.255.255.0
setenv gatewayip 192.168.1.1
setenv bootfile zImage
setenv fdtfile tda4vm.dtb
setenv bootcmd 'tftp ${loadaddr} ${bootfile}; tftp ${fdtaddr} ${fdtfile}; bootz ${loadaddr} - ${fdtaddr}'
saveenv

这里有个细节要注意:loadaddrfdtaddr 不能重叠,否则内核解压时会覆盖设备树。我建议 loadaddr 设为 0x82000000fdtaddr 设为 0x88000000,中间留够空间。

网络启动的流程是:

  • 板子上电,U-Boot启动
  • 执行 bootcmd,通过TFTP下载内核和设备树
  • bootz 命令启动内核

经验之谈:网络启动虽然方便,但不要在生产环境中用。因为网络延迟和丢包可能导致启动失败。我一般只在开发阶段用网络启动,量产时还是用eMMC或SD卡启动。

好了,这一章的内容就这些。U-Boot移植说白了就是三板斧:配好板级文件、调好DDR参数、搞定网络启动。但每一板斧都有不少细节,需要你耐心调试。下一章我们讲内核移植,到时候你会看到U-Boot和内核是怎么配合的。