Raspberry Pi と Raspberry Pi2の非互換性 (2015/03/22)

前に Raspberry Pi と Raspberry Pii2 Model B はソフトウェア的にほとんど互換であり、浮動小数点プロセッサのベクトル演算モードにちょっと非互換なところがあることを書きました。今回も Pi と Pi2 の互換性を細かいところで探ってみたいと思います。


Pi2BLED.jpg


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-SARM1176JZF-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


続く...



このページの目次