はじめに
今回はLED点灯のプログラムを解説していこうと思います。前回紹介したサイトのプログラムを読み解きながらC言語とアセンブリでプログラムを書いていきたいと思います。
C言語のプログラムは下記のサイトのrpi2_boot2.zipを参考にさせていただきました。
https://gyabo.sakura.ne.jp/progpi.html
kernel.imgを構成するファイル
- ・gpio.h
- GPIOピンのメモリアドレスを書いたヘッダファイル(あってもなくてもいい)
- ・main.c
- メインプログラム
- ・boot.s
- CPUに直接命令するプログラム
- ・linker.ld
- リンカスクリプト。プログラムをメモリのどこに配置するかを指定するプログラム
gpio.hコード
GPIOのメモリアドレスを書いたヘッダファイルです。
Raspberry pi3 model B(以下 Raspi3)のメモリアドレスは0x3F000000からが周辺機器のレジスタがマップされており、0x3F200000からがGPIOピンを設定するアドレスになっている。
IO_WRITE関数について
IO_WRITE関数はメモリに値を書き込む関数である。アセンブリでいうところのMOV命令をC言語に直した形
コードを解説すると
(uint32_t)(reg)で引数regの値を32bitの符号なし整数型にキャストする。
(volatile uint32_t *)(uint32_t)(reg)でポインタのキャストでregがメモリのアドレスになる。
volatileはコンパイラが命令を最適化させないようにするための宣言です。
(* (volatile uint32_t *)(uint32_t)(reg)) = (uint32_t)val) 参照演算子を使ってポインタにアクセスしてそこに引数valを32bitの符号なし整数型にキャストした値を書き込む
#include <stdint.h>
#define IO_WRITE(reg, val) (*(volatile uint32_t *)((uint32_t)(reg)) = (uint32_t)val)
#define MMIO_BASE 0x3F000000
#define GPIO_BASE (MMIO_BASE + 0x200000)
#define GPIO_SEL0 (GPIO_BASE + (4 * 0))
#define GPIO_SEL1 (GPIO_BASE + (4 * 1))
#define GPIO_SET0 (GPIO_BASE + 0x1C)
#define GPIO_CLR0 (GPIO_BASE + 0x28)
main.cコード
mainコードでは、参考にしたサイトに書いてあるアセンブリをC言語に直した。
参考にしたサイトでは、ピン16をオフにするアドレスはGPIO_BASEに0x40を足したアドレスですがRaspi3では、GPIO_BASEに0x1Cを足したアドレスになる。
ピンをオンにするアドレスは変わらない。
#include "gpio.h"
int main()
{
IO_WRITE(GPIO_SEL1, (UINT32_C(1) << 18)); // 16番目のピンを有効にする
IO_WRITE(GPIO_SET0, (UINT32_C(1) << 16)); // 16番ピンを点灯させる
}
boot.sコード
最初の行で .text.boot セクションを定義。
_start: でプログラム本体を定義。(_startは関数の名前みたいなもの。シンボルというらしい)。
Raspi3は0x8000番地からプログラムがロードされるので MOV sp, #0x8000 という命令でスタックポイントにロードされるアドレスを書き込む。ちなみに64bitで開発する場合、0x80000番地からプログラムがロードされる。
BL main でmain関数にジャンプします。BL命令はジャンプと同時にリターンアドレスがリンクレジスタに設定される。
B . で . はロケーションカウンタ(現在のアドレス)にジャンプすることで無限ループを作る。
この処理はプログラムが終了した後に誤動作(変なアドレスにジャンプすること)を防ぐ役割があるそうです。
.section ".text.boot"
.global _start
_start:
// Initialization of stack pointer (sp)
MOV sp, #0x8000
// entry main func
BL main
B .
linker.ldコード
ENTRY(_start)はboot.sで書いた_startというシンボルを持つ関数のコードが最初に実行されるプログラムであるということをリンカに伝えています。
. = 0x8000; でロケーションカウンタの値をRaspi3がプログラムをロードするアドレスに設定する。
.textセクション、プログラムの実行コードを配置している。boot.sで定義した.text.bootセクションが配置される。
.dataセクションには、初期値を持つグローバル変数が配置されます。
.bssセクションには、初期値を持たないグローバル変数が配置されます。
ALIGNはアライメントといい、カッコの中の数字で割り切れる場所にプログラムをおかなければならないというCPUの仕様があり、その仕様に従っている。
ENTRY(_start)
SECTIONS
{
. = 0x8000;
.text : { KEEP(*(.text.boot)) *(.text) }
.data : { *(.data) }
.bss : { *(.bss COMMON) }
. = ALIGN(12);
}
コンパイル、リンク
arm-none-eabi-gcc -c -mcpu=cortex-a53 -g main.c -o main.o
main.cのオブジェクトファイルを作る。
arm-none-eabi-as -mcpu=cortex-a53 -mfpu=vfpv3 -g boot.s -o boot.o
boot.sのオブジェクトファイルを作る。
arm-none-eabi-ld -T linker.ld main.o boot.o -o kernel.elf
リンクしてkernel.elfファイルを作る。
arm-none-eabi-objcopy -O binary kernel.elf kernel.img
kernel.imgファイルを作る。
まとめ
このプログラムで無事LEDが点灯しました。
次は文字をディスプレイに表示するプログラムを書いていきたいと思います。
makefileも一緒に作っていきたいと思います。