作者descent (「雄辩是银,沉默是金」)
看板ASM
标题作业系统之前的程式 for rpi2 (0)-点亮led by c
时间Wed Aug 5 23:05:39 2015
这是我第三个平台的 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://webptt.com/cn.aspx?n=bbs/ASM/M.1438787150.A.B0D.html