手把手完成IMX6ULL系统移植实验,包含各种问题的处理(一)

 一. 前言

        在经过前面漫长的裸机试验,帮助我们学习并掌握开发板上的外设的底层原理,在我们之后移植完系统后,就可以将精力放在学习Linux框架上,所以我们要将Linux系统移植到我们的开发板中去,在开始移植的Linux系统的时候我们需要先移植一个Bootloader代码,Booloader用于启动Linux系统

二. 什么是Bootloader

        Bootloader是一个统称,叫做引导加载程序,指设备上电后负责有硬件初始化和操作系统加载的引导程序,并将控制权交给操作系统或应用程序,确保系统可以正常运行

1. Bootloader的基本定义

      Bootloader是设备启动过程中第一个运行的软件模块,位于固件(如Flash),它的功能是加载操作系统内核或者主程序,同时完成设备所需的硬件初始化。所以Bootloader是一个通用概念,表示所有负责引导操作系统或者应用程序的程序
        从手机,嵌入式,到PC都需要用到Bootloader,对于嵌入式设备中Bootloader通常存储在非易失性存储器中,例如
        Flash:常见于嵌入式单片机(如STM32)。通常存储在内置Flash的前几个扇区
        NOR/NAND Flash:用于存储Bootloader的前部分区域
        eMMC/SD卡:在一些设备中,Bootloader存储在eMMC的引导的分区(Boot Partition)或用户分区的起始部分

2. Bootloader的作用和功能

        2.1 硬件初始化

       Bootloader在启动时,会根据硬件平台的特点,完成硬件初始化,首先会对CPU进行设置,例如时钟频率、以及CPU模式等。初始化必要的外设,例如内存控制器(RAM)、串口(UART)、网络控制器等。检查硬件状态,确保设备可以正常运行

        2.2 加载操作系统或主程序

        读取存储器(如Flash、SD卡)中的操作系统内核或主程序。将其解压缩或者加载到RAM中。跳转到内核或程序的入口地址,完成控制权的交接

        2.3 启动模式选择

        根据外部输入或用户配置,决定从何处加载(如网络启动、USB启动、本地存储启动)。提供安全启动选项,确保只有经过认证的系统能够运行

        2.4 提供调试与维护功能

        支持通过串口、网络等接口调试系统。允许烧录新的固件,支持系统升级。提供启动日志输出,帮助开发者定位问题等功能

3. Bootloader分类

        3.1 按层级划分

        Bootloader通常分为两级:
        一级 Bootloader(Primary Bootloader):
        存储在芯片的内部ROM或固定存储区。功能简单,仅负责初始化最基本的硬件环境(如 RAM、时钟),然后将控制权交给二级Bootloader
        二级 Bootloader(Secondary Bootloader):
       
功能更强大,通常是嵌入式开发者或开源社区开发。负责更复杂的硬件初始化(如外设驱动)、加载操作系统,并提供调试功能。例如:U-boot、Barebox   

        3.2 按设备类型划分     

        1. 嵌入式设置Bootloader
       
例如U-boot、Barebox,常见于嵌入式Linux系统。功能丰富,支持多种外设和启动方法
        2. PC系统Bootloader
       
例如GRUB、LILO,负责加载桌面操作系统(如Linux、Windows)

4. 为什么有不同种类的Bootloader

        嵌入式设备的种类和应用场景非常多样化,从简单的单片机设备到复杂的高性能设备,格子的硬件架构和功能需求千差万别
        1. 硬件架构的差异
        不同设备可能使用不同的处理器架构(例如 ARM、RISC-V、x86)、每种架构的启动方式和硬件初始化流程几乎不一样
        且有些设备的硬件资源非常有限(如微控制器只有几kb的Flash和RAM),无法运行功能复杂的Bootloader
        2. 功能需求差异
       
工业控制设备可能需要实时性和更高的可靠性,简化启动流程,减少引导时间
        消费电子设备可能需要图形界面和多功能支持,U-boot的生态更适合这样的场景。小型设备(如IOT芯片)只需要一个非常轻量的Bootloader,例如Barebox的精简模式,或者使用厂家定制的引导程序

5. Bootloader总结

        总的说下来Bootloader是启动过程中不可或缺的组件,它将硬件和操作系统连接起来,无论是嵌入式设备、还是PC系统。Bootloader的核心作用都是,初始化硬件让硬件进入可用状态;加载系统或者应用,确保操作系统或主程序可以正常运行;提供调试与升级功能,方便开发者维护系统

三. 完整的Linux系统移植的顺序

        在系统移植中移植了Bootloader后,再移植Linux的内核,移植完内核以后Linux系统是无法正常启动的,还需要移植一个根文件系统(rootfs),这三者一起构成了一个完整的Linux系统,一个可以正常使用、功能完善的Linux系统

1. Bootloader移植

        前面提到Bootloader负责硬件初始化并加载Linux内核,是系统启动的第一步。我们的任务根据目标硬件平台,移植Bootloader代码,包括CPU初始化,时钟配置、内存控制器、外设初始化等。移植完成后,编译并烧录Bootloader到设备

2. Linux内核移植

        内核是操作系统中的核心部分,提供硬件驱动、任务调度和资源管理。
        我们需要做的就是获取适配我们硬件平台的Linux内核源码,配置内核选项(如驱动支持、文件系统支持等),并移植与目标硬件相关的驱动程序(如网卡、显示器等)。便后将内核镜像与设备树文件烧录到设,通过Bootloader引导内核启动

3. 根文件系统(rootfs)制作

        根文件系统为内核提供用户空间运行环境,包含最基本的命令、库和应用程序
        我们要完成的还是制作适配对应硬件的根文件系统,包含启动脚本、常用工具、库文件以及自定义应用程序。将根文件系统镜像存储在设备指定位置,并确保Bootloader能够正确传递其加载参数

4. 系统调试与优化

        联合调试Bootloader、内核和根文件系统,确保系统可以从上电启动到进入用户空间,根据硬件平台和硬件需求,优化启动时间、驱动性能或文件系统布局

        通过以上三步(Booloader、内核、根文件系统)的移植和调试,最终构建出一个完整、稳定的嵌入式Linux系统

四. U-Boot使用实验

        前面我们说过针对于嵌入式也有很多种类的Bootloader,那么为什么对于Linux我们选择U-Boot呢?首先U-Boot支持多种启动模式(如Flash、SD卡、USB、网络等),并能加载不同的操作系统,特别适用于嵌入式设备
        U-Boot本身就对Linux有深度的支持,U-Boot能高效的加载Linux内核和文件系统,支持设备树(Device Tree)和内核参数传递,方便Linux系统的启动与调试
        具有强大的网络功能,支持TFTP协议远程加载固件,简化了开发调试和系统升级过程。以及高度可配置和拓展,通过环境变量、命令行接口和脚本支持,U-Boot可以根据具体需求灵活配置和定制启动流程,以上都是我们选择U-Boot的关键       

        那么在移植U-boot前,我们可以先使用一下U-Boot,我们直接使用正点原子移植好的U-Boot进行编译,烧写到SD卡中启动,启动U-Boot后就可以开始学习U-Boot的命令

1. U-Boot简介

        uboot全称,Universal Boot Loader,是一个遵循GPL开源协议的开源软件,uboot本质就是一个裸机代码,是一个裸机的综合例程,因为前面说过了uboot需要对CPU、内存等硬件进行初始化,所以就是硬件初始化的综合实例程序
        现在的uboot已经支持液晶屏、网络、USB等高级功能,这些功能的加入使得U-Boot从一个简单的逻辑引导程序发展成了一个功能强大的、具有硬件抽象和操作系统支持的引导框架
        Uboot官网上的Uboot源码在此网址Index of /pub/u-boot/

        这个是U-boot提供的U-boot,相当于是一个通用的框架,为不同的硬件平台提供基本支持。支持多种处理器架构和常见的开发板。但是U-boot官方的U-boot主要是面向开发者和芯片厂商,作为一个基础平台,来针对不同的硬件设施进行移植和二次开发
        主要的特点就是,代码结构模块化,能够支持多个硬件平台,但未针对某个特定的芯片进行深度的优化。并且功能也比较有限,对一些硬件特性或者某些芯片支持不完善,也可能未包含厂商的特殊功能

        而芯片厂商的定制版本,如(NXP 、TI、Rockchip等)会基于u-boot的官方u-boot,针对自家的芯片进行移植、优化和维护。主要目的就是让芯片在开发板或其他硬件上能更好的运行,提供全面的硬件支持和优化。也减少了开发的使用流程,只需要开发者能够快速启动和调试硬件,无需花时间去对u-boot官方的u-boot进行移植
        主要特点包含,会对特定的芯片优化,厂商会在u-boot中加入针对自家芯片特定功能的支持,例如特定的外设、启动模式、时钟配置等。以及功能更全面,相比u-boot官方版本,厂商定制版本通常对自家芯片支持更完善。例如更多硬件的特性、优化启动速度、增强稳定性
        GitHub - nxp-imx/uboot-imx: i.MX U-Boot这个是NXP厂商针对其自身移植的u-boot,其中有NXP维护的各个版本的uboot

        据了解,这里面的u-boot基本支持了NXP当前所有支持Linux系统的芯片,而且支持各种启动方式,如EMMC、NAND、NOR FLASH等等,这些都是u-boot官方不支持的,但是我们使用的是正点原子的开发板,但是NXP是针对自家的评估板
        所以正点原子就在NXP官方的uboot的基础上修改成了,适配自己的阿尔法开发板,所以这个就是第三种uboot,属于开发厂商的uboot,而NXP属于半导体厂商的uboot,所以我们这次就选择移植正点原子移植好的uboot代码,那么接下来就开始实践操作

2.U-Boot初次编译

        首先在我们虚拟机中的Ubuntu中安装ncurses,否则编译的时候会报错,这个库作用很多可以自行去搜索,主要包含ncurses库是一个功能强大且灵活的文本用户界面(TUI)库,为开发者提供了丰富的函数和数据结构来创建基于文本的用户界面。通过学习和掌握ncurses的使用,开发者可以更加高效地开发各种终端应用程序

sudo apt-get install libncurses5-dev

        然后自行创建一个存放uboot的目录,博主路径如下
        

        然后下一步操作就是通过FileZilla将正点原子提供u-boot源码拷贝到对应的目录中

        使用如下命令解压u-boot的压缩包

tar -vxjf uboot-imx-2016.03-2.1.0-g8b546e4.tar.bz2

        使用ls解压出来显示结果如下

        其中除了压缩包以外,其余的文件以及文件夹都是解压出来的uboot源码

        下面参考正点原子,如果使用的是512MB+8GB的EMMC核心板,使用如下命令编译对应的uboot

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean

        这段命令表示,运用以及用的Makefile中定义的make规则完成对应的构建、清理等任务。ARCH=arm指定目标体系结构为ARM,前面提到U-boot支持多种架构,这里通过ARCH告诉make需要为哪种CPU架构生成代码
        CROSS_COPILE=arm-linux-gnueabihf-,是指定交叉编译器的前缀,一个完整的编译器工具链通常包括以下工具

arm-linux-gnueabihf-gcc:C 编译器
arm-linux-gnueabihf-ld:链接器
arm-linux-gnueabihf-objdump:目标文件反汇编工具
arm-linux-gnueabihf-objcopy:目标文件操作工具

        那么此命令gnueabihf的含义表示,使用GNU工具链,eabi采取ARM的嵌入式应用二进制结构(Embedded Application Binary Interface),hf支持硬件浮点运算(Hard Float)
        后面的distclean,是make的一个目标,规则写在Makefile中的,表示清理所有由之前编译生成的文件,恢复到初始状态
        作用就是为u-boot编译环境做清理和准备,指定体系架构以及指定工具链前缀,清除之前的编译结果(虽然之前并没有编译,但是还是要养成这个习惯)

        下一条命令表示,前面的命令都是一样的,完成基本的make规则的基本选择,配置文件定义了U-Boot的启动硬件、外设和功能,如CPU型号(i.MX6ULL,14x14封装,512MB DDR, eMMC存储),启动模式,以及外设的设置
        这里则是说明,mx6ull表示U-Boot是为NXP的i.MX6ULL系列处理器准备的,14x14表示芯片封装规格为14mmx14mm,ddr512表示开发板上搭载512MB的DDR内存,以及emmc表示支持eMMC存储,deconfig就是目标文件,是正点原子针对I.MX6U-ALPHA的EMMC核心板编写的配置文件,这个配置文件的源码放在uboot的config目录中

 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defconfig

        这个命令的主要目的是生成U-Boot的配置文件,快速为我们的开发板加载默认配置 

        第三条命令如下,-j12的含义是指定编译时使用的线程数为12,-j选项就是用于控制并行编译 线程的数量,V=1用于设置编译过程中的信息输出级别;-j一般设置为虚拟机所设置的核心数,假如是14个,那也可以设置为14个

make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

        执行结果如上图 

2.1 编译结果报错

        HOSTCC scripts/basic/fixdep /bin/sh: 1: cc: not found make[3]: *** [scripts/Makefile.host:94: scripts/basic/fixdep] 错误 127 make[2]: *** [Makefile:397: scripts_basic] 错误 2 make[1]: *** 没有规则可以构建目标“mx6ull_14x”。停止。 make: *** [Makefile:459: __build_one_by_one] 错误 2

        博主曾经遇到这个问题,显示找不到我的GCC,原因就是Ubuntu的系统版本过高,所以只需要重新去下载一个低版本的Ubuntu系统就可以,博主使用的是ubuntu-16.04.7-desktop-amd64.iso编译就没有问题
       

2.2 原因分析        

        因为以上问题是博主遇到的,所以很深刻,这里秉承知其然要知其所以然的原则,还是严谨的去分析一下
        首先版本过高为何导致系统无法找到CC(通常就是GCC的符号连接),可能原因如下,旧版本的GCC与高版本的系统库不兼容,Ubuntu高版本系统(如20.04及以上)中许多关键的C库(如glibc、libc)和其他工具链组件可能已经升级到不兼容的版本。旧版GCC编译器可能依赖更老版本的系统库,而这些库高版本ubuntu中可能已经被移除或更新
        也可能符号链接或工具链路径问题,即使安装了旧版本额GCC,但高版系统中的默认工具链(如/usr/bin/ld、make等)可能与旧GCC不完全兼容,也比如旧GCC的链接器找不到合适的库或者路径。老版本的uboot源码可能依赖特定版本的工具链,无法兼容新系统中的默认开发环境,也不排除其他构建工具可能触发错误的问题


        以下问题博主没有遇见,但是收集了一下相关的失败案例
        有第三条命令编译失败的,解决方式有,首先查找自己GCC版本号,使用命令

arm-linux-gnueabihf-gcc -v

        如果这里查找出来的不是博主的这个版本,那就说明GCC的版本安全错误,导致与make规则中所需要的GCC不兼容。查找出来的结果还有可能是,有些系统自带GCC,就会存在两个GCC的情况,而Ubuntu系统一般会倾向于使用系统自带的GCC编译器,所以也有可能导致与编译的GCC版本不兼容。如果只有一个GCC但版本号不对应那就重装GCC;如果有两个GCC那就将系统自带的GCC删除掉

        然后也有第二条编译命令失败了,那么就需要检查一下自己的相关库有没有安装,使用如下命令安装

sudo apt-get install bison ​​​​​​​flex

        但是博主参考了各种问题大部分都可以归咎于,要么就是GCC版本在作祟,或者系统版本作祟,一般就主要排除这两个问题就可以解决绝大多数问题,如果还有其他怪异报错实在解决不了,可以去找一下正点原子的技术客服

2.3 Uboot编译解析

        在成功编译后我们可以看到多出来了一些文件。例如u-boot.bin就是编译出来的uboot的二进制文件

        前面提到过uboot是一个逻辑程序,因此需要在文件前面加上头部(IVT、DCD等数据)才能在I.MX6U上执行,uboot.imx文件就是添加头部以后的u-boot.bin,u-boot.imx,这些文件就是我们最终要烧写到开发板中的uboot镜像,这是正点原子的原话,那么我们如何去理解
        
        也就是为什么需要头部数据(如IVT、DCD等),首先需要知道i.mx6ul的启动流程,它是作为NXP的ARM Cortex-A系统处理器,一般会有一个固定的启动机制
        首先是ROM引导代码(BootROM),i.MX6U的片上ROM会在上电后首先运行,它是固化在硬件里的引导程序,负责加载用户程序(如U-Boot),BootROM通过硬件设置(如BOOT_CFG引脚等)决定从哪里加载用户程序,对应到我们的开发板自然就是通过拨码,之后选择SD卡烧写我们的程序
        BootROM需要知道用户程序(如U-Boot)的加载地址、入口点以及如何初始化硬件,因此,用户程序就必须符合BootROM的格式规范,才能被正确加载和运行

        那就衍生到为什么要加头部数据,原因是为了让BootROM正确识别和加载U-Boot,主要有
        IVT(Image Vector Table),包含映像(image)的描述信息,如入口地址、校验和等,告诉BootROM程序的入口点和其他关键信息,没有IVT,BootROM就不知道从哪里开始执行。
        IVT中包含如下关键信息,Entry Point,程序入口地址(U-Boot的起始执行地址);
DCD Pointer:指向DCD数据的地址 Boot Data:描述加载的内存位置、大小等

        DCD(Device Configuration),包含硬件初始化数据,用于在程序加载前初始化硬件,否则U-Boot无法正常运行
        以及还有其他的头部信息,校验和、启动模式标志,用于确保程序的完整性和适配特定的启动方式
        DCD包含如下关键信息,初始化硬件寄存器的命令序列,如配置DDR的时序参数,设置时钟频率和PLL,初始化IOMUX等;DCD是一个二进制数据块,通常由脚本生成
        

        所以为什么需要编译这些U-Boot代码,加入头部信息,i.MX6U的BootROM固化了一个严格的启动机制,要求用户程序必须满足以下条件
        包含指定格式的头部数据(IVT+DCD),程序加载地址必须与头部中二点信息匹配,DCD负责硬件初始化,比如DDR初始化数据必须提前写入,否则程序加载后,无法正常运行
        如果没有U-Boot没有这些头部数据,那就是BootRom无法识别U-Boot,程序不会被加载。即使被加载了,DDR等外设未被初始化,程序运行也会出错

        编译后的u-boot.bin文件按就是逻辑程序本体,没有头部,加上头部后就生成了uboot.imx,最终的uboot。imx文件可以被i.MX6U的BootROM加载并运行

2.4 U-Boot脚本编写

        上面我们编译的时候都是一条命令一条命令去执行,显得有些繁琐如果之后需要删除重新编译,又要将上面三条冗长的命令重新输入一遍,所以这里就编写一个shell脚本软件
        将这些命令写道shell脚本文件中,然后每次只需要执行shell脚本就可以完成所以的编译工作,学习过一段时间Linux的同学,应该都知道shell脚本,这里就不过多赘述了

        下面我们就在编译uboot的当前目录下创建一个文件,名字叫做mx6ull_alientek_emmc.sh,

touch mx6ull_alientek_emmc.sh

        文件创建成功后,使用编辑软件打开,博主使用nano

sudo nano mx6ull_alientek_emmc.sh 

        然后输入如下代码,第一行是shell脚本的规定语法,也就是必须这么写,或者也可以写成
#!/bin/sh,然后后面就是按顺序执行,我们要执行的命令,这几行命令在上面也解释过了,也不过多赘述

 #!/bin/bash
 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_ddr512_emmc_defc$
 make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

        编写完后保存,然后进行最后一步操作,因为创建最后shell需要是一个可执行文件才可以执行,所以要给这个文件夹可执行权限,输入如下命令,输入密码后,使用ls,就发现这个文件的颜色变了,说明就设置成功了

sudo chmod 777 mx6ull_alientek_emmc.sh

        最后我们也可以自己执行一下这个文件看一下效果

        上面的图片就是在执行的过程当中,

2.5 U-Boot烧写和启动

        在前面U-Boot编译好过后就可以烧写到板子上使用了,和前面的裸机程序一样,将uboot烧写到SD卡,然后通过SD卡来启动来运行uboot。使用imxdownload软件烧写,还是以前的老一套,还是要先给权限,然后将u-boot.bin文件烧写到开发板上,这个步骤不赘述,这个操作进行过很多次了
        打开MobaXterm软件,我们使用此软件,这个软件是一个强大的终端工具,主要用于远程连接和管理Linux系统或嵌入式设备。
        所以下一步我们就是使用这个软件去远程连接我们的开发板,然后将开发板的串口接在电脑上,u-boot调试输出和交互主要通过串口完成,MobaXterm就作为我们主机与开发板之间的串口通信工具
        前面提到U-Boot不仅仅是"引导"操作系统,自身也是一个裸机程序,当我们将U-Boot代码烧写到开发板后,开发板的CPU就自行加载并运行了U-Boot,我们这个U-Boot自行将串口初始化,并通过串口输出调试信息和接受用户命令
        MobaXterm这里的作用就是通过远程操作U-Boot的命令行环境,输入的命令实际上是直接发送给U-Boot程序,它自身会解析这些命令

        那么就开始使用这个软件,是全英文,但是问题不大,只需要记住我们需要常做的操作就行

        右键左边的蓝色文件夹,User sessions, 点击New session

        点击下面的Serial

        选择对应的串口以及波特率,波特率不对会乱码,博主的波特率是115200,如果乱码就自行切换

        然后界面如下,什么信息也没有

        然后按下开发板的复位键,就会有如下数据

        在上图出现Hit any key to stop autoboot倒计时的时候敲下键盘上回车键,默认是三秒倒计时,所以速度要快,3秒倒计时结束如果没有按下回车键的话uboot就会使用默认的参数启动Linux内核,如果3秒按下了回车键,就会进入命令模式
        如果看到这里,觉得看博客错过了倒计时,那就直接重新建立连接,也就是将前面我们创建的链接删掉,然后再新建一次

        ​​​​​​​如下图,就成功的进入了Uboot命令模式,一定要是这样

       那么我们现在可以来分析一下,上面U-Boot输出了哪些信息给我们

        第一行 表示采取的uboot的版本号和编译时间,也就是当前的uboot版本号是2016.03,后面是编译的时间,是2024年十二月十三日,早上5点52分06分
        第三行和第四行 表示CPU的信息,上面显示的是飞思卡尔的I.MX6ULL,因为飞思卡尔被NXP收购了,所以现在说的是NXP的I.MX6ULL,最大主屏支持792MHz,但是当前工作频率只有396MHz,表示芯片是工业级别的,能够运行在40°和105°的温度范围中,后面的38°表示CPU正在运行的温度
        第五行 是复位的原因,当前复位原因是POR。I.MX6ULL芯片上有个POR_B引脚,将这个引脚大力即可复位I.MX6ULL
        第六行 是开发板名字,当前板子名字为 I.MX6U ALPHA|MINI
        第七行 表示I2C已经就位
        第八行 提示当前开发板的DRAM(内存)为512MB
        第九行 提示当前有两个MMC\SD卡控制器;FSL_SDHC(0)和FSL_SDHC(1)。开发板支持两个MMC\SD,正点原子的I.MX6ULL,EMMC核心板上FSL_SDHC(0)接的SD(TF)卡,FSL_SDHC(1)接的EMMC
        后面的IN,Out,Err是标准输入、标准输出和标准错误所有使用的终端,这里都使用串口作为终端
        swtich和下面一行,行是切换到 emmc 的第 0 个分区上,因为当前的 uboot 是 emmc 版本的,也就 是从 emmc 启动的。我们只是为了方便将其烧写到了 SD 卡上,但是它的“内心”还是 EMMC 的。所以 uboot 启动以后会将 emmc 作为默认存储器,当然了,你也可以将 SD 卡作为 uboot 的 存储器,这个我们后面会讲解怎么做
        Net行,是网口信息,提示我们当前使用的 FEC1 这个网口,I.MX6ULL 支持两个网口。
        下面的是Error,提示 FEC1 网卡地址没有设置,后面我们会讲解如何在 uboot 里面设置网卡地址
        而Normal Boot,提示正常启动,也就是说 uboot 要从 emmc 里面读取环境变量和参数信息启动 Linux 内核了
        而Hit这个倒计时,默认倒计时 3 秒,倒计时结束之前按下回车键就会进入 Linux 命令
行模式。如果在倒计时结束以后没有按下回车键,那么 Linux 内核就会启动,Linux 内核一旦启
动,uboot 就会寿终正寝
        有点长,但是基本的信息以及解释完了,所以现在就开始讲解,Uboot命令的使用   

2.6  U-Boot命令使用

        在命令中输入help或者?,就可以差查看在uboot所支持的命令,这里截图只截了一部分,其中命令并不是Uboot支持的所有命令,之间提到过Uboot是可以配置的,需要什么命令就使能什么命令,这里图中的命令是正点原子提供的uboot中的使能命令

        uboot支持的命令有很多,这些命令后面都跟有命令说明,用于描述此命令的用处,如果想知道一个命令具体怎么使用,那就可以输入help 命令名就可以查找命令的详细用法,以bootz这个命令为例,按照上面公式输入如下内容
        可以看到这个命令的信息通过我们输入的命令反馈给我们了
        

2.6.1 信息查询命令

        常用的和信息查询的相关命令有三个,bdinfo、printenv和version。先来看一下bdinfo命令,此命令用于查看开发板的信息直接输入bdinfo即可,简单讲解一下这些信息
        第一行 这个字段通常表示设备架构的类型或特定的系统信息。在这里,它的值为 0x00000000,说明该字段未设置或没有特定架构编号。通常在某些特定的硬件架构上
        第二行 指向存储启动参数的内存地址。0x80000100 表示启动参数的存放地址。这是 U-Boot 在引导操作系统时需要的一些参数,通常包括内核加载的地址、设备树地址等信息。
        第三行 这表示设备的 DRAM 内存 的基地址。0x00000000 表示这个地址的起始位置,可能表明当前系统只配置了一个内存银行。
        第四行 表示系统 DRAM 的起始地址为 0x80000000。这是内存映射的起始位置。
        第五行 表示 DRAM 的大小为 0x20000000(即 512 MB,因为 0x20000000 对应于 2 的 29 次方字节) 
        第六行 :表示设备的以太网 MAC 地址。此处显示为 (not set),意味着当前没有配置以太网接口的 MAC 地址。如果需要使用以太网功能进行网络引导或通信,需要在 U-Boot 中设置此 MAC 地址
        第七行 这是设备的 IP 地址。在此处,它显示为 <NULL>,表示没有分配 IP 地址。通常如果设备通过 DHCP 或静态配置获得 IP 地址,它会显示在此字段中。由于显示为 NULL,表明当前尚未配置 IP 地址。
        第八行 表示串口通信的波特率,这里是 115200 bps,通常用于串口调试和输出 U-Boot 相关信息。该波特率非常常见,用于设备的初始调试和信息输出。
        第九行 表示 Translation Lookaside Buffer(TLB,地址转换快表)的基地址。TLB 用于加速虚拟地址到物理地址的转换。0x9FFF0000 表示该缓冲区的基地址。
        第十行和第十一行 relocaddr表示 U-Boot 被加载到内存中的位置。0x9FF55000 是 U-Boot 被加载到内存的起始地址。reloc off表示内存的偏移量。0x18755000 表示 U-Boot 代码相对于其加载地址的偏移量
        第十十二行 表示 中断服务例程的栈指针(stack pointer)。0x9EF52EA0 是 IRQ(中断)栈的基地址,用于处理系统中断时保存寄存器等信息。
​​​​​​​        第十三行 表示初始栈指针(stack pointer)的地址。0x9EF52E90 是系统启动时栈的起始位置。栈是用于保存函数调用信息、局部变量和中断上下文的内存区域

        然后命令printenv用于输出环境变量信息,uboot也支持TAB键自动补全功能,输入print然后按下TAB键就会自动补全命令,直接输入print也可以。输入print,然后按下回车键,环境变量如下图

        在图 30.4.1.2 中有很多的环境变量,比如 baudrate、board_name、board_rec、boot_fdt、bootcmd 等等。uboot 中的环境变量都是字符串,既然叫做环境变量,那么它的作用就和“变量”一样。 比如 bootdelay 这个环境变量就表示 uboot 启动延时时间,默认 bootdelay=3,也就默认延时 3 秒。前面说的 3 秒倒计时就是由 bootdelay 定义的,如果将 bootdelay 改为 5 的话就会倒计时 5s 了。uboot 中的环境变量是可以修改的,有专门的命令来修改环境变量的值,稍后我们会讲解

        命令version,用于查看uboot的版本号,输入version,uboot版本号如下图所示        

        当前 uboot 版本号为 2016.03,2024 年 12 月 13 日编译的,编译器为 arm-linux-gnueabihf-gcc,这是 NXP 官方提供的编译器,正点原子出厂系统用的此编译器编 译的,但是本教程我们统一使用 arm-linux-gnueabihf-gcc

2.6.2 环境变量操作命令

        1. 修改环境变量

        环境变量的操作涉及到两个命令,setenv和saveen,命令setenv用于设置或者修改环境变量的值。命令saveen用于保存修改后的环境变量,一般环境变量是存放在外部的flash中的,uboot启动的时候会将环境变量从flash读取到DRAM中。所以使用命令setenv修改的是DRAM中的环境变量值,修改以后要使用,saveenv将修改后的环境变量保存到flash中,否则的话uboot下一次重启会继续使用以前的环境变量
        环境变量通常保存在设备的Flash存储中,这样在系统重启后,环境变量依然能够被保持。Flash存储是非易失性的,意味着数据不会因断电或者重启丢失
        DRAM存储,在uboot启动时,环境变量从Flash被读取到DRAM中,供U-Boot使用,由于DRAM是易失性的,它只在U-Boot运行时有效,一旦电源关闭或系统重启,DRAM中的数据就会丢失
        针对环境变量的存储,Flash以及DRAM存储这里简单提一下
        那么这里我们现在就将延时时间设置到五秒

        然后再通过命令保存

        我们在使用命令的时候saveenv保存修改后的环境变量的话会有保存的过程提示,如上图,根据提示信息看到环境变量保存到了MMC(0),也就是SD卡中,因为现在将uboot烧写到了SD卡中,所以会保存到MMC(0)。如果烧写到EMMC里面就会提示保存到EMMC(1)中,也就是EMMC设备

        现在验证一下是否修改成功,重启一下开发板。博主手速不够快,因为它是倒数的,因为要截图,所以每次都要等一秒,所以截图到的就是四秒,但是事实证明确实不是之前的三秒了,那就说明设置是成功的

        有时候修改的环境变量值可能会有空格,比如bootcmd、bootargs,这个时候时候环境变量值就得用单引号空气来,比如下面修改环境变量bootargs的值
        

        上面命令设置 bootargs 的值为“console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw”,其中“console=ttymxc0,115200”、“root=/dev/mmcblk1p2”、“rootwait”和“rw”相当于四组“值”,
这四组“值”之间用空格隔开,所以需要使用单引号‘’将其括起来,表示这四组“值”都属于环境变量 bootargs。

2. 新建环境变量

        命令 setenv 也可以用于新建命令,用法和修改环境变量一样,比如我们新建一个环境变量
author,author 的值就用博主的名称,命令如下
        

        新建命令author以后,重启uboot,然后使用命令printenv查看当前环境变量

        如上图,新建的环境变量authoe,值为Hetertopia,创建成功

3. 删除环境变量

        既然可以新建环境变量,肯定也可以删除环境变量,删除环境变量也是使用命令 setenv,
要删除一个环境变量只要给这个环境变量赋空值即可,比如我们删除掉上面新建的 author 这个
环境变量,命令如下

        上面命令中通过 setenv 给 author 赋空值,也就是什么都不写来删除环境变量 author。重启
uboot 就会发现环境变量 author 没有了

2.6.3 内存操作命令

        内存操作命令就是用于直接对 DRAM 进行读写操作的,常用的内存操作命令有 md、nm、
mm、mw、cp 和 cmp

        1. md命令

        md 命令用于显示内存值,命令如下
    

        命令中的[.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命令中的数字都是十六进制的,不是十进制

        那么这里如果想查看0x80000000开始的20个字节的内存值,显示格式为.b的话,显示命令如下

        而不是

        因为,上面提到了uboot 命令里面的数字都是十六进制的,所以可以不用写“0x”前缀,十进制
的 20 其十六进制为 0x14
        所以命令 md 后面的个数应该是 14,如果写成 20 的话就表示查看32(十六进制为 0x20)个字节的数据。

        上图这三个命令都是查看以 0X80000000 为起始地址的内存数据,第一个命令以.b 格式显
示,长度为 0x10,也就是 16 个字节;第二个命令以.w 格式显示,长度为 0x10,也就是 16*2=32
个字节;最后一个命令以.l 格式显示,长度也是 0x10,也就是 16*4=64 个字节
        

        2. nm命令

        nm 命令用于修改指定地址的内存值,命令格式如下

        nm 命令同样可以以.b、.w 和.l 来指定操作格式,比如现在以.l 格式修改 0x80000000 地址
的数据为 0x12345678,实操如下
        

        问好后面就是要改变你的值,然后按下回车,再输入q就可以退出

        然后查询一下是否修改成功

        如上图,修改结果是成功的

          ​​​​​​​3. mm命令

        mm 命令也是修改指定地址内存值的,使用 mm 修改内存值的时候地址会自增,而使用命
令 nm 的话地址不会自增。比如以.l 格式修改从地址 0x80000000 开始的连续 3 个内存块(3*4=12
个字节)的数据为 0X05050505
        描述起来可能会有点不太好理解,那我们通过实际操作

        如下图在我们实际操作的时候,修改完四个字节的内存的时候,就会继续让你设置后面四字节的内存,依此类推,我们这里修改12个字节的内存数据
        
        然后q退出,之后检查一下我们的设置结果,与上面是一样的,这个就是自增的含义

        4. mw命令

        命令 mw 用于使用一个指定的数据填充一段内存,书写格式如下

        mw 命令同样可以以.b、.w 和.l 来指定操作格式,address 表示要填充的内存起始地址,value为要填充的数据,count 是填充的长度。
        比如使用.l 格式将以 0X80000000 为起始地址的 0x10(转换为10进制就是20) 个内存块(0x10 * 4=64 字节)填充为 0X0A0A0A0A
        修改结果和显示结果如下,也成功的修改以0X80000000起始的,0x10个内存块

        5. cp命令

        cp 是数据拷贝命令,用于将 DRAM 中的数据从一段内存拷贝到另一段内存中,或者把 Nor 
Flash 中的数据拷贝到 DRAM 中。

        cp 命令同样可以以.b、.w 和.l 来指定操作格式,source 为源地址,target 为目的地址,count
为拷贝的长度。我们使用.l 格式将 0x80000000 处的地址拷贝到 0X80000100 处,长度为 0x10 个
内存块(0x10 * 4=64 个字节)
        先使用 md.l 命令打印出地址 0x80000000 和 0x80000100 处的数据,然后
使用命令cp.l将0x80000100处的数据拷贝到0x80000100处。最后使用命令md.l查看0x80000100
处的数据有没有变化,检查拷贝是否成功
        

        6 cmp命令

        cmp 是比较命令,用于比较两段内存的数据是否相等

        cmp 命令同样可以以.b、.w 和.l 来指定操作格式,addr1 为第一段内存首地址,addr2 为第
二段内存首地址,count 为要比较的长度。我们使用.l 格式来比较 0x80000000 和 0X80000100 这
两个地址数据是否相等,比较长度为 0x10 个内存块(16 * 4=64 个字节)
        那我们先比较一段一样的,就是上面0x80000000和0x80000100,如下图翻译结果就是它说这两段完全一样,same就是一样的意思

        而下面这段,就是说没有一个字节是一样的

2.6.4 网络操作命令

        uboot 是支持网络的,我们在移植 uboot 的时候一般都要调通网络功能,因为在移植 linux
kernel 的时候需要使用到 uboot 的网络功能做调试。uboot 支持大量的网络相关命令,比如 dhcp、ping、nfs 和 tftpboot,我们接下来依次学习一下这几个和网络有关的命令

        博主开发板的网线和电脑的网口连接在一起,相当于使用电脑给路由器供网,在使用 uboot 的网络功能之前先用网线将开发板的 ENET2 接口和电脑或者路由器连接起来,I.MX6U-ALPHA 开发板有两个网口:ENET1 和 ENET2,一定要连接 ENET2如下图,不能连接错了,因为ENTT2没有配置前是wan口       

2.6.5 EMMC和SD卡操作命令

        在 U-Boot 启动加载器中,MMC(MultiMediaCard)设备既可以指 eMMC(嵌入式多媒体卡)也可以指 SD 卡(Secure Digital 卡),因为这两种存储介质的操作在 U-Boot 中是类似的,且都通过 MMC 命令集 进行管理和操作。因此,在这篇教程中,为了简化表述,统一将这两种存储卡统称为 MMC
       
 mmc 是一系列的命令,其后可以跟不同的参数,输入“?mmc”即可查看 mmc 有关的命
令,最后我们发现MMC的命令还是挺多的,我们这里挑选几个例子讲解

1. mmc info命令      

  mmc info 命令用于输出当前选中的 mmc info 设备的信息,输入命令“mmc info”即可

        简单描述一下,Name显示了MMC存储卡的名称。在这个例子中,卡的名称为8GTF4,通常是厂商用来表示其具体型号的代码。根据这个名称,推测这卡的容量为8GB
        下一行,Tran Speed(传输速度)表示卡的最大传输速率。在这里,52000000 表示卡的最大传输速率为 52 MHz,即每秒最多可以进行 52000000 次数据传输。对于现代的 SD 卡,传输速度通常以 MHz(兆赫)为单位,表示数据传输时钟频率
        Rd Block Len(读取块长度)表示存储卡的最小读写单元的字节大小。在此,512 表示每个读取的块大小是 512 字节,这通常是存储设备的基本块大小
        信息太多就不赘述了,剩余感兴趣的读者可以自行去搜索一下,总而言之这个命令就是为了输出当前MMC设备的关键信息

         2. mmc rescan命令

        mmc rescan 命令用于扫描当前开发板上所有的 MMC 设备,包括 EMMC 和 SD 卡,没有结果显示,是因为没有新的设备需要扫描,因为设备已经处于活动状态

        3. mmc list命令

        mmc list 命令用于来查看当前开发板一共有几个 MMC 设备,输入“mmc list”

        FSL_SDHC:0这是一个SD卡设备,使用的控制器是FSL_SDHC,设备编号是0。FSL_SDHC(eMMC)这是一个eMMC存储设别,同样使用的是FSL_SDHC控制器,设备编号是1,并且括号中也说明了它是eMMC设备
        这表明开发板上有两个存储设备,分别是 SD 卡eMMC 卡

        默认会将 EMMC 设置为当前 MMC 设备,这就是为什么 输入“mmc info”查询到的是 EMMC 设备信息,而不是 SD 卡。要想查看 SD 卡信息,就要使 用命令“mmc dev”来将 SD 卡设置为当前的 MMC 设备

        4. mmc dev命令

        mmc dev 命令用于切换当前 MMC 设备,命令格式如下
        
        [dev]用来设置要切换的 MMC 设备号,[part]是分区号。如果不写分区号的话默认为分区 0。
使用如下命令切换到 SD 卡

        

        切换到 SD 卡成功,mmc0 为当前的 MMC 设备,输入命令“mmc info”即可查看 SD 卡的信息,结果如图 30.4.5.5 所示,下面就是博主SD卡的基本信息
        

        5. mmc part命令

        有时候 SD 卡或者 EMMC 会有多个分区,可以使用命令“mmc part”来查看其分区,比如
查看 EMMC 的分区情况,我们重新切换到EMMC

        第一个分区起始扇区为20480,长度为262144个扇区;第二个分区起始扇区为282624,长度为14594048个扇区。
        如果EMMC里面烧写了Linux系统的话,EMMC会有三个分区的,第0个分区存放uboot,第一个分区存放Linux镜像文件和设备树,第二个分区存放根文件系统。但是在图中只有两个分区,是因为第0个分区没有格式化,识别不出来,实际第0个分区是存在的

        一个新的SD卡默认只有一个分区,如下图,那就是分区0,所以前面讲解的uboot烧写到SD卡,起始就是将u-boot.bin烧写到了SD卡的分区0中。后面学习Linux内核移植的时候再讲解怎么在SD卡中并创建并格式化第二个分区,并将Linux镜像文件和设备树文件存放到第二个分区中
        

        这里可能有点不太好理解操作的究竟是什么,mmc dev命令用于切换当前操作的MMC设备,当我们输出mmc dev0,mmc dev1,实际上是在切换当前的MMC设备,使得 U-Boot 接下来的 MMC 相关操作(如读取、写入、查看信息等)会应用到你指定的设备上。

      6. 存储器补充说明  

    mmc dev不仅仅是用来选择你要操作的存储设备,它实际上也 决定了哪个存储设备作为当前系统的工作存储器。具体来说,mmc dev 切换的目标不仅是指定哪个设备进行数据操作,还是在某些情况下,决定哪个设备承担实际存储的角色
        当你切换到某个设备(例如 mmc dev 0),U-Boot 会将当前的操作设备上下文设置为 SD 卡。之后,所有的操作(如读取文件系统、加载启动镜像、写入数据等)都会针对 SD 卡进行
        当你使用 mmc dev 0mmc dev 1 切换设备时,U-Boot 会 重新配置控制器,使得当前操作的设备成为主设备。切换到某个设备后,另一个设备实际上 处于非活动状态,即它不与处理器进行数据通信。此时,只有当前选择的设备(SD 卡或 eMMC)在工作,另一个设备会被 "断开"。

        其次就是,SD 卡eMMC 存储设备通常都有 自带的分区表,这些分区表和扇区结构是在制造设备时预设的,尤其是 eMMC 存储设备,它在出厂时通常会设置好分区表,但是同样也可以通过系统或者u-boot来修改或者重新分区
        eMMC 存储设备通常比 SD 卡更大,性能更好,适合用作主存储设备;eMMC 通常有多个分区,而 SD 卡一般只有一个分区。SD 卡通常用于消费级设备(如相机、手机),而 eMMC 常见于嵌入式系统和一些消费类电子设备中,作为主要存储介质。

        7. 补充说明分区与扇区

​​​​​​​        扇区(Sector)是磁盘或存储设备中的最小存储单元,每个扇区通常为512字节或4096个字节。例如Start Sector,表示分区在存储设备上的起始位置,以扇区为单位
        分区(Partition)是存储设备的逻辑划分,用于分隔不同类型的数据。每个分区有一个起始扇区和一个扇区数,所以一般分区分为起始
        起始扇区 (Start Sector) 表示该分区在设备上开始的位置,扇区数 (Num Sectors) 表示该分区占用了多少扇区,从而决定了该分区的大小,分区的大小等于 Start Sector + Num Sectors,乘以每个扇区的字节数
        用下图举例,emmc分区1,起始扇区+Num扇区=282624,每个扇区如果是512个字节,512*282624=145418752≈138.9MB,第二个计算出来为7.3GB

        8. mmc read

        mmc read 命令用于读取 mmc 设备的数据

      这个命令用于从 MMC 存储设备(如 SD 卡或 eMMC)读取数据到 DRAM,addr是数据读取到DRAM中的目标地址,即将数据存储到DRAM中的起始地址。blk是要读取的扇区的起始地址,cnt就是要读取的扇区数量
        addr就相当于<DRAM 地址><起始地址><读取块数>
        下面的步骤就是,切换到 eMMC 设备的 第一个分区(分区 0)。然后:从 eMMC 分区 0 中的第 1536 个块(地址 0x600)开始,读取 16 个块(8192 字节) 的数据,并将其存放到 DRAM 地址 0x80800000 中
        

        ​​​​​​​

        现在我们来验证读取的数据是否正确,从下图可以看到,    baudrate=115200.board_name=EVK.board_rev=14X14.”等字样, 这个就是 uboot 中的环境变量。EMMC 核心板 uboot 环境变量的存储起始地址就是 1536*512=786432

7. mmc write命令

        要将数据写到 MMC 设备里面,可以使用命令mmc write

        addr 是要写入 MMC 中的数据在 DRAM 中的起始地址,blk 是要写入 MMC 的块起始地址 (十六进制),cnt 是要写入的块大小,一个块为 512 字节。我们可以使用命令“mmc write”来升级uboot,也就是在 uboot 中更新 uboot。这里要用到 nfs 或者 tftp命令,通过 nfs 或者 tftp命令将新的 u-boot.bin 下载到开发板的 DRAM 中,然后再使用命令“mmc write”将其写入到 MMC 设备中。我们就来更新一下 SD 中的 uboot,先查看一下 SD 卡中的 uboot 版本号,注意编译时间

        

        可以看出当前 SD 卡中的 uboot 是 2024 年 12 月 13 日 5:42:06 编译的。我们现在重新编译 一下 uboot,然后将编译出来的 u-boot.imx(u-boot.bin 前面加了一些头文件)拷贝到 Ubuntu 中的 tftpboot 目录下。最后使用 tftp 命令将其下载到 0x80800000 地址处

8. mmc erase命令


        如果要擦除MMC设备的指定块就是命令mmc erase,命令格式如下

blk是要擦除的起始块,cnt是要擦除的数量。按照官方的提示最好不要用mmc erase

2.6.6 FAT文件系统格式操作命令

1. fatinfo命令

        fatinfo命令用于查询指定MMC设备分区的文件系统信息

​​​​​​​

        interface表示接口,比如mmc,dev是查询的设备号,part是要查询的分区。比如要查询EMMC分区1的文件系统信息

        从上图可以看出,EMMC分区1的文件系统为FAT32格式的

2. fatls命令

        fatls命令用于查询FAT格式设备的目录和文件信息,命令格式如下

        interface是要查询的接口,比如mmc,dev是要查询的设备号,part是要查询的分区,directory是要查询的目录。比如查询EMMC分区1中的所有的目录和文件

        

        从上图可以看出,emmc的分区1中存放着8个文件

3. fstype命令

        fstype用于查看MMC设备某个分区的文件系统格式,命令格式如下

        正点原子的EMMC核心板上的EMMC默认有三个分区,我们可以查看一下这三个分区的文件系统格式,我们分别输入


        从以上信息可以看到,分区0格式未知,因为分区0存放的是uboot,并且分区0没有格式化,所以文件系统格式未知。分区1的格式为fat,分区1用于存放linux镜像和设备树。分区2的格式为ext4,用于存放Linux的根文件系统(rootfs)

4. fatload命令

        fatload命令用于将指定的文件读取到DRAM中,命令格式如下,

        interface为接口,比如mmc,dev是设备号,part是分区,addr是保存在DRAM中的起始地址,filename是要读取的文件名字。bytes表示读取多少字节的数据,如果bytes为0或者省略的话就表示读取整个文件。pos是要读的文件相对于文件首地址的偏移,如果为0或者省略的话表示从文件首地址开始读取。我们将EMMC分区1中的zImage文件读取到DRAM中的0X80800000地址初

        从上图中可以看出在221ms内读取了6785480个字节的数据,速度为28.8MiB/s,速度是非常快的,因为这是从EMMC里面读取的,而EMMC是八位的,速度肯定会很快的

        因为篇幅实在过于太长,所以后面的我们会继续编写,这一讲跳过了网络的配置,下一期我会专门给大家带来开发板网络层面的配置,如果有讲解的不正确或者不清晰的地方欢迎大家和博主讨论

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值