~~~~~~~~ 一般的,我们做linux开发,移植uboot和kernel的时候并不需要从头开始移植,uboot和kernel庞大又复杂,从头开始移植不仅耗时耗力,而且还很容易出错,没有人比SOC设计人员更熟悉他们自己的芯片,所以我们不管是软件还是硬件的设计都是根据官方出的开发板进行参考设计,并且在设计的过程中,比较重要的部分会和官方保持一致,例如EMMC、内存、NAND、SD卡、网络等,这是因为如果想要启动起uboot内存是必须的,只要比较重要或常用的几个外设跟官方的原理图保持一致或基本保持一致,那么我们下载进官方的uboot或者依据官方给的uboot进行简单更改,就能适配我们自己的板子,大大缩短了我们的开发周期。
uboot只是一个启动linux的作用,所以没必要将项目中使用到的驱动都适配好,因为在uboot阶段如果需要适配驱动是需要去修改源代码的,没有一些经验和基础是做不到这一步的。在项目开发阶段,uboot中最常用的就是控制台(串口)、SD卡、USB、网络这四个,对于i.MX6ULL来说,设计的时候对于这三个外设只要我们跟官方保持一致,那么用官方的uboot拿来就能直接用!毕竟我们项目开发的目的是在linux系统基础上开发应用而已(ps:这也是现在越来越趋向于嵌入式linux应用开发工程师的职位,因为现在有了设备树的开发方式以及半导体厂家做的BSP足够好了,所以嵌入式linux驱动工程师需要做的事越来越少。。。)。
我所使用的开发板是飞凌的,OKMX6UL-C底板+FETMX6UL-C核心板(512MB内存+8G EMMC)。
开发环境使用的是ubuntu1804,64位系统。
交叉编译器使用的是linaro提供的gcc7.5.0版本(用最新版本是因为移植的uboot版本也比较新,旧版本的交叉编译器无法编译新版uboot),im6ull支持硬件浮点,所以要使用带hf字样的编译器,如下图:
https://releases.linaro.org/components/toolchain/binaries/7.5-2019.12/arm-linux-gnueabihf/
编译uboot和内核所必须的依赖库安装:
sudo apt-get install -y libncurses5-dev lsb-core lib32stdc++6 bison flex lzop git
将交叉编译器通过samba传入到虚拟机ubuntu中,解压:
tar xvf gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz
然后将交叉编译器加入到系统环境变量,打开/etc/profile
文件:
sudo gedit /etc/profile
在最后一行加入并保存退出:
export PATH=$PATH:/home/hello/install/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin
最后使环境变量生效:
source /etc/profile
测试一下交叉编译器是否可用了,输入以下命令查看arm-linux-gnueabihf-gcc的版本号:
arm-linux-gnueabihf-gcc -v
下载uboot(下载的很慢):
git clone https://source.codeaurora.org/external/imx/uboot-imx
cd uboot-imx
查看所有分支:
git branch --all
检出分支,这里我检出5.4.70_2.3.0版本,新版本的uboot支持了设备树,基于设备树的uboot可以更刚方便的便于我们移植:
git checkout imx_v2020.04_5.4.70_2.3.0
修改顶层Makefile,指定编译器和架构:
gedit Makefile
找到CROSS_COMPILE
,在下方添加以下内容后并保存退出(效果如下图):
ARCH = arm
CROSS_COMPILE = arm-linux-gnueabihf-
在configs目录下存在许多mx6ull的配置文件,其中带evk字样的为NXP为他们自己的EVK开发板定制的配置文件。
我使用的板子是SOC是IM6ULL,EMMC版本,且也是根据NXP的EVK开发板参考设计而来,所以使用mx6ull_14x14_evk_emmc_defconfig
这个配置文件进行编译。
编译前先清理一下uboot工程:
make distclean
加载编译配置(效果如下图):
make mx6ull_14x14_evk_emmc_defconfig
开始编译:
make -j 2
make的【-j】参数表示多核编译,能够加快编译速度。例如我虚拟机设置的是双核CPU,所以【- j 2】表示双核一块编译uboot源码。
有时候你还会看到make -V=1
,V参数表示编译时候的显示的详情。
编译完成(如下图):
其中u-boot-dtb.imx就是我们放到SD卡中刷写进板子的文件。
以上步骤是基于NXP官方EVK开发板进行的编译,下面我们建立自己的目标板进行编译,这样的话方便我们对工程进行管理。
以下步骤参考的是官网EVK开发板所需的配置以及相关文件,以此来建立自己的板子配置。
添加自己板子的配置文件和头文件:
cp configs/mx6ull_14x14_evk_emmc_defconfig configs/mx6ull_14x14_hello_emmc_defconfig
cp include/configs/mx6ullevk.h include/configs/mx6ullhello.h
修改配置文件:
gedit configs/mx6ull_14x14_hello_emmc_defconfig
找到CONFIG_TARGET_MX6ULL_14X14_EVK=y
改为CONFIG_TARGET_MX6ULL_14X14_HELLO=y
,这是指定编译我们自己的板子
找到CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ullevk/imximage.cfg"
改为CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ullhello/imximage.cfg"
,这是指定我们板子的配置
找到CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-evk-emmc"
改为CONFIG_DEFAULT_DEVICE_TREE="imx6ull-14x14-hello-emmc"
找到CONFIG_DM_74X164=y
改为# CONFIG_DM_74X164=y
,官网EVK开发板使用了一个74LV594,我们的板子没有使用所以屏蔽掉
找到CONFIG_SOFT_SPI=y
改为# CONFIG_SOFT_SPI=y
,屏蔽掉软件模拟的SPI,这是官方驱动74LV594用的,我们自己的板子也用不到
修改头文件:
gedit include/configs/mx6ullhello.h
修改宏编译:
添加自己开发板的设备树内容:
添加自己板子描述文件的文件夹:
cp board/freescale/mx6ullevk board/freescale/mx6ullhello -r
修改board\freescale\mx6ullhello
目录下的文件。
cd board/freescale/mx6ullhello/
重命名mx6ullevk.c
为mx6ullhello.c
:
mv mx6ullevk.c mx6ullhello.c
修改imximage.cfg
文件如下:
gedit imximage.cfg
修改Kconfig
文件如下(注意该文件endif后面必须有换行):
gedit Kconfig
修改MAINTAINERS
文件如下:
gedit MAINTAINERS
修改Makefile
如下:
gedit Makefile
添加自己板子所对应的设备树文件(新版本的uboot支持了设备树,移植阶段可以直接修改设备树进行裁剪移植,更方便开发,这也是选用新版本uboot的原因):
添加imx6ull-14x14-hello-emmc.dts
并修改:
cp arch/arm/dts/imx6ull-14x14-evk-emmc.dts arch/arm/dts/imx6ull-14x14-hello-emmc.dts
gedit arch/arm/dts/imx6ull-14x14-hello-emmc.dts
添加imx6ull-14x14-hello.dts
并修改:
cp arch/arm/dts/imx6ull-14x14-evk.dts arch/arm/dts/imx6ull-14x14-hello.dts
gedit arch/arm/dts/imx6ull-14x14-hello.dts
添加imx6ul-14x14-hello.dtsi
和imx6ul-14x14-hello-u-boot.dtsi
:
cp arch/arm/dts/imx6ul-14x14-evk.dtsi arch/arm/dts/imx6ul-14x14-hello.dtsi
cp arch/arm/dts/imx6ul-14x14-evk-u-boot.dtsi arch/arm/dts/imx6ul-14x14-hello-u-boot.dtsi
修改arch/arm/dts/Makefile文件,将我们开发板的设备树文件添加进编译项:
gedit arch/arm/dts/Makefile
将我们的开发板添加进编译uboot的编译选项,修改arch/arm/mach-imx/mx6/Kconfig
文件:
gedit arch/arm/mach-imx/mx6/Kconfig
找到config TARGET_MX6ULL_14X14_EVK
的配置,在该配置下方添加:
config TARGET_MX6ULL_14X14_HELLO
bool "Support mx6ull_14x14_hello"
select BOARD_LATE_INIT
select DM
select DM_THERMAL
select MX6ULL
imply CMD_DM
找到source "board/freescale/mx6ullevk/Kconfig"
,在下一行将我们板子的配置添加上:
source "board/freescale/mx6ullhello/Kconfig"
如下图所示:
接下来我们检查一下自己板子的SD卡接口和使用到的IO是否和官方EVK开发板使用的一致,因为在uboot移植测试阶段,我们使用SD卡启动uboot是最方便的,所以首先要确保SD卡的驱动是正确的。
一般情况下,SD卡的DATA[0:3]、CLK、CMD引脚是直接使用固定的IO,而CD(Card Detecet)引脚可能会根据自己板子的情况进行更改,我的板子原理图如下:
从原理图可以看到CD引脚使用的是UART1_RTS这个引脚,这个引脚是GPIO1_IO19。
检查uboot设备树文件arch/arm/dts/imx6ul-14x14-hello.dtsi
中SD卡的CD引脚是否是用的这个引脚。
检查后没问题,CD引脚跟我的原理图使用的是一致的。
官方evk开发板使用了一颗扩展IO的芯片74lv595,我的板子没有用,所以需要去掉该芯片相关内容,屏蔽或删除以下内容:
/*
aliases {
spi5 = &{/spi4};
};
*/
/*
reg_can_3v3: regulator-can-3v3 {
compatible = "regulator-fixed";
regulator-name = "can-3v3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
gpios = <&gpio_spi 3 GPIO_ACTIVE_LOW>;
};
spi4 {
compatible = "spi-gpio";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_spi4>;
status = "okay";
pinctrl-assert-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
gpio-sck = <&gpio5 11 0>;
gpio-mosi = <&gpio5 10 0>;
cs-gpios = <&gpio5 7 0>;
num-chipselects = <1>;
#address-cells = <1>;
#size-cells = <0>;
gpio_spi: gpio@0 {
compatible = "fairchild,74hc595";
gpio-controller;
#gpio-cells = <2>;
reg = <0>;
registers-number = <1>;
registers-default = /bits/ 8 <0x57>;
spi-max-frequency = <100000>;
};
};
*/
&can1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_flexcan1>;
/* xceiver-supply = <®_can_3v3>; */
status = "okay";
};
&can2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_flexcan2>;
/* xceiver-supply = <®_can_3v3>; */
status = "okay";
};
/*
pinctrl_spi4: spi4grp {
fsl,pins = <
MX6UL_PAD_BOOT_MODE0__GPIO5_IO10 0x70a1
MX6UL_PAD_BOOT_MODE1__GPIO5_IO11 0x70a1
MX6UL_PAD_SNVS_TAMPER7__GPIO5_IO07 0x70a1
MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x80000000
>;
};
*/
至此,我们已经完成了对自己板子的配置的建立,现在用我们板子的配置进行编译,编译前先清理一下:
make distclean
make mx6ull_14x14_hello_emmc_defconfig
make -j 2
编译完成,无问题!
成功编译通过!
下面我们将编译出的u-boot-dtb.imx使用dd命令刻录到SD卡中:
sudo dd if=u-boot-dtb.imx of=/dev/sdb bs=1k seek=1 conv=fsync
注意:SD卡插入ubuntu后,会出现/dev/sdX,具体是/dev/sdb还是/dev/sdc又或者是/dev/sdd要视个人插入ubuntu的USB口设备情况而定。
dd命令可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出。详细命令解释参见:https://www.runoob.com/linux/linux-comm-dd.html
- if=文件名:输入文件名,默认为标准输入。即指定源文件。
- of=文件名:输出文件名,默认为标准输出。即指定目的文件。
- bs=bytes:同时设置读入/输出的块大小为bytes个字节。
- seek=blocks:从输出文件开头跳过blocks个块后再开始复制。
- conv=<关键字>,关键字可以有以下11种:
- conversion:用指定的参数转换文件。
- ascii:转换ebcdic为ascii
- ebcdic:转换ascii为ebcdic
- ibm:转换ascii为alternate ebcdic
- block:把每一行转换为长度为cbs,不足部分用空格填充
- unblock:使每一行的长度都为cbs,不足部分用空格填充
- lcase:把大写字符转换为小写字符
- ucase:把小写字符转换为大写字符
- swap:交换输入的每对字节
- noerror:出错时不停止
- notrunc:不截短输出文件
- sync:将每个输入块填充到ibs个字节,不足部分用空(NUL)字符补齐。
将SD卡插入板子卡槽,设置板子上的拨码开关为SD卡启动,控制台打印内容如下:
成功启动!
测试一下SD卡和MMC是否都已经识别出来。
mmc list # 列出所有设备
mmc info # 查看当前设备信息
mmc dev 1 # 切换到emmc
emmc和SD卡都没问题了,但是网络显示错误,下面我们更改设备树,使网络也能够驱动起来。
同样的,比对官方EVK开发板和我们自己的板子的enet所使用的的IO:
比对后发现我的板子跟官方EVK开发板使用的IO是一致的,但是我的板子的phy还需要硬件复位的引脚,板子上有两路phy,查看原理图得知我的板子fec1的硬件复位IO是GPIO5_IO8,fec2的硬件复位引脚是GPIO5_IO04。
从原理图得到引脚后,继续修改设备树文件arch/arm/dts/imx6ul-14x14-hello.dtsi
,由于ENET1和ENET2的复位引脚都是在GPIO5下的,所以需要将这两个IO配置在imouxc_snvs节点下,找到iomuxc节点,在上方添加iomuxc_snvs节点:
&iomuxc_snvs {
pinctrl-names = "default";
pinctrl_eth2rst: eth2rstgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER4__GPIO5_IO04 0x17059 /* ENET2 NRST */
>;
};
pinctrl_eth1rst: eth1rstgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 0x17059 /* ENET1 NRST */
>;
};
};
如下图所示:
全局搜索MX6UL_PAD_SNVS_TAMPER8__GPIO5_IO08、MX6UL_PAD_SNVS_TAMPER4__GPIO5_IO04、MX6ULL_PAD_SNVS_TAMPER4__GPIO5_IO04、MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08这四个宏,查看在其他地方有没有被使用,如果有,则屏蔽掉。
找到&fec1
和&fec2
节点,uboot阶段我们只只用fec2即可,修改fec1节点如下:
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1 &pinctrl_eth1rst>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
phy-reset-gpios = <&gpio5 8 GPIO_ACTIVE_LOW>;
phy-reset-duration = <200>;
mac-address = [
0a 1b 2c 3d 4e 5f
];
status = "okay";
};
phy-reset-gpios:指定phy的复位引脚且为拉低复位
pht-reset-duration:指定复位的持续时间
mac-address:指定mac地址
修改fec2节点如下:
&fec2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet2 &pinctrl_eth2rst>;
phy-mode = "rmii";
phy-handle = <ðphy1>;
phy-reset-gpios = <&gpio5 4 GPIO_ACTIVE_LOW>;
phy-reset-duration = <200>;
mac-address = [
a0 b1 c2 d3 e4 f5
];
status = "okay";
mdio {
#address-cells = <1>;
#size-cells = <0>;
ethphy0: ethernet-phy@2 {
reg = <2>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET_REF>;
clock-names = "rmii-ref";
};
ethphy1: ethernet-phy@1 {
reg = <1>;
micrel,led-mode = <1>;
clocks = <&clks IMX6UL_CLK_ENET2_REF>;
clock-names = "rmii-ref";
};
};
};
ethphy0: ethernet-phy@2:这里的2表示的是phy1的地址为2
ethphy1: ethernet-phy@1:这里的1表示的是phy2的地址为1
对比我的原理图,发现phy的地址是一致的:
可选操作:在uboot移植测试阶段可以将随机生成MAC地址使能,方便我们测试使用,但是当移植完了后进入到了上层应用开发阶段建议禁用随机MAC地址,因为使能随机MAC地址的话每次重启板子都会是新的MAC地址,不方便上层程序使用。
打开uboot的图形化配置,使能随机MAC地址:
make menuconfig
[*] Networking support --->
[*] Random ethaddr if unset
补充:如果你没有使能使用随机mac地址,那么需要在uboot中手动设置mac地址,设置命令如下:
setenv ethaddr 00:04:9f:04:d2:35
setenv eth1addr 00:04:9f:04:d2:36
saveenv
其中ethaddr是指定eth0的,eth1addr是指定eth1的,具体需要设置哪一个,需要查看uboot启动时候的Net:
这个信息提示。
至此,网络的配置已经完成,再次编译uboot并将u-boot-dtb.imx刻录到SD卡中,板子使用SD卡启动,控制台打印如下(下图为使能随机MAC地址且在设备树中关闭了fec1节点的信息):
下面这些命令是依次设置ip地址、网关、掩码、ubuntu的IP地址、保存环境变量,设置完这环境变量后就可以使用ping命令来测试网卡是否驱动成功了。
setenv ipaddr 192.168.28.234
setenv gatewayip 192.28.28.1
setenv netmask 255.255.255.0
setenv serverip 192.168.28.254
saveenv
在ping一下ubuntu:
没问题,现在我们的网卡驱动就已经移植好了!
启动uboot的时候看到打印信息为EVK,这怎么能忍!
修改gedit board/freescale/mx6ullhello/mx6ullhello.c
文件,找到board_late_init
函数,修改如下:
gedit gedit board/freescale/mx6ullhello/mx6ullhello.c
找到checkboard
函数,修改如下:
重新编译uboot并刻录至SD卡中,开发板选择SD卡启动,控制台打印如下:
完成!
在开发阶段,在uboot中设置IP地址的操作比较频繁,回复一次默认环境变量,就得重新设置一下,那么有没有什么办法将ip地址增加进boot中呢?
当然有!
只需要在include/configs/mx6ullhello.h
文件中新增以下几个宏定义即可!
#define CONFIG_IPADDR 192.168.28.234 /* board ip */
#define CONFIG_SERVERIP 192.168.28.254 /* ubuntu ip */
#define CONFIG_GATEWAYIP 192.168.28.1 /* board gateway ip */
#define CONFIG_NETMASK 255.255.255.0 /* board netmask */
ends…