嵌入式Linux开发学习路径中,U-Boot是重要但又比较鸡肋的环节。从功能上,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可以说是在SOC上no-os软件集大成之作,整个功能可以说错综复杂;如果是因为有需要去了解,本篇算是抛砖引玉,以实践的方式描述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卡等模块的支持。不过这都是综合完善的需求,作为第一步,我们需要处理最核心的适配,对于一款单片机来说,就是晶振、时钟、串口打印和SDRAM配置。
// 文件: arch/arm/dts/stm32f429.dtsi
// HSE时钟说明
clocks {
clk_hse: clk-hse {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <0>;
};
//......
}
&clk_hse {
clock-frequency = <25000000>;
};
相关系统时钟处理文件参考: “driver/clk/stm32/clk-stm32f.c”。
调试信息是我们开发过程中最常用的,也是最方便的调试方式,必须首先满足;对于STM32F429-Discovery开发板,其串口输出为UART0,对应的引脚是PA9和PA10;这部分和开发板一致,不需要修改;不过这里也列出设备树内相关的配置。
// 文件: arch/arm/dts/stm32f429.dtsi
usart1: serial@40011000 {
compatible = "st,stm32-uart";
reg = <0x40011000 0x400>;
interrupts = <37>;
clocks = <&rcc 0 STM32F4_APB2_CLOCK(USART1)>;
status = "disabled";
dmas = <&dma2 2 4 0x400 0x0>,
<&dma2 7 4 0x400 0x0>;
dma-names = "rx", "tx";
};
// 文件: arch/arm/dts/stm32f429-disco.dts
// 选择serial0(uart1)作为输出口
chosen {
bootargs = "root=/dev/ram";
stdout-path = "serial0:115200n8";
};
aliases {
serial0 = &usart1;
};
&usart1 {
pinctrl-0 = <&usart1_pins_a>;
pinctrl-names = "default";
status = "okay";
};
//文件: arch/arm/dts/stm32f4-pinctl.dtsi
pinctrl: pinctrl@40020000 {
//.....
usart1_pins_a: usart1-0 {
pins1 {
pinmux = <STM32_PINMUX('A', 9, AF7)>; /* USART1_TX */
bias-disable;
drive-push-pull;
slew-rate = <0>;
};
pins2 {
pinmux = <STM32_PINMUX('A', 10, AF7)>; /* USART1_RX */
bias-disable;
};
};
};
对于SDRAM的初始化,其主要差别是STM32F429-Discovery开发板使用的是bank2,而我们开发板使用的是bank1,所以需要修改支持。这部分主要进行设备树的修改。
// 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的时序,因为单片机已经开发过,安装关键字一一修改适配,也并不困难。
这里的启动memory@80000000反而是最初迷惑最多的地方。这个地址在SDRAM初始化后,搬运数据以及后续执行时需要;而SDRAM的地址到底时多少,最开始没有确认,也就没进行改动使用官方的地址;实际执行中不是卡死就是hardfault;后来在英文的STM32F42x手册里有这样一段说明。
我们使用的是第0块SDRAM(对应这里面硬件的bank1),也就是启动region memory对应地址是0x80000000;这是因为STM32F4的DRAM地址区域为了安全考虑默认不支持I-CODE访问,而NAND BANK的地址(0x80000000、0x90000000)是支持访问的,这里就需要交换两个区域的地址映射,从而使SDRAM满足运行要求。
完成上述修改后,重新编译,生成新的uboot镜像,烧录到内部FLASH即可执行,具体显示如下所示。
至此,在STM32f429开发板上移植U-Boot就完成了。可以看到,对于U-Boot来说,能够运行且被感知,主要包含以下模块。
满足这三个模块,即可实现支持输入输出的U-Boot命令行运行环境。
直接开始下一章节说明: uboot命令行脚本和设备树overlay实现