ラズパイ3でベアメタル - その2:GPIO制御
「その1:何もしない無限ループプログラム」からの続きです。
今回はGPIOのあるピンのHigh/Lowを制御するところまでをまとめてみます。
なお、Raspberry Pi 3で64bitベアメタル(bare metal)プログラミングを試してみる
本シリーズの目次はコチラです。
1. 目標
GPIOはGeneral Pin Input/Outputの略で、汎用的にピンの入出力を決める機能です(日本語にしただけですが。。)。
ここではGPIO16番のピンのHigh/Lowを制御してみます。
GPIO16番のピンの場所は以下の記事が参考になります。
(Raspberry Pi 2用の記事ですが、Raspberry Pi 3も同じです。)
- https://i1.wp.com/www.pighixxx.com/test/wp-content/uploads/2015/06/raspberry.png
- http://www.megaleecher.net/Raspberry_Pi_2_Schematic_And_Pinout_Diagram
また、Raspberry Pi公式から回路図の一部は公開されています。
2. GPIO16番を制御するためにやらなければならないこと
VideoCoreがARMのCoreの実行を開始してから、
GPIO16番を制御するために最低限やらなければならないことは以下の2つです。
1. ピンに割り当てられている機能をGPIOに設定する(GPFSELレジスタ)
2. GPIO16番ピンをHigh、あるいはLowに設定する(GPSET/GPCLRレジスタ)
これら2つの設定はレジスタを通して行います。
ARMの場合、レジスタはメモリに割り当てられている(マップされている)ので、
特定のアドレスへ読み書きすれば、その特定のアドレスにマップされているレジスタを読み書きできます。
GPIOのレジスタのアドレスについて、Raspberry Pi 3としての資料は見つけることができなかったのですが、
初代Raspberry Piの以下のページの「Peripheral specification(*1)」が参考になります。
ただし、GPIO含む周辺機能のアドレス変換の仕様が変わっている都合上、Raspberry Pi 2/3の場合、
(*1)の資料のアドレス表記の上位8ビットを0x3fへ読み替える必要があります。
そのため、(*1)のP.90に記載されているGPIOに関するレジスタのアドレスについて、
例えばGPFSEL1は「0x7E20 0004」から「0x3F20 0004」へ読み替えを行います。
3. GPFSEL・GPSET・GPCLRの3つのレジスタについて
4. C言語を使えるようにする
メモリ上のあるアドレスへのデータ書き込みともなると、
アセンブラよりC言語の方が少ない行数で書けるので、
ここでC言語を使用できるようにします。
C言語で書いたコードを使用するためにアセンブラでやらなければならないことは
最低限以下の2つです。
1. スタックポインタ(sp)を設定
2. C言語の関数へジャンプ
これら2つをアセンブラで書くと以下のとおりです。
ARM Coreの実行開始アドレスが0x0008 0000で、spはそこより若いアドレスの領域を使うようにしています。
■ start.S
mov sp, #0x80000 /* スタックポインタを設定 */ bl main /* C言語のmain関数へジャンプ */
なお、main関数からリターンすることを想定していないので、
main関数には無限ループを用意するなどして関数から抜けないようにすることとします。
前回の何もしない無限ループプログラムをCで書くと以下のとおりです。
■ loop.c
int main(void) { while (1); }
以上の2つのファイルからkernel8.imgを生成する手順は以下のとおりです。
aarch64-linux-gnu-as -o start.o start.S aarch64-linux-gnu-gcc -c -o loop.o loop.c aarch64-linux-gnu-ld -Ttext 0x80000 -o kernel8.elf start.o loop.o aarch64-linux-gnu-objcopy -O binary kernel8.elf kernel8.img
ここでのプログラムはコードのみでデータは無いので、
リンカスクリプトは用意せず、ldコマンドの引数でテキストセクションの開始アドレスのみ指定しています。
(0x80000はVideoCoreがARM Coreの実行を開始させる際のARM Coreの実行開始アドレスです。)
5. C言語からGPIO16番のHigh/Lowを制御する
以上の説明を踏まえ、GPIO16番ピンをHighにするプログラムは以下のとおりです。
#define GPFSEL1 0x3F200004 #define GPSET0 0x3F20001C int main(void) { *(volatile unsigned int *)GPFSEL1 = 0x01 << (3 * 6); *(volatile unsigned int *)GPSET0 = 0x01 << 16; while (1); return 0; }
逆にGPIO16番をLowにする際は、
以下のように新たにGPCLR0レジスタ用の定数を定義し、
#define GPCLR0 0x3F200028
main()内の「GPSET0」の箇所を「GPCLR0」へ書き換えるだけです。
6. 動作確認
GPIOピンのHigh/Lowの確認にはテスターやオシロスコープを使用していましたが、
5VのGPIOなので、LEDを光らせるなら150Ω程度の抵抗を接続すると良いと思います。
(順方向電圧(VF)=2.0V、順方向電流(IF)=20mAほどを想定)