上一篇:小白自制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