小白自制Linux开发板 二. u-boot移植
上一篇:小白自制Linux开发板 一. 瞎抄原理图与乱画PCB 中我们做了一个小型而没用的开发板,用的是Licheepi Nano的镜像,那从本篇开始我们开始自己构建它的灵魂吧。
我们都知道,PC在启动的时候,首先是进入BIOS,再根据BIOS中配置信息引导后续的启动操作系统,比如配置Windows启动。
而对于嵌入式linux中,并没有BIOS,这时候就需要一种类似引导程序来处理。于是就有了BootLoader。
BootLoader是一段小程序,可以把它想象成PC机linux上的GRUB/LILO引导程序,可以直接从flash或TF卡中运行,来装载内核。它可以初始化硬件设备,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统做好准备。
1. 嵌入式开发板的启动过程
一个嵌入式系统从软件角度来看分为三个层次:
- 引导加载程序
包括固化在芯片中的boot程序(可选)和BootLoader两大部分,对于固化的boot程序。主要是芯片通过外围电路连接的实际情况选择读入程序的位置,比如:通过TF卡或是SPI以及其他方式启动,至于优先顺序这就要具体看芯片的数据手册,个人没做过具体测试。
- linux内核
特定于嵌入式平台的定制内核
- 文件系统
包括了系统命令和应用程序
BootLoader | Boot Parameters | Kernel | Root Filesystem |
BootLoader启动过程可分为单阶段和多阶段(stage1、stage2),其中stage1完成初始化硬件,如CPU寄存器、内存控制器,为stage2准备内存空间。一般stage1是可以直接在nor flash中运行的,并将stage2复制到内存RAM中,设置堆栈,然后跳转到stage2(从这也可以看出stage2是在RAM中运行的,与stage1不同)
Boot Parameters 顾名思义,就是配置了要启动内核的参数,包含要加载系统内核相关文件的位置,要加载到内存中的位置,定位到文件系统的位置,相关输入输出的呈现等一系列参数。
kernel 在存放在bootloader之后,对于SoC来说,代码都需要在RAM中运行,这里与MCU不一样的地方就是引入了MMU(内存管理单元)。对于MCU而言,由于其执行速度低,因此运行代码都在ROM中直接运行,而对于Flash而言,其读取速度远不及RAM的速度,因此对于运行速度非常快的SoC而言,所有的代码都需要在RAM中运行。但是这里有一个问题,RAM掉电数据将会丢失,故代码保存不可能放在RAM中,当前所有的嵌入式设备而言,代码保存都是放在ROM中,因此在SoC中运行代码需要将代码搬运到RAM中然后再执行。
Root Filesystem 由于其执行过程需要对ROM进行读写操作,因此可以不用搬运到RAM中,但是实际过程中内核启动后会产生一个虚拟的文件系统,该文件系统是挂在根文件系统的关键所在,这里不详细讲解。整体来说,大致的过程为,嵌入式设备上电后将执行bootloader,对硬件进行硬件和堆栈初始化,然后搬运内核到RAM中并启动内核,紧接着挂载根文件系统。
2. 环境配置与参考项目
系统:Ubuntu 16
编辑器:VSCode
参考项目:Lichee-Pi Nano
地址:https://wiki.sipeed.com/soft/Lichee/zh/Nano-Doc-Backup/index.html
Lichee-Pi Nano
需要注意的是一定要选择Nano版本,因为我们开发板使用的主控芯片和Nano的主控是一致的,所以后续我们要编译U-boot,内核都可以参考(bai piao)这里面的配置。
主控芯片:F1c100s/F1c200s,100s内置32MB DDR1内存,200s内置64MB DDR1内存,200s贵一点,他们都是QFN88封装。
ARM926ejs内核,主频默认408MHz,据了解做产品出货的一般在600M左右。
带有100M的SPI接口2个,SDIO接口1个,USB OTG接口,还有CSI摄像头接口,LCD RGB显示屏接口,音频接口,I2C I2S UART PWM等等。
还有就是他们不支持硬件浮点,所以浮点运算使用软浮点方式。
F1c100s/F1c200s芯片功能
3.交叉编译器
我们通过PC版的Linux自带的gcc编译的程序只能在当前系统架构下的cpu架构(x86)下运行,如果我们想要编写的程序在嵌入式Linux下运行,那么就需要用到对应的编译器。
我们做的开发板主控芯片F1C200S,内核为ARM9,其架构使用的是ARMv5架构,所以我们也要选用对应的编译器,同样,这样的编译器很多,这里我们使用最常用的arm-linux-gnueabi- ,因为交叉编译器F1C200S必须高于6.0版本,这里我们使用7.2版本
下载较慢时使用下载工具
下载完成后解压文件:
tar -vxjf gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabi.tar.xz
然后在/usr/local目录下新建arm-linux-gcc目录
sudo mkdir /usr/local/arm-linux-gcc
进入解压目录下:
cd gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabi/
将该目录下的所有文件复制到新建的目录下
sudo cp -rd * /usr/local/arm-linux-gcc/
最后需要添加该工具链的环境变量使其可以在任何目录下执行,打开/etc/profile文件
sudo nano /etc/profile
在文件末尾添加以下内容
PATH=$PATH:/usr/local/arm-linux-gcc/bin
添加完毕,使路径生效
source /etc/profile
接下来在终端输入:
arm-linux-
然后连按两次Tab键,如图在表示成功:
如果没有出现,则进行下面操作,安装必要的动态链接库
sudo apt-get install lib32ncurses5 lib32z1
至此,我们完成了编译工具的配置。
4. 编译U-boot
当Arm开发板上电以后第一个要加载到内存并运行的程序就是BootLoader,BootLoader的同类型程序很多,如U-boot、X-boot、Rt-Thread,这里我们依然选中最常用的U-boot作为目标(因为其他的我也不会呀),
最新版本的uboot几乎包含当前主流的SoC芯片,前面提到本开发板使用的芯片和licheePI nano相同,大部分硬件也是兼容的,为了快速移植该部分,这里采用licheePI nano的u-boot来进行移植。在终端输入如下命令克隆u-boot:
git clone https://github.com/Lichee-Pi/u-boot.git -b nano-v2018.01
克隆完毕文件会保存在当前目录下,进入该目录,
cd u-boot
在该文件夹下有很多分支,我们可以查看所有分支,使用如下命令:
git branch -a
现在我们使用的是nano开发板,所以将当前分支切换到nano分支,命令如下:
git checkout nano-v2018.01u-boot
切换到Nano分支
默认的没有指定交叉工具链和架构,因此在编译之前需要指定交叉工具链和芯片架构,u-boot的交叉编译器在u-boot 的根目录下中的Makefile文件中定义了。打开文件找到CROSS_COMPILE变量,修改为如下:
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabi-
配制交叉编译环境
这样我们就能使用我们指定的编译器来编译u-boot了。
在u-boot项目的config目录下存在对多种板子的配置描述文件,由于每个板子的外设不同,因此编译之前必须要对u-boot进行配置。然而配置是一件比较繁琐的事情,特别是像u-boot这种比较复杂的项目而言,初学者几乎无法完成。幸运的是对于大部分开发板而言,config目录下有其配置好的默认配置文件。进入config目录中,然后执行ls查看当前所有的配置文件
cd config
ls
查看配制文件
找到licheepi_nano_defconfig 和 licheepi_nano_spiflash_defconfig,前者表示为TF卡启动,后者表示从SPI 设备启动,因为我们做的小板只有从TF卡启动,所以我们需要使用 licheepi_nano_defconfig 。
现在回到上级目录,然后执行
make licheepi_nano_defconfig
cd ..
make make licheepi_nano_defconfig
这样我们把licheepi_nano_defconfig 作为默认配置项。
接下来我们就可以用图形界面进行配置了,执行
make menuconfig
此时出现图形配置选项,如下图所示
u-boot Menuconfig配制,注意红框中的配置,我们后续要用到。
至此我们的u-boot环境配置就完成了,但是我们还有个问题要解决:如何让u-boot引导系统
我们在PC端安装Windows系统的时候往往需要选择启动顺序,比如需要优先通过光驱或u盘启动等。
同样在u-boot中也需要这样的配置,当然u-boot比PC配置稍微复杂一丢丢。我们前面提到Linux嵌入式系统结构分布中有个Boot Parameters 部分,这部分就是做引导配置的,那怎么配置呢,总体来说可以分为两部分:
- bootcmd,主要用于描述控制Linux内核文件以及其他描述文件加载到内存中位置以及启动Linux内核系统等
- bootargs,用于配制文件系统、串口信息等。
bootcmd
在最开始提到过,内核一般不在flash中运行,这样就需要将内核搬运到内存中,这个过程需要u-boot来完成。对于mmc (TF卡)而言,在u-boot有专门的命令load mmc,该命令可以将mmc中的代码从flash搬运到指定的地址处。
当u-boot中环境变量bootdelay计数到0时,此时uboot就会开始执行bootcmd中的命令。
bootdelay这个环境变量是一个计数器,当u-boot主体运行完毕后,此时bootdelay该变量的值将会开始递减,递减时间为1s,当递减到0时,此时u-boot将会跳转到bootcmd处开始执行bootcmd命令,(你可以简单理解为PC启动后有一两秒时间等待,你可以可以通过F8或Enter键打断进入Bios设置的过程,这个等待时间就由u-boot中的bootdelay来控制)。
下面我们需要记住这句指令,这就是我们当前制作的开发板需要用到的bootcmd全部内容
load mmc 0:1 0x80008000 zImage;load mmc 0:1 0x80c08000 suniv-f1c100s-licheepi-nano.dtb;bootz 0x80008000 - 0x80c08000;
如果你需要详细了解这句话那就接着往下看,如果不需要则可以跳到 下面的u-boot参数配置环节
对于上面命令,我们根据分号拆分为3部分:
1> load mmc 0:1 0x80008000 zImage;
2> load mmc 0:1 0x80c08000 suniv-f1c100s-licheepi-nano.dtb;
3> bootz 0x80008000 - 0x80c08000;
其中两个 load mmc 命令、一个bootz 命令。
先看第一条:
load mmc 0:1 0x80008000 zImage
load mmc有三个参数:第一个参数是mmc(TF卡)分区,第二个参数是内存中目标地址,第三个参数是源文件。
即上面的命令意思是将mmc的0:1 分区中的zImage复制到内存中的0x80008000地址处。这里的zimage就是Linux内核,后续会提到该文件编译,0:1这个可以这样理解0表示TF卡(TF卡属于mmc存储器的一种),1这表示TF卡的第一个分区(boot分区)后面会提到。
而对于内存位置 0x80008000 地址位置,将其理解为默认值就行了。这样完成了zImage的加载。
下面分析第二条命令:
load mmc 0:1 0x80c08000 suniv-f1c100s-licheepi-nano.dtb
有了上面的加载zImage的说明,可以很轻松的理解上面的命令意思是将mmc的0:1分区中的suniv-f1c100s-licheepi-nano.dtb文件加载到内存中的0x80c08000地址处。对于suniv-f1c100s-licheepi-nano.dtb 这个文件,叫做设备树文件,简单来说就是当前开发板上面所有外设备描述文件,这部分将会在后续内核编译部分进行详细说明。
对于第三条命令:
bootz 0x80008000 - 0x80c08000
的意思是告诉内核镜像的起始地址为0x80008000,加载的设备树地址为0x80c08000。这里是告诉cpu从这里开始启动Linux, bootz命令的格式是:bootz空格0x80008000空格-空格0x80c08000,注意-左右有空格。
除了bootz 命令外,有些系统里面还可能存在一个叫做bootm命令,这是是对没有使用设备树内核的镜像启动命令,早期版本的内核没有引入设备树,因此对于早期的内核一般使用的是bootm,其命令格式为bootm内核地址,比如bootm x0x30008000,意思是从0x30008000开始启动内核,启动内核的过程其实是将pc指针指向该地址,这样处理器就会从该地址处运行代码。
这里我们就完成了bootcmd的说明,接下来我们看另外一个参数。
bootargs
bootargs也是u-boot环境变量中一个非常重要的变量,上面已经讲解了内核的启动可以通过bootcmd来完成,那接下来内核启动完毕后必须挂在根文件系统(rootfs)。但是内核并不知道根文件系统的具体位置,我们必须要告诉根文件的位置后内核才能将其挂载,这时就需要有bootargs变量。该变量的作用是告诉内核根文件系统的位置和属性以及必要的配置,
console=ttyS0,115200 panic=5 rootwait root=/dev/mmcblk0p2 earlyprintk rw
同上面分析的方法一样,我们依然将这部分命令拆成几部分来说明。这里需要说明的是,这部分配置信息是由u-boot 直接按照参数字符串方式提供给Linux内核,然后由Linux内核进行执行的,这也说明里为什么格式与bootcmd配置方式不一致。
console=ttyS0,115200 表示终端为ttyS0即串口0,波特率为115200;
panic=5 字面意思是恐慌,即linux内核恐慌,其实就是linux不知道怎么执行了,此时内核就需要做一些相关的处理,这里的5表示超时时间,当Linux卡住5秒后仍未成功就会执行Linux恐慌异常的一些操作。
rootwait 该参数是告诉内核挂在文件系统之前需要先加载相关驱动,这样做的目的是防止因mmc驱动还未加载就开始挂载驱动而导致文件系统挂载失败,所以一般bootargs中都要加上这个参数。
root=/dev/mmcblk0p2 表示根文件系统的位置在mmc的0:2分区处,/dev是设备文件夹,内核在加载mmc中的时候就会在根文件系统中生成mmcblk0p2设备文件,这个设备文件其实就是mmc的0:2分区(这里对应TF卡的第二个分区:rootfs),这样内核对文件系统的读写操作方式本质上就是读写/dev/mmcblk0p2该设备文件。
earlyprintk 参数是指在内核加载的过程中打印输出信息,这样内核在加载的时候终端就会输出相应的启动信息。rw表示文件系统的操作属性,此处rw表示可读可写。
5.u-boot参数配置
make menuconfig
选中 Enable boot arguments 按空格选中,下面会显示:() Boot arguments
然后选中Boot arguments ,按回车,进入配置窗口,接下来上面解释过的bootargs 参数信息:
console=ttyS0,115200 panic=5 rootwait root=/dev/mmcblk0p2 earlyprintk rw
配置bootargs信息
然后按Tab键选中<OK>,保存并进入主菜单。
同理配置:Enable a default value for bootcmd 按空格选中,下面会显示:() bootcmd value 配置项,
选中bootcmd value 进入配置界面,输入bootcmd命令:
load mmc 0:1 0x80008000 zImage;load mmc 0:1 0x80c08000 suniv-f1c100s-licheepi-nano.dtb;bootz 0x80008000 - 0x80c08000;
配置bootcmd参数
按Tab键选中<OK>,保存并进入主菜单。
6.u-boot编译与烧录
先保存图形配置界面后推出界面,在终端执行make -j4即可对整个u-boot进行编译。
make -j4
编译u-boot
make -j4后面的-j4表示4个核心进行编译,若电脑的处理器是2核心,请使用make -j2进行编译。
编译完成后会在当前目录生成u-boot-sunxi-with-spl.bin烧录文件。
根目录下找到 u-boot-sunxi-with-spl.bin 文件
该文件就是我们最终要烧录的二进制文件。
在当前目录下会有一个隐藏的文件.config,该文件是u-boot编译后根据各个选项产生的配置文件,这个配置文件记录了所有配置选项的宏开关,编译的时候是根据最终的.config文件来进行编译的,当然编译前是需要有脚本解析.config文件然后进行相应的编译。
烧录到TF卡
只要将u-boot-sunxi-with-spl.bin烧录到tf卡的8k偏移处地址就可以了,烧录步骤如下:使用dd命令进行块搬移:
sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/sdb bs=1024 seek=8
该命令中:
if 文件名:输入文件名,缺省为标准输入。即指定源文件。< if=input file >
of 文件名:输出文件名,缺省为标准输出。即指定目的文件。< of=output file >
bs bytes:同时设置读入/输出的块大小为bytes个字节。
seek blocks:从输出文件开头跳过blocks个块后再开始复制。
这里的输出文件(of)为主机电脑的/dev/sdb文件,也就是TF卡,这里也体现了Linux一切皆文件的思想。
/dev/sdb 这个可以用gparted 软件查看,该软件可以直接用命令安装即可:
sudo apt-get install gparted
此时在Ubuntu下面可以看到如下软件:
安装好GParted软件
打开软件
GParted
在右上角可以看到两个硬盘,/dev/sda 为本地硬盘,/dev/sdb 是我们将要写数据的TF(当然这只是墨云自己的配置使然,具体情况请根据实际情况而定),因此这里的of=/dev/sdb 烧录到8k偏移地址处是指绝对地址,这个绝对地址指的是TF卡的物理地址。这8K的值是由F1C200S 中固化的启动代码决定的,所以照抄即可。
烧写u-boot
然后我们正常退出TF卡,然后插入我们自制的开发板,通过USB线连接电脑,
连接开发板
打开电脑中的命令行工具,我这里使用Xshell,
打开Xshell,新建连接:
配置名称 ,协议选择Serial,
配置串口
通过下拉选中com端口,波特率为115200,其他默认即可,点击确定,然后双击主界面左侧会话管理中的刚建立的会话,此时进入连接状态。
因为在你插入USB通电的时候开发板就已经启动了,所以当你打开串口连接的时候可能未必会看到信息,所以按一下重启键,就可以看到如下的输出信息了,这就是我们的u-boot,执行到u-bbot计数完成后会产生错误,那是因为我们还没有进行系统内核的移植,所以默认就会进入u-boot命令模式。
启动信息
输入pri命令打印环境变量的所有值,可以找到已经配置的bootcmd 和bootargs
pri命令结果
至此完成了u-boot移植的全部内容,对于u-boot的移植方法,在后续移植Linux内核和文件系统时都会用到,都是大同小异的,所以有了本篇的说明,之后操作将会非常简单。
而关于u-boot的内容事实上非常的复杂繁琐,有兴趣的可以自行去了解到,毕竟作为一个小白的我初衷只是先让小板先跑起来。
参考资料
Lite200 (lishanwen) -- https://lishanwen.cn/index.php/2021/07/03/lite200/
全志F1C200S F1C100S 介绍 ( 迪卡魏曼依奇君 ) https://blog.csdn.net/tunqimai9331/article/details/95938903
荔枝派Nano 全流程指南 (矽速科技) https://wiki.sipeed.com/soft/Lichee/zh/Nano-Doc-Backup/index.html
小白自制Linux开发板 二. u-boot移植的更多相关文章
- 小白自制Linux开发板 三. Linux内核与文件系统移植
上一篇完成了uboot的移植,但是想要愉快的在开发板上玩耍还需要移植Linux内核和文件系统. 1.Linux内核 事实上对于F1C100S/F1C200S,Linux官方源码已经对licheepi ...
- 小白自制Linux开发板 四. 通过SPI使用ESP8266做无线网卡
本文章基于 WhyCan Forum(哇酷开发者社区) https://whycan.com/t_4149.htmlhttps://whycan.com/t_5870.html整理而成. 为了尊重原作 ...
- 小白自制Linux开发板 八. Linux音频驱动配置
不知不觉小白自制开发板系列已经到第八篇了,本篇要配置的是音频驱动,也算是硬件部分的最后一片了,积攒的文章也差不多全放完了,后续更新可能会放缓,还请见谅. 对于F1C200s是自带了多媒体处理功能的,所 ...
- 小白自制Linux开发板 九. 修改开机Logo
许久不见啊,今天我们继续来修改我们的系统. 通过前面的几篇文章我们已经能轻松驾驭我们的开发板了,但是现在都是追求个性化的时代,我们在开发板上打上了自己的Logo,那我们是否可以改变开机启动的Logo呢 ...
- 小白自制Linux开发板 一. 瞎抄原理图与乱画PCB
因为墨云是基于高中物理水平的电路知识来学习.而且此前也就玩过树莓派.Esp8266之类的开发板,水平基础趋近于零,所以在写这个系列的时候抱着记录的心态.还望不足之处还望大佬们指正. <论语> ...
- 小白自制Linux开发板 六. SPI TFT屏幕修改与移植
本文章参考:https://www.bilibili.com/read/cv9947785?spm_id_from=333.999.0.0 本篇通过SPI接口,使用ST7789V TFT焊接屏(13p ...
- 小白自制Linux开发板 七. USB驱动配置
本文章基于https://whycan.com/t_3087.htmlhttps://whycan.com/t_6021.html整理 F1c100s芯片支持USB的OTG模式,也就是可以通过更改Us ...
- 小白自制Linux开发板(第二季 V3s篇) 一. 换个核心再来一次
1.前言 大家心心念念(个人认为)的小白自制开发板全新系列正式来了,之前我们使用全志的F1C200s芯片制作了一个小电脑,众所周知,调试很艰难,坑也很多,以至于墨云到现在还是没找到对应的补救方案,为了 ...
- 小白自制Linux开发板 番外篇 一 modprobe加载驱动问题(转载整理)
使用modprobe加载驱动 转载地址:https://blog.csdn.net/qq_39101111/article/details/78773362 前面我们提到,modprobe并不需要指定 ...
随机推荐
- 单例对象 (Singleton)设计模式
单例的目的是为了保证运行时Singleton类只有唯一的一个实例,用于一些较大开销的操作. 饿汉式(没有线程安全问题): ' 由于使用static关键字进行了修饰,只能获取到一个对象,从而达到了单例, ...
- ajax传字符串时出现乱码问题的解决
字符乱码的解决: 第一种在@RequestMapping中添加 @RequestMapping(value="queryAllToTree",method=RequestMetho ...
- springmvc学习日志三
一.文件的上传 1.首先在lib中添加相应的jar包 2.建立jsp页面,表单必须是post提交,编码必须是multipart/form-data,文件上传文本框必须起名 <body> & ...
- uwp 语音指令
Xml code -------------------------------- <Page x:Class="MyApp.MainPage" xmlns="ht ...
- 华为oj-判断输入的字符串是不是一个有效的IP地址
题目标题: 判断输入的字符串是不是一个有效的IP地址 详细描述: 请实现如下接口 boolisIPAddressValid(constchar* pszIPAddr) 输入:pszIPAddr 字符串 ...
- io中的特殊流Properties
对于去年学习IO的时候一些代码贴上来: 初识properties,因为继承自hashtable,其中可以使用put操作: package special; import java.util.Prope ...
- PyTorch安装及试用 基于Anaconda3
设置Torch国内镜像 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/ ...
- Java中Scanner用法总结
最近在做OJ类问题的时候,经常由于Scanner的使用造成一些细节问题导致程序不通过(最惨的就是网易笔试,由于sc死循环了也没发现,导致AC代码也不能通过...),因此对Scanner进行了一些总结整 ...
- Python中的列表、元组、字典、字符串及切片操作
我们引入一个新的概念:数据结构 数据结构是通过某种方式组织在一起的数据元素的集合,这些数据元素可以是数字或字符,甚至可以是其他数据结构,在python中,最基本的数据结构是序列,序列中的每个元素匾被分 ...
- 前后端数据交互(四)——fetch 请求详解
fetch 是 XMLHttpRequest 的升级版,使用js脚本发出网络请求,但是与 XMLHttpRequest 不同的是,fetch 方式使用 Promise,相比 XMLHttpReques ...