本帖最后由 正点原子运营 于 2021-6-15 12:30 编辑
1)实验平台:正点原子STM32MP157开发板
2) 章节摘自【正点原子】《STM32MP157嵌入式Linux驱动开发指南》
3)购买链接:https://item.taobao.com/item.htm?&id=629270721801
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyzmp157.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32MP157技术交流群:691905614
第十章 U-boot使用
在移植U-Boot之前,我们肯定要先使用一下U-Boot,得先体验一下U-Boot是个什么东西。STM32MP157开发板光盘资料里面已经提供了一个正点原子团队已经移植好的U-Boot,本章我们就直接编译这个移植好的U-Boot,然后烧写到EMMC里面启动,启动U-Boot以后就可以学习使用U-Boot的命令。
10.1 U-Boot简介
Linux 系统要启动需要通过bootloader 程序引导,也就说芯片上电以后先运行一段bootloader程序。这段bootloader程序会先初始化DDR等外设,然后将Linux内核从flash(NAND,NOR FLASH,SD,EMMC 等)拷贝到 DDR 中,最后启动 Linux 内核。当然了,bootloader 的实际工作要复杂的多,但是它最主要的工作就是启动 Linux 内核,bootloader 和 Linux 内核的关系就跟 PC 上的 BIOS 和 Windows 的关系一样,bootloader 就相当于 BIOS。所以我们要先搞定bootloader,很庆幸,有很多现成的 bootloader 软件可以使用,比如 U-Boot、vivi、RedBoot 等等,其中以 U-Boot 使用最为广泛,为了方便书写,本书会将 U-Boot 写为 uboot。
uboot 的全称是 Universal Boot Loader,uboot 是一个遵循 GPL 协议的开源软件,uboot是一个裸机代码,可以看作是一个裸机综合例程。现在的 uboot 已经支持液晶屏、网络、USB等高级功能。uboot 官网为http://www.denx.de/wiki/U-Boot/,如图10.1.1所示:
图10.1.1 uboot官网
我们可以在 uboot 官网下载 uboot 源码,点击图10.1.1中左侧 Topics 中的“Source Code”,打开以后如图10.1.2所示:
图10.1.2 uboot源码界面
点击图10.1.2中的“FTP”,进入其 FTP 服务器即可看到 uboot 源码,如图10.1.3所示:
图10.1.3 uboot源码
图10.1.3中就是 uboot 原汁原味的源码文件,目前最新的版本是 2020.10。但是我们一般不会直接用 uboot 官方的 U-Boot 源码的。uboot 官方的 uboot 源码是给半导体厂商准备的,半导体厂商会下载 uboot 官方的 uboot 源码,然后将自家相应的芯片移植进去。也就是说半导体厂商会自己维护一个版本的 uboot,这个版本的 uboot 相当于是他们定制的。既然是定制的,那么肯定对自家的芯片支持会很全,虽然 uboot 官网的源码中一般也会支持他们的芯片,但是绝对是没有半导体厂商自己维护的 uboot 全面。ST提供了2020.01版本的uboot,在6.1.1小节获取ST官方系统源码中我们已经得到了ST官方uboot源码, 进入到如下目录:
/stm32mp1-openstlinux-5.4-dunfell-mp1-20-06-24/sources/arm-ostl-linux-gnueabi
uboot源码如图10.1.4所示:
图10.1.4 ST官方uboot源码
图10.1.4中的“u-boot-stm32mp-2020.01-r0”就是ST官方uboot源码包,它支持了STM32MP1家族全系列芯片(后续ST也会一直更新,添加新的SOC进去),而且支持各种启动方式,比如EMMC、NAND 等等,这些都是 uboot 官方所不支持的。但是图10.1.4中的 uboot 是针对ST 自家评估板的,如果要在我们自己的板子上跑,那么就需要对其进行修改,使其支持我们自己做的板子,正点原子的 STM32MP157开发板就是自己做的板子,虽然大部分都参考了 ST 官方的STM32MP157 EVK开发板,但是还是有很多不同的地方,所以需要修改 ST 官方的 uboot,使其适配正点原子的 STM32MP157开发板。所以当我们拿到开发板以后,是有三种uboot 的,这三种 uboot的区别如表10.1.1所示:
种类 描述
uboot官方的uboot代码 由uboot官方维护开发的uboot版本,版本更新快,基本包含所有常用的芯片。
半导体厂商的uboot代码 半导体厂商维护的一个uboot,专门针对自家的芯片,在对自家芯片支持上要比uboot官方的好。
开发板厂商的uboot代码 开发板厂商在半导体厂商提供的uboot基础上加入了对自家开发板的支持。
表10.1.1 三种uboot的区别
那么这三种uboot该如何选择呢?首先uboot官方的基本是不会用的,因为支持太弱了。最常用的就是半导体厂商或者开发板厂商的 uboot,如果你用的半导体厂商的评估板,那么就使用半导体厂商的 uboot,如果你是购买的第三方开发板,比如正点原子的 STM32MP157开发板,那么就使用正点原子提供的 uboot 源码(也是在半导体厂商的 uboot 上修改的)。当然了,你也可以在购买了第三方开发板以后使用半导体厂商提供的 uboot,只不过有些外设驱动可能不支持,需要自己移植,这个就是我们常说的 uboot 移植。本节是 uboot 的使用,所以就直接使用正点原子已经移植好的 uboot,这个已经放到了开发板光盘中了,路径为:开发板光盘1、程序源码1、正点原子Linux出厂系统源码u-boot-stm32mp-2020.01-gdb8d2374-v1.0.tar.bz2。
10.2 U-Boot初次编译
10.2.1 编译
首先需要在 Ubuntu 中安装一些库,否则编译uboot会报错,安装命令如下:
sudo apt-get install libncurses5-dev bison flex
在 Ubuntu 中创建存放 uboot 的目录,比如我这里新建了一个名为“alientek_uboot”的文件夹用于存放正点原子提供的 uboot 源码。alientek_uboot文件夹创建成功以后使用 FileZilla 软件将正点原子提供的 uboot 源码拷贝到此目录中,正点原子提供的 uboot 源码已经放到了开发板光盘中,路径为:开发板光盘1、程序源码1、正点原子Linux出厂系统源码u-boot-stm32mp-2020.01-xxxxxxxx-v1.0.tar.bz2(“xxxxxxxxx”为uboot打包时候的版本号,每次打包其版本号都不同!所以大家不要纠结于开发板光盘中的uboot源码打包版本号是否和教程里面的一致)。将stm32mp-2020.01-xxxxxxxx-v1.0.tar.bz2拷贝到前面新建的 alientek_uboot 文件夹下,完成以后如图10.2.1.1所示:
图10.2.1.1 正点原子出厂uboot源码
使用如下命令对其进行解压缩:
tar -vxf u-boot-stm32mp-2020.01-gdb8d2374-v1.0.tar.bz2
解压完成以后如图10.2.1.2所示:
图10.2.1.2 解压后的Uboot
图10.2.1.2中除了u-boot-stm32mp-2020.01-gdb8d2374-v1.0.tar.bz2这个正点原子提供uboot源码压缩包以外,其他的文件和文件夹都是解压出来的 uboot 源码。执行以下命令,编译正点原子提供的uboot。
- <font size="4">make distclean
- make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- stm32mp157d_atk_defconfig
- make V=1 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- DEVICE_TREE=stm32mp157d-atk all</font>
复制代码
上面命令每次编译的时候都要指定ARCH、CROSS_COMPILE和DEVICE_TREE,这三个含义如下:
ARCH:指定所使用的平台架构,这里肯定是arm。
CROSS_COMPILE:所使用的交叉编译器前缀,本教程使用的是交叉编译器前缀为arm-none-linux-gnueabihf-。
DEVICE_TREE:设备树文件,uboot也支持设备树,所以在编译的时候需要指定设备树文件,不同的硬件其设备树文件肯定不同,这里为stm32mp157d_atk,也就是正点原子的STM32MP157开发板对应的设备树。
编译的时候每次都输入ARCH和CROSS_COMPILE比较麻烦,为了方便起见,我们可以直接修改uboot的Makefile文件,在里面直接对ARCH和CROSS_COMPILE进行赋值,也就是直接将ARCH设置为arm,CROSS_COMPILE设置为arm-none-linux-gnueabihf-,修改完成以后如图10.2.1.3所示:
图10.2.1.3 设置ARCH和CROSS_COMPILE值
注意!不能在Makefile里面对DEVICE_TREE进行复制,因为没用,必须在编译的时候手动输入!
设置好Makefile里面的ARCH和CROSS_COMPILE以后就可以将编译命令简化为如下所示:
- <font size="4">make distclean //清除
- make stm32mp157d_atk_defconfig //配置uboot
- make V=1 DEVICE_TREE=stm32mp157d-atk all //编译</font>
复制代码
上述命令和前面的相比就要简洁很多,最后的“make V=1”是真正的编译命令,V=1表示编译uboot的时候输出详细的编译过程,方便我们观察uboot编译过程。直接输入“make”命令的话默认使用单线程编译,编译速度会比较慢,可以通过添加“-j”选项来使用多线程编译,比如使用8线程编译,最后的编译命令就是:
- <font size="4"> make V=1 DEVICE_TREE=stm32mp157d-atk all -j8 //8线程编译</font>
复制代码
uboot编译完成如图10.2.1.4所示:
图10.2.1.4 uboot编译成功
编译完成以后的 就会在uboot源码目录下生成相应的镜像文件,如图10.2.1.5所示:
图10.2.1.5 编译后生成的uboot可执行文件
可以看出,编译完成以后 uboot 源码多了一些文件,重点是u-boot.bin和u-boot.stm32这两个文件。u-boot.bin是uboot的二进制可执行文件,u-boot.stm32是在u-boot.bin前面添加了256个字节头部信息。STM32MP1内部ROM代码和TF-A在运行uboot的时候要求前面添加头部信息,所以这就是为什么uboot也有这个头部信息的原因。
10.2.2 烧写
使用STM32CubeProgrammer将上面编译出来的u-boot.stm32镜像烧写到开发板的EMMC里面,修改前面创建的tf-a.tsv文件,添加uboot烧写指令(其实在9.3.2小节已经讲过了),在最后面添加下面这行:
示例代码10.2.2.1 uboot烧写指令
P 0x06 ssbl Binary mmc1 0x00080000 u-boot.stm32
修改以后的tf-a.tsv如图10.2.2.1所示:
图10.2.2.1 修改后的tf-a.tsv
最后将上一小节编译出来的u-boot.stm32,拷贝到前面创建的images目录下(在做TF-A实验的就有u-boot.stm32这个文件,我们只要替换就行)。
一切准备就绪以后就可以使用STM32CubeProgrammer软件通过USB OTG将uboot烧写到开发板上的EMMC里面,等到烧写完成。完成以后设置开发板上的拨码开关,设置从EMMC启动,然后用USB Type-C线将开发板上的USB_TTL接口与电脑连接起来,因为我们要在串口终端里面输入命令来操作uboot。
打开MobaXterm,设置好串口参数,最后复位开发板。在 MobaXterm 上出现“Hit any key tostop autoboot: ”倒计时的时候按下键盘上的回车键,默认是 1 秒倒计时,在 1 秒倒计时结束以后如果没有按下回车键的话 uboot 就会使用默认参数来启动 Linux 内核了(如果内核存在的话,如果Linux内核不存在那么就会进入到uboot的命令行模式)。如果在 1 秒倒计时结束之前按下回车键,那么就会进入 uboot 的命令行模式,如图10.2.2.2所示:
图10.2.2.2 uboot启动log信息
从图10.2.2.2可以看出,当进入到 uboot 的命令行模式以后,左侧会出现一个“STM32MP=>”标志。uboot 启动的时候会输出一些信息,这些信息如下所示:
示例代码10.2.2.2 uboot启动log信息
- <font size="4">1 U-Boot 2020.01-stm32mp-r1 (Nov 24 2020 - 17:17:20 +0800)
- 2
- 3 CPU: STM32MP157DAA Rev.Z
- 4 Model: STMicroelectronics STM32MP157D eval daughter
- 5 Board: stm32mp1 in trusted mode (st,stm32mp157d-atk)
- 6 DRAM: 1 GiB
- 7 Clocks:
- 8 - MPU : 800 MHz
- 9 - MCU : 208.878 MHz
- 10 - AXI : 266.500 MHz
- 11 - PER : 24 MHz
- 12 - DDR : 533 MHz
- 13 WDT: Started with servicing (32s timeout)
- 14 NAND: 0 MiB
- 15 MMC: STM32 SD/MMC: 0, STM32 SD/MMC: 1
- 16 Loading Environment from MMC... OK
- 17 In: serial
- 18 Out: serial
- 19 Err: serial
- 20 invalid MAC address in OTP 00:00:00:00:00:00
- 21 Net:
- 22 Error: ethernet@5800a000 address not set.
- 23 No ethernet found.
- 24
- 25 Hit any key to stop autoboot: 0
- 26 STM32MP> </font>
复制代码
简单讲解一下uboot启动过程的log信息:
第 1 行是 uboot 版本号和编译时间,可以看出,当前的 uboot 版本号是 2020.01,编译时间是 2020 年 11月 24 日 17: 17。
第3行是CPU的信息,可以看出CPU型号为STM32MP157DAA。
第4行是板子信息,当前板子是ST公司的STM32MP157D eval开发板,这个信息是可以改的,因为正点原子是直接参考ST公司的EVK开发板移植的uboot,所以这部分信息也就没改。
第5行是板子的一些信息,比如工作在trusted模式下。
第6行是DDR的大小为1GB。
第7~12行它们的频率分别为,MPU频率、MCU频率、AXI总线频率、PER的频率、DDR频率。
第13行是看门狗信息,喂狗时间为32s。
第14行是NAND的大小,因为正点原子的STM32MP157开发板没有NAND,所以这里就是0MB。
第15行是板子上MMC设备,一共有两个,SD/MMC0 (SD卡)和SD/MMC1 (EMMC)。
第16行是从MMC里获取环境变量。
第17~19行是标准输入、标准输出和标准错误所使用的终端,这里都使用串口(serial)作为终端。
第20~23行是网络相关信息,网络的MAC地址从OTP里获取,因为我们的OTP没有设置MAC地址,所以就获取失败。这里的网络是可以用的,只是因为没有MAC地址所以提示没有找到网络,可以自行添加相关环境变量来设置MAC地址,后面会讲如何设置。
第 25 行是倒计时提示,默认倒计时 1 秒,倒计时结束之前按下回车键就会进入 Linux 命令行模式。如果在倒计时结束以后没有按下回车键,那么 Linux 内核就会启动,Linux 内核一旦启动,uboot 就会寿终正寝。
uboot的主要作用是引导kernel,我们现在已经进入 uboot 的命令行模式了,进入命令行模式以后就可以给 uboot 发号施令了。当然了,不能随便发号施令,得看看 uboot 支持哪些命令,然后使用这些uboot 所支持的命令来做一些工作,下一节就讲解 uboot 命令的使用。
10.3 U-Boot命令使用
进入uboot的命令行模式以后输入“help”或者“?”,然后按下回车即可查看当前uboot所支持的命令,如图10.3.1所示:
图10.3.1 uboot的命令列表(部分)
图10.3.1中只是 uboot 的一部分命令,具体的命令列表以实际为准。图10.3.1中的命令并不是 uboot 所支持的所有命令,说过 uboot 是可配置的,需要什么命令就使能什么命令。所以图10.3.1中的命令是正点原子提供的 uboot 中使能的命令,uboot 支持的命令还有很多,而且也可以在 uboot 中自定义命令。这些命令后面都跟有命令说明,用于描述此命令的作用,但是命令具体怎么用呢?我们输入“help(或?) 命令名”既可以查看命令的详细用法,以“bootz”这个命令为例,我们输入如下命令即可查看“bootz”这个命令的用法:
? bootz 或 help bootz
结果如图10.3.2所示:
图10.3.2 bootz 命令使用说明
图10.3.2列出了“bootz”这个命令的详细说明,其它的命令也可以使用此方法查询具体的使用方法。接下来我们学习一下一些常用的 uboot 命令。
10.3.1 查询命令
常用的和信息查询有关的命令有 3 个:bdinfo、printenv 和 version。先来看一下 bdinfo 命令,此命令用于查看板子信息,直接输入“bdinfo”即可,结果如图10.3.1.1示:
图10.3.1.1 bdinfo 命令
从图10.3.1.1中可以看出 DRAM 的起始地址和大小、BOOT参数保存起始地址、波特率、sp(堆栈指针)起始地址等信息。命令“printenv”用于输出环境变量信息,uboot 也支持 TAB 键自动补全功能,输入“print”然后按下 TAB 键就会自动补全命令。直接输入“print”也可以,因为整个uboot命令中只有printenv的前缀是“print”,所以当输入print以后就只有printenv命令了。输入“print”,然后按下回车键,环境变量如图10.3.1.2所示:
图10.3.1.2 printenv 命令部分结果
图10.3.1.2只是printenv命令的部分内容,STM32MP1系列的环境变量有很多,比如 baudrate、board、board_name、boot_device、bootcmd、bootdelay等等。uboot 中的环境变量都是字符串,既然叫做环境变量,那么它的作用就和“变量”一样。比如 bootdelay 这个环境变量就表示 uboot 启动延时时间,默认 bootdelay=1,也就默认延时 1秒。前面说的 1 秒倒计时就是由 bootdelay 定义的,如果将 bootdelay 改为 5 的话就会倒计时 5s了。uboot 中的环境变量是可以修改的,有专门的命令来修改环境变量的值,稍后我们会讲解。
命令 version 用于查看 uboot 的版本号,输入“version”,uboot 版本号如图10.3.1.3所示:
图10.3.1.3 version命令结果
10.3.2 环境变量操作命令
1、修改环境变量
环境变量的操作涉及到两个命令:setenv 和 saveenv,setenv命令用于设置或者修改环境变量的值。命令 saveenv 用于保存修改后的环境变量,一般环境变量存放在外部 flash 中,uboot 启动的时候会将环境变量从 flash 读取到 DRAM 中。所以使用命令 setenv 修改的是 DRAM中的环境变量值,修改以后要使用 saveenv 命令将修改后的环境变量保存到 flash 中,否则uboot下一次重启会继续使用以前的环境变量值。
命令saveenv使用起来很简单,格式为:
saveenv
比如我们要将环境变量 bootdelay 改为 5,就可以使用如下所示命令:
- <font size="4">setenv bootdelay 5
- saveenv</font>
复制代码
上述命令执行过程如图10.3.2.1所示:
图10.3.2.1 环境变量修改
在图10.3.2.1中,当我们使用命令 saveenv 保存修改后的环境变量会有保存过程提示信息,根据提示可以看出环境变量保存到了 MMC(1)中,也就是 EMMC中。因为我用EMMC启动的,所以会保存到MMC(1)中。修改 bootdelay以后,重启开发板,uboot 就是变为 5 秒倒计时,如图10.3.2.2所示:
图10.3.2.2 5秒倒计时
从图10.3.2.2可以看出,此时uboot启动倒计时变为了5秒。
2、新建环境变量
命令setenv也可以用于新建命令,用法和修改环境变量一样,比如我们新建一个环境变量author,author的值为‘console=ttySTM0,11520 root=/dev/mmcblk2p2 rootwait rw’,那么就可以使用如下命令:
setenv author 'console=ttySTM0,11520 root=/dev/mmcblk2p2 rootwait rw '
saveenv
上面命令设置author 的值为“console=ttySTM0,11520 root=/dev/mmcblk2p2 rootwait rw”,其中“console=ttySTM0,115200”、“root=/dev/mmcblk2p2”、“rootwait”和“rw”相当于四组“值”,
这四组“值”之间用空格隔开,所以需要使用单引号‘’将其括起来,表示这四组“值”都属
于环境变量 author。
author命令创建完成以后重启 uboot,然后使用命令 printenv 查看当前环境变量,如图10.3.2.3所示:
图10.3.2.3 新建的author环境变量值
3、删除环境变量
既然可以新建环境变量,那么就可以删除环境变量,删除环境变量也是使用命令 setenv,
要删除一个环境变量只要给这个环境变量赋空值即可,比如我们删除掉上面新建的 author 环境变量,命令如下:
- <font size="4">setenv author
- saveenv</font>
复制代码
上面命令中通过 setenv 给 author 赋空值,也就是什么都不写来删除环境变量 author。重启uboot 就会发现环境变量 author 没有了。
10.3.3 内存操作命令
内存操作命令就是用于直接对 DRAM 进行读写操作的,常用的内存操作命令有 md、nm、
mm、mw、cp 和 cmp,我们依次来看一下这些命令都是做什么的。
1、md命令
md 命令用于显示内存值,格式如下:
- <font size="4">md[.b, .w, .l] address [# of objects]</font>
复制代码
命令中的[.b .w .l]对应 byte、word 和 long,也就是分别以 1个字节、2个字节、4个字节来显示内存值。address就是要查看的内存起始地址,[# of objects]表示要查看的数据长度,这个数据长度单位不是字节,而是跟你所选择的显示格式有关。比如你设置要查看的内存长度为20(十六进制为0x14),如果显示格式为.b的话那就表示20个字节;如果显示格式为.w的话就表示 20个word,也就是 20*2=40个字节;如果显示格式为.l 的话就表示 20个long,也就是20*4=80个字节,另外要注意:
uboot命令中的数字都是十六进制的!不是十进制的!
比如你想查看0XC0100000开始的20个字节的内存值,显示格式为.b的话,应该使用如下所示命令:
- <font size="4">md.b C0100000 14</font>
复制代码
而不是:
- <font size="4">md.b C0100000 20</font>
复制代码
上面说了,uboot命令里面的数字都是十六进制的,所以可以不用写“0x”前缀,十进制的 20对应的十六进制为 0x14,所以命令md后面的个数应该是14,如果写成20的话就表示查看32(十六进制为0x20)个字节的数据。分析下面三个命令的区别:
- <font size="4">md.b C0100000 10
- md.w C0100000 10
- md.l C0100000 10</font>
复制代码
上面这三个命令都是查看以0XC0100000为起始地址的内存数据,第一个命令以.b格式显示,长度为0x10,也就是16个字节;第二个命令以.w 格式显示,长度为 0x10,也就是 16*2=32个字节;最后一个命令以.l 格式显示,长度也是 0x10,也就是16*4=64个字节。这三个命令的执行结果如图10.3.3.1所示:
图10.3.3.1 md命令使用示例
2、nm命令
nm 命令用于修改指定地址的内存值,命令格式如下:
- <font size="4">nm [.b, .w, .l] address</font>
复制代码
nm命令同样可以以.b、.w 和.l 来指定操作格式,比如现在以.l格式修改0XC0100000地址的数据为0x12345678。输入命令:
- <font size="4">nm.l C0100000</font>
复制代码
输入上述命令以后如图10.3.3.2所示:
图10.3.3.2 nm命令
在图10.3.3.2中,C0100000表示现在要修改的内存地址,ea0000b8表示地址0xc0100000 现在的数据,‘?’后面就可以输入要修改后的数据 0x12345678,输入完成以后按下回车,然后再输入‘q’即可退出,如图10.3.3.3所示:
图10.3.3.3 修改内存数据
修改完成以后再使用md命令来查看一下有没有修改成功,如图10.3.3.4所示:
图10.3.3.4 查看修改后的值
从图10.3.3.4可以看出,此时地址 0XC0100000 的值变为了0X12345678。
3、mm命令
mm命令也是修改指定地址内存值的,使用mm修改内存值的时候地址会自增,而使用nm 命令的话地址不会自增。比如以.l格式修改从地址0XC0100000开始的连续3个内存块(3*4=12个字节)的数据为0X05050505,操作如图10.3.3.5所示:
图10.3.3.5 命令mm
从图10.3.3.5可以看出,修改了地址0XC0100000、0XC0100004 和 0XC0100008 的内容为0x05050505。使用命令 md 查看修改后的值,结果如图10.3.3.6所示:
图10.3.3.6 查看修改后的内存数据
从图10.3.3.6可以看出内存数据修改成功。
4、mw命令
命令mw用于使用一个指定的数据填充一段内存,命令格式如下:
mw [.b, .w, .l] address value [count]
mw命令同样以.b、.w和.l来指定操作格式,address表示要填充的内存起始地址,value为要填充的数据,count是填充的长度。比如使用.l格式将以0XC0100000为起始地址的0x10 个内存块(0x10 * 4=64字节)填充为0X0A0A0A0A,命令如下:
mw.l C0100000 0A0A0A0A 10
然后使用命令md来查看,如图10.3.3.7所示:
图10.3.3.7 查看修改后的内存数据
从图10.3.3.7以看出内存数据修改成功。
5、cp命令
cp是数据拷贝命令,用于将DRAM中的数据从一段内存拷贝到另一段内存中,或者把 NorFlash中的数据拷贝到DRAM中。命令格式如下:
cp [.b, .w, .l] source target count
cp命令同样以.b、.w和.l来指定操作格式,source为源地址,target为目的地址,count为拷贝的长度。我们使用.l格式将0xC0100000处的地址拷贝到0xC0100100处,长度为0x10个内存块(0x10 * 4=64个字节),命令如下所示:
cp.l c0100000 c0100100 10
结果如图10.3.3.8所示:
图10.3.3.8 cp命令操作结果
在图10.3.3.8中,先使用md.l命令打印出地址0xC0100000和0xC0100100处的数据,然后使用命令cp.l将0xC0100000处的数据拷贝到0xC0100100处。最后使用命md.l查看0xC0100100处的数据有没有变化,检查拷贝是否成功。
6、cmp命令
cmp是比较命令,用于比较两段内存的数据是否相等,命令格式如下:
cmp [.b, .w, .l] addr1 addr2 count
cmp命令同样以.b、.w和.l来指定操作格式,addr1为第一段内存首地址,addr2为第二段内存首地址,count为要比较的长度。我们使用.l格式来比较0xC0100000和0xC0100100这两个地址数据是否相等,比较长度为0x10个内存块(16 * 4=64个字节),命令如下所示:
cmp.l c0100000 c0100100 10
结果如图10.3.3.9所示:
图10.3.3.9 cmp命令比较结果
从图10.3.3.9可以看出两段内存的数据相等。我们再随便挑两段内存比较一下,比如地址0xC0100000和0xC0100200,长度为0X10,比较结果如图10.3.3.10所示:
图10.3.3.10 cmp命令比较结果
从图10.3.3.10可以看出,0xC0100000处的数据和0xC0100200处的数据就不一样。
10.3.4 网络操作命令
uboot是支持网络的,我们在移植uboot的时候一般都要调通网络功能,因为在移植linux kernel的时候需要使用到uboot的网络功能做调试。uboot支持大量的网络相关命令,比如dhcp、ping、nfs和tftpboot,我们接下来依次学习一下这几个和网络有关的命令。
建议开发板和主机PC都连接到同一个路由器上!最后设置表10.3.4.1所示的几个环境变量:
环境变量 描述
ipaddr 开发板ip地址,可以不设置,使用dhcp命令来从路由器获取IP地址。
ethaddr 开发板的MAC地址,一定要设置。
gatewayip 网关地址。
netmask 子网掩码。
serverip 服务器IP地址,也就是Ubuntu主机IP地址,用于调试代码。
表10.3.4.1 网络相关环境变量
表10.3.4.1中环境变量设置命令如下所示:
- <font size="4">setenv ipaddr 192.168.1.250
- setenv ethaddr 00:04:9f:04:d2:35
- setenv gatewayip 192.168.1.1
- setenv netmask 255.255.255.0
- setenv serverip 192.168.1.249
- saveenv</font>
复制代码
注意,网络地址环境变量的设置要根据自己的实际情况,确保Ubuntu主机和开发板的IP地址在同一个网段内,比如我现在的开发板和电脑都在192.168.1.0 这个网段内,所以设置开发板的IP地址为192.168.1.250,我的Ubuntu主机的地址为192.168.1.249,因此serverip就是192.168.1.249。ethaddr为网络MAC地址,是一个48bit的地址,如果在同一个网段内有多个开发板的话一定要保证每个开发板的ethaddr是不同的,否则通信会有问题!设置好网络相关的环境变量以后就可以使用网络相关命令了。
1、ping命令
开发板的网络能否使用,是否可以和服务器(Ubuntu主机)进行通信,通过ping命令就可以验证,直接ping服务器的IP地址即可,比如我的服务器IP地址为192.168.1.249,命令如下:
ping 192.168.1.249
结果如图10.3.4.1所示:
图10.3.4.1 ping命令
从图10.3.4.1可以看出,192.168.1.249这个主机存在,说明ping成功,uboot的网络工作正常。
注意!只能在uboot中ping其他的机器,其他机器不能ping uboot,因为uboot没有对ping命令做处理,如果用其他的机器ping uboot的话会失败!
2、dhcp 命令
dhcp用于从路由器获取IP地址,前提是开发板得连接到路由器上的,如果开发板是和电脑直连的,那么dhcp命令就会失效。直接输入dhcp命令即可通过路由器获取到IP地址,如图10.3.4.2所示:
图10.3.4.2 dhcp 命令
从图10.3.4.2可以看出,开发板通过dhcp获取到的IP地址为192.168.1.7。DHCP不单单是获取IP地址,其还会通过TFTP来启动linux内核,输入“? dhcp”即可查看dhcp命令详细的信息,如图10.3.4.3所示:
图10.3.4.3 dhcp命令使用查询
3、nfs 命令
nfs(Network File System)网络文件系统,通过nfs可以在计算机之间通过网络来分享资源,比如我们将linux镜像和设备树文件放到Ubuntu中,然后在uboot中使用nfs命令将Ubuntu中的linux 镜像和设备树下载到开发板的DRAM中。这样做的目的是为了方便调试linux镜像和设备树,也就是网络调试,网络调试是Linux开发中最常用的调试方法。原因是嵌入式linux开发不像单片机开发,可以直接通过JLINK或STLink等仿真器将代码直接烧写到单片机内部的flash中,嵌入式Linux通常是烧写到EMMC、NAND Flash、SPI Flash等外置flash中,但是嵌入式Linux开发也没有MDK,IAR这样的IDE,更没有烧写算法,因此不可能通过点击一个“download”按钮就将固件烧写到外部flash中。虽然半导体厂商一般都会提供一个烧写固件的软件,但是这个软件使用起来比较复杂,这个烧写软件一般用于量产的。其远没有MDK、IAR的一键下载方便,在Linux内核调试阶段,如果用这个烧写软件的话将会非常浪费时间,而这个时候网络调试的优势就显现出来了,可以通过网络将编译好的linux镜像和设备树文件下载到DRAM中,然后就可以直接运行。
我们一般使用uboot中的nfs命令将Ubuntu中的文件下载到开发板的DRAM中,在使用之前需要开启Ubuntu主机的NFS服务,并且要新建一个NFS使用的目录,以后所有要通过NFS访问的文件都需要放到这个NFS目录中。Ubuntu的NFS服务开启我们在4.2.1小节已经详细讲解过了,包括NFS文件目录的创建,如果忘记的话可以去查看一下4.2.1小节。我设置的NFS文件目录为:/home/zuozhongkai/linux/nfs,uboot中的nfs命令格式如下所示:
nfs [loadAddress] [[hostIPaddr:]bootfilename]
loadAddress是要保存的DRAM地址,[[hostIPaddr:]bootfilename]是要下载的文件地址。这里我们将正点原子官方编译出来的Linux镜像文件uImage下载到开发板DRAM的0xC2000000这个地址处(ST官方指定的Linux内核加载地址)。正点原子编译出来的uImage文件已经放到了开发板光盘中,路径为:开发板光盘A 8、系统镜像1、教程系统镜像linux-mp1-5.4.31-g032f9dcc0-v1.0uImage。将整个文件通过FileZilla发送到Ubuntu中的NFS目录下:/home/zuozhongkai/linux/nfs,完成以后的NFS目录如图10.3.4.4所示:
图10.3.4.4 nfs目录
准备好以后就可以使用nfs命令来将uImage下载到开发板DRAM的0XC2000000地址处,命令如下:
nfs C2000000 192.168.1.249:/home/zuozhongkai/linux/nfs/uImage
命令中的“C2000000”表示uImage在DDR中的首地址,“192.168.1.249:/home/zuozhongkai/linux/nfs/uImage”表示uImage在192.168.1.249这个主机中,路径为/home/zuozhongkai/linux/nfs/uImage。下载过程如图10.3.4.5所示:
图10.3.4.5 nfs命令下载uImage过程
图10.3.4.5中通过网络下载uImage的时候会打印出‘#’表示正在下载,如果网络环境不好,下载有干扰的话就会打印出一个‘T’,这个没事的,只要它能正常下载成功,最终打印出“done”即可。
下载完成以后查看0xC2000000地址处的数据,使用命令md.b来查看前0x100(256)个字节的数据,如图10.3.4.6所示:
图10.3.4.6 下载的数据
再使用winhex软件来查看刚刚下载的uImage,检查一下前面的数据是否和图10.3.4.6中的一致,结果如图10.3.4.7所示:
图10.3.4.7 winhex查看uImage
可以看出图10.3.4.和图10.3.4.7中的前0x100个字节的数据一致,说明nfs命令下载到的uImage是正确的。
4、tftp命令
tftp命令的作用和nfs命令一样,都是用于通过网络下载东西到DRAM中,只是tftp命令使用的是TFTP协议,Ubuntu主机作为TFTP服务器。因此需要在Ubuntu上搭建TFTP服务器,需要安装tftp-hpa和tftpd-hpa,命令如下:
sudo apt-get install tftp-hpa tftpd-hpa
sudo apt-get install xinetd
和NFS一样,TFTP也需要一个文件夹来存放文件,在用户目录下新建一个目录,命令如下:
mkdir /home/zuozhongkai/linux/tftpboot
chmod 777 /home/zuozhongkai/linux/tftpboot
这样我就在我的电脑上创建了一个名为tftpboot的目录(文件夹),路径为/home/zuozhongkai /linux/tftpboot。注意!我们要给tftpboot文件夹权限,否则的话uboot不能从tftpboot文件夹里面下载文件。
最后配置tftp,新建文件/etc/xinetd.d/tftp,如果没有/etc/xinetd.d目录的话自行创建,然后在里面输入如下内容:
示例代码 10.3.4.1 /etc/xinetd.d/tftp文件内容
- <font size="4">1 server tftp
- 2 {
- 3 socket_type = dgram
- 4 protocol = udp
- 5 wait = yes
- 6 user = root
- 7 server = /usr/sbin/in.tftpd
- 8 server_args = -s /home/zuozhongkai/linux/tftpboot/
- 9 disable = no
- 10 per_source = 11
- 11 cps = 100 2
- 12 flags = IPv4
- 13 }</font>
复制代码
完了以后启动tftp服务,命令如下:
- <font size="4">sudo service tftpd-hpa start</font>
复制代码
打开/etc/default/tftpd-hpa文件,将其修改为如下所示内容:
示例代码10.3.4.2 /etc/default/tftpd-hpa文件内容
- <font size="4">1 # /etc/default/tftpd-hpa
- 2
- 3 TFTP_USERNAME="tftp"
- 4 TFTP_DIRECTORY="/home/liangwencong/linux/tftpboot"
- 5 TFTP_ADDRESS=":69"
- 6 TFTP_OPTIONS="-l -c -s" </font>
复制代码
TFTP_DIRECTORY就是我们上面创建的tftp文件夹目录,以后我们就将所有需要通过TFTP传输的文件都放到这个文件夹里面,并且要给予这些文件相应的权限。
最后输入如下命令, 重启tftp服务器:
sudo service tftpd-hpa restart
tftp服务器已经搭建好了,接下来就是使用了。将uImage镜像文件拷贝到tftpboot文件夹中,并且给予uImage相应的权限,命令如下:
- <font size="4">cp uImage /home/zuozhongkai/linux/tftpboot/
- cd /home/zuozhongkai/linux/tftpboot/
- chmod 777 uImage</font>
复制代码
万事俱备,只剩验证了,uboot中的tftp命令格式如下:
- <font size="4">tftpboot [loadAddress] [[hostIPaddr:]bootfilename]</font>
复制代码
看起来和nfs命令格式一样的,loadAddress是文件在DRAM中的存放地址,[[hostIPaddr:]bootfilename]是要从Ubuntu中下载的文件。但是和nfs命令的区别在于,tftp命令不需要输入文件在Ubuntu中的完整路径,只需要输入文件名即可。比如我们现在将tftpboot文件夹里面的uImage文件下载到开发板DRAM的0XC2000000地址处,命令如下:
- <font size="4">tftp C2000000 uImage</font>
复制代码
下载过程如图10.3.4.8所示:
图10.3.4.8 tftp命令下载过程
从图10.3.4.8可以看出,uImage下载成功了,网速为2.3MibB/s,文件大小为7312880字节。同样的,可以使用md.b命令来查看前0x100个字节的数据是否和图10.3.4.7中的相等。有时候使用tftp命令从Ubuntu中下载文件的时候会出现如图10.3.4.9所示的错误提示:
图10.3.4.9 tftp下载出错
在图10.3.4.9中可以看到“TFTP error: 'Permission denied' (0)”这样的错误提示,提示没有权限,出现这个错误一般有两个原因:
①、在Ubuntu中创建tftpboot目录的时候没有给予tftboot相应的权限。
②、tftpboot目录中要下载的文件没有给予相应的权限。
针对上述两个问题,使用命令“chmod 777 xxx”来给予权限,其中“xxx”就是要给予权限的文件或文件夹。
好了,uboot中关于网络的命令就讲解到这里,我们最常用的就是ping、nfs和tftp这三个命令。使用ping命令来查看网络的连接状态,使用nfs和tftp命令来从Ubuntu主机中下载文件。
10.3.5 EMMC和SD卡操作命令
uboot支持EMMC和SD卡,因此也要提供EMMC和SD卡的操作命令。一般认为EMMC和SD卡是同一个东西,所以没有特殊说明,本教程统一使用MMC来代指EMMC和SD卡。uboot中常用于操作MMC设备的命令为“mmc”。
mmc是一系列的命令,其后可以跟不同的参数,输入“?mmc”即可查看mmc有关的命令,如图10.3.5.1所示:
图10.3.5.1 mmc 命令
从图10.3.5.1可以看出,mmc后面跟不同的参数可以实现不同的功能,如表10.3.5.1所示:
命令 描述
mmc info 输出MMC设备信息
mmc read 读取MMC中的数据。
mmc wirte 向MMC设备写入数据。
mmc rescan 扫描MMC设备。
mmc part 列出MMC设备的分区。
mmc dev 切换MMC设备。
mmc list 列出当前有效的所有MMC设备。
mmc hwpartition 设置MMC设备的分区。
mmc bootbus…… 设置指定MMC设备的BOOT_BUS_WIDTH域的值。
mmc bootpart…… 设置指定MMC设备的boot和RPMB分区的大小。
mmc partconf…… 设置指定MMC设备的PARTITION_CONFG域的值。
mmc rst 复位MMC设备
mmc setdsr 设置DSR寄存器的值。
表10.3.5.1 mmc命令
1、mmc info命令
mmc info命令用于输出当前选中的mmc info设备的信息,输入命令“mmc info”即可,如图10.3.5.2所示:
图10.3.5.2 mmc info命令
从图10.3.5.2可以看出,当前选中的MMC设备是EMMC,EMMC芯片版本为5.1,容量为7.3GiB(EMMC为8GB),速度为52000000Hz=52MHz,8位宽的总线。还有一个与mmc info命令相同功能的命令:mmcinfo,“mmc”和“info”之间没有空格。
2、mmc rescan命令
mmc rescan命令用于扫描当前开发板上所有的MMC设备,包括EMMC和SD卡,输入“mmc rescan”即可。
3、mmc list命令
mmc list命令用于来查看当前开发板一共有几个MMC设备,输入“mmc list”,结果如图10.3.5.3所示:
图10.3.5.3 扫描MMC设备
可以看出当前开发板有两个MMC设备:STM32 SD/MMC:0和STM32 SD/MMC:1 (eMMC),这是因为我现在用的是EMMC版本的核心板,加上SD卡一共有两个MMC设备,STM32 SD/MMC:0是SD卡,STM32 SD/MMC:1 (eMMC)是EMMC。默认会将EMMC设置为当前MMC设备,这就是为什么输入“mmc info”查询到的是EMMC设备信息,而不是SD卡。要想查看SD卡信息,就要使用命令“mmc dev”来将SD卡设置为当前的MMC设备。
4、mmc dev命令
mmc dev命令用于切换当前MMC设备,命令格式如下:
- <font size="4">mmc dev [dev] [part]</font>
复制代码
[dev]用来设置要切换的MMC设备号,[part]是分区号,如果不写分区号的话默认为分区0。使用如下命令切换到SD卡:
- <font size="4">mmc dev 0 //切换到SD卡,0为SD卡,1为eMMC</font>
复制代码
结果如图10.3.5.4所示:
图10.3.5.4 切换到SD卡
从图30.4.5.4可以看出,切换到SD卡成功,mmc0为当前的MMC设备,输入命令“mmc info”即可查看SD卡的信息(要插入SD卡),结果如图10.3.5.5所示:
图10.3.5.5 SD信息
从图10.3.5.5可以看出当前SD卡版本号为3.0,容量为14.8GiB(16GB的SD卡),4位宽的总线。
5、mmc part命令
有时候SD卡或者EMMC会有多个分区,可以使用命令“mmc part”来查看其分区,比如查看EMMC的分区情况,输入如下命令:
- <font size="4">mmc dev 1 //切换到EMMC
- mmc part //查看EMMC分区</font>
复制代码
结果如图10.3.5.6所示:
图10.3.5.6 查看EMMC分区
从图10.3.5.6中可以看出,此时EMMC是EFI类型并且有1个分区,如果你的核心板EMMC烧写过系统的话会有3个分区,正点原子出厂开发板都烧写系统,所以一般是3个分区,如图10.3.5.7所示:
图10.3.5.7 烧写过系统的分区
从图10.3.5.7中可以看出,有3个分区,第一个分区为名字为“ssbl”,用来存放uboot镜像,范围为:扇区0x400~0x13ff。第二个分区名字为“boot”,用来存放linux内核镜像,范围为:扇区0x1400~0x213ff。第三个分区名字为“rootfs”,这个是根文件系统分区,占用了剩余的所有扇区,也就是扇区0x21400~0xe8fbff。
如果要将EMMC的分区2设置为当前MMC设置分区,可以使用如下命令:
mmc dev 1 2
结果如图10.3.5.8所示:
图10.3.5.8 设置EMMC分区2为当前设备
6、mmc read命令
mmc read命令用于读取mmc设备的数据,命令格式如下:
- <font size="4">mmc read addr blk# cnt</font>
复制代码
addr是数据读取到DRAM中的地址,blk是要读取的块起始地址(十六进制),一个块是512字节,这里的块和扇区是一个意思,在MMC设备中我们通常说扇区,cnt是要读取的块数量(十六进制)。比如从EMMC的第1024(0x400)个块开始,读取16(0x10)个块的数据到DRAM的0XC0000000地址处,命令如下:
- <font size="4">mmc dev 1 //切换到EMMC
- mmc read c0000000 400 10 //读取数据</font>
复制代码
结果如图10.3.5.9所示:
图10.3.5.9 mmc read命令
这里我们还看不出来读取是否正确,通过md.b命令查看0xc0000000处的数据就行了,查看16*512=8192(0x2000)个字节的数据,命令如下:
- <font size="4">md.b c0000000 2000</font>
复制代码
结果如图10.3.5.10所示:
图10.3.5.10 读取到的数据(部分截图)
从图30.4.5.9可以看出,前面的265个字节就是STM32MP1的头部信息。
10.3.6 EXT格式文件系统操作命令
uboot有ext2和ext4这两种格式的文件系统的操作命令, STM32MP1的系统镜像都是ext4格式的,所以我们重点讲解一下和ext4有关的三个命令:ext4ls、ext4ls和ext4write。注意,由于只有linux内核、设备树和根文件系统是以ext4格式存放在EMMC中的,因此在测试ext4相关命令的时候要先确保EMMC里面烧写了完整的出厂系统!
1、ext4ls
ext4ls命令用于查询EXT4格式设备的目录和文件信息,命令格式如下:
- <font size="4">ext4ls <interface> [<dev[:part]>] [directory]</font>
复制代码
interface是要查询的接口,比如mmc,dev是要查询的设备号,part是要查询的分区,directory是要查询的目录。比如查询EMMC分区2中的所有的目录和文件,输入命令:
- <font size="4">ext4ls mmc 1:2</font>
复制代码
结果如图10.3.6.1所示:
图10.3.6.1 EMMC分区2文件查询
从上图可以看出,emmc的分区2中存放了10个文件,其中比较重要的就是三个.dtb设备树文件和Linux内核的uImage镜像文件。
2、ext4load命令
extload命令用于将指定的文件读取到DRAM中,命令格式如下:
fatload <interface> [<dev[:part]> [<addr> [<filename> [bytes [pos]]]]]
interface为接口,比如mmc,dev是设备号,part是分区,addr是保存在DRAM中的起始地址,filename是要读取的文件名字。bytes表示读取多少字节的数据,如果bytes为0或者省略的话表示读取整个文件。pos是要读的文件相对于文件首地址的偏移,如果为0或者省略的话表示从文件首地址开始读取。我们将图10.3.6.1中EMMC分区2中的uImage文件读取到DRAM中的0XC2000000地址处,命令如下:
- <font size="4">ext4load mmc 1:2 C2000000 uImage</font>
复制代码
操作过程如图10.3.6.2所示:
图10.3.6.2 读取过程
从上图可以看出在207ms内读取了8125872个字节的数据,速度为37.4MiB/s,速度是非常快的,因为这是从EMMC里面读取的,而EMMC是8位的,速度肯定会很快。
3、ext4write
ext4wirte命令用于将DRAM中的数据写入到MMC设备中,命令格式如下:
ext4write <interface> <dev[:part]> <addr> <absolute filename path> [sizebytes] [file offset]
interface为接口,比如mmc;dev是设备号;part是分区;addr是要写入的数据在DRAM中的起始地址;absolute filename path是写入的数据文件名字,注意是要带有绝对路径,以‘/’开始;sizebytes表示要写入多少字节的数据;file offset为文件偏移。我们可以通过fatwrite命令在uboot中更新linux镜像文件和设备树。我们以更新linux镜像文件uImage为例,首先将正点原子STM32MP157开发板提供的uImage镜像文件拷贝到Ubuntu中的tftpboot目录下,使用命令tftp将uImage下载到DRAM的0XC0000000地址处,命令如下:
tftp C0000000 uImage
下载过程如图10.3.6.3所示:
图10.3.6.3 uImage下载过程
uImage大小为7313888(0X6F99E0)个字节,接下来使用命令ext4write将其写入到EMMC的分区2中。为了和原有的uImage文件区分,我们将要写入的文件命名为test_uImage,命令如下:
- <font size="4">ext4write mmc 1:2 c0000000 /test_uImage 0x6f99e0</font>
复制代码
结果如图10.4.6.4所示:
图10.3.6.4 将uImage烧写到EMMC扇区2中
完成以后使用“ext4ls”命令查看一下EMMC分区2里面的文件,结果如图10.3.6.5所示:
图10.3.6.5 写入的test_uImage文件
从图10.3.6.5可以看出,test_uImage写入成功。
10.3.7 BOOT操作命令
uboot的本质工作是引导Linux,所以uboot肯定有相关的boot(引导)命令来启动Linux。常用的跟boot有关的命令有:bootm、bootz和boot。
1、bootm命令
要启动Linux,需要先将Linux镜像文件拷贝到DRAM中,如果使用到设备树的话也需要将设备树拷贝到DRAM中。可以从EMMC或者NAND等存储设备中将Linux镜像和设备树文件拷贝到DRAM,也可以通过nfs或者tftp将Linux镜像文件和设备树文件下载到DRAM中。不管用那种方法,只要能将Linux镜像和设备树文件存到DRAM中就行,然后使用bootm命令来启动,bootm命令用于启动uImage镜像文件,bootm命令格式如下:
bootm [addr [arg ...]]
命令bootm主要有三个参数,addr是Linux镜像文件在DRAM中的位置,后面的“arg…”表示其他可选的参数,比如要指定initrd的话,第二个参数就是initrd在DRAM中的地址。如果Linux内核使用设备树的话还需要第三个参数,用来指定设备树在DRAM中的地址,如果不需要initrd的话第二个参数就用‘-’来代替。
现在我们使用网络和EMMC两种方法来启动Linux系统,首先将STM32MP157开发板的Linux镜像和设备树发送到Ubuntu主机中的tftpboot文件夹下。Linux镜像文件前面已经放到了tftpboot文件夹中,现在把设备树文件放到tftpboot文件夹里面。以EMMC核心板为例,将开发板光盘8、系统镜像1、教程系统镜像linux-mp1-5.4.31-g032f9dcc0-v1.0stm32mp157d-atk.dtb发送到Ubuntu主机中的tftpboot文件夹里面,完成以后的tftpboot文件夹如图10.3.7.1所示:
图10.3.7.1 tftpboot文件夹
现在Linux镜像文件和设备树都准备好了,我们先学习如何通过网络启动Linux,使用tftp命令将uImage下载到DRAM的0XC2000000地址处,然后将设备树stm32mp157d-atk.dtb下载到DRAM中的0XC400000地址处,最后使用命令bootm启动,命令如下:
- <font size="4">tftp c2000000 uImage
- tftp c2000000 stm32mp157d-atk.dtb
- bootm c2000000 - c4000000</font>
复制代码
命令运行结果如图10.3.7.2所示:
图10.3.7.2 通过网络启动Linux
从图10.3.7.2可以看出,此时Linux内核已经启动成功,相关的log信息已经打印出来了。
注意!只要打印出Linux内核启动信息就说明Linux系统启动成功,由于没有给定bootargs参数,所以Linux系统启动最终会失败,因为找不到根文件系统!
如果我们要从EMMC中启动Linux系统的话只需要使用命令ext4load将uImage和stm32mp157d-atk.dtb从EMMC的分区2中拷贝到DRAM中,然后使用命令bootm启动即可。先使用命令ext4ls查看要下EMMC的分区2中有没有uImage和stm32mp157d-atk.dtb,如果没有的话请先使用STM32CubeProgrammer将正点原子出厂系统烧写到开发板中。
使用命令ext4load将uImage和stm32mp157d-atk.dtb文件拷贝到DRAM中,地址分别为0XC2000000和0XC4000000,最后使用bootm启动,命令如下:
- <font size="4">ext4load mmc 1:2 c2000000 uImage
- ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb
- bootm c2000000 - c4000000</font>
复制代码
命令运行结果如图10.3.7.5所示:
图10.3.7.5 从EMMC中启动Linux
2、bootz命令
bootz和bootm功能类似,但是bootz用于启动zImage镜像文件,bootz命令格式如下:
bootz [addr [initrd[:size]] [fdt]]
命令bootz有三个参数,addr是Linux镜像文件在DRAM中的位置,initrd是initrd文件在DRAM中的地址,如果不使用initrd的话使用‘-’代替即可,fdt就是设备树文件在DRAM中的地址,使用方法和bootm一模一样,只是所引导的Linux镜像格式不同,NXP的I.MX6ULL就是使用bootz命令来引导Linux内核的。
3、boot和bootd命令
注意!ST官方uboot并没有使能boot和bootd这两个命令,需要自行配置uboot来启动这两个命令,正点原子出厂系统已经使能了这两个命令,后面uboot移植章节会给大家讲解如何使能这两个命令。
如果你使用别的厂商的开发板,在学习本小节的时候发现其uboot下没有使能boot和bootd命令,请联系对应的厂商,让其提供使能了boot和bootd命令的uboot内核和相应的uboot镜像文件,然后使用新的uboot镜像完成本实验。
boot和bootd其实是一个命令,它们最终执行的是同一个函数。为了方便起见,后面就统一使用boot命令,此命令也是用来启动Linux系统的,只是boot会读取环境变量bootcmd来启动Linux系统,bootcmd是一个很重要的环境变量!其名字分为“boot”和“cmd”,也就是“引导”和“命令”,说明这个环境变量保存着引导命令,其实就是多条启动命令的集合,具体的引导命令内容是可以修改的。比如我们要想使用tftp命令从网络启动Linux那么就可以设置bootcmd为“tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000”,然后使用saveenv将bootcmd保存起来。然后直接输入boot命令即可从网络启动Linux系统,命令如下:
- <font size="4">setenv bootcmd 'tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 -c4000000'
- saveenv
- boot</font>
复制代码
运行结果如图10.3.7.6所示:
图10.3.7.6 设置bootcmd从网络启动Linux
前面说过uboot倒计时结束以后就会启动Linux系统,其实就是执行的bootcmd中的启动命令。只要不修改bootcmd中的内容,以后每次开机uboot倒计时结束以后都会使用tftp命令从网络下载uImage和stm32mp157d-atk.dtb,然后启动Linux内核。
如果想从EMMC启动系统,那就设置bootcmd环境变量为“ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000”,然后使用boot命令启动即可,命令如下:
- <font size="4">setenv bootcmd 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000 stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
- saveenv
- boot</font>
复制代码
运行结果如图10.3.7.7所示:
图10.3.7.7 设置bootcmd从EMMC启动Linux
如果不修改bootcmd的话,每次开机uboot倒计时结束以后都会自动从EMMC里面读取uImage和stm32mp157d-atk.dtb,然后启动Linux。
在启动Linux内核的时候可能会遇到如下错误:
“---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---”
如图10.3.7.8所示:
图10.3.7.8 Linux内核启动失败
这个错误的原因是linux内核没有找到根文件系统,这个很正常,因为没有在bootargs环境变量中指定根文件系统路径,关于bootargs环境变量后面会讲解!此处我们重点是验证boot命令,linux内核已经成功启动了,说明boot命令工作正常。
10.3.8 UMS命令
在uboot下我们可以将开发板虚拟成一个U盘,我们可以选择使用哪个Flash作为这个U盘的存储器,比如将正点原子STM32MP157开发板上的EMMC或者SD卡虚拟成U盘。当我们将EMMC虚拟成U盘以后就可以直接在电脑上向开发板拷贝文件了,比如我们在产品开发阶段,就可以直接在uboot下将某个文件拷贝到开发板的根文件系统中,这样就不需要进入系统或者通过网络来替换文件。
uboot提供的ums命令就是来完成此功能的,ums命令格式如下:
- <font size="4">ums <USB_controller> [<devtype>] <dev[:part]> </font>
复制代码
其中USB_controller是usb接口索引,有的开发板有多个USB SLAVE接口,具体要使用哪个就可以通过USB_controller参数指定。正点原子STM32MP157开发板的只有一个USB_OTG口可以作为USB SLAVE,对应的索引为0。Devtype是要挂载的设备,默认为mmc,dev[:part]是要挂载的Flash设备,part是要挂载的分区。
这里我们将开发板的EMMC挂载到电脑上,首先使用USB Type-C线将开发板的USB_OTG口与电脑连接起来,然后用以下命令启动。
ums 0 mmc 1
运行结果如图10.3.8.1所示:
图10.3.8.1 ums命令运行结果
挂载成功以后就会在电脑上有多个U盘,U盘数量取决于你当前开发板EMMC分区数量,如果烧写过出厂系统的话默认是3个U盘,如图10.3.8.2所示:
图10.3.8.1 U盘
注意,在Windows下这三个U盘是无法操作的,因为这三个U盘是ext4格式的,而Windows是不支持ext4格式!所以大家在操作的时候发现Windows报出U盘识别有问题,让格式化的,千万不要格式化!负责开发板整个Linux系统都会被格式化掉!
既然Windows识别不了ext4格式,那么我们可以将其挂载到Ubuntu下,这样就可以正常操作这三个U盘了。
如果要结束挂载,在终端下运行先按住CTRL+C键就能结束这个挂载。
10.3.9 其他常用命令
uboot中还有其他一些常用的命令,比如reset、go、run和mtest等。
1、reset命令
reset命令顾名思义就是复位的,输入“reset”即可复位重启,如图10.3.9.1所示:
图10.3.9.1 reset命令运行结果
2、go命令
go命令用于跳到指定的地址处执行应用,命令格式如下:
go addr [arg ...]
addr是应用在DRAM中的首地址。
3、run命令
run命令用于运行环境变量中定义的命令,比如可以通过“run bootcmd”来运行bootcmd中的启动命令,但是run命令最大的作用在于运行我们自定义的环境变量。在后面调试Linux系统的时候常常要在网络启动和EMMC启动之间来回切换,而bootcmd只能保存一种启动方式,如果要换另外一种启动方式的话就得重写bootcmd,会很麻烦。这里我们就可以通过自定义环境变量来实现不同的启动方式,比如定义环境变量mybootemmc表示从emmc启动,定义mybootnet表示从网络启动。如果要切换启动方式的话只需要运行“run mybootxxx(xxx为emmc或net)”即可。
说干就干,创建环境变量mybootemmc和mybootnet,命令如下:
- <font size="4">setenv mybootemmc 'ext4load mmc 1:2 c2000000 uImage;ext4load mmc 1:2 c4000000(有空格) stm32mp157d-atk.dtb;bootm c2000000 - c4000000'
- setenv mybootnet 'tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 - (有空格) c4000000'
- saveenv</font>
复制代码
创建环境变量成功以后就可以使用run命令来运行mybootemmc、mybootnet或mybootnand来实现不同的启动:
- <font size="4">run mybootemmc</font>
复制代码
或
- <font size="4">run mybootnet</font>
复制代码
4、mtest命令
mtest命令是一个简单的内存读写测试命令,可以用来测试自己开发板上的DDR,命令格式如下:
- <font size="4">mtest [start [end [pattern [iterations]]]]</font>
复制代码
start是要测试的DRAM开始地址,end是结束地址,比如我们测试0XC0000000~0XC0001000这段内存,输入“mtest C0000000 C0001000”,结果如图10.3.9.2所示:
图10.3.9.2 mtest命令运行结果
从图10.3.9.2可以看出,测试范围为0XC0000000~0XC0001000,已经测试了527次,如果要结束测试就按下键盘上的“Ctrl+C”键。
至此,uboot常用的命令就讲解完了,如果要使用uboot的其他命令,可以查看uboot中的帮助信息,或者上网查询一下相应的资料。 |