やりたいこと
/dev/gpiomemを操作してLEDをチカチカさせます。
以下のページを参考にさせていただきました。
https://www.ei.tohoku.ac.jp/xkozima/lab/raspTutorial3.html
なるべくナイーブに実装して、GPIO4にLEDを繋いでチカチカさせます。
ソースとか
ソース
//for printf
#include <stdio.h>
//for O_RDWR, O_SYNC
#include <fcntl.h>
//for mmap
#include <sys/mman.h>
//for sleep, open, close
#include <unistd.h>
//for uint32_t
#include <stdint.h>
#define PERI_BASE 0x20200000
#define BLOCK_SIZE 4096
int main() {
printf("start\n");
// ①/dev/gpiomemを開く
int fd = open("/dev/gpiomem", O_RDWR | O_SYNC);
if( fd == -1 ) {
printf("cannot open device file\n");
return 1;
}
// ②物理アドレス 0x20200000 を仮想アドレスにマッピング
void *gpiomem = mmap(NULL, BLOCK_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PERI_BASE);
if( (long)gpiomem == -1 ) {
printf("cannot mmap gpio mem\n");
return 1;
}
close(fd);
volatile uint32_t *gpio = (uint32_t*)gpiomem;
// ③GPIO4に対応するGPFSELのビット列(3bit)を出力(0b001)に設定
gpio[0] = 0b00000000000000000001000000000000;
for(int i = 0; i < 10; ++i) {
// ④GPIO4に対応するGPSETを1にする(Highを出力)
gpio[7] = 0b00000000000000000000000000010000;
sleep(1);
// ⑤GPIO4に対応するGPCLRを1にする(Lowを出力)
gpio[10] = 0b00000000000000000000000000010000;
sleep(1);
}
printf("end\n");
return 0;
}
コンパイル
gcc main.c
実行
./a.out
解説
①/dev/gpiomemを開く
物理メモリの特定のアドレスに特定の値を書き込むとそれがGPIOへの命令と解釈されて、GPIOを操作することができます。
Linuxのプロセスからメモリは通常、仮想メモリとしてしか見えません。物理メモリの特定のアドレスにアクセスするためにLinuxは物理メモリへのアクセス経路としてデバイスファイルの/dev/memを用意してくれています。このファイルの先頭が0x00000000の物理アドレスに対応していて、このファイルの特定のアドレスにwirteしてあげると、物理メモリの対応するアドレスに書き込まれます。
通常、この/dev/memはroot所有になっているので通常ユーザからはアクセスできません。RaspberryPiOSでは通常ユーザから物理メモリへアクセスできるようにするため、同じ意味合いの/dev/gpiomemを用意してくれています。
おそらく、/dev/memと/dev/gpiomemは同じもので権限が異なるだけだと思われます。
②物理アドレス 0x20200000 を仮想アドレスにマッピング
Linuxにはファイルのread/writeをメモリへのread/writeに対応させるmmapというシステムコールが用意されてます。
これを/dev/gpiomemに対して呼び出して、仮想メモリ上に/dev/gpiomemの内容を展開するイメージです。
GPIOへの命令は0x20200000以降のアドレスに書き込むので、0x20200000からmmapします。
③GPIO4に対応するGPFSELのビット列(3bit)を出力(0b001)に設定
gpip[0]〜gpio[3]の各ビットがGPIOピンごとの3bitのGPFSELという命令に対応しています。
"出力"とか"入力"などのどういうふうにGPIOを使うかの設定を行うことができます。
今回は、GPIO4を出力ピンとして使用したいので、対応するビットに0b001を書き込みます。
④GPIO4に対応するGPSETを1にする(Highを出力)
gpio[7]の各ビットがGPIOピンごとのHigh出力命令に対応しています。
ここでは、GPIO4にHighを出力するので、対応するビットに0b1を書き込みます。
⑤GPIO4に対応するGPCLRを1にする(Lowを出力)
gpio[10]の各ビットがGPIOピンごとのLow出力命令に対応しています。
ここでは、GPIO4にLowを出力するので、対応するビットに0b1を書き込みます。