OS開發愛好者福利:樹莓派上編譯C語言,順便掌握一波硬件知識
樹莓派雖小,小到僅有信用卡大小,但功能卻和普通電腦無異,可以將其連接電視、顯示器、鍵盤鼠標等設備使用。也可以處理文字、電子表格、媒體甚至是游戲。那么這個神奇的小電腦,怎樣用它來進行編程呢?下面介紹一篇利用樹莓派進行裸機編程的教程,順便學習一下接口、硬件等知識。
近日,有人在 GitHub 上開源了一個關于樹莓派的教程。不同于以往的樹莓派開發,這篇教程的核心內容是討論如何在樹莓派上進行裸機編程。

教程地址:https://github.com/bztsrc/raspi3-tutorial
在樹莓派 3 上進行裸機編程
該系列教程面向那些想要編譯自己的樹莓派裸機應用程序的人,具體目標受眾是那些對樹莓派硬件不熟悉,但在業余時間又愛好 OS 的開發人員。在這篇教程里,作者給出了一些示例來完成基本的操作,比如:將代碼寫入串行控制臺、從串行控制臺中讀取按鍵、設置屏幕分辨率并繪制到線性幀緩沖區。此外,作者還展示了如何獲取硬件的序列號、硬件支持的隨機數,以及如何從啟動分區讀取文件。
需要注意的是:這篇教程沒有涉及編寫 OS。諸如內存管理、虛擬文件系統、實現多任務處理之類的主題也不會介紹。該教程將重點介紹與硬件的接口,而不是關于 OS 的理論。此外,該教程假設你具有一定的 GNU/Linux 知識,對編譯程序、創建磁盤和文件系統鏡像有一定的了解。
對于為何選擇樹莓派 3,作者給出了解釋:首先,它既便宜又容易買到。第二,它是 64 位的,擁有非常大的地址空間。第三,它只使用 MMIO,這使得編程更容易。
該教程使用 C 語言進行開發,因為 C 語言能夠直接對硬件進行開發。
預備知識
在開始前,你將需要在 FAT 文件系統上使用交叉編譯器(有關詳細信息,請參見 00_crosscompiler 目錄)和帶有固件文件的 Micro SD 卡。
每個目錄都有一個 Makefile.gcc 以及 Makefile.clang。確保 Makefile 符號鏈接根據你自己選擇的交叉編譯器指向版本。
作者給出的建議是買一個 Micro SD 卡 USB 適配器(許多制造商都會提供這種適配器的 SD 卡),這樣就可以像 USB 一樣將該卡連接到任何臺式計算機上,而不需要特殊的讀卡器接口(盡管現在很多筆記本電腦都有這種接口)。如果你不喜歡 dd 命令,你也可以選擇 USBImager,這是一個簡單的 GUI 應用程序,具有可移植的可執行文件,可用于 Windows、MacOSX 和 Linux 操作系統。
Micro-SD 卡 USB 適配器。
在帶有 LBA FAT32(類型 0x0C)分區的 SD 卡上創建 MBR 分區方案,并對其格式化,然后將 bootcode.bin、start.elf 以及 fixup.dat 復制到其中。或者,你可以下載一個 raspbian 鏡像,dd 命令燒寫到 SD 卡,mount 掛載并刪除不必要的. img 文件。不管你喜歡哪種方法,重點是你將使用這些教程創建 kernel8.img,而且必須復制到 SD 卡的根目錄中,后者不應該存在其他. img 文件。
建議使用 USB 串行調試電纜。把它連接到 GPIO 引腳 14/15,然后在電腦上按如下方式運行 minicom:
USB 串行調試電纜
仿真
不幸的是,官方的 qemu 二進制文件還不支持樹莓派 3。但作者已經實現了,并將很快發布(更新:在 qemu2.12 中提供)(https://wiki.qemu.org/ChangeLog/2.12#ARM)。在此之前,你必須從最新的源代碼編譯 qemu。編譯后,可進行如下操作:
或者:
-M raspi3:讓 qemu 仿真樹莓派 3 硬件。
-kernel kernel8.img:告知要使用的內核文件名。
-drive file=$(yourimagefile),if=sd,format=raw:在第二種情況下,該參數為 SD 卡鏡像,它也可以是標準的 rasbian 鏡像。
-serial stdio
-serial null -serial stdio:將模擬的 UART0 重定向到運行 qemu 的終端的標準輸入 / 輸出,以便顯示發送到串行線路的所有內容,并且 vm 會接收終端中鍵入的每個鍵。該操作僅適用于教程 05 及更高版本,因為默認情況下不會重定向 UART1。為此,必須添加一些類似于 - chardev socket,host=localhost,port=1111,id=aux -serial chardev:aux 的參數,或者簡單地使用兩個 -serial 參數。
硬件資源
下面簡單介紹一下所需硬件資源,BCM2837 SoC 芯片。包括:
VideoCore GPU;
ARM-Cortex-A53 CPU (ARMv8);
MMIO 映射外部設備。
有趣的是,CPU 不是主板上的主處理器。當它通電后,第一個 GPU 運行。當初始化完成時,通過執行 bootcode.bin,它將加載并執行 start.elf。這不是一個 ARM 可執行文件,而是專門為 GPU 編譯的。比較有意思的是,start.elf 尋找不同的 ARM 可執行文件,都以 kernel 開頭,以. img 結尾。由于要在 AArch64 模式下對 CPU 進行編程,因此只需要 kernel8.img,這也是最后一個要查找的。加載后,GPU 觸發 ARM 處理器上的復位線,開始在地址 0x80000(或更準確地說是 0)處執行代碼。
RAM(1G Raspberry Pi3)在 CPU 和 GPU 之間共享,這意味著一個可以讀取另一個寫入內存的內容。為了避免混淆,需要定義好 mailbox 接口。CPU 將消息寫入 mailbox,并通知 GPU 讀取它。GPU(知道消息完全在內存中)解釋它,并將響應消息放在同一個地址。CPU 必須循環訪問內存以知道 GPU 何時完成,然后它才能讀取響應。
相似的,所有外部設備都在內存中與 CPU 通信。每個設備都有從 0x3F000000 開始的專用內存地址,但是它不在真實的 RAM 中(稱為內存映射 IO)。現在沒有用于外圍設備的 mailbox,而是每個設備都有其自己的協議。這些設備的共同點是:必須以 32 位為單位在 4 個字節對齊的地址(所謂的字)上讀取和寫入其內存,并且每個設備都有控制 / 狀態和數據字(data words)。不幸的是,Broadcom(SoC 芯片的制造商)在記錄產品方面很差。現在所擁有的最好的是 BCM2835 文檔,這個文檔就足夠了。
CPU 中還有一個內存管理單元,允許創建虛擬地址空間。這可以通過特定的 CPU 寄存器進行編程,并且在將這些 MMIO 地址映射到虛擬地址空間時必須小心。
一些更有趣的 MMIO 地址是:
更多信息,請參見:https://github.com/raspberrypi