GPIOのレジスタをC言語で直接叩いてLチカする
前回までのところで、ラズベリーパイのGPIOを使って
などをやってみたが、
- SoC(BCM2711)のGPIOのレジスタ値を書き換えてLチカする
という方法もある。今回はC言語やラズパイのハードウェアの勉強も兼ねて、この方法でLチカをやってみる。回路はいつものごとく、以下のセットを使ってブレッドボード上につくる。
実行環境
Raspberry Pi 4 Model B
Raspberry Pi OS (Bullseye)
gcc version 10.2.1 20210110 (Raspbian 10.2.1-6+rpi1)
ブレッドボードに回路をつくる
ここはいつもの回路。LEDと抵抗をラズベリーパイの35pin(GPIO 19)と39pin(GND)に接続した。抵抗値は330Ω。
BCM2711のレジスタ仕様の調査
早速C言語のコードを書いていく・・・と行きたいところだが、まずはGPIOを操作するレジスタの仕様がわからないとどうしようもないので、仕様の調査から。Raspberrry Pi 4 Model BにはBCM2711というBroadcom製のSoCが搭載されている。
BCM2711の詳細については、以下の公式ページから確認することができる。
https://www.raspberrypi.com/documentation/computers/processors.html
https://datasheets.raspberrypi.com/bcm2711/bcm2711-peripherals.pdf
このデータシートの67ページ以降にレジスタの仕様が書いてある。今回はGPIO19をOUTにしてHIGH/LOWを切り替えてLチカするので
- 0x04:GPFSEL1
- 0x1C:GPSET0
- 0x28:GPCLR0
あたりのレジスタを叩いてやればLチカができそう。ちなみにBase Addressは0x7E200000と書いてあるのだが、こちらはVPUのアドレス空間で、CPU(ARM)のアドレス空間では0xFE200000にマッピングされているので注意。C言語のコードでもCPUのアドレス空間でBase Addressを指定する。
※最初ここをよくわかっていなかったためにCのコードを書く時GPIOレジスタを操作できず、結構な時間悩んでしまった…
このあたりの話はRspberry Pi Forumの以下のスレッドにも記載がある。
Raspberry pi 4 Arm Assembly blink – Raspberry Pi Forums
Pi 4 – How is GPIO base address obtained in the kernel? – Raspberry Pi Forums
Cのコードを書く
C言語については以下のページのコードがわかりやすかったので参考にさせてもらった。
実際に今回作成してラズパイ4で動かしたのが以下のコード。上記のページだとGPIO42を操作しているが、今回私の回路ではGPIO19を操作するため設定先のレジスタが異なる。また、LED点滅の周期は0.5秒にした。
#include<stdio.h> #include<stdlib.h> #include<fcntl.h> #include<sys/mman.h> #include<unistd.h> #include<stdint.h> #define SIZE 4096 #define GPIO_BASE 0xFE200000 #define GPFSEL1 (0x4/4) #define GPSET0 (0x1C/4) #define GPCLR0 (0x28/4) volatile uint32_t* gpio_addr; int main() { int fd; if((fd = open("/dev/mem", O_RDWR|O_SYNC)) < 0 ) { printf("Can't open /dev/mem \n"); return -1; } gpio_addr = (uint32_t*)mmap(NULL, SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_BASE); gpio_addr[GPFSEL1] &= ~(7 << 27); gpio_addr[GPFSEL1] |= 1 << 27 ; while(1){ gpio_addr[GPSET0] = (1 << 19); usleep(500000); gpio_addr[GPCLR0] = (1 << 19); usleep(500000); } return 0; }
細かい説明は省くが、備忘録としてポイントだけ書いておく。
まずdefineでBase AddressとGPFSEL1、GPSET0、GPCLR0のアドレスを定義してmmapで仮想アドレスにマッピングするのだが、mmapで32bit(4byte)でマッピングするため、gpio_addr[n]のnが1増えると、実際にアクセスしているGPIOレジスタのアドレスは0x04増える。そのため、BCM2711のレジスタ仕様書の1byteアドレスと対応させるためにGPFSEL1、GPSET0、GPCLR0を4で割っている。
また、レジスタ仕様を読むと、GPIO19はGPFSEL1レジスタの[29:27]bitに 割り当てられているので、
gpio_addr[GPFSEL1] &= ~(7 << 27);
で29~27bitを初期化したあとに、
gpio_addr[GPFSEL1] |= 1 << 27 ;
で27bitに1を書き込む。(1がoutputの設定。)
GPSET0レジスタとGPCLR0レジスタでは、Set/ClearするGPIOの番号が各bitに対応してるので、19bit目を0.5秒ごとにSet、ClearすることでGPIO19のOUTをHIGH/LOWに切り替えて、Lチカをしている。
gccでコンパイルして実行
あとは前回と同じ要領で、C言語のコードをラズパイのgccでコンパイルして実行するだけ。先ほどのコードはblink_led.cという名前で保存したので
$ gcc -Wall -o blink_led blink_led.c
でコンパイルして、生成された実行ファイルblink_ledを以下で実行。
$ sudo ./blink_led
これで無事に光った。以下が光っている様子。ちなみにこの時は1秒ごとに点滅させるコードにしてたので、上のコードよりちょっと点滅が遅い。
GPIOレジスタのBase Addressを間違えてた…😭
というわけで正しいアドレスに直したら無事に動いた。相変わらず動画は変わり映えしない笑
スッキリしたから今日は寝よう pic.twitter.com/R6OuHedOJI— Wakky (@wakky_free) April 29, 2022
これで目的は達成。そろそろLチカにも飽きてきたので笑、次回からはキットに入っていた違う部品も使って遊んでみたい。
参考文献
日本語の文献だと、以下のページを参考にさせて頂きましたm(_ _)m
物理アドレスと仮想アドレス、mmapなどについて書かれてます。
Raspberry pi の AP に関して – いかにして問題を解くか
Raspberry pi で AP のレジスタを操作する前に – いかにして問題を解くか
Raspberry pi で AP のレジスタをC言語で直接変更して GPIO を操作する – いかにして問題を解くか
【Raspberry Pi】ユーザランドからレジスタ経由でGPIOを制御する小さなメモ – Qiita
日記まとめ
ラズパイで遊んでみた軌跡を以下でまとめてます。