0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Raspberry pi3 model Bでベアメタルプログラム② LED点灯

Last updated at Posted at 2025-03-16

はじめに

今回は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も一緒に作っていきたいと思います。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?