OrangePi-PCを使ってみる

Python GPIOライブラリ(Adafruit_Blinka)


Python GPIOライブラリのAdafruit_Blinkaを紹介します。
変わった名前のライブラリですが、CircuitPython マスコットの Blinka(とぐろを巻いた紫のヘビ)にちなんで名付けられています。
以前はAdafruit_Python_GPIOとして、こちらに公開 されていたライブラリです。

Adafruitは各種センサーと、Arduino環境でそれらを使うためのC++ライブラリを提供しています。
Linux用のpythonライブラリも提供していることを初めて知りました。
ホームページはこちらで、 対応するボードの情報がこ ちらに有ります。
bananapi nanopi orangepi raspberrypi 等のSBCに対応しています。
Orange Piシリーズのサポートはこ ちらに有りますが、ほぼすべてのSoCをサポートしています。

Adafruit_Blinkaのインストールは以下の手順で行います。
aptでインストールできるpipはバージョンが古いので、一度古いバージョンを入れてからアップデートします。
$ sudo apt update
$ sudo apt install python3-libgpiod python3-pip python3-setuptools
$ python3 -m pip install -U pip
$ sudo python3 -m pip install adafruit-blinka

まずはLチカを試してみます。Pin#11のLEDが点滅します。
board.PA1はこ ちらに定義されています。
allwinner.h3のボードについてはこれが使われるので、OPI-LiteやOPI-ONEなどにも対応しています。
import board
import digitalio
import time

pin = digitalio.DigitalInOut(board.PA1)
pin.direction = digitalio.Direction.OUTPUT

for _ in range(3):
  pin.value = True
  time.sleep(1)
  pin.value = False
  time.sleep(1)

GPIOポートは設定によりSPI、i2c、UARTなどに機能を変更することができます。
これをGPIOのALT機能(Alternative Function)と呼びます。
各ポートのALT状態はWiringPiのgpio readallコマンドで確認することができます。
こ ちらにdigitalioクラスのドキュメントが公開されています。
ポートの状態としてdirection、drive_mode、value、pullの状態を設定、取得することができますが、
ALTの状態は設定、取得することができません。



こ ちらにsmbusライブラリを使って、BMP180のi2cセンサーから、温度と気圧を読み出すコードが公開されています。
そこで、これをAdafruit_Blinkaライブラリに移植してみます。
BMP180とは以下の様に接続します。
BMP180 Host Pin#
3V3 3.3V
ECC *NotUse*
CLR *NotUse*
SCL board.SCL #5
SDA board.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/
#
#--------------------------------------
##import smbus
import board
import busio
import time
from ctypes import c_short

DEVICE = 0x77 # Default device I2C address

##bus = smbus.SMBus(0) # 0 indicates /dev/i2c-0
##bus = smbus.SMBus(1) # 1 indicates /dev/i2c-1
#i2c = busio.I2C(board.SCL, board.SDA)
i2c = busio.I2C(scl=board.SCL, sda=board.SDA)

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)
  result = bytearray(2)
  i2c.writeto_then_readfrom(addr, bytes([0xD0]), result)
##  return (chip_id, chip_version)
  return (result[0], result[1])

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 = bytearray(22)
  i2c.writeto_then_readfrom(addr, bytes([REG_CALIB]), cal)

  # 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)
  i2c.writeto(addr, bytes([REG_MEAS, CRV_TEMP]), stop=True)
  time.sleep(0.005)
##  (msb, lsb) = bus.read_i2c_block_data(addr, REG_MSB, 2)
  temperature = bytearray(2)
  i2c.writeto_then_readfrom(addr, bytes([REG_MSB]), temperature)
  msb = temperature[0]
  lsb = temperature[1]
  UT = (msb << 8) + lsb

  # Read pressure
##  bus.write_byte_data(addr, REG_MEAS, CRV_PRES + (OVERSAMPLE << 6))
  i2c.writeto(addr, bytes([REG_MEAS, CRV_PRES + (OVERSAMPLE << 6)]), stop=True)
  time.sleep(0.04)
##  (msb, lsb, xsb) = bus.read_i2c_block_data(addr, REG_MSB, 3)
  pressure = bytearray(3)
  i2c.writeto_then_readfrom(addr, bytes([REG_MSB]), pressure)
  msb = pressure[0]
  lsb = pressure[1]
  xsb = pressure[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.deinit()


if __name__=="__main__":
   main()

今回、BMP180ではなく、完全機能互換のBMP085を使いましたが、あっさりと動きました。
$ sudo python3 bmp180.py
Chip ID     : 85
Version     : 2
Press CTRL+C to exit
Temperature : 19.9 C
Pressure    : 1013.43 mbar
Temperature : 19.9 C
Pressure    : 1013.4 mbar
Temperature : 19.9 C
Pressure    : 1013.44 mbar



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

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

6ピン仕様のモジュールのピンマーキングは向って左から
SDO CSB SDA SCL Gnd Vcc
となっていますが、SPIで使う場合、以下の結線となります。
BMP280 Host 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兼用)


こ ちらで公開されている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)のまま、
更新されないことが分かったので、初期値のままかどうかの判定処理を行っています。
"""
Read BMP280 using Adafruit_Blinka
"""
#!/usr/bin/python
#-*- encoding: utf-8 -*-
##import spidev
import board
import busio
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)
    resp = bytearray(len(a))
    spi.write_readinto(a, resp)
    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 = busio.SPI(board.SCLK, board.MOSI, board.MISO)
  while not spi.try_lock():
    pass
  spi.configure(baudrate=1000000) # 1MHz
  spi.unlock()

  print('Read the contents of the ID register')
  out = [0xD0, 0x00]
##  resp = spi.xfer(out)
  resp = bytearray(len(out))
  spi.write_readinto(out, resp)
  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])
  spi.write([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])
  spi.write([0x75,config_reg])

  # Check control[0xF4] & config register[0xF5]
  print('Check Register')
  out = [0xF4, 0xF5, 0x00]
##  resp = spi.xfer(out)
  resp = bytearray(len(out))
  spi.write_readinto(out, resp)
  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)
  resp = bytearray(len(a))
  spi.write_readinto(a, resp)
  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.deinit()

データの受信を行う場合、1バイト余計に書き込みを行う必要が有るのは、他のSPIライブラリと同じです。
例えば0xD0のレジスターアドレスからデータを読む時は、[0xD0 0x00]の2バイトをtransferで指定し、
transferからの戻り値の先頭バイトは読み捨てます。
$ 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 : 20.08 C
Pressure    : 1016.65 mbar
Temperature : 20.1 C
Pressure    : 1016.66 mbar
Temperature : 20.11 C
Pressure    : 1016.63 mbar



このライブラリはSoftwareSPI(Bit Bannging SPI)や、SoftwareI2C(Bit Bannging i2c)をサポートしています。
SoftwareSPIやSoftwareI2Cを行うためには、こ ちらのモジュールの追加が必要です。
$ sudo python3 -m pip install adafruit-circuitpython-bitbangio

そこで、BMP280を使ってSoftwareSPI(Bit Bannging SPI)を試してみました。
SoftwareSPIを行う場合、CSの制御はアプリで行う必要があります。
さらにSPIデバイスのLock/Unlockを実装する必要が有ります。
"""
Read BMP280 using Adafruit_Blinka
"""
#!/usr/bin/python
#-*- encoding: utf-8 -*-
##import spidev
import board
import digitalio
import adafruit_bitbangio as bitbangio
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)
    resp = bytearray(len(a))
    cs.value = False
    while not spi.try_lock():
      pass
    spi.write_readinto(a, resp)
    cs.value = True
    spi.unlock()
    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
  SCLK_PIN = board.PC2
  MOSI_PIN = board.PC0
  MISO_PIN = board.PC1
  CS_PIN = board.PC3
  cs = digitalio.DigitalInOut(CS_PIN)
  cs.switch_to_output(value=True)
  #spi = busio.SPI(board.SCLK, board.MOSI, board.MISO)
  spi = bitbangio.SPI(SCLK_PIN, MOSI=MOSI_PIN, MISO=MISO_PIN)
  while not spi.try_lock():
    pass
  spi.configure(baudrate=1000000) # 1MHz
  spi.unlock()

  print('Read the contents of the ID register')
  out = [0xD0, 0x00]
##  resp = spi.xfer(out)
  resp = bytearray(len(out))
  cs.value = False
  while not spi.try_lock():
    pass
  spi.write_readinto(out, resp)
  cs.value = True
  spi.unlock()
  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])
  cs.value = False
  while not spi.try_lock():
    pass
  spi.write([0x74,ctrl_meas_reg])
  cs.value = True
  spi.unlock()

  # 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])
  cs.value = False
  while not spi.try_lock():
    pass
  spi.write([0x75,config_reg])
  cs.value = True
  spi.unlock()

  # Check control[0xF4] & config register[0xF5]
  print('Check Register')
  out = [0xF4, 0xF5, 0x00]
##  resp = spi.xfer(out)
  resp = bytearray(len(out))
  cs.value = False
  while not spi.try_lock():
    pass
  spi.write_readinto(out, resp)
  cs.value = True
  spi.unlock()
  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)
  resp = bytearray(len(a))
  cs.value = False
  while not spi.try_lock():
    pass
  spi.write_readinto(a, resp)
  cs.value = True
  spi.unlock()
  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.deinit()

1回目は正常に動作します。
但し、一度スクリプトを終了して、再度スクリプトを実行するとデータが正しくとれません。
他のライブラリも正しく動かなくなります。
Shutdownして再起動すると正しく動きます。
何かのリソースの開放が必要だと思いますが、良く分かりません。
$ sudo python3 software-bmp280.py
Read the contents of the ID register
ChipId = 0x58 BMP280
Check Register
Read calibration data
Press CTRL+C to exit
Temperature : 26.58 C
Pressure    : 1015.87 mbar
Temperature : 26.59 C
Pressure    : 1015.89 mbar
Temperature : 26.62 C
Pressure    : 1015.88 mbar
Temperature : 26.63 C
Pressure    : 1015.89 mbar



今までいくつかのPythonライブラリを紹介してきました。
これらのライブラリは、i2cやSPIなど特定のI/F専用ライブラリと、GPIO/SPI/i2cを扱うことが出来る統合ライブラリに分類する 事ができます。
また、OrangePi PC専用のライブラリと、OrangePiシリーズで使えるライブラリと、どのようなLinuxでも利用できる汎用ライブラリに分類する事ができま す。

OPI-PC専用 シリーズ汎用 Linux汎用 GPIO GPIOの速度
(MillSec)
SPI i2c
smbus × × ×
×
spidev × × ×
×
SPI-Py × × ×
×
orangepi_PC_gpio_pyH3 × × 34.581 ×
OPI.GPIO × × 4352.571 × ×
python-periphery × × 184.835
WiringPi-Python-OP × × 66.923 ×
adafruit_blinka × × 350.193

GPIOの速度はON/OFFを全速力で10000回繰り返したときの所要時間です。

GPIOだけを使うアプリケーションの場合は、あまり気にする必要は有りませんが、
GPIO+SPIやGPIO+i2cを使うアプリケーションの場合は、ライブラリの選定が重要になります。
GPIO+SPI+i2cの全てを使うアプリケーションの場合、使用できるライブラリは限られたものとなります。
また、パラレルIOやSPI-TFTのCDピン制御など、GPIOの切り替えを多用するアプリの場合、GPIOのスピードは重要になります。

SPIとi2cについては、いずれのライブラリもデバイスファイルに対するコントロールなので、APIに違いは有りますが、
パフォーマンスに大きな違いは有りません。

次回はFlaskとWiringPi-Python-OPを使っ た、ブラウザー経由でのGPIO 操作を紹介します。