干什么
昨天我跟随着正点原子的教程学习了U-Boot如何移植,今天我决定从官网上下载最新的U-Boot并尝试移植到自己的板子上。访问官网,下载最新版的U-Boot源码压缩包(截止文章创建时是2026.01),拖入虚拟机并解压。(亲测在正点原子提供的16.04下会出许多问题,所以我在下载好编译器后转到了24.04的ubuntu版本运行。)
图 1 文件目录
怎么干
配置 Makefile
在根目录下的Makefile中添加以下语句,并保存
1 2
| ARCH = arm CROSS_COMPILE = arm-linux-gnueabihf-
|
尝试编译标准配置
在根目录下configs文件夹中找到mx6ull_14x14_evk_defconfig,在终端中输入如下命令:
make clean
make mx6ull_14x14_evk_defconfig
make V=1 -j16
不出意外的话就要出意外了,正点原子提供的编译器版本过老,需要先升级一下。
图 2 报错
在网站下载11.2版本的GCC工具链,并在虚拟机中安装。先下载安装包,拖入后解压,将U-Boot根目录下Makefile改为
CROSS_COMPILE = /home/alientek/gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf/bin/arm-none-linux-gnueabihf-
再次编译,会报一些无依赖的错误,我们依次安装。最终编译成功
图 3 第一次编译成功
将SD卡插入开发板,用串口连接输出以下信息
图 4 输出
由上图可见,只有网络设备出了问题,我们就移植这一部分。
创建自己的开发板文件
新建 defconfig 文件
在根目录下的configs文件夹中复制一份mx6ull_14x14_evk_defconfig并重命名为mx6ull_hcw_defconfig。
新建include/configs/xxx.h文件
在此文件夹中复制一份mx6ull_14x14_evk.h,并将其改名为mx6ull_hcw.h
新建板级文件
在board/freescale文件夹下,复制mx6ullevk并重命名为mx6ull_hcw,修改其中C文件为文件夹同名.c,并且修改其中Makefile为:
1 2 3 4
|
obj-y := mx6ull_hcw.o
|
将 imximage.cfg 中的下面一句:
PLUGIN board/freescale/mx6ullevk/plugin.bin 0x00907000
改为:
PLUGIN board/freescale/mx6ull_hcw/plugin.bin 0x00907000
将原文件中的原目录名改成现目录名
1 2 3 4 5 6 7 8 9 10 11 12
| if TARGET_MX6UL_14X14_EVK || TARGET_MX6UL_9X9_EVK
config SYS_BOARD default "mx6ull_alientek_emmc"
config SYS_VENDOR default "freescale"
config SYS_CONFIG_NAME default "mx6ull_alientek_emmc"
endif
|
修改此目录下的MAINTAIN文件,同上几步,也是改成自己的文件目录
试着编译一下
运行
1 2 3
| make clean make mx6ull_hcw_defconfig make V=1 -j16
|
无报错,烧录,可以看到我们新建的板级文件可以正常运行,接下来让我们修改网口部分。
图 5 输出
移植网口部分
2026版本和2016不同的是使用了默认设备树方式进行配置,从mx6ull_hcw_defconfig中可以看出
1
| CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-evk"
|
进入/arch/arm/dts文件夹,打开imx6ull-14x14-evk.dts,可以看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
/dts-v1/;
#include "imx6ull.dtsi" #include "imx6ul-14x14-evk.dtsi"
/ { model = "Freescale i.MX6 UltraLiteLite 14x14 EVK Board"; compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull"; };
&clks { assigned-clocks = <&clks IMX6UL_CLK_PLL3_PFD2>; assigned-clock-rates = <320000000>; };
|
dts和dtsi文件的关系如下
.dts = 最终可用的“整板描述文件”
.dtsi = 被包含的“设备树片段 / 模块”
dts会被直接编译生成dtb,能被uboot使用
而我们涉及到的这几个文件的主要作用分别是:
1 2 3
| imx6ull.dtsi ← 芯片出厂说明书 imx6ul-14x14-evk.dtsi ← 官方参考板接线图 imx6ull-14x14-evk.dts ← “把这块板子声明出来”
|
因此我们应着重修改imx6ul-14x14-evk.dtsi文件。
复制imx6ull-14x14-evk.dts和imx6ul-14x14-evk.dtsi,并将其重命名为imx6ull_hcw.dts和imx6ul-hcw.dtsi,将imx6ull-hcw.dts中的头文件包含改为。
1 2
| #include "imx6ull.dtsi" #include "imx6ul-hcw.dtsi"
|
同时需要在 xxx_defconfig 中将CONFIG_DEFAULT_DEVICE_TREE修改为 “imx6ull-hcw”
修改好后,试着重新编译,发现无报错。接下来我们就分析imx6ul-hcw.dtsi文件,让网口驱动跑起来!
打开imx6ul-hcw.dtsi,找到原来的网口配置代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| &fec2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet2>; phy-mode = "rmii"; phy-handle = <ðphy1>; phy-supply = <®_peri_3v3>; status = "okay";
mdio { #address-cells = <1>; #size-cells = <0>;
ethphy0: ethernet-phy@2 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <2>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET_REF>; clock-names = "rmii-ref";
};
ethphy1: ethernet-phy@1 { compatible = "ethernet-phy-id0022.1560"; reg = <1>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET2_REF>; clock-names = "rmii-ref"; }; }; };
|
| 姓名 |
是什么 |
作用 |
| FEC/MAC |
i.MX6ULL 芯片内部的一个 硬件模块 |
帧收发、DMA、校验和、高速数据通道。 |
| PHY |
一颗真正的芯片 |
模拟信号、10/100M编码、网线插拔检测、LED驱动,网口与SOC之间的桥梁 |
| MDIO |
一条管理总线 |
读 PHY ID、读链路状态、配速率。MDIO 引脚是 从 FEC 出来的,PHY 只是“被管理对象” |
SoC 与网口之间的数据传输通过 PHY 完成,而 PHY 的配置与状态管理通过 MDIO 进行。 以 fec2(第二个 FEC MAC)为例,这份代码第2-3行配置了这个设备只有一种引脚模式,为default,并且配置是pinctrl_enet2,在下面代码中有定义。phy-mode = “rmii”表明在这颗phy和soc之间使用的是RMII接口模式,phy-handle = <ðphy1>表明这个网口接的是哪个芯片,在下面的代码中有定义,phy-supply = <®_peri_3v3>告诉系统这个PHY的电源使用3.3V,status = “okay”表示设备启用。
mdio{…}是指MDIO总线节点,ethphy0是我的第一个phy,compatible = “ethernet-phy-ieee802.3-c22”表示这是一个符合 IEEE 802.3 Clause 22 规范的以太网 PHY,设备地址是2,clocks = <&clks IMX6UL_CLK_ENET_REF>;表明这个 PHY 使用 i.MX6ULL 输出的 ENET 参考时钟,clock-names 用于为该时钟命名以便驱动引用。
那么接下来我们开始移植,找到引脚配置代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| pinctrl_enet1: enet1grp { fsl,pins = < MX6UL_PAD_ENET1_RX_EN__ENET1_RX_EN 0x1b0b0 MX6UL_PAD_ENET1_RX_ER__ENET1_RX_ER 0x1b0b0 MX6UL_PAD_ENET1_RX_DATA0__ENET1_RDATA00 0x1b0b0 MX6UL_PAD_ENET1_RX_DATA1__ENET1_RDATA01 0x1b0b0 MX6UL_PAD_ENET1_TX_EN__ENET1_TX_EN 0x1b0b0 MX6UL_PAD_ENET1_TX_DATA0__ENET1_TDATA00 0x1b0b0 MX6UL_PAD_ENET1_TX_DATA1__ENET1_TDATA01 0x1b0b0 MX6UL_PAD_ENET1_TX_CLK__ENET1_REF_CLK1 0x4001b031 >; };
pinctrl_enet2: enet2grp { fsl,pins = < MX6UL_PAD_GPIO1_IO07__ENET2_MDC 0x1b0b0 MX6UL_PAD_GPIO1_IO06__ENET2_MDIO 0x1b0b0 MX6UL_PAD_ENET2_RX_EN__ENET2_RX_EN 0x1b0b0 MX6UL_PAD_ENET2_RX_ER__ENET2_RX_ER 0x1b0b0 MX6UL_PAD_ENET2_RX_DATA0__ENET2_RDATA00 0x1b0b0 MX6UL_PAD_ENET2_RX_DATA1__ENET2_RDATA01 0x1b0b0 MX6UL_PAD_ENET2_TX_EN__ENET2_TX_EN 0x1b0b0 MX6UL_PAD_ENET2_TX_DATA0__ENET2_TDATA00 0x1b0b0 MX6UL_PAD_ENET2_TX_DATA1__ENET2_TDATA01 0x1b0b0 MX6UL_PAD_ENET2_TX_CLK__ENET2_REF_CLK2 0x4001b031 >; };
|
以pinctrl_enet1: enet1grp为例,这段代码定义了一组ENET1网口要用到的引脚,并指定每个引脚的功能,每个引脚用什么电气参数(PAD控制),其格式为
[物理引脚]__ [复用成什么功能] [电气参数]
我们新建一个网口复位引脚,如下
1 2 3 4 5 6 7 8 9 10
| pinctrl_enet1_reset: enet1resetgrp { fsl,pins = < MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x000000 >; }; pinctrl_enet2_reset: enet2resetgrp { fsl,pins = < MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x000000 >; };
|
FEC配置代码修改后如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| &fec1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet1>; phy-mode = "rmii"; phy-handle = <ðphy0>; phy-supply = <®_peri_3v3>; status = "disabled"; };
&fec2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_enet2>; phy-mode = "rmii"; phy-handle = <ðphy1>; phy-supply = <®_peri_3v3>; status = "okay"; local-mac-address = [02 12 34 56 78 9a];
mdio { #address-cells = <1>; #size-cells = <0>;
ethphy0: ethernet-phy@2 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <2>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET_REF>; clock-names = "rmii-ref";
reset-gpios = <&gpio5 7 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; reset-deassert-us = <10000>;
};
ethphy1: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <1>; micrel,led-mode = <1>; clocks = <&clks IMX6UL_CLK_ENET2_REF>; clock-names = "rmii-ref";
reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>; reset-assert-us = <10000>; reset-deassert-us = <10000>; }; }; };
|
主要修改了以下几个部分
①、关闭FEC1,只使能FEC2,因为正点原子的MINI板上与ENET相连的是FEC2。
②、修改 compatible ,告诉内核 PHY 是一个标准 Clause22 PHY,让内核自己识别。
③、添加复位代码,当 U-Boot 决定要用这个 PHY 时,拉这根复位脚。
④、设定本地 MAC 地址
图 6 网卡驱动成功
结语
在这个过程中,我逐步理清了几个过去容易混淆的概念:FEC 只是 SoC 内部的 MAC 控制器,真正与网线打交道的是 PHY;RMII 承载的是高速数据,而 MDIO 只是一个低速的“管理通道”;设备树的职责不是“写驱动”,而是准确描述硬件连接关系,让通用驱动能够工作。
接下来,可以在此基础上继续深入:例如将 MAC 地址移入 EEPROM、分析 PHY 自协商过程,或者进一步对接 Linux 内核网络子系统。
但无论走到哪一步,这次移植经历已经证明了一点——理解结构,比跑通结果更重要。