松山事務所の石丸です。
ベアメタル(OSが載っていないハードウェア)のRaspberry Pi 2で、組み込みソフトウェア界のHelloWorldこと、Lチカ(LEDをチカチカと点滅させること)をやってみました。
今回もまだOS開発以前なので、西永俊文「BareMetalで遊ぶ Raspberry Pi」を参考に進めました。
1日目に構築した開発環境でビルドしたバイナリが動作するのか確認するためでもあったので、C言語のソース分割はしていません。
- startup.S: スタートアップコード
- kernel.lds: リンカスクリプト
- Makefile: ビルドスクリプト
- main.c: Lチカプログラム
今回作成した4つのファイルとkernel.imgはGitHubで公開しています。
https://github.com/maruichi82/raspberry/tree/master/examples/LED_blink
スタートアップコードの作成
スタートアップコードは参考文献そのままです。
1
2
3
4
5
6
7
8
9
10
11
|
@ startup
.global _start
.align
_start:
ldr r0, =0x000000d3
msr cpsr, r0
ldr sp, =0x06400000
blmain
b .
|
CPSR(Current Program Status Register)とスタックポインタの初期化をしてmain関数を呼び出します。
これがC言語はmain関数から実行される理由。
リンカスクリプトの作成
リンカスクリプトもほぼ参考文献通りです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x8000;
.text : {
*(.text*)
}
. = ALIGN(4);
.rodata : {
*(.rodata*)
}
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
__bss_start = .;
.bss : {
*(.bss*)
}
. = ALIGN(4);
__bss_end = .;
}
|
自分のようにリンカスクリプトってなに?って人は次のサイトの解説がとてもわかりやすかったです。
知られざるリンクの世界 – KOZOSプロジェクト
http://kozos.jp/documents/linker_kernelvm5.pdf
0から作るOS開発 カーネルことはじめ
http://softwaretechnique.jp/OS_Development/startup_kernel.html
Makefileの作成
Makefileもほぼ参考文献通りです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
CC = arm-none-eabi-gcc
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
CFLAGS =-mfloat-abi=soft -mlittle-endian
CFLAGS += -nostdinc -fno-builtin
CFLAGS += -O2
STARTUP= startup.o
OBJS= main.o
all: kernel.img
kernel.elf: $(STARTUP) $(OBJS)
$(LD) -static -nostdlib -T kernel.lds $^ `$(CC) -print-libgcc-file-name` -o $@
.SUFFIXES: .elf .img
.elf.img:
$(OBJCOPY) -O binary $< $@
.c.o:
$(CC) $(CFLAGS) -c $< -o $@
.S.o:
$(CC) $(CFLAGS) -c $< -o $@
clean:
$(RM) -f *.o *.img *.elf
|
Makefileをちゃんと書ける大人になりたいと思ったので、わからないところを調べました。
$@
とか$<
は自動変数。それぞれターゲットファイル名、最初の依存するファイルの名を表す。.c.o:
は.c
から.o
を作るという意味でサフィックスルールという。.SUFFIXES
で.elf
と.img
を登録しているので、.elf.img:
というサフィックスルールが書ける。
トリビアなmakefile入門
http://www.jsk.t.u-tokyo.ac.jp/~k-okada/makefile/
make基礎知識
http://exlight.net/devel/make/basics.html
Lチカプログラムの実装
今回唯一わかるC言語だったのでmain.cは自分好みにアレンジしています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#define GPFSEL4 0x3F200010
#define GPSET10x3F200020
#define GPCLR10x3F20002C
#define SYST_CLO 0x3F003004
void clear_bss()
{
extern unsigned int __bss_start;
extern unsigned int __bss_end;
unsigned int *start = (unsigned int *)&__bss_start;
unsigned int *end = (unsigned int *)&__bss_end;
for (unsigned int *p = start; p < end; p++)
{
*p = 0x00;
}
}
unsigned int get_system_count()
{
return *(volatile unsigned int *)SYST_CLO;
}
void wait(unsigned int msec)
{
unsigned int wait_count = msec * 1000;
unsigned int start = get_system_count();
while (get_system_count() - start < wait_count);
}
int main()
{
clear_bss();
*(unsigned int *)GPFSEL4 = 0x01 << (47 - 40) * 3;
while (1)
{
*(volatile unsigned int *)GPCLR1 = 0x01 << 47 - 32;
wait(500);
*(volatile unsigned int *)GPSET1 = 0x01 << 47 - 32;
wait(500);
}
return 0;
}
|
main関数内のマジックナンバーはごめんなさい。どうしても意味が伝わるいい名前がつけられませんでした。47はGPIO47です。
Raspberry Piのモデルの違い
まず大事な変更点ですが、参考文献でターゲットとしている Rspberry Pi Type B
と私の Raspberry Pi 2
とではハードウェアの仕様に違いがあり、次の2点の修正が必要でした。
- 今回制御したいACT LEDがGPIO16からGPIO47に変更
- IO物理アドレスが0x20200000から0x3F200000に変更
Raspberry Pi メモ (42) Raspberry Pi2 の非互換性
https://www.mztn.org/rpi/rpi42.html
wait
waitの実現のために参考文献では64bitのシステムタイマーを使用しているのですが、2つの32bitレジスタから値を取ったり桁上りをチェックしたりが面倒なので、64bitシステムタイマーの下位32bitだけを使用しました。
1MHzのカウンタが32bitを1周するのに
1
2
3
|
2^32=4294967296 // 32bitで表せる値
4294967296/1000000=4294.967296 // 1MHzで最大値までカウントアップするのにかかる秒数
4294.967296/60=およそ71(分)
|
約71分かかるので500msの経過を計測するには十分だと判断し、経過時間の判定も符号なし整数の引き算なので
1
2
3
|
unsigned int start = 0xffffffff;
unsigned int now = 0x00000000;
unsigned int elapsed = now - start; // elapsed === 1
|
カウンタがラップアラウンドしても71分までならwaitできます。
はじめてのvolatile
そんな予約語があると存在は知っていましたが、初めて使って大事さを知りました。
最適化されて困るところにつけます。コンパイラの気持ちになって考えて最終的に3箇所に落ち着きました。
職業としてのプログラミング volatileで最適化を抑制する
http://proger.blog10.fc2.com/blog-entry-20.html
Tech Village 「組み込み」ならではの基礎知識 スタートアップ・ルーチンからハードウェアまで
http://www.kumikomi.net/archives/2003/05/10kumi.php?page=14
最適化されてどんなコードになっていたのか確認できればよかったのですが、今はARMアセンブラが読めないので・・・
make&run
実装が完了したのでmakeしてkernel.imgを生成します。
1
2
3
4
5
|
$ make
arm-none-eabi-gcc -mfloat-abi=soft -mlittle-endian -nostdinc -fno-builtin -O2 -c startup.S -o startup.o
arm-none-eabi-gcc -mfloat-abi=soft -mlittle-endian -nostdinc -fno-builtin -O2 -c main.c -o main.o
arm-none-eabi-ld -static -nostdlib -T kernel.lds startup.o main.o `arm-none-eabi-gcc -print-libgcc-file-name` -o kernel.elf
arm-none-eabi-objcopy -O binary kernel.elf kernel.img
|
1日目に作成したファームウェアが起動するSDカードに、このkernel.imgをコピーして、Raspberry Piに挿し込み電源を入れます。
プログラムが動いて感動するなんて久しぶりでした。