Raspberry Pi 3でGPIOライブラリを書く
概要
Raspberry PiのGPIOを対象としたライブラリを作成する。既存のライブラリなどは数多く存在するが実際に自分で作成することでその内部を理解できると思う。
環境
Paspberry Pi 3 Model B
$ sudo cat /etc/os-release PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)" NAME="Raspbian GNU/Linux" VERSION_ID="9" VERSION="9 (stretch)" ID=raspbian ID_LIKE=debian HOME_URL="http://www.raspbian.org/" SUPPORT_URL="http://www.raspbian.org/RaspbianForums" BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs" $ sudo uname -a Linux raspberrypi 4.9.41-v7+ #1023 SMP Tue Aug 8 16:00:15 BST 2017 armv7l GNU/Linux
chipset
まずデータシートからチップセットを調べる。
Processer - Broadcom BCM2387 chipset. - 1.2GHz Quad-Core ARM Cortex-A53 (64Bit)
引用:https://www.terraelectronica.ru/pdf/show?pdf_file=%252Fds%252Fpdf%252FT%252FTechicRP3.pdf
上記からBCM2387であることがわかった。
BCM2387
https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf
上記のデータシートから抜粋したメモリマップが以下。
ペリフェラルレジスタ
ペリフェラルデバイスのレジスタは、ペリフェラル(プログラマブル・カウンタや割り込み制御、シリアル通信ポートなどのハードウェア)の動作を設定したり、動作状況を読み出したりするためのレジスタである。 引用: https://ja.wikipedia.org/wiki/%E3%83%AC%E3%82%B8%E3%82%B9%E3%82%BF_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF)#%E3%83%9A%E3%83%AA%E3%83%95%E3%82%A7%E3%83%A9%E3%83%AB%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%AE%E3%83%AC%E3%82%B8%E3%82%B9%E3%82%BF
データシートを見ると以下のような記述があった。
Peripherals (at physical address 0x3F000000 on) are mapped into the kernel virtual address space starting at address 0xF2000000. 引用: https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf p6
上記からペリフェラルレジスタが0x3F000000
からマッピングされていることがわかる。
以下のドキュメントからbcm_host_get_peripheral_address()
でペリフェラルレジスタのアドレスを取得できることがわかったので実際に以下のコードで確かめる。
https://www.raspberrypi.org/documentation/hardware/raspberrypi/peripheral_addresses.md
$ cat samples/get_peripheral_address.c #include <stdio.h> #include <bcm_host.h> int main(int argc, char* argv[]) { printf("0x%08X\n", bcm_host_get_peripheral_address()); return 0; }
$ gcc samples/get_peripheral_address.c -I/opt/vc/include -L/opt/vc/lib -lbcm_host -o myfile -Wimplicit-function-declaration $ ./myfile 0x3F000000
加えて以下の/proc/iomem
からもメモリマップが確認できる。
$ sudo cat /proc/iomem 00000000-3b3fffff : System RAM 00008000-00afffff : Kernel code 00c00000-00d3da63 : Kernel data 3f006000-3f006fff : dwc_otg 3f007000-3f007eff : /soc/dma@7e007000 3f00b840-3f00b84e : /soc/vchiq 3f00b880-3f00b8bf : /soc/mailbox@7e00b880 3f101000-3f102fff : /soc/cprman@7e101000 3f200000-3f2000b3 : /soc/gpio@7e200000 3f201000-3f201fff : /soc/serial@7e201000 3f201000-3f201fff : /soc/serial@7e201000 3f202000-3f2020ff : /soc/sdhost@7e202000 3f215000-3f215007 : /soc/aux@0x7e215000 3f300000-3f3000ff : /soc/mmc@7e300000 3f980000-3f98ffff : dwc_otg
GPIO制御用レジスタ
The GPIO has 41 registers. All accesses are assumed to be 32-bit. https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf p90
BCM2387のデータシート p90にGPIOレジスタのマッピング表が記載されている。
Address | Field Name | Description | Size | Read/Write |
---|---|---|---|---|
0x7E200000 | GPFSEL0 | GPIO Function Select 0 | 32 | R/W |
0x7E200000 | GPFSEL0 | GPIO Function Select 0 | 32 | R/W |
0x7E200004 | GPFSEL1 | GPIO Function Select 1 | 32 | R/W |
0x7E200008 | GPFSEL2 | GPIO Function Select 2 | 32 | R/W |
0x7E20000C | GPFSEL3 | GPIO Function Select 3 | 32 | R/W |
0x7E200010 | GPFSEL4 | GPIO Function Select 4 | 32 | R/W |
0x7E200014 | GPFSEL5 | GPIO Function Select 5 | 32 | R/W |
0x7E200018 | - | Reserved | - | - |
0x7E20001C | GPSET0 | GPIO Pin Output Set 0 | 32 | W |
0x7E200020 | GPSET1 | GPIO Pin Output Set 1 | 32 | W |
0x7E200024 | - | Reserved | - | - |
0x7E200028 | GPCLR0 | GPIO Pin Output Clear 0 | 32 | W |
0x7E20002C | GPCLR1 | GPIO Pin Output Clear 1 | 32 | W |
0x7E200030 | - | Reserved | - | - |
0x7E200034 | GPLEV0 | GPIO Pin Level 0 | 32 | R |
0x7E200038 | GPLEV1 | GPIO Pin Level 1 | 32 | R |
GPIO "Function Select" Registers (GPFSEL0 ~ GPFSEL5)
The function select registers are used to define the operation of the general-purpose I/O pins https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf p91
上記にもあるようにGPFSEL(n)
はGPIOピンの操作を定義するためのものとなっている。
GPFSEL0
の使用方法と各ビットの対応の表を以下に抜粋した。
Bit(s) | Field Name | Description | Type | Reset |
---|---|---|---|---|
31-30 | --- | Reserved | R | 0 |
29-27 | FSEL9 | FSEL9 - Function Select 9 000 = GPIO Pin 9 is an input 001 = GPIO Pin 9 is an output |
R/W | 0 |
26-24 | FSEL8 | FSEL8 - Function Select 8 | R/W | 0 |
23-21 | FSEL7 | FSEL7 - Function Select 7 | R/W | 0 |
20-18 | FSEL6 | FSEL6 - Function Select 6 | R/W | 0 |
17-15 | FSEL5 | FSEL5 - Function Select 5 | R/W | 0 |
14-12 | FSEL4 | FSEL4 - Function Select 4 | R/W | 0 |
11-9 | FSEL3 | FSEL3 - Function Select 3 | R/W | 0 |
8-6 | FSEL2 | FSEL2 - Function Select 2 | R/W | 0 |
5-3 | FSEL1 | FSEL1 - Function Select 1 | R/W | 0 |
2-0 | FSEL0 | FSEL0 - Function Select 0 | R/W | 0 |
上記から3bitずつが各ピンに対応していることがわかる。上記と同じ要領でGPFSEL5
まで続き54ピン全てに対応する。
GPIO Pin Output Set Registers (GPSET0 ~ GPSET1)
The output set registers are used to set a GPIO pin. https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf p95
アウトプット用のレジスタで各ビットが各ピンに対応している。
GPSET0
を例に対応表をいかに示す。
Bit(s) | Field Name | Description | Type | Reset |
---|---|---|---|---|
31-0 | SETn (n=0..31) | 0 = No effect 1 = Set GPIO pin n |
R/W | 0 |
上記と同じ要領でGPSET1
を用いて54ピンまで対応している。
GPIO Pin Output Clear Registers (GPCLR0 ~ GPCLR1)
The output clear registers) are used to clear a GPIO pin. https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf p95
これはアウトプットのクリア用レジスタで先ほどと同様に各ビットが各ピンに対応している。
GPCLR0
を例に対応表をいかに示す。
Bit(s) | Field Name | Description | Type | Reset |
---|---|---|---|---|
31-0 | CLRn (n=0..31) | 0 = No effect 1 = Clear GPIO pin n |
R/W | 0 |
上記と同じ要領でGPCLR1
を用いて54ピンまで対応している。
GPIO Pin Level Registers (GPLEV0 ~ GPLEV1)
The pin level registers return the actual value of the pin. https://web.stanford.edu/class/cs140e/docs/BCM2837-ARM-Peripherals.pdf p96
ピンの値を取得する用のレジスタで先ほどと同様に各ビットが各ピンに対応している。
GPLEV0
を例に対応表をいかに示す。
Bit(s) | Field Name | Description | Type | Reset |
---|---|---|---|---|
31-0 | LEVn (n=0..31) | 0 = GPIO pin n is low | 1 = GPIO pin n is high | R/W|0 |
上記と同じ要領でGPLEV1
を用いて54ピンまで対応している。
実装に関して
- /dev/mem
- mmap()
- PAGE_SIZE
/dev/mem
mem はコンピュータのメインメモリーイメージのキャラクターデバイスファ イル(character device file)である。 https://linuxjm.osdn.jp/html/LDP_man-pages/man4/mem.4.html
当該デバイスファイルを用いることで物理メモリにアクセスすることでき、先ほど調べたペリフェラルレジスタがマッピングされているアドレスにアクセスする。
mmap()
ファイルやデバイスをメモリーにマップ/アンマップする。 https://linuxjm.osdn.jp/html/LDP_man-pages/man2/mmap.2.html
当該関数を用いて先ほどのデバイスファイルをプロセスのメモリにマッピングしアクセスを行う。
PAGE_SIZE
仮想メモリを扱う単位であるページのサイズを取得する。これはmmap()
関数でマッピングするメモリのサイズに用いる(ページサイズでマッピングするのが定石らしい)
$ getconf PAGE_SIZE 4096
実際にアクセスしてみる。
$ cat write_peripheral_register.c #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <errno.h> #define PERIPHERAL_ADDRESS 0x3F000000 #define GPIO_ADDRESS (PERIPHERAL_ADDRESS + 0x00200000) #define PAGE_SIZE 4096 #define GPFSEL0_OFFSET 0x0000 #define GPSET0_OFFSET 0x001C #define GPCLR0_OFFSET 0x0028 #define GPLEV0_OFFSET 0x0034 #define DEVICE_FILE "/dev/mem" #define MEMORY(addr) (*((volatile unsigned int*)(addr))) #define GPFSEL0 MEMORY(address + GPFSEL0_OFFSET) #define GPSET0 MEMORY(address + GPSET0_OFFSET) #define GPCLR0 MEMORY(address + GPCLR0_OFFSET) #define SEL_IN(n) (0b000 << (n * 3)) #define SEL_OUT(n) (0b001 << (n * 3)) #define SET(n) (1 << n) #define CLR(n) (1 << n) int main(int argc, char* argv[]) { unsigned int address; int fd; if ((fd = open(DEVICE_FILE, O_RDWR | O_SYNC)) < 0) { perror("failed to open()\n"); return -1; } address = (unsigned int)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_ADDRESS); if (address == MAP_FAILED) { perror("failed with mmap()\n"); close(fd); return -1; } GPFSEL0 = SEL_OUT(4); GPSET0 = SET(4); sleep(1); GPCLR0 = CLR(4); munmap((void*)address, PAGE_SIZE); close(fd); return 0; }
MEMOEY
マクロは少々複雑だがアドレスとして保存した値をunsigned int
のポインタに変換しそのアドレスを参照しにいっているものである。
コンパイルすると以下のエラーが出力されるが問題はない。
$ gcc write_peripheral_register.c write_peripheral_register.c: In function 'main': write_peripheral_register.c:37:15: warning: comparison between pointer and integer if (address == MAP_FAILED) { ^~
問題なく実行することが確認できた。
$ sudo ./a.out $
ライブラリ化
上記をより汎用的にし、GPIOをデジタルピンとしてON/OFF切り替え及びピンの状態を取得するライブラリを作成した。
https://github.com/k-onishi/gpio-lib
参考文献
(上記の記事には本当に感謝です)