OrangePi-PCを使ってみる

Python GPIOライブラリ(python-periphery)

Python GPIOライブラリのpython-peripheryを紹介します。
このライブラリは、ほとんどネット上で紹介されていませんが、なかなかよくできたライブラリです。
非常に移植性が高く、GPIOの操作も/sys/class/gpioのデバイスファイルを直接操作するので、
WiringPiライブラリが存在しないボードでも、ポート番号(PAxxとかPCxx)さえ分かれば操作することができます。
python-peripheryのインストールは以下の通りです。
$ sudo apt update
$ sudo apt install git python3-pip python3-setuptools

$ mkdir ~/.pip
$ vi ~/.pip/pip.conf
[global]
break-system-packages = true

$ python3 -m pip install python-periphery


サンプルコードのインストール
$ git clone https://github.com/vsergeev/python-periphery.git

python-peripheryライブラリでGPIOのLチカを行うコードをこ ちらに公開しています。
実行時の引数でGPIO番号を指定します。
$ sudo -E python3 leds.py -g 12

python-peripheryライブラリのピン番号の対応は以下の通りです。
portからピン番号は単純な計算で求めることができます。
PA12-->12
PB12-->32+12
PC12-->64+12
PD12-->96+12
PE12-->128+12
PF12-->160+12
PG12-->192+12
port python-periphery physical Pin# python-periphery port


1 2

PA12 12 3 4

PA11 11 5 6

PA6 6 7 8 13 PA13


9 10 14 PA14
PA1 1 11 12 110 PD14
PA0 0 13 14

PA3 3 15 16 68 PC4


17 18 71 PC7
PC0 64 19 20

PC1 65 21 22 2 PA2
PC2 66 23 24 67 PC3


25 26 21 PA21
PA19 19 27 28 18 PA18
PA7 7 29 30

PA8 8 31 32 200 PG8
PA9 9 33 34

PA10 10 35 36 201 PG9
PA20 20 37 38 198 PG6


39 40 199 PG7

PA1のLチカは、ライブラリ内部で以下のコマンドを実行しています。
# GPIO(led, "out")
$ sudo sh -c "echo 1 >/sys/class/gpio/export"
$ sudo sh -c "echo out >/sys/class/gpio/gpio1/direction"

# gpio_out.write(True)
$ sudo sh -c "echo 1 > /sys/class/gpio/gpio1/value"

# gpio_out.write(False)
$ sudo sh -c "echo 0 > /sys/class/gpio/gpio1/value"

# gpio_out.close()
$ sudo sh -c "echo 1 >/sys/class/gpio/unexport"

GPIOのON/OFFを100000回繰り返すと、それなりの時間が掛かりますが、この時、/sys/class/gpioを見ると、
以下の様に/sys/class/gpio/gpioXが作られているのが分かります。
デバイスファイルへの書き込みですが、比較的高速に動作します。
$ ls /sys/class/gpio
export  gpio1  gpiochip0  gpiochip352  unexport



OPiには赤と緑の2つのオンボードLEDが有ります。
デバイスファイルは以下のファイルです。
$ ls /sys/class/leds/
orangepi:green:pwr  orangepi:red:status

python-peripheryライブラリでオンボードLEDのLチカを行うコードをこ ちらに公開しています。
以下の引数で赤のオンボードLEDが点滅します。
$ sudo -E python3 leds.py -d "orangepi:red:status"

以下の引数で緑のオンボードLEDが点滅します。
$ sudo -E python3 leds.py -d "orangepi:green:pwr"



こ ちらにsmbusライブラリを使って、BMP180のi2cセンサーから、温度と気圧を読み出すコードが公開されています。
そこで、これをpython-peripheryライブラリに移植してみます。
BMP180とは以下の様に接続します。
BMP180 Opi(Pin#)
3V3 3.3V
ECC *NotUse*
CLR *NotUse*
SCL SCL(#5)
SDA SDA(#3)
5V *NotUse*
GND GND

このライブラリのために、変更した箇所は行頭が##のコードです。
#!/usr/bin/python
#--------------------------------------
#    ___  ___  _ ____
#   / _ \/ _ \(_) __/__  __ __
#  / , _/ ___/ /\ \/ _ \/ // /
# /_/|_/_/  /_/___/ .__/\_, /
#                /_/   /___/
#
#           bmp180.py
#  Read data from a digital pressure sensor.
#
# Author : Matt Hawkins
# Date   : 17/02/2017
#
# http://www.raspberrypi-spy.co.uk/
#
# https://bitbucket.org/MattHawkinsUK/rpispy-misc/raw/master/python/bmp180.py
#
#--------------------------------------
##import smbus
from periphery import I2C
import time
from ctypes import c_short
import argparse

DEVICE = 0x77 # Default device I2C address

def convertToString(data):
  # Simple function to convert binary data into
  # a string
  return str((data[1] + (256 * data[0])) / 1.2)

def getShort(data, index):
  # return two bytes from data as a signed 16-bit value
  return c_short((data[index] << 8) + data[index + 1]).value

def getUshort(data, index):
  # return two bytes from data as an unsigned 16-bit value
  return (data[index] << 8) + data[index + 1]

def readBmp180Id(addr=DEVICE):
  # Chip ID Register Address
  REG_ID     = 0xD0
##  (chip_id, chip_version) = bus.read_i2c_block_data(addr, REG_ID, 2)
  msgs = [I2C.Message([REG_ID]), I2C.Message([0,0], read=True)]
  i2c.transfer(DEVICE, msgs)
  chip_id = msgs[1].data[0]
  chip_version = msgs[1].data[1]
  return (chip_id, chip_version)

def readBmp180(addr=DEVICE):
  # Register Addresses
  REG_CALIB  = 0xAA
  REG_MEAS   = 0xF4
  REG_MSB    = 0xF6
  REG_LSB    = 0xF7
  # Control Register Address
  CRV_TEMP   = 0x2E
  CRV_PRES   = 0x34
  # Oversample setting
  OVERSAMPLE = 3    # 0 - 3

  # Read calibration data
  # Read calibration data from EEPROM
##  cal = bus.read_i2c_block_data(addr, REG_CALIB, 22)
  cal = [0 for i in range(22)]
  msgs = [I2C.Message([REG_CALIB]), I2C.Message(cal, read=True)]
  i2c.transfer(DEVICE, msgs)
  cal = []
  for x in range(22):
    data = msgs[1].data[x]
    cal.append(data)

  # Convert byte data to word values
  AC1 = getShort(cal, 0)
  AC2 = getShort(cal, 2)
  AC3 = getShort(cal, 4)
  AC4 = getUshort(cal, 6)
  AC5 = getUshort(cal, 8)
  AC6 = getUshort(cal, 10)
  B1  = getShort(cal, 12)
  B2  = getShort(cal, 14)
  MB  = getShort(cal, 16)
  MC  = getShort(cal, 18)
  MD  = getShort(cal, 20)

  # Read temperature
##  bus.write_byte_data(addr, REG_MEAS, CRV_TEMP)
  msgs = [I2C.Message([REG_MEAS,CRV_TEMP]), I2C.Message([0], read=False)]
  i2c.transfer(DEVICE, msgs)
  time.sleep(0.005)
##  (msb, lsb) = bus.read_i2c_block_data(addr, REG_MSB, 2)
  msgs = [I2C.Message([REG_MSB]), I2C.Message([0,0], read=True)]
  i2c.transfer(DEVICE, msgs)
  msb = msgs[1].data[0]
  lsb = msgs[1].data[1]
  UT = (msb << 8) + lsb

  # Read pressure
##  bus.write_byte_data(addr, REG_MEAS, CRV_PRES + (OVERSAMPLE << 6))
  msgs = [I2C.Message([REG_MEAS,CRV_PRES + (OVERSAMPLE << 6)]), I2C.Message([0], read=False)]
  i2c.transfer(DEVICE, msgs)
  time.sleep(0.04)
##  (msb, lsb, xsb) = bus.read_i2c_block_data(addr, REG_MSB, 3)
  msgs = [I2C.Message([REG_MSB]), I2C.Message([0,0,0], read=True)]
  i2c.transfer(DEVICE, msgs)
  msb = msgs[1].data[0]
  lsb = msgs[1].data[1]
  xsb = msgs[1].data[2]
  UP = ((msb << 16) + (lsb << 8) + xsb) >> (8 - OVERSAMPLE)

  # Refine temperature
  X1 = ((UT - AC6) * AC5) >> 15
  X2 = (MC << 11) / (X1 + MD)
  B5 = X1 + X2
  temperature = int(B5 + 8) >> 4

  # Refine pressure
  B6  = B5 - 4000
  B62 = int(B6 * B6) >> 12
  X1  = (B2 * B62) >> 11
  X2  = int(AC2 * B6) >> 11
  X3  = X1 + X2
  B3  = (((AC1 * 4 + X3) << OVERSAMPLE) + 2) >> 2

  X1 = int(AC3 * B6) >> 13
  X2 = (B1 * B62) >> 16
  X3 = ((X1 + X2) + 2) >> 2
  B4 = (AC4 * (X3 + 32768)) >> 15
  B7 = (UP - B3) * (50000 >> OVERSAMPLE)

  P = (B7 * 2) / B4

  X1 = (int(P) >> 8) * (int(P) >> 8)
  X1 = (X1 * 3038) >> 16
  X2 = int(-7357 * P) >> 16
  pressure = int(P + ((X1 + X2 + 3791) >> 4))

  return (temperature/10.0,pressure/100.0)

def main():

  (chip_id, chip_version) = readBmp180Id()
  print("Chip ID     : {0}".format(chip_id))
  print("Version     : {0}".format(chip_version))
  print

  try:
    print ("Press CTRL+C to exit")
    while True:
      (temperature,pressure)=readBmp180()
      print("Temperature : {0} C".format(temperature))
      print("Pressure    : {0} mbar".format(pressure))
      time.sleep(1)

  except KeyboardInterrupt:
    i2c.close()

if __name__=="__main__":
  p = argparse.ArgumentParser()
  p.add_argument('-d', '--device', default='/dev/i2c-0')
  args = p.parse_args()

  print("args.device={}".format(args.device))
  i2c = I2C(args.device)
  main()

msgsは構造体のリストになっていて、最初の構造体がレジスター・アドレス、2番目が読み込むデータ領域とread/writeのフラ グです。
キャリブレーションデータを22個読むときには、2番目には22個のリストを渡す必要が有ります。
今回、BMP180ではなく、BMP085を使いました。
ちょっと移植にてこずりましたが、何とか動きました。
$ sudo -E python3 bmp180.py
Chip ID     : 85
Version     : 2
Press CTRL+C to exit
Temperature : 22.8 C
Pressure    : 1003.34 mbar
Temperature : 22.8 C
Pressure    : 1003.22 mbar
Temperature : 22.8 C
Pressure    : 1003.24 mbar



i2cdetectというツールが有ります。
これを使うとi2cバス上のデバイスをスキャンすることができます。
$ sudo apt install i2c-tools
$ sudo i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 77

python-peripheryライブラリでも同じことができます。
コードはこ ちらに公開しています。
実行すると同じ結果となります。
$ sudo -E python3 ./i2cscan.py
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:           -- -- -- -- -- -- -- -- -- -- -- -- --
10:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30:  -- -- -- -- -- -- -- -- -- -- -- -- 3c -- -- --
40:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70:  -- -- -- -- -- -- 76 77



このライブラリはSPI通信もサポートしています。
そこで、このライブラリを使ってSPI仕様のBMP280から温度を読み出してみました。

BMP280は4ピン仕様(i2c専用)と6ピン仕様(i2c/SPI兼用)のモジュールが有りますが、
SPIで使うときは6ピン仕様のモジュー ルを使う必要が有ります。

6ピン仕様のモジュールのピンマーキングは向って左から
SDO CSB SDA SCL Gnd Vcc
となっています。

i2cで使う場合、以下の結線となります。
BMP280 OrangePi Pin#
Vcc 3.3V
Gnd Gnd
SCL SCL 5
SDA SDA 3
CSB 3.3V
SDO i2cアドレス選択
Gnd=0x76/3.3V=0x77


SPIで使う場合、以下の結線となります。
BMP280 OrangePi Pin#
Vcc 3.3V
Gnd Gnd
SCL SPI SCK 23
SDA SPI MOSI 19
CSB SPI CS 24
SDO SPI MISO 21

左からBMP085(i2c専用) BMP280(i2c専用) BMP280(i2c/SPI兼用)


SPIのIOについては、こ ちらで公開されているC言語のコードを参考にさせていただきました。
BMP280のサンプルは圧倒的にi2cを使ったサンプルが多いのですが、
BitBang-SPI(ソフトウェアSPI)での使い方を紹介している貴重なページです。
SPIのコードはこ ちらで公開しています。

C言語をPythonに移植する場合、Pythonには符号付整数の考え方が無いので、少し注意が必要です。
BMP280は0x88から0x9Fのレジスターにキャリブレーションデータを格納していますが、
一部のキャリブレーションデータは16ビット符号付整数値です。
サンプルのコードではc_short関数を使って16ビット符号付整数値に変換しています。

また、0xF4と0xF5のレジスターを設定しますが、SPIの通信仕様では、
書き込み時のレジスターアドレスは7ビット(0x00〜0x7F)に規定されているので、
0xF4と0xF5のレジスターを設定するときは、0x74と0x75のアドレスを指定します。
こ ちらにBMP280のデータシートが有ります。
BMP280のデータシートの6.3章にきちんと書かれていますが、気が付くまで苦労しました。

In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not
used and replaced by a read/write bit (RW = ‘0’ for write and RW = ‘1’ for read).
Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the
byte 0x77 is transferred, for read access, the byte 0xF7 is transferred.

BMP280のデータシートの4.2章にMemoryMapの初期値が公開されています。
0xF7から0xFCの6バイトに測定データが格納されますが、測定が終わらないと初期値(0x80 0x00 0x00 0x80 0x00 0x00)のまま、
更新されないことが分かったので、初期値以外になるまで読み込みを繰り返す処理を行っています。

レジスターからデータを読む場合、1バイト余計に書き込みを行う必要が有ります。
例えば0xD0のレジスターアドレスからデータを読む時は、[0xD0 0x00]の2バイトをtransferで指定し、
transferからの戻り値の先頭バイトは読み捨てます。
$ sudo -E python3 bmp280-spi.py
Read the contents of the ID register
ChipId = 0x58 BMP280
Check Register
Read calibration data
Press CTRL+C to exit
-----------------------
ChipId      : 0x58
Temperature : 25.21 C
Pressure    : 1005.34 mbar
-----------------------
ChipId      : 0x58
Temperature : 25.22 C
Pressure    : 1005.34 mbar
-----------------------
ChipId      : 0x58
Temperature : 25.22 C
Pressure    : 1005.33 mbar
-----------------------
ChipId      : 0x58
Temperature : 25.23 C
Pressure    : 1005.35 mbar
-----------------------

BMP280をi2cインタフェースで使うコードはこ ちらに公開しています。

次回はWiringPi-Pythonライブラリを紹 介します。