OrangePi-PCを使ってみる

Python SPIライブラリ(spidev)


RaspberryPiのSPIライブラリとして良く使われるspidevライブラリを紹介します。
このライブラリはSPI専用のライブラリですが、/dev/spidev*のデバイスが有れば、(おそらく)どのLinuxでも使うことができま す。
そこで、このライブラリを使って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 N/C または Vcc
SDO i2cアドレス選択
Vcc(=0x76)/Gnd(=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兼用)


spidevのインストールは以下の通りです。
SPIデバイスを扱う場合、root権限が必要になるので、ライブラリもroot権限でインストールします。
$ sudo apt install git python3-dev python3-setuptools
$ git clone https://github.com/doceme/py-spidev
$ cd py-spidev
$ sudo python3 setup.py install

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

C言語をPythonに移植する場合、Pythonには符号付整数の考え方が無いので、少し注意が必要です。
BMP280は0x88から0x9Fのレジスターにキャリブレーションデータを格納していますが、
一部のキャリブレーションデータは16ビット符号付整数値です。
以下のコードではint16()関数を使って符号なし整数値を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に測定データが格納されますが、測定が終わらないと初期値(0x80 0x00 0x00 0x80 0x00 0x00)のまま、
更新されないことが分かったので、初期値のままかどうかの判定処理を行っています。

spidevにはxfer()/xfer2()/xfer3()の3つの関数が有りますが、今回はxfer()を使いました。
こちらにその違いの説明が有りますが、 xfer2()の方がChipSelectを毎回操作しない分、早いかもしれません。
"""
Read BMP280 using spidev
"""
#!/usr/bin/python
#-*- encoding: utf-8 -*-
import spidev
import sys
import time
import os.path

DEBUG = 0

def calibration_T(adc_T):
  global t_fine
  var1 = ((((adc_T >> 3) - (dig_T1<<1))) * (dig_T2)) >> 11
  var2 = (((((adc_T >> 4) - (dig_T1)) * ((adc_T>>4) - (dig_T1))) >> 12) * (dig_T3)) >> 14

  t_fine = var1 + var2
  t_fine = int32(t_fine)
  T = (t_fine * 5 + 128) >> 8
  return T

def calibration_P(adc_P):
  var1 = ((t_fine)>>1) - 64000
  if (DEBUG == 1):print("var1(1) = %d " % var1)
  var2 = (((var1>>2) * (var1>>2)) >> 11) * (dig_P6)
  if(DEBUG == 1):print("var2(2) = %d " % var2)
  var2 = var2 + ((var1*(dig_P5))<<1)
  if(DEBUG == 1):print("var2(3) = %d " % var2)
  var2 = (var2>>2)+((dig_P4)<<16)
  if(DEBUG == 1):print("var2(4) = %d " % var2)
  var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + (((dig_P2) * var1)>>1))>>18
  if(DEBUG == 1):print("var1(5) = %d " % var1)
  var1 = ((((32768+var1))*(dig_P1))>>15)
  if(DEBUG == 1):print("var1(6) = %d " % var1)
  if (var1 == 0):
    return 0
  P = ((((1048576)-adc_P)-(var2>>12)))*3125
  if(P<0x80000000):
    P = int((P << 1) / (var1))
  if(P>=0x80000000):
    P = (P / var1) * 2;
  var1 = ((dig_P9) * ((((P>>3) * (P>>3))>>13)))>>12
  var2 = (((P>>2)) * (dig_P8))>>13
  if(DEBUG == 1):
    print("var1 = %d" % var1),
    print("var2 = %d" % var2)
  P = (P + ((var1 + var2 + dig_P7) >> 4))
  return P

def int16(x):
  if x>0xFFFF:
    raise OverflowError
  if x>0x7FFF:
    x=int(0x10000-x)
    if x<2147483648:
      return -x
    else:
      return -2147483648
  return x

def int32(x):
  if x>0xFFFFFFFF:
    raise OverflowError
  if x>0x7FFFFFFF:
    x=int(0x100000000-x)
    if x<2147483648:
      return -x
    else:
      return -2147483648
  return x

def readData():
  while(1):
    a = []
    for x in range(0xF7, 0xFD):
      a.append(x)
    a.append(0)
    resp = spi.xfer(a)
    if(DEBUG == 1):print(resp)
    # Check valid data
    if (resp[2] != 0 or resp[3] != 0):break
    time.sleep(1)

  pres_raw = (resp[1] << 12) | (resp[2] << 4) | (resp[3] >> 4)  #0xF7, msb+lsb+xlsb=19bit
  if(DEBUG == 1):print("pres_raw = %d " % pres_raw)
  temp_raw = (resp[4] << 12) | (resp[5] << 4) | (resp[6] >> 4)  #0xFA, msb+lsb+xlsb=19bit
  if(DEBUG == 1):print("temp_raw = %d " % temp_raw)

  temp_cal = calibration_T(temp_raw)
  if(DEBUG == 1):print("temp_cal = %d " % temp_cal)
  press_cal = calibration_P(pres_raw)
  if(DEBUG == 1):print("press_cal = %d " % press_cal)
  temp_act = temp_cal / 100.0
  press_act = press_cal / 100.0
  return temp_act, press_act


if __name__=="__main__":
  t_sb = 5    #stanby 1000ms
  filter = 0  #filter O = off
  spi3or4 = 0 #SPI 3wire or 4wire, 0=4wire, 1=3wire
  osrs_t = 4  #OverSampling Temperature x8
  osrs_p = 4  #OverSampling Pressure x8
  Mode = 3    #Normal mode

  temp_raw = 0
  pres_raw = 0
  t_fine = 0

  # Open spidev
  spi = spidev.SpiDev()
  if os.path.exists("/dev/spidev0.0"):
    spi.open(0,0)
  if os.path.exists("/dev/spidev1.0"):
    spi.open(1,0)
  spi.mode = 0
  spi.max_speed_hz = 1000000 # 1MHz

  print('Read the contents of the ID register')
  out = [0xD0, 0x00]
  resp = spi.xfer(out)
  if(DEBUG == 1):print(resp)
  ChipId = resp[1]
  print("ChipId = 0x%x " % ChipId, end="")
  if (ChipId == 0x58):
    print("BMP280")
  elif (ChipId == 0x60):
    print("BME280")
  else:
    print("Unknown")
    while True:
      pass

  # Send a command to the control register[0xF4]
  ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | Mode
  if(DEBUG == 1):print("ctrl_meas_reg = %x" % ctrl_meas_reg)
  spi.xfer([0x74,ctrl_meas_reg])

  # Send a command to the config register[0xF5]
  config_reg = (t_sb << 5) | (filter << 2) | spi3or4
  if(DEBUG == 1):print("config_reg = %x " % config_reg)
  spi.xfer([0x75,config_reg])

  # Check control[0xF4] & config register[0xF5]
  print('Check Register')
  out = [0xF4, 0xF5, 0x00]
  resp = spi.xfer(out)
  if(DEBUG == 1):
    print(resp)
    print("ctrl_meas_reg = %x" % resp[1])
    print("config_reg    = %x" % resp[2])
  if(resp[1] != ctrl_meas_reg):
    print("INVALID control register %x" % resp[1])
  if(resp[2] != config_reg):
    print("INVALID config  register %x" % resp[2])

  print('Read calibration data')
  a = []
  for x in range(0x88, 0xA0):
    a.append(x)
  a.append(0)
  resp = spi.xfer(a)
  if(DEBUG == 1):print(resp)

  dig_T1 = resp[2] * 256 + resp[1]
  dig_T2 = int16(resp[4] * 256 + resp[3])
  dig_T3 = int16(resp[6] * 256 + resp[5])
  if(DEBUG == 1):
    print("dig_T1 = %d" % dig_T1),
    print("dig_T2 = %d" % dig_T2),
    print("dig_T3 = %d" % dig_T3)

  dig_P1 = resp[8] * 256 + resp[7]
  dig_P2 = int16(resp[10] * 256 + resp[9])
  dig_P3 = int16(resp[12] * 256 + resp[11])
  dig_P4 = int16(resp[14] * 256 + resp[13])
  dig_P5 = int16(resp[16] * 256 + resp[15])
  dig_P6 = int16(resp[18] * 256 + resp[17])
  dig_P7 = int16(resp[20] * 256 + resp[19])
  dig_P8 = int16(resp[22] * 256 + resp[21])
  dig_P9 = int16(resp[24] * 256 + resp[23])
  if(DEBUG == 1):
    print("dig_P1 = %d" % dig_P1),
    print("dig_P2 = %d" % dig_P2),
    print("dig_P3 = %d" % dig_P3)
    print("dig_P4 = %d" % dig_P4),
    print("dig_P5 = %d" % dig_P5),
    print("dig_P6 = %d" % dig_P6)
    print("dig_P7 = %d" % dig_P7),
    print("dig_P8 = %d" % dig_P8),
    print("dig_P9 = %d" % dig_P9)

  try:
    print ("Press CTRL+C to exit")
    while True:
      temp_act, press_act = readData()
      print("Temperature : {} C".format(temp_act))
      print("Pressure    : {} mbar".format(press_act))
      time.sleep(1)

  except KeyboardInterrupt:
    spi.close()

あっさりと動きました。
$ sudo python3 bmp280.py
Read the contents of the ID register
ChipId = 0x58 BMP280
Check Register
Read calibration data
Press CTRL+C to exit
Temperature : 19.1 C
Pressure    : 1010.22 mbar
Temperature : 19.1 C
Pressure    : 1010.24 mbar
Temperature : 19.13 C
Pressure    : 1010.22 mbar

次回は別のSPIライブラリを紹介します。