4. U-Boot移植:从源码到跑起来
U-Boot移植,说白了就是让bootloader认识你的板子。很多新手一上来就急着改代码,结果连DDR都没初始化对,板子直接变砖。我刚开始做网关项目时也踩过这个坑——烧了十几次,板子就是不跑,最后发现是时钟配置差了那么几个MHz。
这一章,咱们就一步步来。从拿源码开始,到最终在串口看到U-Boot的启动信息。
4.1 获取U-Boot源码
我个人习惯直接从官方仓库拉代码。别用那些第三方改得面目全非的版本,出了问题你连问的地方都没有。
# 从官方Git仓库获取
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
# 切到稳定版本(我建议用LTS)
git checkout v2024.01
# 看看当前支持的板子
ls configs/ | grep -i "你的芯片型号"
4.2 板级文件配置
U-Boot用Kconfig管理配置,跟内核一样。你需要创建一个defconfig文件,告诉U-Boot你的板子用哪个CPU、多大内存、哪些外设。
我一般先找一个参考板,比如你的芯片是i.MX6ULL,那就找mx6ull_14x14_evk_defconfig,复制一份改名字。
# 创建你自己的板级配置
cp configs/mx6ull_14x14_evk_defconfig configs/my_gateway_defconfig
# 编辑配置
make my_gateway_defconfig
make menuconfig
在menuconfig里,你需要关注这几个地方:
| 配置项 | 说明 | 常见坑 |
|---|---|---|
| Target select | 选择你的SoC型号 | 选错了CPU核,编译都过不了 |
| DDR memory | DDR类型、大小、时序 | 时序参数抄错,板子直接挂 |
| Network support | 使能网卡驱动 | PHY地址不对,ping不通 |
| Boot media | 从SD卡/NAND/eMMC启动 | 启动设备选错,烧了也白烧 |
4.3 DDR参数修改——最容不得马虎的地方
DDR初始化是U-Boot移植的第一关。板子能不能跑起来,全看这一步。我见过太多人在这里翻车——参数抄错了、时序算错了、甚至DDR型号都选错了。
你需要修改的文件一般在:board/你的厂商/你的板子/ 或者 arch/arm/dts/ 里。
以i.MX6ULL为例,DDR参数在 board/freescale/mx6ullevk/mx6ullevk.c 里:
static struct mx6_ddr3_cfg mem_ddr3_cfg = {
.mem_speed = 800, // DDR频率,单位MHz
.density = 4, // 4Gb颗粒
.width = 16, // 16位宽
.banks = 8, // 8个bank
.rowaddr = 15, // 行地址位数
.coladdr = 10, // 列地址位数
.pagesz = 2, // 页面大小
.trcd = 14, // tRCD时序
.trp = 14, // tRP时序
.tras = 35, // tRAS时序
};
- mem_speed:DDR颗粒的额定频率,不是你想跑多快就跑多快
- trcd/trp/tras:从DDR颗粒数据手册里抄,别自己编
- density:单颗DDR的容量,4Gb=512MB,8Gb=1GB
我曾经在一个项目里,DDR死活初始化不过。用示波器量了时钟,发现频率是对的,但数据线有根虚焊。嗯,硬件问题有时候比软件更隐蔽。
4.4 时钟树配置
时钟配置决定了CPU跑多快、外设能不能工作。U-Boot里时钟树一般写在 arch/arm/mach-你的芯片/ 下。
你需要关注这几个时钟:
- CPU时钟:核心频率,别超频,芯片会发热
- DDR时钟:必须和DDR参数里的mem_speed匹配
- 网络时钟:PHY和MAC需要同频,否则不通
- UART时钟:串口波特率靠它,错了就看不到打印
// 以i.MX6ULL为例,时钟初始化
void init_clocks(void)
{
// 设置ARM PLL为996MHz
set_arm_pll(996000000);
// 设置DDR PLL为528MHz
set_ddr_pll(528000000);
// 设置系统PLL为480MHz(给USB和网络用)
set_sys_pll(480000000);
}
4.5 GPIO引脚复用
嵌入式Linux里,GPIO不是你想用就能用的。每个引脚可能有七八种功能,你得在U-Boot里配好复用关系。
引脚复用配置一般在 board/你的板子/board.c 或者设备树里。我建议用设备树的方式,更清晰。
// 设备树里配置引脚复用
&iomuxc {
pinctrl_uart1: uart1grp {
fsl,pins = <
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
>;
};
pinctrl_enet1: enet1grp {
fsl,pins = <
MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0
MX6UL_PAD_ENET1_RXD0__ENET1_RX_DATA0 0x1b0b0
// ... 其他网络引脚
>;
};
};
这里有个坑:引脚配置的最后那个数字(比如0x1b0b1)是电气特性参数,包括驱动能力、上下拉、速度等级。我建议先抄参考板的,等板子跑起来了再优化。
4.6 网络驱动使能——PHY芯片适配
网关嘛,网络是命根子。U-Boot里网络驱动分两部分:MAC控制器驱动(芯片内置)和PHY芯片驱动(外挂)。
PHY芯片适配是重头戏。不同厂家的PHY,寄存器布局不一样,你得告诉U-Boot怎么读写PHY。
// 在设备树里描述PHY
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@0 {
compatible = "ethernet-phy-id0011.0140"; // PHY ID
reg = <0>; // PHY地址
max-speed = <100>;
};
};
};
PHY ID怎么查?看PHY芯片数据手册,或者直接读寄存器:
# 在U-Boot命令行里读PHY ID
mdio read 0 2 // 读PHY ID寄存器高16位
mdio read 0 3 // 读PHY ID寄存器低16位
4.7 烧录与验证
代码改完了,怎么烧到板子上?常见方式有几种:
| 烧录方式 | 适用场景 | 工具 |
|---|---|---|
| SD卡烧录 | 开发阶段,最方便 | dd命令、balenaEtcher |
| JTAG烧录 | 板子完全没系统时 | OpenOCD、JLink |
| 网络烧录 | 批量生产 | TFTP、NFS |
| 串口烧录 | 没有网络、没有JTAG时 | kermit、lrzsz |
我开发时最常用SD卡烧录,简单粗暴:
# 编译U-Boot
make my_gateway_defconfig
make -j4
# 烧录到SD卡(假设SD卡是/dev/sdb)
sudo dd if=u-boot.imx of=/dev/sdb bs=1k seek=1 conv=fsync
# 插到板子上,上电,看串口
sudo minicom -D /dev/ttyUSB0 -b 115200
如果一切顺利,你会看到:
U-Boot 2024.01 (Jan 15 2025 - 10:30:00 +0800)
CPU: i.MX6ULL rev1.1 at 996 MHz
DRAM: 512 MiB
MMC: FSL_SDHC: 0
Net: FEC1
Hit any key to stop autoboot: 0
=>
- 串口能打印U-Boot信息
- DRAM大小正确(512MB/1GB等)
- MMC/SD卡能识别
- 网络能ping通(ping 192.168.1.1)
- 能加载内核(tftp或fatload)
如果卡在某一步,别慌。先检查硬件——电压对不对、时钟有没有、复位信号正不正常。我遇到过最奇葩的问题,是板子上一个电阻焊错了,导致DDR的VREF电压不对,U-Boot能跑但DDR校验过不去。
嗯,移植U-Boot就是这样,软件和硬件的问题交织在一起。但只要你把DDR、时钟、GPIO、网络这四关过了,后面的路就好走了。