pigpioによるI2CとSPIインタフェースの実装

Raspberry Pi 3で、pigpioを使ってI2CとSPIインタフェースを使ったpythonスクリプトを作成します。pigpioでpythonインタフェースに関する詳細は、pigpio libraryに記述されています。
pigpioを使ったI2Cインタフェースの確認のために、Raspberry Pi 3に環境センサー「BME280」を接続します。接続の詳細については、「raspberry pi 3でC言語による環境センサー「BME280」の接続」を参照してください。pigpioを使ったSPIインタフェースの確認のために、Raspberry Pi 3にADC「MCP3208」を接続します。接続の詳細については、「Raspberry PiでADC「MCP3208」のspi接続」を参照してください。

メインメニューの「設定」→「Raspberry Pi の設定」の「インターフェイス」タブからRaspberry Pi 3の設定を次のようにします。

  • I2C:Enabled
  • SPI:Disabled
  • Remote GPIO:Enabled

インターフェイス設定

pigpioによる環境センサー「BME280」の実装

Raspberry Pi 3に環境センサー「BME280」をI2Cインタフェースにより接続し、次のコマンドで接続を確認し、アドレス「0×76」になっていることを確認します。

$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

次のPythonスクリプト「BME280.py」を作成します。

#!/usr/bin/env python

import time

AUX_SPI=256

# Sampling

OVER_SAMPLE_1 = 1
OVER_SAMPLE_2 = 2
OVER_SAMPLE_4 = 3
OVER_SAMPLE_8 = 4
OVER_SAMPLE_16 = 5

class sensor:
   """
   A class to read the BME280 pressure, humidity, and temperature sensor._
   """

   # BME280 Registers

   _calib00    = 0x88

   _T1         = 0x88 - _calib00
   _T2         = 0x8A - _calib00
   _T3         = 0x8C - _calib00

   _P1         = 0x8E - _calib00
   _P2         = 0x90 - _calib00
   _P3         = 0x92 - _calib00
   _P4         = 0x94 - _calib00
   _P5         = 0x96 - _calib00
   _P6         = 0x98 - _calib00
   _P7         = 0x9A - _calib00
   _P8         = 0x9C - _calib00
   _P9         = 0x9E - _calib00

   _H1         = 0xA1 - _calib00

   _chip_id    = 0xD0
   _reset      = 0xE0

   _calib26    = 0xE1

   _H2         = 0xE1 - _calib26
   _H3         = 0xE3 - _calib26   
   _xE4        = 0xE4 - _calib26
   _xE5        = 0xE5 - _calib26
   _xE6        = 0xE6 - _calib26
   _H6         = 0xE7 - _calib26

   _ctrl_hum   = 0xF2
   _status     = 0xF3
   _ctrl_meas  = 0xF4
   _config     = 0xF5

   _rawdata    = 0xF7

   _p_msb      = 0xF7 - _rawdata
   _p_lsb      = 0xF8 - _rawdata
   _p_xlsb     = 0xF9 - _rawdata
   _t_msb      = 0xFA - _rawdata
   _t_lsb      = 0xFB - _rawdata
   _t_xlsb     = 0xFC - _rawdata
   _h_msb      = 0xFD - _rawdata
   _h_lsb      = 0xFE - _rawdata

   _os_ms = [0, 1, 2, 4, 8, 16]

   def __init__(self, pi, sampling=OVER_SAMPLE_1,
                   bus=1, address=0x76,
                   channel=0, baud=10000000, flags=0):
      self.pi = pi

      self.sampling = sampling
      self.h = pi.i2c_open(bus, address)

      self._load_calibration()

      self.measure_delay = self._measurement_time(sampling, sampling, sampling)

      self.t_fine = 0.0

   def _measurement_time(self, os_temp, os_press, os_hum):
      ms = ( (1.25  + 2.3 * sensor._os_ms[os_temp]) +
             (0.575 + 2.3 * sensor._os_ms[os_press]) +
             (0.575 + 2.3 * sensor._os_ms[os_hum]) )
      return (ms/1000.0)

   def _u16(self, _calib, off):
      return (_calib[off] | (_calib[off+1]<<8))

   def _s16(self, _calib, off):
      v = self._u16(_calib, off)
      if v > 32767:
         v -= 65536
      return v

   def _u8(self, _calib, off):
      return _calib[off]

   def _s8(self, _calib, off):
      v = self._u8(_calib,off)
      if v > 127:
         v -= 256
      return v

   def _write_registers(self, data):
      self.pi.i2c_write_device(self.h, data)

   def _read_registers(self, reg, count):
      return self.pi.i2c_read_i2c_block_data(self.h, reg, count)

   def _load_calibration(self):

      c, d1 = self._read_registers(sensor._calib00, 26)

      self.T1 = self._u16(d1, sensor._T1)
      self.T2 = self._s16(d1, sensor._T2)
      self.T3 = self._s16(d1, sensor._T3)

      self.P1 = self._u16(d1, sensor._P1)
      self.P2 = self._s16(d1, sensor._P2)
      self.P3 = self._s16(d1, sensor._P3)
      self.P4 = self._s16(d1, sensor._P4)
      self.P5 = self._s16(d1, sensor._P5)
      self.P6 = self._s16(d1, sensor._P6)
      self.P7 = self._s16(d1, sensor._P7)
      self.P8 = self._s16(d1, sensor._P8)
      self.P9 = self._s16(d1, sensor._P9)

      self.H1 = self._u8(d1, sensor._H1)

      c, d2 = self._read_registers(sensor._calib26, 7)

      self.H2 = self._s16(d2, sensor._H2)

      self.H3 = self._u8(d2, sensor._H3)

      t = self._u8(d2, sensor._xE5)

      t_l = t & 15
      t_h = (t >> 4) & 15

      self.H4 = (self._u8(d2, sensor._xE4) << 4) | t_l

      if self.H4 > 2047:
         self.H4 -= 4096

      self.H5 = (self._u8(d2, sensor._xE6) << 4) | t_h

      if self.H5 > 2047:
         self.H5 -= 4096

      self.H6 = self._s8(d2, sensor._H6)

   def _read_raw_data(self):

      # Set oversampling rate and force reading.

      self._write_registers(
         [sensor._ctrl_hum, self.sampling,
          sensor._ctrl_meas, self.sampling << 5 | self.sampling << 2 | 1])

      # Measurement delay.

      time.sleep(self.measure_delay)

      # Grab reading.

      c, d = self._read_registers(sensor._rawdata, 8)

      msb = self._u8(d, sensor._t_msb)
      lsb = self._u8(d, sensor._t_lsb)
      xlsb = self._u8(d, sensor._t_xlsb)
      raw_t = ((msb << 16) | (lsb << 8) | xlsb) >> 4

      msb = self._u8(d, sensor._p_msb)
      lsb = self._u8(d, sensor._p_lsb)
      xlsb = self._u8(d, sensor._p_xlsb)
      raw_p = ((msb << 16) | (lsb << 8) | xlsb) >> 4

      msb = self._u8(d, sensor._h_msb)
      lsb = self._u8(d, sensor._h_lsb)
      raw_h = (msb << 8) | lsb

      return raw_t, raw_p, raw_h

   def read_data(self):
      """
      Returns the temperature, pressure, and humidity as a tuple.

      Each value is a float.

      The temperature is returned in degrees centigrade.  The
      pressure is returned in Pascals.  The humidity is returned
      as the relative humidity between 0 and 100%.
      """

      raw_t, raw_p, raw_h = self._read_raw_data()

      var1 = (raw_t/16384.0 - (self.T1)/1024.0) * float(self.T2)
      var2 = (((raw_t)/131072.0 - (self.T1)/8192.0) *
              ((raw_t)/131072.0 - (self.T1)/8192.0)) * (self.T3)

      self.t_fine = var1 + var2

      t = (var1 + var2) / 5120.0

      var1 = (self.t_fine/2.0) - 64000.0
      var2 = var1 * var1 * self.P6 / 32768.0
      var2 = var2 + (var1 * self.P5 * 2.0)
      var2 = (var2/4.0)+(self.P4 * 65536.0)
      var1 = ((self.P3 * var1 * var1 / 524288.0) + (self.P2 * var1)) / 524288.0
      var1 = (1.0 + var1 / 32768.0)*self.P1
      if var1 != 0.0:
         p = 1048576.0 - raw_p
         p = (p - (var2 / 4096.0)) * 6250.0 / var1
         var1 = self.P9 * p * p / 2147483648.0
         var2 = p * self.P8 / 32768.0
         p = p + (var1 + var2 + self.P7) / 16.0
      else:
         p = 0

      h = self.t_fine - 76800.0

      h = ( (raw_h - ((self.H4) * 64.0 + (self.H5) / 16384.0 * h)) *
            ((self.H2) / 65536.0 * (1.0 + (self.H6) / 67108864.0 * h *
            (1.0 + (self.H3) / 67108864.0 * h))))

      h = h * (1.0 - self.H1 * h / 524288.0)

      if h > 100.0:
         h = 100.0
      elif h < 0.0:
         h = 0.0

      return t, p, h

   def cancel(self):
      """
      Cancels the sensor and releases resources.
      """
      if self.h is not None:
         self.pi.i2c_close(self.h)
         self.h = None

if __name__ == "__main__":

   import time
   import BME280
   import pigpio

   pi = pigpio.pi()

   if not pi.connected:
      exit(0)

   s = BME280.sensor(pi)

   stop = time.time() + 60

   while stop > time.time():
      t, p, h = s.read_data()
      print("h={:.2f} p={:.1f} t={:.2f}".format(h, p/100.0, t))
      time.sleep(0.9)

   s.cancel()

   pi.stop()

Pythonスクリプト「BME280.py」を次のコマンドで実行します。

$ python3 BME280.py
h=57.33 p=1019.2 t=19.55
h=57.34 p=1019.2 t=19.56
h=57.34 p=1019.2 t=19.56
h=57.33 p=1019.2 t=19.57
h=57.36 p=1019.3 t=19.57
h=57.38 p=1019.3 t=19.56
h=57.38 p=1019.2 t=19.57
h=57.39 p=1019.3 t=19.58

ロジアナでI2C信号「SCL (シリアルクロック)」「SDA (シリアルデータ)」を測定しました。
ロジアナでI2C信号の測定

pigpioによるADC「MCP3208」の実装

Raspberry Pi 3にADC「MCP3208」をSPIインタフェースにより接続し、次のPythonスクリプト「spitest.py」を作成します。「

import time
import pigpio

#MCP3208から値を取得するクラス
class MCP3208_Class:
    channel = 1
    baud = 50000
    flags = 0

    """コンストラクタ"""
    def __init__(self, pi,ref_volts):
        self.pi = pi
        self.ref_volts = ref_volts
        self.h = pi.spi_open(self.channel, self.baud, self.flags)

    """電圧取得"""
    def GetVoltage(self,ch):
#        c, raw = self.pi.spi_xfer(self.h,[0x6,(8+ch)<<4,0])
#        c, raw = self.pi.spi_xfer(self.h,[0x6,ch<<6,0])
        c, raw = self.pi.spi_xfer(self.h,[1,(8+ch)<<4,0])
        print("c: {0} raw: {1}".format(c, raw))
        raw2 = ((raw[1]&3) << 8) + raw[2]
        volts = (raw2 * self.ref_volts ) / float(1023)
        volts = round(volts,4)
        return volts

    """終了処理"""
    def Cleanup(self):
        self.pi.spi_close(self.h)

"""メイン関数"""
if __name__ == '__main__':
    pi = pigpio.pi()

    if not pi.connected:
       exit(0)

    ADC = MCP3208_Class(pi,ref_volts=3.3)
    try:
        while True:
            volts = ADC.GetVoltage(ch=0)
            print("volts ch0: {:8.2f}".format(volts))
            volts = ADC.GetVoltage(ch=1)
            print("volts ch1: {:8.2f}".format(volts))
            volts = ADC.GetVoltage(ch=2)
            print("volts ch2: {:8.2f}".format(volts))
            time.sleep(1)

    except KeyboardInterrupt  :         #Ctl+Cが押されたらループを終了
        print("\nCtl+C")
    except Exception as e:
        print(str(e))
    finally:
        ADC.Cleanup()
        print("\nexit program")

Pythonスクリプト「spitest.py」を次のコマンドで実行します。

$ python3 spitest.py
c: 3 raw: bytearray(b'\x00\x02\xff')
volts ch0:     2.47
c: 3 raw: bytearray(b'\x00\x01\x96')
volts ch1:     1.31
c: 3 raw: bytearray(b'\x00\x00\x03')
volts ch2:     0.01
c: 3 raw: bytearray(b'\x00\x03\x17')
volts ch0:     2.55
c: 3 raw: bytearray(b'\x00\x01\x95')
volts ch1:     1.31
c: 3 raw: bytearray(b'\x00\x00\x03')
volts ch2:     0.01

SPIインタフェースを「」にすると、MCP3208から「0」の値が入力されます。

ロジアナでSPI信号「MOSI(Master-Out Slave-In)」「MISO(Master-In Slave-Out)」「SCLK(Serial Clock)」「CE0/CE1」を測定しました。SCLKが50kHzになっていることが確認できます。
ロジアナでSPI信号の測定