作業系統之前的程式 for rpi2 (0)-點亮led by c
這是我第三個平台的 bare-metal 程式, 第一個是 x86 在 legacy bios/uefi, 有
bios/uefi 擋路, 不算真的是 bare-metal。第二個是 stm32f4 - discovery, 有 jtag,
而且真的是從上電開始寫程式, 真的是 bare-metal 程式, 過癮, 可惜沒有 mmu, 不能練
習 mmu。rpi2 是 cortex a7, 又是一個新平台, 我已經有了不少經驗, 該怎麼開始呢?
沒有 bcm2836 datasheet 實在很麻煩, 沒有 boot code source 也是一樣麻煩。好在有
不少人也對 bare-metal 有興趣, 本篇文章大量參考: Step01 – Bare Metal
Programming in C Pt1 ( http://goo.gl/k1T6AZ ) (後文以 Pt1 稱呼), 能遇上同好真
是開心, 我不用一人辛苦了。
不過沒有 jtag, 要除錯時就得靠「冥想」了, 而且也無法驗證程式跑的流程是不是和我
想的一樣, 依照以前的經驗, 通常是不一樣 (我只是個普通程式員, 別難為我了)。
rpi2 開機流程:
Pt1 ( http://goo.gl/k1T6AZ ) 裡頭就寫到了, 不過還是大概提一下, 和一般的 arm 開
發板從 arm core boot 並從 address 0 讀入第一個 arm 指令不同, rpi2 是從 gpu 開
機, 也就是說某個地方放著執行 gpu 的程式碼, 它會去找 sd card 上的 bootcode.bin
然後載入並執行 bootcode.bin, bootcode.bin 再去找 sd card 上的 start.elf, 這兩
個檔案都是 gpu 執行檔, 不是 arm machine code, 再來終於到 kernel.img,
kernel7.img, 那個 7 就是給 rpi2 (arm cortex-A7) 用的, kernel.img 則是原來 rpi
(ARM1176JZF) 用的, start.elf 會自動去判斷載入正確的 kernel*.img。
descent@NB-debian:boot$ file start.elf
start.elf: ELF 32-bit LSB executable, Broadcom VideoCore III, version 1
(SYSV), statically linked, stripped
和這篇的 boot 方式比較一下, 也許會覺得 rpi1/2 很與眾不同。
嵌入式系統 Boot Loader 技術內幕
http://www.ibm.com/developerworks/cn/linux/l-btloader/
和 Pt1 提到的不同, 我使用的是 raspberrypi 官方提供的 toolchain (
https://goo.gl/UXR4Sg )。
在 os 下的程式有 os 提供的 loader 來幫我們載入程式, rpi2 bare-metal 程式呢? 正
常來說應該是 cpu 幫我們載入, 不過目前看來只能透過 star.elf 來載入我們的
bare-metal 程式, 把它想成 pc 的 bios/uefi 載入作業系統那樣的感覺, os kernel 也
是 bare-metal 程式。
那 star.elf 從哪裡載入 0x8000? 所以你知道 linker script 要設定 0x8000 為
enter point, 為什麼? 說來複雜, 你照辦就是了, 除非你的程式可以 relocation, 搬到
任意位址都可以正常執行。c.sh L3 就是在做這件事情。
c.sh
1 arm-linux-gnueabihf-gcc -O2 -mfpu=neon-vfpv4 -mfloat-abi=hard
-march=armv7-a -mtune=cortex-a7 -nostartfiles -g -c v.s
2 arm-linux-gnueabihf-gcc -O2 -mfpu=neon-vfpv4 -mfloat-abi=hard
-march=armv7-a -mtune=cortex-a7 -nostartfiles -g -c armc-02.c
3 arm-linux-gnueabihf-ld -Ttext 0x8000 v.o armc-02.o -o armc-02.elf
4 arm-linux-gnueabihf-objcopy armc-02.elf -O binary armc-02.bin
5 mv armc-02.bin kernel.img
該使用的 cpu 參數作者也一並列出來了, 我們不用辛苦的找這些資料。
我們的目的在點亮 OK led 燈, 就是那個綠色的 led, 這顆接在 gpio 47 和 rpi 1 不同
哦, Pt1 提供的資訊。
以下的 gpio 資料是從 bcm2835 datasheet 節錄出來的, 咦 ... 我知道你的疑惑, 沒有
bcm2836 datasheet, bcm2835 勉強撐著用了, 玩 rpi2 真辛苦, 我真佩服 Pt1 作者是
從哪裡得到這些資料。當然還有最重要的 physical address, 要不然就不知道要寫入哪
個位址了。
bcm2835 datasheet gpio
1 0x 7E20 0000 GPFSEL0 GPIO Function Select 0 32 R/W
2 0x 7E20 0000 GPFSEL0 GPIO Function Select 0 32 R/W
3 0x 7E20 0004 GPFSEL1 GPIO Function Select 1 32 R/W
4 0x 7E20 0008 GPFSEL2 GPIO Function Select 2 32 R/W
5 0x 7E20 000C GPFSEL3 GPIO Function Select 3 32 R/W
6 0x 7E20 0010 GPFSEL4 GPIO Function Select 4 32 R/W
7 0x 7E20 0014 GPFSEL5 GPIO Function Select 5 32 R/W
8
9 0x 7E20 001C GPSET0 GPIO Pin Output Set 0 32 W
10 0x 7E20 0020 GPSET1 GPIO Pin Output Set 1 32 W
11
12 0x 7E20 0028 GPCLR0 GPIO Pin Output Clear 0 32 W
13 0x 7E20 002C GPCLR1 GPIO Pin Output Clear 1 32 W
Table 6-6 - GPIO Alternate function select register 4
1 Bit(s) Field Name Description
2 31-30 --- Reserved
3 29-27 FSEL49 FSEL49 - Function Select 49
4 000 = GPIO Pin 49 is an input
5 001 = GPIO Pin 49 is an output
6 100 = GPIO Pin 49 takes alternate functio
7 101 = GPIO Pin 49 takes alternate functio
8 110 = GPIO Pin 49 takes alternate functio
9 111 = GPIO Pin 49 takes alternate functio
10 011 = GPIO Pin 49 takes alternate functio
11 010 = GPIO Pin 49 takes alternate functio
12 26-24 FSEL48 FSEL48 - Function Select 48
13 23-21 FSEL47 FSEL47 - Function Select 47
14 20-18 FSEL46 FSEL46 - Function Select 46
15 17-15 FSEL45 FSEL45 - Function Select 45
16 14-12 FSEL44 FSEL44 - Function Select 44
17 11-9 FSEL43 FSEL43 - Function Select 43
18 8-6 FSEL42 FSEL42 - Function Select 42
19 5-3 FSEL41 FSEL41 - Function Select 41
20 2-0 FSEL40 FSEL40 - Function Select 40
這個要怎麼看呢? 呼 ... 比 stm32f4 簡單多了。我們要做幾件事情:
把 gpio 47 設定為 output - Table 6-6 L13, 把 bit 21, 22, 23 設定為 001 就是
ouput
把 gpio 47 寫入 0, led 暗, gpio bit 47 在 GPCLR1 bit 15
把 gpio 47 寫入 1, led 亮, gpio bit 47 在 GPSET1 bit 15
就這樣, 不用設像 stm32f4 那麼複雜的屬性。
該文章的程式碼, 我在 rpi2 上測試, 只能亮 led, 滅 led 後就無法再亮 led, 我做了
些修改。
https://github.com/descent/arm-tutorial-rpi/tree/master/part-1/armc-02
gpio[11] 就是 gpclr1 的位址
gpio[8] 就是 gpset1 的位址
針對這個位址寫入 bit 0/1 就是針對 gpio 47 寫入 bit 0/1。
bit 15 寫 1 到 gpio[8] 就是將gpio 47 寫入 1
bit 15 寫 1 到 gpio[11] 就是將gpio 47 寫入 0, bit 0 是 gpio 32, bit 1 是 gpio
33 ... bit 15 就是 gpio 47
很奇怪對吧, 不過人家這麼設計你就這麼用吧!
ok_led.c L30 很重要, 這是 GPIO base phyical address。
ok_led.c
29
30 #define GPIO_BASE 0x3F200000UL
31
86
87
88 #define DELAY_MAX 1000
89
90 /** Main function - we'll never return from here */
91 int notmain(void)
92 {
93 /** GPIO Register set */
94 volatile unsigned int* gpio;
95
96 /** Simple loop variable */
97 volatile unsigned int i,j;
98
99 /* Assign the address of the GPIO peripheral (Using ARM Physical
Address) */
100 gpio = (unsigned int*)GPIO_BASE;
101
102 /* Write 1 to the GPIO16 init nibble in the Function Select 1 GPIO
103 peripheral register to enable GPIO16 as an output */
104 gpio[4] &= (~(7 << 21));
105 gpio[4] |= (1 << 21);
106
107 while(1)
108 {
109 // gpclr1
110 gpio[11] = (1 << 15); // led off
111 for(i = 0; i < DELAY_MAX; i++)
112 for(j = 0; j < 1000; j++) ;
113
114 // gpset1
115 gpio[8] = (1 << 15); // led on
116 for(i = 0; i < DELAY_MAX; i++)
117 for(j = 0; j < 1000; j++) ;
118
119 // gpclr1
120 gpio[11] = (1 << 15); // led off
121 for(i = 0; i < DELAY_MAX; i++)
122 for(j = 0; j < 1000; j++) ;
123
124 // gpset1
125 gpio[8] = (1 << 15); // led on
126 while(1);
127
137 }
138 return 0;
139 }
v.s
1
2 .globl _start
3 _start:
4 mov sp,#0x8000
5 bl notmain
6
7 hang: b hang
8 .globl PUT32
9 PUT32:
10 str r1,[r0]
11 bx lr
12
13 .globl GET32
14 GET32:
15 ldr r0,[r0]
16 bx lr
ref: ok_led.c, v.s, makefile
這是參考 https://github.com/dwelch67/raspberrypi 改出來的。
由於我沒有 jtag, 我只能猜測是 stack 問題, 但是 armc-02.c 實在看不出來哪裡和
stack 有關。
ok_led.c 已經開始用 auto variable, 由於在 v.s 設定了 stack, 所以就放心的用
auto variable。編譯出來的 .bin 檔複製到 kernel.img 的那個分割區, 蓋掉
kernel.img, kernel7.img, 再插回 rpi2, 上電應該就可以看到 led 在閃爍。
我改變了閃燈的邏輯, 先暗 led, 再亮 led, 再暗 led, 然後永遠亮著, 以便確定有正常
亮滅。綠色的那個 led 燈就是 ok led, 請參考以下影片。
有了 bootcode 之後的 bare-metal 程式簡單不少, 不用設定 dram controller 參數,
這樣才能存取 1g ram, 不用作 remap, 將 address 0 map to dram, 有些不過癮, 這些
都被 bootcode 做完了, 又少學了不少東西。以這個例子來說, 從組合語言設定好 sp
register 後, 就可以使用 stack, 再來便可以使用 c 語言了, 不過 bss 因為沒有作初
始化, 會有不預期的行為, 但其實影響不大。也因為沒使用中斷, 所以也看不到中斷部份
的程式碼, 慢慢來補上他們吧!
最後提一下有關 bootcode.bin, start.elf 我直接複製這些檔案到 fat partition 上,
似乎無法正確 work, 一定要用 2015-05-05-raspbian-wheezy.img dd 後產生的
partition fat 才能正確載入 kernel7.img, 真是奇怪。
Preparing Raspberry PI for JTAG Debugging ( http://goo.gl/niWmEU ), 我不確定能
不能在 rpi2 上使用, 我們沒有 datasheet 記得嗎? 就算有, 我也看不懂, 硬體是我的
弱點。
Raspberry Pi: Boot Process
http://output.to/sideway/default.asp?qno=140100001
// 本文使用 Blog2BBS 自動將Blog文章轉成縮址的BBS純文字 http://goo.gl/TZ4E17 //
blog 版本:
http://descent-incoming.blogspot.tw/2015/06/for-rpi2-0-led-by-c.html
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 180.217.248.108
※ 文章網址: https://www.ptt.cc/bbs/ASM/M.1438787150.A.B0D.html
ASM 近期熱門文章
PTT數位生活區 即時熱門文章