上一篇:小白自制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 部分,這部分就是做引導配置的,那怎麼配置呢,總體來說可以分為兩部分:

  1. bootcmd,主要用於描述控制Linux核心檔案以及其他描述檔案載入到記憶體中位置以及啟動Linux核心系統等
  2. 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