由于Ubuntu出现了一些问题,后面都是使用正点原子官方版本。
一、U-boot使用
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。
在第五节的时候就下载了ST官网的uboot源码包。
uboot区别:
uboot 官方的 uboot 代码:由 uboot 官方维护开发的 uboot 版本,版本更新快,基本包含所有常用的芯片。
半导体厂商的 uboot 代码:半导体厂商维护的一个 uboot,专门针对自家的芯片,在对自家芯片支持上要比 uboot 官方的好。
开发板厂商的 uboot 代码:开发板厂商在半导体厂商提供的 uboot 基础上加入了对自家开发板的支持。
首先 uboot 官方的基本是不会用的,因为支持太弱了。最常用的就是半导体厂商或者开发板厂商的 uboot。你也可以在购买了第三方开发板以后使用半导体厂商提供的 uboot,只不过有些外设驱动可能不支持,需要自己移植,这个就是我们常说的 uboot 移植。这里我直接使用的是正点原子的Uboot源码(u-boot-stm32mp-2020.01-gdb2b13ef-v1.6.tar)。如果需要这个源码,这里贴上官网:http://47.111.11.73/docs/boards/arm-linux/zdyzmp157.html
2. U-boot编译
2.1 编译
首先在Ubuntu安装库:sudo apt-get install libncurses5-dev bison flex
我在linux/atk-mpl/下创建了uboot目录,并且在uboot里面还创建了alientek_uboot目录。把uboot源码拷贝至alientek_uboot下。
使用命令压缩: tar -vxf u-boot-stm32mp-2020.01-gdb8d2374-v1.0.tar.bz2
解压完成后
编译uboot:
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
ARCH: 指定所使用的平台架构,这里肯定是 arm。
CROSS_COMPILE: 所使用的交叉编译器前缀,全文使用的是交叉编译器前缀为 armnone-linux-gnueabihf-。
DEVICE_TREE: 设备树文件, uboot 也支持设备树,所以在编译的时候需要指定设备树文件,不同的硬件其设备树文件肯定不同,这里为 stm32mp157d_atk,也就是正点原子的STM32MP157 开发板对应的设备树。
编译的时候每次都输入 ARCH 和 CROSS_COMPILE 比较麻烦,为了方便起见,我们可以直接修改 uboot 的 Makefile 文件,在里面直接对 ARCH 和 CROSS_COMPILE 进行赋值,也就是直接将 ARCH 设置为 arm, CROSS_COMPILE 设置为 arm-none-linux-gnueabihf-,下面是修改后图:
配置完成后就可以简化编译命令:
make distclean //清除
make stm32mp157d_atk_defconfig //配置 uboot
make V=1 DEVICE_TREE=stm32mp157d-atk all //编译
述命令和前面的相比就要简洁很多,最后的“make V=1”是真正的编译命令, V=1 表示编译 uboot 的时候输出详细的编译过程,方便我们观察 uboot 编译过程。直接输入“make”命令的话默认使用单线程编译,编译速度会比较慢,可以通过添加“-j”选项来使用多线程编译,比如使用 8 线程编译,最后的编译命令为:
make V=1 DEVICE_TREE=stm32mp157d-atk all -j8 // 8 线程编译
编译完成会有uboot相应的镜像文件。
重点是 u-boot.bin 和 u-boot.stm32 这两个文件。 u-boot.bin 是 uboot 的二进制可执行文件, u-boot.stm32 是在 u-boot.bin 前面添加了256 个字节头部信息。
2.2 烧录
使用 STM32CubeProgrammer 将上面编译出来的 u-boot.stm32 镜像烧写到开发板的 EMMC里面,修改前面创建的 tf-a.tsv 文件,添加 uboot 烧写指令,在最后面添加下面这行:
最后将上一小节编译出来的 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 keytostop autoboot: ”倒计时的时候按下键盘上的回车键,默认是 1 秒倒计时,在 1 秒倒计时结束以后如果没有按下回车键的话 uboot 就会使用默认参数来启动 Linux 内核了(如果内核存在的话,如果 Linux 内核不存在那么就会进入到 uboot 的命令行模式)。如果在 1 秒倒计时结束之前按下回车键,那么就会进入 uboot 的命令行模式。
当进入到 uboot 的命令行模式以后,左侧会出现一个“STM32MP=>”标志。
当uboot启动的时候会输出一些信息,简单讲一下这些信息。
U-Boot 2020.01-stm32mp-r1 (Jul 30 2023 - 12:37:27 +0800) // 第 1 行是 uboot 版本号和编译时间,可以看出,当前的 uboot 版本号是 2020.01,编译时间是 2023 年 7 月 30 日 12: 37。 CPU: STM32MP157DAA Rev.Z // 第 3 行是 CPU 的信息,可以看出 CPU 型号为 STM32MP157DAA。
Model: STMicroelectronics STM32MP157D eval daughter // 第 4 行是板子信息,当前板子是 ST 公司的 STM32MP157D eval 开发板,这个信息是可以改的,因为正点原子是直接参考 ST 公司的 EVK 开发板移植的 uboot,所以这部分信息也就没改。
Board: stm32mp1 in trusted mode (st,stm32mp157d-atk) // 第 5 行是板子的一些信息,比如工作在 trusted 模式下。
DRAM: 1 GiB // 第 6 行是 DDR 的大小为 1GB。
Clocks:
- MPU : 800 MHz
- MCU : 208.878 MHz
- AXI : 266.500 MHz
- PER : 24 MHz
- DDR : 533 MHz // 第 7~12 行它们的频率分别为, MPU 频率、 MCU 频率、 AXI 总线频率、 PER 的频率、 DDR频率。
WDT: Started with servicing (32s timeout) // 第 13 行是看门狗信息,喂狗时间为 32s。
NAND: 0 MiB // 第 14 行是 NAND 的大小,因为正点原子的 STM32MP157 开发板没有 NAND,所以这里就是 0MB。
MMC: STM32 SD/MMC: 0, STM32 SD/MMC: 1 // 第 15 行是板子上 MMC 设备,一共有两个, SD/MMC0 (SD 卡)和 SD/MMC1 (EMMC)。
Loading Environment from MMC... OK // 第 16 行是从 MMC 里获取环境变量。
In: serial
Out: serial
Err: serial // 第 17~19 行是标准输入、标准输出和标准错误所使用的终端,这里都使用串口(serial)作为终端。
invalid MAC address in OTP 00:00:00:00:00:00
Net:
Error: ethernet@5800a000 address not set.
No ethernet found. // 第 20~23 行是网络相关信息,网络的 MAC 地址从 OTP 里获取,因为我们的 OTP 没有设置 MAC 地址,所以就获取失败。这里的网络是可以用的,只是因为没有 MAC 地址所以提示没有找到网络,可以自行添加相关环境变量来设置 MAC 地址,后面会讲如何设置。lcd_id = 02 // 第 25 行是倒计时提示,默认倒计时 1 秒,倒计时结束之前按下回车键就会进入 Linux 命令行模式。如果在倒计时结束以后没有按下回车键,那么 Linux 内核就会启动, Linux 内核一旦启动, uboot 就会寿终正寝。
Hit any key to stop autoboot: 0
Boot over mmc1!
switch to partitions #0, OK
mmc1(part 0) is current device
** Unrecognized filesystem type **
STM32MP>
uboot 的主要作用是引导 kernel,我们现在已经进入 uboot 的命令行模式了,进入命令行模式以后就可以给 uboot 发号施令了。
3. U-boot命令使用
我们输入“help(或?) 命令名”既可以查看命令的详细用法, 以“bootz”这个命令为例,我们输入如下命令即可查看“bootz”这个命令的用法:
? bootz
① 查询命令
常用的和信息查询有关的命令有 3 个: bdinfo、 printenv 和 version。先来看一下 bdinfo 命令,此命令用于查看板子信息,直接输入“bdinfo”即可。。
从图上图中可以看出 DRAM 的起始地址和大小、 BOOT 参数保存起始地址、波特率、sp(堆栈指针)起始地址等信息。命令“printenv”用于输出环境变量信息, uboot 也支持 TAB 键自动补全功能,输入“print”然后按下 TAB 键就会自动补全命令。直接输入“print”也可以,因为整个 uboot 命令中只有 printenv 的前缀是“print”,所以当输入 print 以后就只有 printenv 命令了。输入“print”,然后按下回车键,环境变量如下图所示
上图只是 printenv 命令的部分内容, STM32MP1 系列的环境变量有很多,比如baudrate、 board、 board_name、 boot_device、 bootcmd、 bootdelay 等等。 uboot 中的环境变量都是字符串,既然叫做环境变量,那么它的作用就和“变量”一样。比如 bootdelay 这个环境变量就表示 uboot 启动延时时间,默认 bootdelay=1,也就默认延时 1 秒。前面说的 1 秒倒计时就是由 bootdelay 定义的,如果将 bootdelay 改为 5 的话就会倒计时 5s 了。 uboot 中的环境变量是可以修改的,有专门的命令来修改环境变量的值。
version用于查看uboot版本号。
3.1 环境变量操作命令
① 修改环境变量
环境变量的操作涉及到两个命令: setenv 和 saveenv, setenv 命令用于设置或者修改环境变量的值。命令 saveenv 用于保存修改后的环境变量,一般环境变量存放在外部 flash 中, uboot启动的时候会将环境变量从 flash 读取到 DRAM 中。所以使用命令 setenv 修改的是 DRAM中的环境变量值,修改以后要使用 saveenv 命令将修改后的环境变量保存到 flash 中,否则uboot 下一次重启会继续使用以前的环境变量值。
比如我们要将环境变量 bootdelay 改为 5,就可以使用如下所示命令:
setenv bootdelay 5
saveenv
当我们使用命令 saveenv 保存修改后的环境变量会有保存过程提示信息,根据提示可以看出环境变量保存到了 MMC(1)中,也就是 EMMC 中。因为我用 EMMC 启动的,所以会保存到 MMC(1)中。修改 bootdelay 以后,重启开发板, uboot 就是变为 5 秒倒计时。
② 新建环境变量
命令 setenv 也可以用于新建命令,用法和修改环境变量一样,比如我们新建一个环境变量author, author 的值为‘console=ttySTM0,11520 root=/dev/mmcblk2p2 rootwait rw’,那么就可以使用如下命令:
setenv author 'console=ttySTM0,115200 root=/dev/mmcblk2p2 rootwait rw '
saveenv
上面命令设置 author 的值为“console=ttySTM0,115200 root=/dev/mmcblk2p2 rootwait rw”,其中“console=ttySTM0,115200”、“root=/dev/mmcblk2p2”、“rootwait”和“rw”相当于四组“值”,这四组“值”之间用空格隔开,所以需要使用单引号‘’将其括起来,表示这四组“值”都属于环境变量 author。author 命令创建完成以后重启 uboot,然后使用命令 printenv 查看当前环境变量。
③ 删除环境变量
既然可以新建环境变量,那么就可以删除环境变量,删除环境变量也是使用命令 setenv,要删除一个环境变量只要给这个环境变量赋空值即可,比如我们删除掉上面新建的 author 环境变量,命令如下:
setenv author
saveenv
上面命令中通过 setenv 给 author 赋空值,也就是什么都不写来删除环境变量 author。重启 uboot 就会发现环境变量 author 没有了。
3.2 内存操作命令
内存操作命令就是用于直接对 DRAM 进行读写操作的,常用的内存操作命令有 md、 nm、mm、 mw、 cp 和 cmp。
① md命令
md命令用于显示内存值,格式如下:
md[.b, .w, .l] address [# of objects]
命令中的[.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命令中的数字都是十六进制!!!uboot命令中的数字都是十六进制!!!uboot命令中的数字都是十六进制!!!
比如你想查看 0XC0100000 开始的 20 个字节的内存值,显示格式为.b 的话,应该使用如下所示命令:
md.b C0100000 14 而不是md.b C0100000 20,因为都是用16进制表示
上面说了, uboot 命令里面的数字都是十六进制的,所以可以不用写“0x”前缀,十进制的20 对应的十六进制为 0x14,所以命令 md 后面的个数应该是 14,如果写成 20 的话就表示查看32(十六进制为 0x20)个字节的数据。
试着分析下面三个命令的区别 :
md.b C0100000 10
md.w C0100000 10
md.l C0100000 10
上面这三个命令都是查看以 0XC0100000 为起始地址的内存数据,第一个命令以.b 格式显示,长度为 0x10,也就是 16 个字节;第二个命令以.w 格式显示,长度为 0x10,也就是 16*2=32个字节;最后一个命令以.l 格式显示,长度也是 0x10,也就是 16*4=64 个字节。这三个命令的执行结果如下图所示:
② nm命令
nm 命令用于修改指定地址的内存值,命令格式如下:
nm [.b, .w, .l] address
nm 命令同样可以以.b、 .w 和.l 来指定操作格式,比如现在以.l 格式修改 0XC0100000 地址的数据为 0x12345678 。
nm.l C0100000
在上图中,C0100000 表示现在要修改的内存地址, ea0000b8 表示地址 0xc0100000现在的数据,‘?’后面就可以输入要修改后的数据 0x12345678,输入完成以后按下回车,然后再输入‘q’即可退出,如下图所示:
这就代表修改成功,地址 0XC0100000 的值变为了 0X12345678。
③ mm命令
mm 命令也是修改指定地址内存值的,使用 mm 修改内存值的时候地址会自增,而使用 nm命令的话地址不会自增。比如以.l 格式修改从地址 0XC0100000 开始的连续 3 个内存块(3*4=12个字节)的数据为 0X05050505,操作如下图所示:
可以看出内存数据修改成功。
④ 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
⑤ 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
⑥ 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
这是相同的情况。
cmp.l c0100000 c0100200 10
这是不想同的情况。
3.2 网络操作命令
uboot 是支持网络的,我们在移植 uboot 的时候一般都要调通网络功能,因为在移植 linuxkernel 的时候需要使用到 uboot 的网络功能做调试。uboot 支持大量的网络相关命令,比如 dhcp、ping、 nfs 和 tftpboot。
(环境变量:描述)
ipaddr:开发板 ip 地址,可以不设置,使用 dhcp 命令来从路由器获取 IP 地址。
ethaddr:开发板的 MAC 地址,一定要设置。
gatewayip:网关地址。
netmask:子网掩码。
serverip:服务器 IP 地址,也就是 Ubuntu 主机 IP 地址,用于调试代码。
环境变量设置命令如下:
setenv ipaddr 192.168.1.106
setenv ethaddr b8:ae:1d:01:01:00
setenv gatewayip 192.168.1.1
setenv netmask 255.255.255.0
setenv serverip 192.168.1.105
saveenv
网络地址环境变量的设置要根据自己的实际情况,确保 Ubuntu 主机和开发板的 IP地址在同一个网段内,比如我现在的开发板和电脑都在 192.168.1.0 这个网段内,所以设置开发板的 IP 地址为 192.168.1.104,我的 Ubuntu 主机的地址为 192.168.1.105,因此 serverip 就是192.168.1.105。 ethaddr 为网络 MAC 地址,是一个 48bit 的地址,如果在同一个网段内有多个开发板的话一定要保证每个开发板的 ethaddr 是不同的,否则通信会有问题!设置好网络相关的环境变量以后就可以使用网络相关命令了。
① ping命令
开发板的网络能否使用,是否可以和服务器(Ubuntu 主机)进行通信,通过 ping 命令就可以验证,直接 ping 服务器的 IP 地址即可,比如我的服务器 IP 地址为 192.168.1.104,命令如下:
ping 192.168.1.105
说明192.168.1.105主机存在,ping成功,uboot网络正常工作。
注意!只能在 uboot 中 ping 其他的机器,其他机器不能 ping uboot,因为 uboot 没有对 ping命令做处理,如果用其他的机器 ping uboot 的话会失败!
② dhcp命令
dhcp 用于从路由器获取 IP 地址,前提是开发板得连接到路由器上的,如果开发板是和电脑直连的,那么 dhcp 命令就会失效。直接输入 dhcp 命令即可通过路由器获取到 IP 地址,如下图所示:
从上图可以看出,开发板通过 dhcp 获取到的 IP 地址为 192.168.1.7。 DHCP 不单单是获取 IP 地址,其还会通过 TFTP 来启动 linux 内核,输入“? dhcp”即可查看 dhcp 命令详细的信息,如下图所示:
③ nfs命令
nfs(Netwnork File System)网络文件系统,通过 nfs 可以在计算机之间通过网络来分享资源,比如我们将 linux 镜像和设备树文件放到 Ubuntu 中,然后在 uboot 中使用 nfs 命令将 Ubuntu 中的 linux 镜像和设备树下载到开发板的 DRAM 中。这样做的目的是为了方便调试 linux 镜像和设备树,也就是网络调试,网络调试是 Linux 开发中最常用的调试方法。
一般使用 uboot 中的 nfs 命令将 Ubuntu 中的文件下载到开发板的 DRAM 中,在使用之前需要开启 Ubuntu 主机的 NFS 服务,并且要新建一个 NFS 使用的目录,以后所有要通过NFS 访问的文件都需要放到这个 NFS 目录中。 设置的 NFS 文件目录为: /home/alientek/linux/nfs, uboot 中的 nfs 命令格式如下所示 :
nfs [loadAddress] [[hostIPaddr:]bootfilename]
loadAddress 是要保存的 DRAM 地址, [[hostIPaddr:]bootfilename]是要下载的文件地址。这里我们将正点原子官方编译出来的 Linux 镜像文件 uImage 下载到开发板 DRAM 的 0xC2000000这个地址处(ST 官方指定的 Linux 内核加载地址)。
将uImage文件用FileZilla发送到Ubuntu中的NFS目录下:
可以使用 nfs 命令来将 uImage 下载到开发板 DRAM 的 0XC2000000 地址处,命令如下:
nfs C2000000 192.168.1.105:/home/alientek/linux/nfs/uImage
上图中通过网络下载 uImage 的时候会打印出‘#’表示正在下载,如果网络环境不好,下载有干扰的话就会打印出一个‘T’,最终打印出“done” 即可。
下载完成以后查看 0xC2000000 地址处的数据,使用命令 md.b 来查看前 0x100(256)个字节的数据,如下图所示:
使用 winhex 软件来查看刚刚下载的 uImage,检查一下前面的数据是否一致, 可以看出前 0x100 个字节的数据一致,说明 nfs 命令下载到的uImage 是正确的。
④ 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/alientek/linux/tftpboot
chmod 777 /home/alientek/linux/tftpboot
这样我就在我的电脑上创建了一个名为 tftpboot 的目录(文件夹),路径为/home/alientek/linux/tftpboot。注意!我们要给 tftpboot 文件夹权限,否则的话 uboot 不能从 tftpboot 文件夹里面下载文件。chmod用于更改文件或目录的权限。
最后配置 tftp,新建文件/etc/xinetd.d/tftp,如果没有/etc/xinetd.d 目录的话自行创建,然后在里面输入如下内容:
server tftp
{socket_type = dgramprotocol = udpwait = yesuser = rootserver = /usr/sbin/in.tftpdserver_args = -s /home/zuozhongkai/linux/tftpboot/disable = noper_source = 11cps = 100 2flags = IPv4
}
完成以后启动 tftp 服务,命令如下:
sudo service tftpd-hpa start
打开/etc/default/tftpd-hpa 文件,将其修改为如下所示内容:
# /etc/default/tftpd-hpaTFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/liangwencong/linux/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="-l -c -s"
TFTP_DIRECTORY 就是我们上面创建的 tftp 文件夹目录,以后我们就将所有需要通过TFTP 传输的文件都放到这个文件夹里面,并且要给予这些文件相应的权限。最后输入如下命令, 重启 tftp 服务器:
sudo service tftpd-hpa restart
tftp 服务器已经搭建好了,接下来就是使用了。将 uImage 镜像文件拷贝到 tftpboot 文件夹中,并且给予 uImage 相应的权限,命令如下:
cp uImage /home/alientek/linux/tftpboot/
cd /home/alientek/linux/tftpboot/
chmod 777 uImage
uboot 中的 tftp 命令格式如下:
tftpboot [loadAddress] [[hostIPaddr:]bootfilename]
看起来和 nfs 命令格式一样的 , loadAddress 是 文 件 在 DRAM 中 的存放地址 ,[[hostIPaddr:]bootfilename]是要从 Ubuntu 中下载的文件。但是和 nfs 命令的区别在于, tftp 命令不需要输入文件在 Ubuntu 中的完整路径,只需要输入文件名即可。比如我们现在将 tftpboot 文件夹里面的 uImage 文件下载到开发板 DRAM 的 0XC2000000 地址处,命令如下:
tftp C2000000 uImage
uImage 下载成功了。有时候使用 tftp 命令从 Ubuntu 中下载文件的时候会出现错误提示:“TFTP error: 'Permission denied' (0)”这样的错误提示,一般有两个原因:
① 在 Ubuntu 中创建 tftpboot 目录的时候没有给予 tftboot 相应的权限。
② tftpboot 目录中要下载的文件没有给予相应的权限。
针对上述两个问题,使用命令“chmod 777 xxx”来给予权限,其中“xxx”就是要给予权限的文件或文件夹。
最常用的就是 ping、 nfs 和 tftp 这三个命令。使用 ping 命令来查看网络的连接状态,使用 nfs 和 tftp 命令来从 Ubuntu 主机中下载文件。
3.3 EMMC 和 SD 卡操作命令
(由于我现在板子上显示Card did not respond to voltage select!本小节暂时放下)
3.4 EXT 格式文件系统操作命令
跟上一小节类似,暂时放下。
3.5 Boot操作命令
uboot 的本质工作是引导 Linux,所以 uboot 肯定有相关的 boot(引导)命令来启动 Linux。常用的跟 boot 有关的命令有: bootm、 bootz 和 boot。
① 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 的话第二个参数就用‘-’来代替。
现在我们使用网络来启动 Linux 系统,首先将 STM32MP157 开发板的Linux 镜像和设备树发送到 Ubuntu 主机中的 tftpboot 文件夹下。 Linux 镜像文件前面已经放到了 tftpboot 文件夹中,现在把设备树文件放到 tftpboot 文件夹里面。 现在需要把 stm32mp157d-atk.dtb 发送到tftpboot文件里面。
需要把stm32mp157d-atk.dtb文件给予权限。
chomd 777 stm32mp157d-atk.dtb
现在 Linux 镜像文件和设备树都准备好了,我们先通过网络启动 Linux,使用 tftp命令将 uImage 下载到 DRAM 的 0XC2000000 地址处,然后将设备树 stm32mp157d-atk.dtb 下载到 DRAM 中的 0XC400000 地址处,最后使用命令 bootm 启动,命令如下:
tftp c2000000 uImage
tftp c4000000 stm32mp157d-atk.dtb
bootm c2000000 - c4000000
② 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 内核的。
③ boot和bootd 命令
ST 官方 uboot 并没有使能 boot 和 bootd 这两个命令,需要自行配置 uboot 来启动这两个命令, 正点原子官方是使能了这两个命令。
boot 和 bootd 其实是一个命令,它们最终执行的是同一个函数。为了方便起见,后面就统一使用 boot 命令,此命令也是用来启动 Linux 系统的,只是 boot 会读取环境变量 bootcmd 来启动 Linux 系统, bootcmd 是一个很重要的环境变量!其名字分为“boot”和“cmd”,也就是“引导”和“命令”,说明这个环境变量保存着引导命令,其实就是多条启动命令的集合,具体的引导命令内容是可以修改的。 命令如下:
setenv bootcmd 'tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 -c4000000'
saveenv
boot
我理解的是bootcmd就是环境变量,boot来读环境变量。修改bootcmd之后输入boot其实就是重复这段tftp c2000000 uImage;tftp c4000000 stm32mp157d-atk.dtb;bootm c2000000 -c4000000'
saveenv
uboot 倒计时结束以后就会启动 Linux 系统,其实就是执行的 bootcmd 中的启动命令。只要不修改 bootcmd 中的内容,以后每次开机 uboot 倒计时结束以后都会使用 tftp 命令从网络下载 uImage 和 stm32mp157d-atk.dtb,然后启动 Linux 内核。
3.6 ums 命令
在 uboot 下我们可以将开发板虚拟成一个 U 盘,我们可以选择使用哪个 Flash 作为这个 U盘的存储器,比如将正点原子 STM32MP157 开发板上的 EMMC 或者 SD 卡虚拟成 U 盘。当我们将 EMMC 虚拟成 U 盘以后就可以直接在电脑上向开发板拷贝文件了,比如我们在产品开发阶段,就可以直接在 uboot 下将某个文件拷贝到开发板的根文件系统中,这样就不需要进入系统或者通过网络来替换文件。ums 命令格式如下:
ums <USB_controller> [<devtype>] <dev[:part]>
其中 USB_controller 是 usb 接口索引,有的开发板有多个 USB SLAVE 接口,具体要使用哪个就可以通过 USB_controller 参数指定。 正点原子 STM32MP157 开发板的只有一个 USB_OTG口可以作为 USB SLAVE,对应的索引为 0。 Devtype 是要挂载的设备,默认为 mmc, dev[:part]是要挂载的 Flash 设备, part 是要挂载的分区。
首先使用 Type-C 将开发板的USB_OTG与电脑相连接,使用以下命令启动。
ums 0 mmc 1
3.7 其他常用命令
① reset 命令
输入'reset'既可复位重启。
② go 命令
go 命令用于跳到指定的地址处执行应用,命令格式如下:
go addr [arg ...]
addr 是应用在 DRAM 中的首地址。
③ run 命令
run 命令用于运行环境变量中定义的命令,比如可以通过“run bootcmd”来运行 bootcmd 中的启动命令,但是 run 命令最大的作用在于运行我们自定义的环境变量。在后面调试 Linux 系统的时候常常要在网络启动和 EMMC 启动之间来回切换,而 bootcmd 只能保存一种启动方式,如果要换另外一种启动方式的话就得重写 bootcmd,会很麻烦。这里我们就可以通过自定义环境变量来实现不同的启动方式,比如定义环境变量 mybootemmc 表示从 emmc 启动,定义mybootnet 表示从网络启动。如果要切换启动方式的话只需要运行“run mybootxxx(xxx 为 emmc或 net)”即可。
首先创建环境变量 mybootemmc 和 mybootnet,命令如下:
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
创建环境变量成功以后就可以使用 run 命令来运行 mybootemmc、 mybootnet 或 mybootnand来实现不同的启动:
run mybootemmc
或
run mybootnet
④ mtest 命令
mtest 命令是一个简单的内存读写测试命令,可以用来测试自己开发板上的 DDR,命令格式如下:
mtest [start [end [pattern [iterations]]]]
start 是要测试的 DRAM开始地址 ,end 是结束地址 , 比如我们测试0XC0000000~0XC0001000 这段内存,输入“mtest C0000000 C0001000”,结果如下图所示:
从上图可以看出,测试范围为 0XC0000000~0XC0001000,已经测试了 2672 次,如果要结束测试就按下键盘上的“Ctrl+C”键。
3.8 MII 命令使用说明
MII 命令是网络相关命令,主要用于读取网络 PHY 芯片寄存器,在 uboot 中调试网络 PHY芯片的时候非常有用!另外, MDIO 命令也是与网络 PHY 芯片有关的。uboot 启动以后,如果网络 PHY 芯片工作正常,那么我们可以直接使用 MII 命令来读取核心板上 PHY芯片的内部寄存器。但是在 STM32MP157 上是不能读取的,原因是 ST 官方提供的 uboot 中,每次网络通信完成以后会关闭 ETHMAC 时钟,所以 MII 命令就会无法工作。解决这个方法很简单,每次使用 MII 或者 MDIO 命令来操作 PHY 芯片的时候,先使能 ETHMAC 时钟。将0X50000218 这个寄存器的 bit8~10 置 1 即可。具体步骤如下:
1、ping其他网络设备看是否存在
2、使能ETHMAC时钟
3、 使用 MII 命令获取 PHY 芯片寄存器值
接下来就可以使用 MII 命令读取 PHY 芯片寄存器值, MII 是一系列命令,如下图所示
'mii info' 命令格式如下:
mii info <addr>
其中 addr 就是 PHY 芯片地址,正点原子 V1.2 版本核心板上的 PHY 芯片为 RTL8211,地址为 0X01。 V1.3 版本核心板上的 PHY 芯片为 YT8511,地址为 0X00。