計算機實驗室之樹莓派:課程 1 OK01

計算機實驗室之樹莓派:課程 1 OK01

OK01 課程講解了樹莓派如何入門,以及在樹莓派上如何啟用靠近 RCA 和 USB 端口的 OK 或 ACT 的 LED 指示燈。這個指示燈最初是為了指示 OK 狀態的,但它在第二版的樹莓派上被改名為 ACT。

1、入門

我們假設你已經訪問了 下載 頁面,並且已經獲得了必需的 GNU 工具鏈。也下載了一個稱為操作系統模板的文件。請下載這個文件並在一個新目錄中解開它。

2、開始

現在,你已經展開了這個模板文件,在 source 目錄中創建一個名為 main.s 的文件。這個文件包含了這個操作系統的代碼。具體來看,這個文件夾的結構應該像下面這樣:

build/

(empty)

source/

main.s

kernel.ld

LICENSE

Makefile

用文本編輯器打開 main.s 文件,這樣我們就可以輸入彙編代碼了。樹莓派使用了稱為 ARMv6 的彙編代碼變體,這就是我們即將要寫的彙編代碼類型。

擴展名為 .s 的文件一般是彙編代碼,需要記住的是,在這裡它是 ARMv6 的彙編代碼。

首先,我們複製下面的這些命令。

.section .init

.globl _start

_start:

實際上,上面這些指令並沒有在樹莓派上做任何事情,它們是提供給彙編器的指令。彙編器是一個轉換程序,它將我們能夠理解的彙編代碼轉換成樹莓派能夠理解的機器代碼。在彙編代碼中,每個行都是一個新的命令。上面的第一行告訴彙編器 1 在哪裡放我們的代碼。我們提供的模板中將它放到一個名為 .init 的節中的原因是,它是輸出的起始點。這很重要,因為我們希望確保我們能夠控制哪個代碼首先運行。如果不這樣做,首先運行的代碼將是按字母順序排在前面的代碼!.section 命令簡單地告訴彙編器,哪個節中放置代碼,從這個點開始,直到下一個 .section 或文件結束為止。

在彙編代碼中,你可以跳行、在命令前或後放置空格去提升可讀性。

接下來兩行是停止一個警告消息,它們並不重要。

2

3、第一行代碼

現在,我們正式開始寫代碼。計算機執行彙編代碼時,是簡單地一行一行按順序執行每個指令,除非明確告訴它不這樣做。每個指令都是開始於一個新行。

複製下列指令。

ldr r0,=0x20200000

ldr reg,=val 將數字 val 加載到名為 reg 的寄存器中。

那是我們的第一個命令。它告訴處理器將數字 0x20200000 保存到寄存器 r0 中。在這裡我需要去回答兩個問題, 寄存器(register)是什麼?0x20200000 是一個什麼樣的數字?

寄存器在處理器中就是一個極小的內存塊,它是處理器保存正在處理的數字的地方。處理器中有很多寄存器,很多都有專門的用途,我們在後面會一一接觸到它們。最重要的有十三個(命名為 r0、r1、r2、…、r9、r10、r11、r12),它們被稱為通用寄存器,你可以使用它們做任何計算。由於是寫我們的第一行代碼,我們在示例中使用了 r0,當然你可以使用它們中的任何一個。只要後面始終如一就沒有問題。

樹莓派上的一個單獨的寄存器能夠保存任何介於 0 到 4,294,967,295(含)之間的任意整數,它可能看起來像一個很大的內存,實際上它僅有 32 個二進制比特。

0x20200000 確實是一個數字。只不過它是以十六進制表示的。

因此,我們所寫的第一個彙編命令是將數字 2020000016 加載到寄存器 r0 中。那個命令看起來似乎沒有什麼用,但事實並非如此。在計算機中,有大量的內存塊和設備。為了能夠訪問它們,我們給每個內存塊和設備指定了一個地址。就像郵政地址或網站地址一樣,它用於標識我們想去訪問的內存塊或設備的位置。計算機中的地址就是一串數字,因此上面的數字 2020000016 就是 GPIO 控制器的地址。這個地址是由製造商的設計所決定的,他們也可以使用其它地址(只要不與其它的衝突即可)。我之所以知道這個地址是 GPIO 控制器的地址是因為我看了它的手冊, 3 地址的使用沒有專門的規範(除了它們都是以十六進制表示的大數以外)。

4、啟用輸出


計算機實驗室之樹莓派:課程 1 OK01


A diagram showing key parts of the GPIO controller.

閱讀了手冊可以得知,我們需要給 GPIO 控制器發送兩個消息。我們必須用它的語言告訴它,如果我們這樣做了,它將非常樂意實現我們的意圖,去打開 OK 的 LED 指示燈。幸運的是,它是一個非常簡單的芯片,為了讓它能夠理解我們要做什麼,只需要給它設定幾個數字即可。

mov r1,#1

lsl r1,#18

str r1,[r0,#4]

mov reg,#val 將數字 val 放到名為 reg 的寄存器中。

lsl reg,#val 將寄存器 reg 中的二進制操作數左移 val 位。

str reg,[dest,#val] 將寄存器 reg 中的數字保存到地址 dest + val 上。

這些命令的作用是在 GPIO 的第 16 號插針上啟用輸出。首先我們在寄存器 r1 中獲取一個必需的值,接著將這個值發送到 GPIO 控制器。因此,前兩個命令是嘗試取值到寄存器 r1 中,我們可以像前面一樣使用另一個命令 ldr 來實現,但 lsl 命令對我們後面能夠設置任何給定的 GPIO 針比較有用,因此從一個公式中推導出值要比直接寫入來好一些。表示 OK 的 LED 燈是直接連線到 GPIO 的第 16 號針腳上的,因此我們需要發送一個命令去啟用第 16 號針腳。

寄存器 r1 中的值是啟用 LED 針所需要的。第一行命令將數字 110 放到 r1 中。在這個操作中 mov 命令要比 ldr 命令快很多,因為它不需要與內存交互,而 ldr 命令是將需要的值從內存中加載到寄存器中。儘管如此,mov 命令僅能用於加載某些值。 4 在 ARM 彙編代碼中,基本上每個指令都使用一個三字母代碼表示。它們被稱為助記詞,用於表示操作的用途。mov 是 “move” 的簡寫,而 ldr 是 “load register” 的簡寫。mov 是將第二個參數 #1 移動到前面的 r1 寄存器中。一般情況下,# 肯定是表示一個數字,但我們已經看到了不符合這種情況的一個反例。

第二個指令是 lsl(邏輯左移)。它的意思是將第一個參數的二進制操作數向左移第二個參數所表示的位數。在這個案例中,將 110 (即 12 )向左移 18 位(將它變成 10000000000000000002=26214410 )。

再強調一次,我們只有去閱讀手冊才能知道我們所需要的值。手冊上說,GPIO 控制器中有一個 24 字節的集合,由它來決定 GPIO 針腳的設置。第一個 4 字節與前 10 個 GPIO 針腳有關,第二個 4 字節與接下來的 10 個針腳有關,依此類推。總共有 54 個 GPIO 針腳,因此,我們需要 6 個 4 字節的一個集合,總共是 24 個字節。在每個 4 字節中,每 3 個比特與一個特定的 GPIO 針腳有關。我們想去啟用的是第 16 號 GPIO 針腳,因此我們需要去設置第二組 4 字節,因為第二組的 4 字節用於處理 GPIO 針腳的第 10-19 號,而我們需要第 6 組 3 比特,它在上面的代碼中的編號是 18(6×3)。

最後的 str(“store register”)命令去保存第一個參數中的值,將寄存器 r1 中的值保存到後面的表達式計算出來的地址上。這個表達式可以是一個寄存器,在上面的例子中是 r0,我們知道 r0 中保存了 GPIO 控制器的地址,而另一個值是加到它上面的,在這個例子中是 #4。它的意思是將 GPIO 控制器地址加上 4 得到一個新的地址,並將寄存器 r1 中的值寫到那個地址上。那個地址就是我們前面提到的第二組 4 字節的位置,因此,我們發送我們的第一個消息到 GPIO 控制器上,告訴它準備啟用 GPIO 第 16 號針腳的輸出。

5、生命的信號

現在,LED 已經做好了打開準備,我們還需要實際去打開它。意味著需要給 GPIO 控制器發送一個消息去關閉 16 號針腳。是的,你沒有看錯,就是要發送一個關閉的消息。芯片製造商認為,在 GPIO 針腳關閉時打開 LED 更有意義。 5 硬件工程師經常做這種反常理的決策,似乎是為了讓操作系統開發者保持警覺。可以認為是給自己的一個警告。

mov r1,#1

lsl r1,#16

str r1,[r0,#40]

希望你能夠認識上面全部的命令,先不要管它的值。第一個命令和前面一樣,是將值 1 推入到寄存器 r1 中。第二個命令是將二進制的 1 左移 16 位。由於我們是希望關閉 GPIO 的 16 號針腳,我們需要在下一個消息中將第 16 比特設置為 1(想設置其它針腳只需要改變相應的比特位即可)。最後,我們寫這個值到 GPIO 控制器地址加上 40

10 的地址上,這將使那個針腳關閉(加上 28 將打開針腳)。

6、永遠幸福快樂

似乎我們現在就可以結束了,但不幸的是,處理器並不知道我們做了什麼。事實上,處理器只要通電,它就永不停止地運轉。因此,我們需要給它一個任務,讓它一直運轉下去,否則,樹莓派將進入休眠(本示例中不會,LED 燈會一直亮著)。

loop$:

b loop$

name: 下一行的名字。

b label 下一行將去標籤 label 處運行。

第一行不是一個命令,而是一個標籤。它給下一行命名為 loop$,這意味著我們能夠通過名字來指向到該行。這就稱為一個標籤。當代碼被轉換成二進制後,標籤將被丟棄,但這對我們通過名字而不是數字(地址)找到行比較有用。按慣例,我們使用一個 ​$ 表示這個標籤只對這個代碼塊中的代碼起作用,讓其它人知道,它不對整個程序起作用。b(“branch”)命令將去運行指定的標籤中的命令,而不是去運行它後面的下一個命令。因此,下一行將再次去運行這個 b 命令,這將導致永遠循環下去。因此處理器將進入一個無限循環中,直到它安全關閉為止。

代碼塊結尾的一個空行是有意這樣寫的。GNU 工具鏈要求所有的彙編代碼文件都是以空行結束的,因此,這就可以你確實是要結束了,並且文件沒有被截斷。如果你不這樣處理,在彙編器運行時,你將收到煩人的警告。

7、樹莓派上場

由於我們已經寫完了代碼,現在,我們可以將它上傳到樹莓派中了。在你的計算機上打開一個終端,改變當前工作目錄為 source 文件夾的父級目錄。輸入 make 然後回車。如果報錯,請參考排錯章節。如果沒有報錯,你將生成三個文件。 kernel.img 是你的編譯後的操作系統鏡像。kernel.list 是你寫的彙編代碼的一個清單,它實際上是生成的。這在將來檢查程序是否正確時非常有用。kernel.map 文件包含所有標籤結束位置的一個映射,這對於跟蹤值非常有用。

為安裝你的操作系統,需要先有一個已經安裝了樹莓派操作系統的 SD 卡。如果你瀏覽 SD 卡中的文件,你應該能看到一個名為 kernel.img 的文件。將這個文件重命名為其它名字,比如 kernel_linux.img。然後,複製你編譯的 kernel.img 文件到 SD 卡中原來的位置,這將用你的操作系統鏡像文件替換現在的樹莓派操作系統鏡像。想切換回來時,只需要簡單地刪除你自己的 kernel.img 文件,然後將前面重命名的文件改回 kernel.img 即可。我發現,保留一個原始的樹莓派操作系統的備份是非常有用的,萬一你要用到它呢。

將這個 SD 卡插入到樹莓派,並打開它的電源。這個 OK 的 LED 燈將亮起來。如果不是這樣,請查看故障排除頁面。如果一切如願,恭喜你,你已經寫出了你的第一個操作系統。 課程 2 OK02 將指導你讓 LED 燈閃爍和關閉閃爍。


  1. 是的,我說錯了,它告訴的是鏈接器,它是另一個程序,用於將彙編器轉換過的幾個代碼文件鏈接到一起。直接說是彙編器也沒有大問題。 ↩
  2. 其實它們對你很重要。由於 GNU 工具鏈主要用於開發操作系統,它要求入口點必須是名為 _start 的地方。由於我們是開發一個操作系統,無論什麼時候,它總是從 _start 開時的,而我們可以使用 .section .init 命令去設置它。因此,如果我們沒有告訴它入口點在哪裡,就會使工具鏈困惑而產生警告消息。所以,我們先定義一個名為 _start 的符號,它是所有人可見的(全局的),緊接著在下一行生成符號 _start 的地址。我們很快就講到這個地址了。 ↩
  3. 本教程的設計減少了你閱讀樹莓派開發手冊的難度,但是,如果你必須要閱讀它,你可以在這裡 SoC-Peripherals.pdf 找到它。由於添加了混淆,手冊中 GPIO 使用了不同的地址系統。我們的操作系統中的地址 0x20200000 對應到手冊中是 0x7E200000。 ↩
  4. mov 能夠加載的值只有前 8 位是 1 的二進制表示的值。換句話說就是一個 0 後面緊跟著 8 個 1 或 0。 ↩
  5. 一個很友好的硬件工程師是這樣向我解釋這個問題的: ↩

原因是現在的芯片都是用一種稱為 CMOS 的技術來製成的,它是互補金屬氧化物半導體的簡稱。互補的意思是每個信號都連接到兩個晶體管上,一個是使用 N 型半導體的材料製成,它用於將電壓拉低,而另一個使用 P 型半導體材料製成,它用於將電壓升高。在任何時刻,僅有一個半導體是打開的,否則將會短路。P 型材料的導電性能不如 N 型材料。這意味著三倍大的 P 型半導體材料才能提供與 N 型半導體材料相同的電流。這就是為什麼 LED 總是通過降低為低電壓來打開它,因為 N 型半導體拉低電壓比 P 型半導體拉高電壓的性能更強。

還有一個原因。早在上世紀七十年代,芯片完全是由 N 型材料製成的(NMOS),P 型材料部分使用了一個電阻來代替。這意味著當信號為低電壓時,即便它什麼事都沒有做,芯片仍然在消耗能量(併發熱)。你的電話裝在口袋裡什麼事都不做,它仍然會發熱並消耗你的電池電量,這不是好的設計。因此,信號設計成 “活動時低”,而不活動時為高電壓,這樣就不會消耗能源了。雖然我們現在已經不使用 NMOS 了,但由於 N 型材料的低電壓信號比 P 型材料的高電壓信號要快,所以仍然使用了這種設計。通常在一個 “活動時低” 信號名字上方會有一個條型標記,或者寫作 SIGNAL_n 或 /SIGNAL。但是即便這樣,仍然很讓人困惑,那怕是硬件工程師,也不可避免這種困惑!


via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok01.html

作者: Robert Mullins 選題: lujun9972 譯者: qhwdw 校對: wxy

本文由 LCTT 原創編譯, Linux中國 榮譽推出


分享到:


相關文章: