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はバージョンが古いので、一度古いバージョンを入れてからアップデートします。
以下の例ではaptでインストールされるのはv20.3.4ですが、更新後はv25.1.1になります。
$ sudo apt update
$ sudo apt install python3-libgpiod python3-pip python3-setuptools
python3-dev 
$ python3 -m pip --version
pip 20.3.4 from /usr/lib/python3/dist-packages/pip (python 3.9)
$ python3 -m pip install -U pip
$ python3 -m pip --version
pip 25.1.1 from /home/orangepi/.local/lib/python3.9/site-packages/pip (python 3.9)

$ sudo python3 -m pip install adafruit-blinka

$ pip list
Package                                  Version
---------------------------------------- -----------
Adafruit-Blinka                          8.57.0
adafruit-circuitpython-busdevice         5.2.11
adafruit-circuitpython-connectionmanager 3.1.3
adafruit-circuitpython-requests          4.1.10
adafruit-circuitpython-typing            1.11.2
Adafruit-PlatformDetect                  3.79.0
Adafruit-PureIO                          1.1.11

まずはLチカを試してみます。
ピン番号はこ ちらのGPIO番号を指定します。
以下のコード(blink.py)で、Pin#11のLEDが点滅します。
GPIO番号は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)

実行時にはroot権限が必要になります。
$ sudo -E python3 blink.py

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 -E python3
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")
        sys.exit()

    # 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)

    for i in range(10):
        temp_act, press_act = readData()
        print("Temperature : {} C".format(temp_act))
        print("Pressure : {} mbar".format(press_act))
        time.sleep(1)

    spi.deinit()

データの受信を行う場合、1バイト余計に書き込みを行う必要が有るのは、他のSPIライブラリと同じです。
例えば0xD0のレジスターアドレスから1バイトのデータを読む時は、[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
Temperature : 29.3 C
Pressure : 1019.08 mbar
Temperature : 29.32 C
Pressure : 1019.09 mbar



このライブラリはSoftwareSPI(Bit Bannging SPI)や、SoftwareI2C(Bit Bannging i2c)をサポートしています。
SoftwareSPIやSoftwareI2Cを行うためには、こ ちらのモジュールの追加が必要です。
$ 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")
        sys.exit()

    # 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)

    for i in range(10):
        temp_act, press_act = readData()
        print("Temperature : {} C".format(temp_act))
        print("Pressure : {} mbar".format(press_act))
        time.sleep(1)

    spi.deinit()
    cs.deinit()

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



Adafruit-BlinkaライブラリをベースにしたAdafruit CircuitPythonライブラリと言うライブラリが有ります。
周辺機器やネットワークなどのためのライブラリで、こ ちらにライブラリの一覧が公開されています。
2024年時点で利用可能なライブラリは 273 個もあります。
これらのリポジトリを見ると全てのリポジトリのrequirements.txtにAdafruit-Blinkaが登録されています。
つまりこれらのライブラリは全てAdafruit-Blinkaをベースに構築されています。

このライブラリはCircuitPython用のライブラリです。
OrangePiなどのLinuxにインストールされているPythonはCPythonで、CircuitPythonとは一部非互換な部分が 有ります。
従って、全てのAdafruit CircuitPythonライブラリがLinux環境で使えるわけではありません。
こ ちらのBMP280ライブラリイブラリをインストールし、公開されているサンプルコードを一部変更しました。
import time
import board
import digitalio # For use with SPI
import adafruit_bmp280

# Create sensor object, communicating over the board's default I2C bus
#i2c = board.I2C()   # uses board.SCL and board.SDA
#bmp280 = adafruit_bmp280.Adafruit_BMP280_I2C(i2c)

# OR Create sensor object, communicating over the board's default SPI bus
spi = board.SPI()
bmp_cs = digitalio.DigitalInOut(board.PC3)
bmp280 = adafruit_bmp280.Adafruit_BMP280_SPI(spi, bmp_cs)

# change this to match the location's pressure (hPa) at sea level
bmp280.sea_level_pressure = 1013.25

for i in range(10):
    print("\nTemperature: %0.1f C" % bmp280.temperature)
    print("Pressure: %0.1f hPa" % bmp280.pressure)
    print("Altitude = %0.2f meters" % bmp280.altitude)
    time.sleep(1)

spi.deinit()

あっさりと動きました。
$ sudo -E python3 bmp280-blinka.py

Temperature: 26.8 C
Pressure: 1021.2 hPa
Altitude = -65.97 meters

Temperature: 26.9 C
Pressure: 1021.2 hPa
Altitude = -65.83 meters

Temperature: 26.9 C
Pressure: 1021.2 hPa
Altitude = -66.09 meters

Temperature: 26.9 C
Pressure: 1021.2 hPa
Altitude = -65.70 meters

i2c専用デバイスも以下のコードであっさりと動きました。
import time
import board
import digitalio # For use with SPI
import adafruit_bmp280

# Create sensor object, communicating over the board's default I2C bus
i2c = board.I2C()    # uses board.SCL and board.SDA
bmp280 = adafruit_bmp280.Adafruit_BMP280_I2C(i2c, 0x76)

# OR Create sensor object, communicating over the board's default SPI bus
#spi = board.SPI()
#bmp_cs = digitalio.DigitalInOut(board.PC3)
#bmp280 = adafruit_bmp280.Adafruit_BMP280_SPI(spi, bmp_cs)

# change this to match the location's pressure (hPa) at sea level
bmp280.sea_level_pressure = 1013.25

for i in range(10):
    print("\nTemperature: %0.1f C" % bmp280.temperature)
    print("Pressure: %0.1f hPa" % bmp280.pressure)
    print("Altitude = %0.2f meters" % bmp280.altitude)
    time.sleep(1)

i2c.deinit()



今までいくつかの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などの様に、複数のI/Fを同時に使うアプリケーションの場合は、ライブラリの選定が重要になります。
GPIO+SPI+i2cの全てのI/Fを使うアプリケーションの場合、使用できるライブラリは限られたものとなります。
また、パラレルIOやSPI-TFTのCDポート制御など、GPIOのON/OFFを多用するアプリの場合、GPIOのスピードは重要になりま す。

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

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