嵌入式Linux开发学习路径中,U-Boot是重要但又比较鸡肋的环节。从功能上,U-Boot主要完成启动,加载内核以及设备树,引导跳转执行的功能;只是整个工作流程中比较简短的一部分。不像内核和文件系统,和我们的产品功能中息息相关,U-Boot可以说感知不深,描述功能实现的文档和说明也远少于内核和文件系统。
以我的项目经历,大部分情况都是适配后,除非启动中遇到问题,或者有新的需求(例如修改调试口,尽快控制某些I/O电平);U-Boot开发在流程中的工作就完成了。这也导致网上关于移植的资料虽然有一些,但也比较零散;如果需要使用U-Boot本身不支持的功能,例如添加硬件操作,支持扩展命令,基本没有相关资料说明。
另外对于SOC来说,如I.MX6ULL、T113-i、RK3568等,其寄存器和模块功能又过于复杂,部分芯片资料还不全;在框架上进行移植U-Boot支持工作还比较简单;在此基础上,扩展U-Boot功能,并用来学习U-Boot应用,十分困难。本篇则通过单片机移植U-Boot,并添加功能的方式来学习U-Boot相关的知识,如设备树维护,驱动实现,命令控制等工作,就是可行的选择;当然这是建立在我对STM32理解比较深入,有着寄存器,标准库,HAL库的开发经验基础上的;适合本身有单片机经验,去理解U-Boot运行机制的开发者。
其实,U-Boot已经实现了对于单片机芯片STM32F429芯片的支持,不过是基于官方开发板的。而我手里有块正点原子的STM32F429-Alpha开发板,所以就从这块开发板开始移植;当然选择这块开发板也是因为我对STM32F429这款芯片掌握的还算深刻,也总结了类似的文档,可以参考如下路径:
对于U-Boot的移植,肯定需要源码和对应硬件;既然是学习,肯定从主干进行移植。相应的资源如下。
有了这些资源,下面可以正式开始移植工作;具体目录如下所示。
对于U-Boot的移植,参考U-Boot平台移植文档,主要有以下几个步骤。
下面安装上述步骤进行说明。
# 下载编译工具
wget https://mirrors.tuna.tsinghua.edu.cn/armbian-releases/_toolchain/gcc-arm-11.2-2022.02-x86_64-arm-none-eabi.tar.xz
tar -xvf gcc-arm-11.2-2022.02-x86_64-arm-none-eabi.tar.xz
# 导入到系统环境下
export PATH=$PATH:$(pwd)/gcc-arm-11.2-2022.02-x86_64-arm-none-eabi/bin
# 检测是否支持arm-none-eabi-gcc
arm-none-eabi-gcc --version
如果安装成功,可以显示如下所示。
# 下载u-boot
wget https://source.denx.de/u-boot/u-boot/-/archive/v2025.04/u-boot-v2025.04.tar.gz
tar -xvf u-boot-v2025.04.tar.gz
# 进入u-boot源码目录
cd u-boot-v2025.04
# Makefile指定编译工具
vim Makefile
###############################
ARCH=arm
CROSS_COMPILE=arm-none-eabi-
##############################
# 编译
make stm32f429-discovery_defconfig
make -j6
如果编译成功,可以显示如下所示。
其中u-boot.bin文件就是生成的U-Boot镜像,不过这个镜像是适配STM32F429-Discovery的,而我使用的开发板并不适用;直接下载没有打印信息,也无法正常启动。那么下面一步进行开发板的适配。前面讲过,对于U-Boot的适配就其实就是硬件的适配。
对于产品来说,U-Boot的适配考虑到应用需求,可能需要LOGO的显示、网络通讯的处理、加载其它程序等;这就需要GUI、NETWORK、SD卡等模块的支持。不过这都是综合完善的需求,作为第一步,我们需要的其实只有两部分。
对于STM32F429-Discovery开发板,其串口输出为UART0,对应的引脚是PA9和PA10;这部分和开发板一致,不需要修改。对于SDRAM的初始化,其主要差别是STM32F429-Discovery开发板使用的是bank1,而我们开发板使用的是bank2,所以需要支持。这部分主要进行设备树的修改。
// fmc引脚复用修改
// 文件: arch/arm/dts/stm32f429-disco-u-boot.dtsi
&pinctrl {
fmc_pins: fmc@0 {
bootph-all;
pins
{
pinmux = <
//......
<STM32_PINMUX('G', 2, AF12)>, /* + A12 增加引脚支持,扩充memory空间*/
<STM32_PINMUX('G', 1, AF12)>, /* A11 */
<STM32_PINMUX('G', 0, AF12)>, /* A10 */
<STM32_PINMUX('F',15, AF12)>, /* A09 */
<STM32_PINMUX('F',14, AF12)>, /* A08 */
<STM32_PINMUX('F',13, AF12)>, /* A07 */
//......
<STM32_PINMUX('C', 2, AF12)>, /* SDNE0 U SDNE1->SDNE0*/
<STM32_PINMUX('C', 3, AF12)>, /* SDCKE0 U SDCKE1->SDCKE0*/
<STM32_PINMUX('C', 0, AF12)>, /* SDNWE */
<STM32_PINMUX('F',11, AF12)>, /* SDNRAS */
<STM32_PINMUX('G',15, AF12)>, /* SDNCAS */
<STM32_PINMUX('G', 8, AF12)>; /* SDCLK */
slew-rate = <2>;
bootph-all;
};
};
}
// fmc配置修改
// 文件: arch/arm/dts/stm32f429-disco-u-boot.dtsi
fmc: fmc@A0000000 {
compatible = "st,stm32-fmc";
reg = <0xa0000000 0x1000>;
clocks = <&rcc 0 STM32F4_AHB3_CLOCK(FMC)>;
pinctrl-0 = <&fmc_pins>;
pinctrl-names = "default";
st,syscfg = <&syscfg>;
st,swp_fmc = <1>; //stm32f4默认SDRAM空间不支持软件执行,需要交换SDRAM空间到可执行区域
bootph-all;
/*
* Memory configuration from sdram datasheet
* IS42S16400J
*/
bank0: bank@0 { //根据硬件确定使用band0, 后续相应时序根据SDRAM参数确认
st,sdram-control = /bits/ 8 <NO_COL_9
NO_ROW_13
MWIDTH_16
BANKS_4
CAS_3
SDCLK_2
RD_BURST_EN
RD_PIPE_DL_1>;
st,sdram-timing = /bits/ 8 <TMRD_2
TXSR_8
TRAS_6
TRC_6
TWR_2
TRP_2
TRCD_2>;
st,sdram-refcount = < 1283 >;
};
};
// 修改启动映射地址和memory空间
// 文件: arch/arm/dts/stm32f429-disco.dts
memory@80000000 { //对于stm32, bank0地址对应0x80000000, 详细参考后续说明
device_type = "memory";
reg = <0x80000000 0x2000000>;
};
完成这一步后,最基础的U-Boot就完成了。关于FMC I/O的配置,参考硬件原理图确认修改即可。bank的选择和SDRAM的时序,因为单片机已经开发过,安装关键字一一修改适配,也并不困难。启动region memory反而是最初迷惑最多的地方。这个地址在SDRAM初始化后,会在SDRAM中执行前搬运数据时需要;而SDRAM的地址到底时多少,最开始没有确认,也就没进行改动;后果就是执行中不是卡死就是hardfault;后来在英文的STM32F42x手册里有这样一段说明。
我们使用的是bank0(对应这里面硬件的bank1),所以对应的启动region memory地址是0x80000000;这是因为STM32F4的DRAM地址区域为了安全考虑默认不支持I-CODE访问,而NAND BANK的地址是支持访问的,这里就是交换两个区域的地址映射,从而使SDRAM满足运行要求。
完成上述修改后,重新编译,生成新的uboot镜像,烧录到内部FLASH即可执行,具体显示如下所示。
至此,关于u-boot在STM32的移植就完成了。不过我们主要是用于学习U-Boot的知识,去理解U-Boot设备树节点处理,驱动加载方式和应用命令的实现。所以接下来就开始学习U-Boot的相关知识的学习。
直接开始下一章节说明: uboot命令行脚本和设备树overlay实现