5ドル!ラズパイ・ゼロ(Raspberry pi Zero)でIoT (40) 気圧センサ2 SPI MPL115A1

自分で温度補正をして気圧を求める

 フリースケール(現NXP)の大気圧センサMPL115A1をDIP化したモジュールを秋月電子通商から入手しました。

  MPL115A1使用大気圧センサーモジュール(SPI)

 細ピンのピンヘッダが付属していましたが、通常の太さのピンヘッダをはんだ付けしました。ブレッドボードに挿し込むときは細ピンのほうがストレスは少ないです。ジャンパ・ケーブルのメスを挿し込むとすぐに抜けてしまいます。

 モジュールには、電源が入ったことを示すLEDがついていて実験では便利です。本体の消費電流は5uAですが、LEDには数mA流れるので、省電力機器を予定している場合は、LEDを削除します。

 同社のページにリンクされているフリースケールのデータシートは古く、計算式も異なっています。新しいのはこちらです。

MPL115A1のスペック

  • 電源電圧;2.375~5V
  • 消費電流;5uA
  • 測定範囲;50~115kPa(1kPaは10hPa)
  • 分解能;0.15kPa
  • 確度;±1kPa
  • インターフェース;SPI

 はんだ付けは、ブレッドボード上で行うと、ゆがんだりせずに仕上がります。

接続

 データシートを読むと、CAP端子に1uFがGND間に入っていますが役目が不明です。入っていないと、読み出すたびにデータが20%ほど変化したので必須のようです。

 ラズパイとの接続は下記のようになります。

MPL115A1 ラズパイ
SCLK(8) 23番ピン(CLK)
Din(7) 19番ピン(MOSI)
Dout(6) 21番ピン(MISO)
/CS(5) 24番ピン(CE0)
Vdd(1) 1番ピン
GND(3) 5番ピン

 

レジスタ

 圧力と温度は10ビットで得られます。圧力は温度で補正が必要です。読み出しが正常でないときは50kPaが返ります。

 気圧の生データを補正するための係数を最初に読み出し、次式から補正した気圧Pcompを求めます。

Pcomp = a0 + (b1 +  c12*Tadc) * Padc + b2  * Tadc
  Padc 気圧の生データ
  Tadc 温度の生データ
  a0 気圧のオフセット係数
  b1 気圧の感度係数
  c11 気圧の直線性 (2次係数)。使われない。
  c12 温度感度の係数(TCS)
  b2 1次温度オフセット係数 (TCO)
  c22 2次温度オフセット係数。使われない。

 入力がないとき50kPa、フルスケール時115kPa、10ビット=2^10なので、実際の気圧は、

Pcomp*((115-50)/1024)+50
=Pcomp*0.0635+50

(2020/05/09)1023は間違いなので、1024に変更した。下記のスケッチも修正したが、実行結果は変更していない。

で求まります。単位はkPaですから、10倍すればhPa単位になります。

◆係数のフォーマット

a0(16ビット)符号S+整数部12ビット+小数部3ビットの固定小数点形式
  S I11 I10 I9 I8 I7 I6 I5 I4 I3 I2 I1 I0 . F2 F1 F0

b1(16ビット)符号S+整数部2ビット+小数部13ビットの固定小数点形式
  S I1 I0 . F12 F11 F10 F9 F8 F7 F6 F5 F4 F3 F2 F1 F0

b2(16ビット)符号S+整数部1ビット+小数部14ビットの固定小数点形式
  S I0 . F13 F12 F11 F10 F9 F8 F7 F6 F5 F4 F3 F2 F1 F0

c12(14ビット)符号S+整数部0+小数部13ビットの固定小数点形式(左詰め、9個の0が先頭に入る)
  S 0 . 000 000 000 F12 F11 F10 F9 F8 F7 F6 F5 F4 F3 F2 F1 F0

c11(14ビット)符号S+整数部0+小数部11ビット
  新しいデータシートでは不要になった。

c22(14ビット)符号S+整数部0+小数部10ビット
  新しいデータシートでは不要になった。

◆メモリ・マップ

アドレス 係数
00 気圧上位バイト
01 気圧下位バイト
02 温度上位バイト
03 温度下位バイト
04 a0上位バイト
05 a0下位バイト
06 b1上位バイト
07 b1下位バイト
08 b2上位バイト
09 b2下位バイト
0a c12上位バイト
0b c12下位バイト
0c c11上位バイト
0d c11下位バイト
0e c22上位バイト
0f c22下位バイト
12 変換開始

係数の処理

 係数で使われている固定小数点形式は、通常の数値と異なった扱いをします。最上位ビットは符号で、1なら負の値なので、全体のビットを反転し、LSBに1を加えます。正の整数部分はそのまま10進数に直せます。小数点以下は、重みをもちます。2進数に変更し、小数点第1位は2^-1、第2位は2^-2、第3位は2^-3のように重みを付け、小数点の桁数全部を加算して1を超えない数値が求まります。そのとき、0は0です。整数部分と小数部分を加算すると10進データに変換できます。

 係数はそのデバイス固有なので一度読み出せば何度も計算する必要がなく、手計算で求めて代入する方法が確実です。新しいデータシートには、サンプルがあるので、計算手順の確認ができます。

係数の読み出し

 SPIはCS信号をLowにしてコマンドを送ると同時にクロックも送ります。マスタのラズパイからはMOSIがコマンドで、CLKがクロックです。コマンドを送るとスレーブからはMISOにデータが送られてきます。データを受け取った後、CSをHighに戻して通信が終了します。

 これらの手順は、ラズパイではspidevライブラリを利用します。スレーブ・デバイスがデータを送ってくるタイミングはデバイスによって異なります。MPL115A1は、1バイトのコマンドを送った後ダミーの0x00を送るとそのときにデータを受け取ります。

 送るデータは次のようになります。最後にダミーの0x00を送るようにデータシートには書かれています。有効なデータは00hのときなので、奇数のデータを使います。

[88h], [00h], [8Ah], [00h], [8Ch], [00h], [8Eh], [00h], [90h], [00h], [92h], [00h],
[94h], [00h], [96h], [00h], [00h]

データの読み出し

 [24h], [00h]を送ったあと3ms待ち [80h], [00h], [82h], [00h], [84h], [00h], [86h], [00h], [00h]を送ります。有効なデータは00hのときなので、奇数のデータを使います。

補正データを求める

 a0、b1、b2は、読み出したデータdatahexと整数部分の桁数integerを受け取り、固定小数点形式を10進データに変換する関数kotei()を作って利用しました。負のデータと正のデータでは処理を分けています。最初の符号が’1’であれば、負の処理を行います。まず、ビットを反転し、LSBに'1'を加えます。Pythonでは~はビット反転をせず、+1してマイナス符号をつけるという動作をします。ビット反転は、ビットの長さ分だけの'1'を作ってXORします。

 I0は整数部分の切り出した数値です。これをビット反転しマイナス符号をつけたのが整数部分のIntegerです。F0は小数分を切り出した数値です。それぞれの桁で2^-nの重み付けをして合算しマイナス符号をつけたのがFactionalです。

 正の整数は、符号ビットをマスクし、桁数分右にシフトをするとIntegerが求まります。fdata3は小数点部分の数値の文字列を求めています。負の小数と同じく、それぞれの桁で2^-nの重み付けをして合算しマイナス符号をつけたのがFactionalです。


def kotei(datahex,integer) :
if (datahex & 0b1000000000000000 ) :
I1=(~datahex ^ 0xffff)
I0=bin(datahex)[3:(3+integer)]
Integer=-(int(I0,2) ^ int(("1"*integer),2))
F0= bin(I1)[(3+integer):]
Fractional =-sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(F0)])
else:
Integer=(datahex & 0b0111111111111111 )>> (15-integer)
fdata3=str(bin((datahex & 0b0111111111111111 ) & int( '1' * (15-integer),2)))[2:]
Fractional =sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(fdata3)])
return(Integer+Fractional)

 c12は、読み出したデータを右にシフトし、先頭に0を9個入れ、固定小数点形式の小数部分を計算します。データに対してbin()関数を使うと先頭の0は捨てられます。ここではデータシートと同じく0x3xxxが読み出され最初の桁は'0011'なので、0を二つ追加しました。

c12= ((adc[13] << 8) | adc[15])>>2
D='0000000000'+str(bin(c12))[2:]
c12 = sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(D)])

 この計算結果は、秋月電子通商のサンプル (((adc[13]<<8) | adc[15])>>2)/4194304.0 と同じ結果になります。

 何度も使っている固定小数点形式の小数部分

sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(data)])

は、下記のルーチンを内包表現にできるように変えたものです。

data="110"#sample
sum0 =Decimal(0)
for (i,x) in enumerate(data) :
sum0 += Decimal(x)*Decimal(2**(-(i+1)))

プログラム

#!/usr/bin/env python
from decimal import *
import spidev
import time
spi = spidev.SpiDev()
spi.open(0,0) #port 0,cs 0
spi.bits_per_word = 8
spi.max_speed_hz = 100000

def kotei(datahex,integer) :
if (datahex & 0b1000000000000000 ) :
I1=(~datahex ^ 0xffff)
I0=bin(datahex)[3:(3+integer)]
Integer=-(int(I0,2) ^ int(("1"*integer),2))
F0= bin(I1)[(3+integer):]
Fractional =-sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(F0)])
else:
Integer=(datahex & 0b0111111111111111 )>> (15-integer)
fdata3=str(bin((datahex & 0b0111111111111111 ) & int( '1' * (15-integer),2)))[2:]
Fractional =sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(fdata3)])
#print Integer,Fractional,(Integer+Fractional)
return(Integer+Fractional)

#main
adc = spi.xfer2([0x88,0x00,0x8a,0x00,0x8c,0x00,0x8e,0x00,0x90,0x00,0x92,0x00,0x94,0x00,0x96,0x00,0x00])
a0 = (adc[1] << 8) | adc[3]
b1 = (adc[5] << 8) | adc[7]
b2 = (adc[9] << 8) | adc[11]
c12= ((adc[13] << 8) | adc[15])>>2
print "a0",kotei(a0,12)
print "b1",kotei(b1,2)
print "b2",kotei(b2,1)
a0k=kotei(a0,12)
b1k=kotei(b1,2)
b2k=kotei(b2,1)
D='0000000000'+str(bin(c12))[2:]
c12 = sum([(float(Decimal(x)*Decimal(2**(-(i+1))))) for (i,x) in enumerate(D)])
print "c12",c12
while 1 :
spi.xfer2([0x24,0x00])
time.sleep(0.003)
adc = spi.xfer2([0x80,0x00,0x82,0x00,0x84,0x00,0x86,0x00,0x00])
press = ((adc[1] << 8) | adc[3])>>6
temp = ((adc[5] << 8) | adc[7])>>6
Pcomp = a0k +(b1k+c12*temp)*press+b2k*temp
hPa=(Pcomp*(65/1024.0)+50)*10
print press,temp,round(Pcomp),round(hPa),"hPa"
time.sleep(1)



実行結果
 読み出したデータです。

※補正値について。Sparkfunおよび秋月電子通商のコード( Arduino動作確認資料用のライブラリ)を実行して、a0、b1、C12は全桁同じですが、b2は0.5%の差が出ています。プログラムに問題があるかもしれません。

※2017/12/30 プログラム14行目F0= bin(I1)[(3+integer):] をF0= bin(I1)[(2+integer):]に。取り出す小数部分の桁数を正しく修正すると、b2の値も一致しますが、1040hPa付近まで気圧が上がったので、まだバグが残っているかもしれません。

※固定小数点形式の小数部分 参考URL http://d.hatena.ne.jp/r_ikeda/20111030/decimalbin

※執筆時点;2017-11-29版をダウンロードし、sudo apt-get update と sudo apt-get upgrade -y および sudo rpi-update で更新し、カーネルはuname -a で確認。4.9.70でした。

※プログラムを仮にmpl115.pyという名前で/home/piに保存すると、sudo chmod 755 mpl115.py で実行権を付け、ターミナルから、python mpl115.pyで実行します。I2CやSPIのグループにpiユーザが属しているので、sudoは不要です。
 プログラム・リストは、表示の関係でTabキーが無視されるので、スペースに代えてあります。また、リスト中を2回クリックすると全選択になるので、CTRL-Cでコピーし、テキスト・エディタにCTRL-Vで貼り付けて利用してください。ラズパイに持っていくと、スペースやリターン・コードなどが化けていることがあるので、一度消して、ラズパイのテキスト・エディタで修正してください。

※I2Cの有効化は、こちらの説明を参照ください。SPIなどにもEnableにチェックを入れています。