【電子工作】C言語でRaspberry Pi 4 Model BのGPIOレジスタを叩いてLチカしてみる(Raspberry Pi 4で遊ぼう日記 その9)

投稿日:2022年5月6日
最終更新日:2022年5月10日

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言語については以下のページのコードがわかりやすかったので参考にさせてもらった。

Blink | de.ci.phe.red

実際に今回作成してラズパイ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秒ごとに点滅させるコードにしてたので、上のコードよりちょっと点滅が遅い。

これで目的は達成。そろそろLチカにも飽きてきたので笑、次回からはキットに入っていた違う部品も使って遊んでみたい。

 

参考文献

日本語の文献だと、以下のページを参考にさせて頂きましたm(_ _)m

物理アドレスと仮想アドレス、mmapなどについて書かれてます。

Raspberry pi の AP に関して – いかにして問題を解くか

Raspberry pi で AP のレジスタを操作する前に – いかにして問題を解くか

Raspberry pi で AP のレジスタをC言語で直接変更して GPIO を操作する – いかにして問題を解くか

【Raspberry Pi】ユーザランドからレジスタ経由でGPIOを制御する小さなメモ – Qiita

 

日記まとめ

ラズパイで遊んでみた軌跡を以下でまとめてます。

【電子工作】Raspberry Pi 4で遊ぼう日記 まとめ

 


投稿者: wakky

映画と旅行が大好きなエンジニア。お酒、ゲーム、読書も好き。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください