Raspberry Pi と Raspberry Pi2の非互換性 (2015/03/22)
前に Raspberry Pi と Raspberry Pii2 Model B はソフトウェア的にほとんど互換であり、浮動小数点プロセッサのベクトル演算モードにちょっと非互換なところがあることを書きました。今回も Pi と Pi2 の互換性を細かいところで探ってみたいと思います。
CPUコアの個数が4倍になって、クロックが900MHzになって、メモリが1GBになっていますが、普通に使うには単に速くなっただけの変化です。 Raspberry Pi2 で、ボードに付いている LED を点滅させようとしていて、非互換なところに気が付きました。で、まとめたのが以下の表です。
ボード | Pi B rev1 | Pi B rev2 | Pi B+ | Pi2 B |
---|---|---|---|---|
チップ名称 | BCM2835 | BCM2835 | BCM2835 | BCM2836 |
基本命令セット | ARMv6 | ARMv6 | ARMv6 | ARMv7 |
コア名称 | ARM1176JZF-S | ARM1176JZF-S | ARM1176JZF-S | Cortex-A7 |
コア数 | 1 | 1 | 1 | 4 |
浮動小数点演算 | VFPv2 | VFPv2 | VFPv2 | VFPv4 + NEON |
クロック | 700MHz | 700MHz | 700MHz | 900MHz |
メモリ | 256MB | 512MB | 512MB | 1GB |
LED1 | OK (GPIO16) | ACT (GPIO16) | ACT (GPIO47) | ACT (GPIO47) |
LED2 | PWR | PWR | PWR (GPIO35) | PWR (GPIO35) |
IO物理アドレス | 0x20200000 | 0x20200000 | 0x20200000 | 0x3F200000 |
SDカードのアクセスを示すLEDである ACT は B+ 以降にGPIOのピンが増えた影響で GPIO-16 から GPIO-47 を使うように変更されています。また、PWR LED は電源直結から GPIO-35 を使うように変更されて、電源電圧が 4.63±0.07V 以下の場合に消灯するようになっています( https://elinux.org/R-Pi_Troubleshooting )。
また、Raspberry Pi2 では周辺回路のレジスタの物理アドレスが変更されています ( https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=98904 )。 Raspberry Pi B+ までの Peripherals Base Address は 0x20000000 でしたが、Raspberry Pi2 B では 0x3F000000 に変更されています。Raspberry Pi専用ライブラリが物理アドレスの違いを吸収してくれるので、 普通に使うには問題ないのですが、電源ランプやアクセスランプをコントロールするといった変な使い方までは面倒を見てくれません。 そういった( GPIOを低レベルでコントロール ) するような使い方は、物理アドレスの違いをプログラムに反映する必要があります。
ハードウェア寄りの低レベルな部分は、CPU の周辺回路の説明書である BCM2835-ARM-Peripherals.pdf に書いてあります。 周辺回路のレジスタの先頭の物理メモリアドレスは Raspberry Piでは 0x20000000 でしたが、Raspberry Pi2では 0x3F000000 に変更されています。
GPIOピンヘッダ
GPIOピンヘッダは初代 Raspberry Pi の26ピンから B+ 以降は40ピンになっています。26番ピンまでは互換で、27番ピン以降が拡張された分です。 拡張された36番ピンにModel B まで ACTの LED がつながっていたGPIO16が使われています。 その結果として ACT が GPIO47 に変更されたものと思われます。
pin# | 機能 | GPIO# | pin# | 機能 | GPIO# |
---|---|---|---|---|---|
1 | 3.3V | - | 2 | 5v | - |
3 | SDA0 | 2 | 4 | 5v | - |
5 | SCL0 | 3 | 6 | 0v | - |
7 | GPIO in | 4 | 8 | TxD | 14 |
9 | 0v | - | 10 | RxD | 15 |
11 | GPIO in | 17 | 12 | GPIO in | 18 |
13 | GPIO in | 27 | 14 | 0v | - |
15 | GPIO in | 22 | 16 | GPIO in | 23 |
17 | 3.3v | - | 18 | GPIO | 24 |
19 | GPIO in | 10 | 20 | 0v | - |
21 | GPIO in | 9 | 22 | GPIO in | 25 |
23 | GPIO in | 11 | 24 | GPIO in | 8 |
25 | 0V | - | 26 | GPIO in | 7 |
- | |||||
27 | EEPROM | ID_SD | 28 | EEPROM | ID_SC |
29 | GPIO | 5 | 30 | 0v | - |
31 | GPIO | 6 | 32 | GPIO | 12 |
33 | GPIO | 13 | 34 | 0V | - |
35 | GPIO | 19 | 36 | GPIO | 16 |
37 | GPIO | 26 | 38 | GPIO | 20 |
39 | 0V | - | 40 | GPIO | 21 |
GPIOのコントロール
ピンヘッダに出ている GPIO を簡単に使用するには、仮想ファイルシステムである Sysfs を使います。 /sys/class/gpio/export に対してGPIO番号を書き込むと対応する番号のディレクトリ ( /sys/class/gpio/gpio番号/ ) ができます。 次に /sys/class/gpio/gpio番号/direction に "in" または "out" を書き込むとGPIOを入力モードか出力モードに設定できます。LED を点滅させるためには、 /sys/class/gpio/gpio番号/direction に "out" を書き込みます。 例えば「sudo bash」して、ルートの権限で実行する必要があります。 コマンドラインから次のコマンドを実行して17番のGPIOをコントロールすることで、11番ピンの GPIO 出力を変更できます。
echo "17" > /sys/class/gpio/export echo "out" > /sys/class/gpio/gpio17/direction echo "1" > /sys/class/gpio/gpio17/value echo "0" > /sys/class/gpio/gpio17/value
Raspberry Pi
初代 Raspberry Pi でGPIO17には書込みできますが、ACT LEDをコントロールするGPIO16は以下のように失敗します。
root@raspberrypi:~# echo "17" > /sys/class/gpio/export root@raspberrypi:~# echo "16" > /sys/class/gpio/export bash: echo: write error: Device or resource busy
Raspberry Pi2
Raspberry Pi2 では、GPIO16には書込みできますが、ACT LEDをコントロールするGPIO47は以下のように失敗します。
root@raspberrypi:~# echo "16" > /sys/class/gpio/export root@raspberrypi:~# echo "47" > /sys/class/gpio/export bash: echo: write error: Device or resource busy
ACT や PWR のLEDをコントロールするためには次のように低レベルのコントロールが必要になります。
低レベルは方法で Raspberry Pi2 の GPIO にアクセス
上にも書きましたが、 初代 Raspberry Pi では、周辺回路のレジスタの先頭物理メモリアドレスは 0x20000000 でしたが、Raspberry Pi2では 0x3F000000 に変更されています。 Raspberry Pi 1 のGPIOを低レベルでコントロールするLuaJIT用のプログラム を修正して、Raspberry Pi2 のボード上のLED (ACTとPWR) を点滅する luajit のプログラムを作成しました。 Raspbian には最初から luajit が含まれているので、なにも準備しなくても下の実行例のようにそのまま実行できます。
#!/usr/bin/luajit -- --------------------------------------------- -- gpio_led.lua 2015/03/21 -- Copyright (c) 2015 Jun Mizutani, -- released under the MIT open source license. -- --------------------------------------------- local bit = require("bit") local ffi = require("ffi") local C = ffi.C ffi.cdef[[ void *mmap(void *addr, size_t len, int prot, int flags, int fd, int offset); int munmap(void *addr, size_t len); int open(const char *pathname, int flags, int mode); typedef unsigned long int nfds_t; int poll(struct pollfd *fds, nfds_t nfds, int timeout); ]] function sleep(sec) C.poll(nil, 0, sec * 1000) end -- 物理アドレス空間のデバイスファイルをオープン function mem_open() local O_RDONLY = 0 local O_WRONLY = 1 local O_RDWR = 2 local fd = C.open("/dev/mem", O_RDWR, 0) return fd end -- メモリをマッピング function mem_map(fd, addr, length, offset) local PROT_READ = 1 local PROT_WRITE = 2 local PROT_EXEC = 4 local MAP_SHARED = 1 local MAP_PRIVATE = 2 if fd > 0 then local p = C.mmap(addr, length, PROT_READ + PROT_WRITE, MAP_SHARED, fd, offset) local mem = ffi.cast("int32_t *", p) if mem > ffi.cast("int32_t *", 0) then return mem end end return nil end -- メモリのマッピングを解除 function mem_unmap(addr, length) local p = ffi.cast("int32_t *", addr) return C.munmap(p, length) end local gpio -- 初代 Raspberry Pi 用のベースアドレス -- local BCM_PEREIFERAL_ADDR = 0x20200000 -- Raspberry Pi2 用のベースアドレス local BCM_PEREIFERAL_ADDR = 0x3F200000 local GPSET0 = 7 local GPSET1 = 8 local GPCLR0 = 10 local GPCLR1 = 11 local GPLEV0 = 13 local GPLEV1 = 14 -- GPIO のレジスタを gpio 配列に設定 function gpioOpen() local fd = mem_open() local mem = mem_map(fd, nil, 256, BCM_PEREIFERAL_ADDR) if mem == nil then print("You must run 'sudo luajit gpio.lua'.") end gpio = mem end -- GPIO のレジスタのマッピングを解除 function gpioClose() mem_unmap(BCM_PEREIFERAL_ADDR, 256) end -- pinNo の GPIO のモードを設定 -- mode: 0/input, 1/output function gpioSetPinMode(pinNo, mode) if pinNo > 53 or pinNo < 0 then print("PinNo is out of range.") return elseif mode > 7 then print("Mode must be [0..7].") return else local reg = math.floor(pinNo / 10) local shift = (pinNo % 10) * 3 local mask = bit.bnot(bit.lshift(7, shift)) -- ~(7 << shift) local val = bit.lshift(mode, shift) local orig = gpio[reg] local new = bit.bor(bit.band(orig, mask), val) gpio[reg] = new end end -- pinNo の GPIO のモードを取得 function gpioGetPinMode(pinNo) if pinNo > 53 or pinNo < 0 then print("PinNo is out of range.") return nil else local reg = math.floor(pinNo / 10) local shift = (pinNo % 10) * 3 local mask = bit.lshift(7, shift) local val = bit.band(gpio[reg], mask) local mode = bit.rshift(val, shift) return mode end end -- pinNo の GPIO に 1 を出力 function gpioSet(pinNo) if pinNo < 32 then gpio[GPSET0] = bit.lshift(1, pinNo) elseif pinNo < 54 then gpio[GPSET1] = bit.lshift(1, pinNo - 32) end end -- pinNo の GPIO に 0 を出力 function gpioClear(pinNo) if pinNo < 32 then gpio[GPCLR0] = bit.lshift(1, pinNo) elseif pinNo < 54 then gpio[GPCLR1] = bit.lshift(1, pinNo - 32) end end -- pinNo の GPIO を読み出し function gpioRead(pinNo) local val if pinNo < 32 then val = bit.band(gpio[GPLEV0], bit.lshift(1, pinNo)) elseif pinNo < 54 then val = bit.band(gpio[GPLEV1], bit.lshift(1, pinNo - 32)) end if val ~= 0 then return 1 end return 0 end -- PWR(赤)と ACT(緑)を点滅させる gpioOpen() -- GPIO アクセス準備 gpioSetPinMode(35, 1) -- PWR : 出力モード gpioSetPinMode(47, 1) -- ACT : 出力モード for i = 1, 30 do gpioSet(35) -- PWR を 点灯 gpioSet(47) -- ACT を 点灯 sleep(0.5) -- 0.5秒間待つ gpioClear(35) -- PWR を 消灯 gpioClear(47) -- ACT を 消灯 sleep(0.3) -- 0.3秒間待つ end gpioClose()
実行
この ソース を gpio_led.lua という名前で保存して、次のように実行します。ACT と PWR の2つの LED が同時に24秒間点滅します。
$ sudo bash # luajit gpio_led.lua