1 参考网址
小白自制Linux开发板
一. 瞎抄原理图与乱画PCB
二. u-boot移植
三. Linux内核与文件系统移植
四. 通过SPI使用ESP8266做无线网卡
五. Debian文件系统制作,以及WIFI配置、交换分区配置
六. SPI TFT屏幕修改与移植
七. USB驱动配置
八. Linux音频驱动配置
九. 修改开机Logo
十. NES游戏玩起来
SIPEDD WIKI: https://en.wiki.sipeed.com/soft/Lichee/zh/Nano-Doc-Backup/get_started/first_eat.html
外国大神自制名片: https://www.thirtythreeforty.net/posts/2020/01/mastering-embedded-linux-part-3-buildroot/
https://www.cnblogs.com/yanye0xcc/p/16341719.html
https://linux-sunxi.org/Bootable_SD_card
https://linux-sunxi.org/Main_Page
https://linux-sunxi.org/F1C100s
2 概念
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中并启动内核,紧接着挂载根文件系统。
bootcmd主要用于描述控制Linux内核文件以及其他描述文件加载到内存中位置以及启动Linux内核系统等
bootargs用于配制文件系统、串口信息等。
3 f1c100s 特性
- 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等等。
- 还有就是他们不支持硬件浮点,所以浮点运算使用软浮点方式。
4 问题
linux卡片机:u-boot编译烧录。https://zhuanlan.zhihu.com/p/652556669
4.1 启动流程?
4.2 uboot地址
这里的启动流程图是全志V3S
的,F1C200S/F1C100S
用户手册中并没有相关说明,启动顺利都是一样的
\1. 上电后, f1c100s
内部BROM
(芯片内置,无法擦除)启动
\2. 首先检查 SD0
有没有插卡, 如果有插卡就读卡8k
偏移数据,是否是合法的启动数据, 如果是BROM
引导结束, 否则进入下一步
\3. 检测SPI0 NOR FLASH(W25QXXX, MX25LXXX)
是否存在, 是否有合法的启动数据, 如果是BROM
引导结束, 否则进入下一步
\4. 检测SPI0 NAND FLASH
是否存在, 是否有合法的启动数据, 如果是BROM
引导结束, 否则进入下一步
因为找不到任何可以引导的介质,系统进入usb fel
模式, 可以用USB
烧录了。
根据上面这些内容,我们知道F1C200s
启动的时候会优先去SD0
的8k
偏移处读取数据,所以需要我们把uboot
写入到sd
卡8k
偏移处即可被正常读取。
序号 | 启动介质 | 注意事项 |
---|---|---|
1st | MicroSD Card/eMMC | 只支持PF0 ~ PF5这六个脚复用为SDC0启动, 控制器支持到SD2.0和eMMC4.41,设备端可以使用主流的eMMC5.1(eMMC存储器可以向前兼容) |
2nd | SPI Nor/Nand Flash | 只支持PC0 ~ PC3这四个脚复用为SPI0启动, 支持标准SPI和DOUT模式, BROM启动时SPI Nand固定为1024字节/页,SPI Nor无特殊限制 |
3rd | BROM FEL Mode | 以上介质都无启动代码时,自动运行芯片内部USB程序(FEL Mode)(USB Full-Speed@12Mbps),USB脚是固定的无其它复用功能 |
SDC0启动
硬件连线使用以下引脚,BootROM读取eMMC或SDCard中的SPL程序时,只使用了1bit-sdio,且最大速度限制在25MHz,因此正常启动最少需要接CMD、CLK、D0这三根线;
但eMMC或SDCard在启动完成后还可作为应用程序的存储器,为了提升传输速度建议CMD、CLK D0~D3
这6根线都接上,F1C100S的SDIO控制器最大支持SDR-4bit@50MHz。
通过逻辑分析仪抓取BootROM读取eMMC中的SPL程序的过程,可以看到只使用了1bit模式。
SPL程序需要从eMMC UDA物理分区的第16个扇区(512字节/扇区)开始烧录,下图是eMMC UDA物理分区内的布局图:
前8KB(扇区0 ~ 扇区15)
存放MBR分区表或者非标准的GPT分区表,扇区16
开始存放SPL程序,BROM将从这里读取程序到SRAM并执行,扇区80
开始可以存放用户的程序,如果是启动linux,这里可以存放uboot,如果是嵌入式系统或者裸机,这里直接存放应用程序。
SPI0启动
标准的4线SPI接口,最大速度是AHB总线时钟的2分频,AHB通常设置为200MHz,因此SPI的时钟最大为100MHz。
BootROM加载SPL程序时默认使用标准SPI,具体时钟速度忘记了,大概10MHz以内,这段启动时间是无法优化的。
SPL启动后,加载用户程序时,推荐使用DOUT模式,用带宽换时钟,降低SPI布线要求,100MHz的SPI读写Flash实测不是很稳定。
nor-flash和nand-flash需要从物理0地址(0号扇区0地址)
开始烧录SPL程序。
spi nor-flash直接使用sunxi-fel工具烧录
spi nand-flash有一个改版的sunxi-fel工具可用,目前大部分的spi nand-flash都是4K/扇区的,烧录的时候每扇区只使用前1K,剩下的空着(spi nand启动还未测试过)。
USB启动
当SDC0,SPI0都没有读取到SPL程序时,自动运行芯片固化的USB程序(FEL),此时可以通过该程序提供的API,通过USB对芯片下载程序或执行ARM汇编代码。
在usb-fel模式下,usb接口默认是全速的(12Mbps),F1C100S的usb也支持高速模式(480Mbps),因此在USB端口的ESD二极管等效电容尽可能小,推荐使用5pF左右的。
windows下需要使用zadig
安装winusb(libusb)驱动,然后才能使用sunxi-fel
工具进行nor-flash烧录,发送程序到DRAM执行等。
4.3 Linux地址
boot分区
4.4 dts
boot分区
5 交叉编译器工具
交叉编译器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-gnueabi-gcc -v
#安装动态链接库
sudo apt-get install lib32ncurses5 lib32z1
6 u-boot
最新版本的uboot几乎包含当前主流的SoC芯片,前面提到本开发板使用的芯片和licheePI nano相同,大部分硬件也是兼容的,为了快速移植该部分,这里采用licheePI nano的u-boot来进行移植。
#在终端输入如下命令克隆u-boot:
git clone https://gitee.com/LicheePiNano/u-boot.git
#克隆完毕文件会保存在当前目录下,进入该目录,
cd u-boot
#在该文件夹下有很多分支,我们可以查看所有分支,使用如下命令:
git branch -a
#现在我们使用的是nano开发板,所以将当前分支切换到nano分支,命令如下:
git checkout nano-v2018.01
默认的没有指定交叉工具链和架构,因此在编译之前需要指定交叉工具链和芯片架构,u-boot的交叉编译器在u-boot 的根目录下中的Makefile文件中定义了。打开文件找到CROSS_COMPILE变量,修改为如下:
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabi-
在u-boot项目的config目录下存在对多种板子的配置描述文件,由于每个板子的外设不同,因此编译之前必须要对u-boot进行配置。然而配置是一件比较繁琐的事情,特别是像u-boot这种比较复杂的项目而言,初学者几乎无法完成。幸运的是对于大部分开发板而言,config目录下有其配置好的默认配置文件。进入config目录中,然后执行ls查看当前所有的配置文件
cd configs
ls
找到licheepi_nano_defconfig 和 licheepi_nano_spiflash_defconfig,前者表示为TF卡启动,后者表示从SPI 设备启动,因为我们做的小板只有从TF卡启动,所以我们需要使用 licheepi_nano_defconfig。
现在回到上级目录,把licheepi_nano_defconfig 作为默认配置项。
make licheepi_nano_defconfig
sudo apt-get install build-essential
sudo apt-get install make
sudo apt-get install libncurses5-dev
图形界面进行配置
make menuconfig
6.1 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;
对于上面命令,我们根据分号拆分为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的说明,接下来我们看另外一个参数。
6.2 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表示可读可写。
6.3 u-boot参数配置
load mmc 0:1 0x80008000 zImage;load mmc 0:1 0x80c08000 suniv-f1c100s-licheepi-nano.dtb;bootz 0x80008000 - 0x80c08000;
console=ttyS0,115200 panic=5 rootwait root=/dev/mmcblk0p2 earlyprintk rw
6.4 u-boot编译
make -j8
libfdt_env错误,编译的时候需要卸载
sudo apt-get remove libfdt-dev
sudo apt-get install libfdt-dev
sudo ln -sf /usr/bin/python3 /usr/bin/python
sudo apt-get install python3-distutils
sudo apt install python-dev
sudo apt install swig
编译完成后会在当前目录生成u-boot-sunxi-with-spl.bin烧录文件。该文件就是我们最终要烧录的二进制文件。
在当前目录下会有一个隐藏的文件.config,该文件是u-boot编译后根据各个选项产生的配置文件,这个配置文件记录了所有配置选项的宏开关,编译的时候是根据最终的.config文件来进行编译的,当然编译前是需要有脚本解析.config文件然后进行相应的编译。
6.5 u-boot烧录
只要将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
打开软件
在右上角可以看到两个硬盘,/dev/sda 为本地硬盘,/dev/sdb 是我们将要写数据的TF(当然这是自己的配置使然,具体情况请根据实际情况而定),因此这里的of=/dev/sdb 烧录到8k偏移地址处是指绝对地址,这个绝对地址指的是TF卡的物理地址。这8K的值是由F1C200S 中固化的启动代码决定的,所以照抄即可。
sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/sdb bs=1024 seek=8
命令执行完后注意这里是烧写速度,如果是几百MB/s,大概率你烧写出问题了。可能是选择TF卡
6.6 u-boot验证
6.6.1 需要的硬件:
- 正常退出TF卡
- Lichee_nano开发板
- USB线
- TTL-USB转换模块
6.6.2 调试端口:
使用开发板的串口UART0
6.6.3 连接:
Lichee_nano开发板通过TTL转换模块连接到电脑。
打开MobaXterm,新建串口连接:115200 8 N 1
6.6.3.1 无卡插入,板载flash
打开串口连接的时候可能未必会看到信息,重启,就可以看到如下的输出信息了,这是板载flash内的系统。
这就是u-boot,执行到u-bbot计数完成后会产生错误,那是因为还没有进行系统内核的移植,所以默认就会进入u-boot命令模式。
6.6.3.2 插入TF卡
6.6.3.2.1 程序启动流程
6.6.3.2.2 插入TF卡
插入上一步做好的TF卡,打开串口连接的时候可能未必会看到信息,重启,就可以看到如下的输出信息了,这就是u-boot,执行到u-boot计数完成后会产生错误,那是因为还没有进行系统内核的移植,所以默认就会进入u-boot命令模式。
输入pri命令打印环境变量的所有值,可以找到已经配置的bootcmd 和bootargs
至此完成了u-boot移植的全部内容,对于u-boot的移植方法,在后续移植Linux内核和文件系统时都会用到,都是大同小异的,所以有了本篇的说明,之后操作将会非常简单。