稼働中

マイクロビット(e_43)大気圧センサー BMP280

BMP280

大気圧センサーモジュールGY-BMP280-3.3をmicro:bitのMicroPythonで使ってみました。
温度と気圧(圧力)が測定できます。BMP280はI2C、SPI通信で制御できます。
補償計算(Compensation formula)が非常にややこしかったです。計算の内容はわかりません。械的にデータシートのサンプルプログラムを焼き直しました。機械的とはいえ大変で疲れました。
※GY-BMP280-3.3をBMP280と記載する場合があります。

外観

GY-BMP280-3.3の外観です。
BMP280外観1 BMP280外観2

接続例

GY-BMP280は3.3Vで動作するのでmicro:bitと直接接続しました。
BMP280は設定によってI2C、SPI通信で動作が可能です。今回はI2C通信で制御します。そのためCSB(chip select)をVCCに接続しました。SOD(serial data out)をGNDに接続したのでスレーブアドレスは0x76になります。VCCに接続すると0x77になります。
接続例
※micro:bitのピン配置図は「https://tech.microbit.org/hardware/edgeconnector/」からの引用です。

実体図

以下の写真が実体配線図になります。
実体図
ちなみにI2Cデバイスのスレーブアドレスは簡単にわかります。※thonny-microbitのMicroPythonを使っています。


>>> from microbit import i2c
>>> i2c.scan()
[14, 29, 118]
>>> hex(118)
'0x76'
>>>

I2C通信が出来ています。118(0x76)がSODをGNDに接続したGY-BMP280です。
14(0xe)がMAG3110(地磁気センサー)、29(0x1d)がMMA8653(加速度センサー)でmicro:bitに搭載されているI2Cデバイスです。

スクリプト

GY-BMP280で気圧、温度を測定します。単純にレジスタデータを読み出すだけで無く、何をしているのかよくわからない補償計算とかあります。
いろいろとややこしいので、補償用データを抽出するスクリプトと測定結果を表示するスクリプトを分けました。※詳しくはデータシートを参照ください。
以下「補償係数を読み出す>初期設定をする(設定、測定条件)>測定データを読む(温度、圧力)>補償計算をする>測定結果を表示する」の順番で記載して行こうと思います。

補償係数を読み出す

デバイス個別に補償データがnon-volatile memory (NVM)に保存されているそうです。温度、圧力のレジスタデータをこのデータを使って補償計算します。何をどうしているのかよく分かりません。
データシートに則って機械的にやるだけです。

下図のdig_T1・・dig_P1・・はデータシートにある補償計算のサンプルプログラムの中で同名の変数で使われています。そのため読み出した補償係数データは同名に近い形で保存することにします。

具体的には、digT[]、digP[]のリスト型で保存しました。従って、補償計算ではdig_T1はdigT[0]・・dig_P1はdigP[0]・・に読み替えて使うことになります。

また、dig_T1・・dig_P1・・はDataTypeがあり、signed shortのデータは符号計算が必要になります。符号計算を行い、その結果をdigT[]、digP[]に保管します。
補償データ
※BMP280データシートより

■スクリプト


from microbit import i2c
# 補償用データを抽出>>リストdigT、digPに保存
addr=0x76               # BMP280のスレーブアドレス
#i2c.init(freq=100000, sda=pin20, scl=pin19)
i2c.init()
# Compensation
digT = [] # 補償用データ配列に保管
digP = []
# 読み出し開始レジスタの番地を設定
def r_reg(dat):
    buf=bytearray(1)
    buf[0]=dat
    i2c.write(addr,buf)
# signed dataの符号計算
def sig(dat):
    if dat >= (2**15):
        dat=dat-(2**16)
    return dat
# 補償データ読出し
def read_compensate():
    ## dig_T1~T3 0x88~0x8d 6byte
    r_reg(0x88)
    com_t=i2c.read(addr,6)    # dig_T1~T3 6byte
    # 16bitデータに変換
    for i in range(0,6,2):
        digT.append((com_t[i+1] <<8) | (com_t[i])) 
    for i in range(1,3):      # dig_T2,T3 signed data   
        digT[i]=sig(digT[i])
    ## dig_P1~P9 0x8E~0x9f 18byte
    r_reg(0x8E)
    com_p=i2c.read(addr,18)   # dig_P1~P9 18byte
    # 16bitデータに変換
    for i in range(0,18,2):
        digP.append((com_p[i+1] <<8) | (com_p[i])) 
    for i in range(1,9):      # dig_P2~P9 signed data 
        digP[i]=sig(digP[i])

read_compensate()
print('digT',digT)
print('digP',digP)

■大まかな説明
dig_T1を例として説明します。dig_T1は0x88/0x89で16bitのデータになります。そのためMSBの0x89のデータを<<8で8bitシフトさせて、LSBの0x89のデータと和をとって16bitデータにします。
同様にdig_T2、dig_T3を16bitデータにしてdigT[]に追加して保存します。
従って、digT[0]がdig_T1の16ビットデータ、digT[1]がdig_T2・・digT[2]がdig_T3となります。
次に、dig_T2(digT[1])dig_T3(digT[2])は符号付きデータなので変換して再保管します。
dig_P1~P9も同じように処理をしてdigP[]に保存しました。

■実行結果
Thonnyのshellに表示されます。これで補償用データを抽出して補償係数を算出できました。


>>> %Run bmp280_comp_readout_01b.py
digT [27981, 26705, -1000]
digP [37717, -10811, 3024, 2557, 124, -7, 15500, -14600, 6000]

初期設定をする

設定、測定条件を設定します。赤枠のconfig、ctrl_measレジスタに条件を書込みます。
メモリーマップ
※BMP280データシートより 赤枠は追記

今回は次のように設定しました。
※t_sb、filter・・osrs_tなど記号の内容はデータシートを参照ください。


t_sb      101     standby time  1000msec(1sec)
filter    001     IIR filter    Filter無 
spi(en)    x0     spi          not use
従って、config(0xF5)には0b1010010('0x52')を書込みました。

osrs_t    001    oversampling ×1 16 bit / 0.0050 °C 
osrs_p    001    oversampling ×1 16 bit / 2.62 Pa (Ultra low power)
mode       11    power mode ノーマル
従ってctr_meas(0xF4)には0b00100111('0x27')を書込みました。

■スクリプト例
初期設定のスクリプトは以下のようにしました。


# write data regi-addr/data
def w_reg(reg,dat):
    buf=bytearray(2)
    buf[0]=reg
    buf[1]=dat
    i2c.write(addr,buf)
#BME280の設定
## mes_init ctr_meas(0xF4)'0x27'config(0xF5)'0x52'
def init_bme280():
    w_reg(0xF4,0x27)
    w_reg(0xF5,0x52)

測定データを読む

気圧、温度の測定データは、先のレジスタマップをみると0xF7~0xFCのレジスタ6bytesにあります。
0xF7から6byteを読み出します。

気圧(press)データを例にすると、
|0xF7|0xF8|0xF9[7:4]|の20bitのデータになります。
0xF7を<<16ビットシフト、0xF8を<<8ビットシフト、0xF9を>>4ビットシフトの和をとった20bitのデータになります。

■スクリプト例
測定データを読むのスクリプトは以下のようにしました。


# register address set
def r_reg(dat):
    buf=bytearray(1)
    buf[0]=dat
    i2c.write(addr,buf)

r_reg(0xF7)
dat=i2c.read(addr,6)   # dig_P1~P9 6byte
print('r_mdata=',dat)

#データ変換
dat_p = (dat[0] << 16 | dat[1] << 8 | dat[2]) >> 4  # 0xF7~0xF9 20bit
dat_t = (dat[3] << 16 | dat[4] << 8 | dat[5]) >> 4  # 0xFA~0xFC 20bit

補償計算をする

データシートに補償計算のサンプルプログラムがあります。
何をやってるのかよくわかりません。機械的にこれを焼き直して使います。
サンプルプログラム
※BMP280データシートより

■温度の補償計算
以下の点を変更してサンプルプログラムを焼き直しました。
t_fineはglobal値にする、関数の引数はadc_Tにする、(BMP280_S32_t)は削除する、dig_T2などはdigT[1]のようにリストデータに替える(付数値が1下がる)、末尾「;」は削除する、計算値を1/100を追加する


def bmp280_compensate_t(adc_T):
    global  t_fine
    var1  = ( ((adc_T>>3)-(digT[0]<<1)) * (digT[1]) ) >> 11
    var2  = (( ( ((adc_T>>4)-(digT[0])) * ((adc_T>>4)-(digT[0])) ) >> 12) \
             * (digT[2]))>> 14
    t_fine = var1 + var2 
    t = (t_fine * 5 + 128) >> 8
    t= t/100
    print(var1,var2,t_fine)
    return t 

■気圧の補償計算
以下の点を変更してサンプルプログラムを焼き直しました。
t_fineはglobal値にする(温度の補償計算値を先にする)、関数の引数はadc_Pにする、(BMP280_S32_t)は削除する、dig_P3などはdigP[2]のようにリストデータに替える(付数値が1下がる)、if文を修正、p=int(p)を追加する、末尾「;」は削除する、計算値を1/256/100を追加する


def bmp280_compensate_p(adc_P):
    global  t_fine
    var1 = (t_fine)-128000 
    var2 = var1 * var1 * digP[5] 
    var2 = var2 + ((var1*digP[4])<<17) 
    var2 = var2 + ((digP[3])<<35) var1 = ( (var1 * var1 * digP[2]) >>8 ) + \
    ( (var1 * digP[1]) <<12 ) 
    var1 = ( ( ((1)<<47) +var1 ) )*(digP[0])>>33

    if var1 == 0:
        return 0 # avoid exception caused by division by zero 

    p = 1048576 - adc_P 
    p = ( ( (p<<31)-var2 )*3125 )/var1  # p(float)
    #print('p',p,type(p)) # p 6.7116e+09 <class 'float'>
    p=int(p)             # float >> int
    #print('p',p,type(p)) # p 6711599104 <class 'int'>
    var1 = (digP[8] * (p>>13) * (p>>13)) >> 25
    var2 = (digP[7] * p) >>19 
    p = ( (p + var1 + var2) >>8) + (digP[6] <<4)
    p = p / 256 / 100
    return p

測定結果を表示する

以上をまとめます。
press、tempの各々20ビットデータを補償計算式に送ってその結果を表示します。


##  測定データ読込>>表示出力
def read_data():
    ## 測定データ読出し press、temp 0xF7~0xFC 6byte
    r_reg(0xF7)
    dat=i2c.read(addr,6)   # dig_P1~P9 6byte
    #print('r_mdata=',dat)

    #データ変換
    dat_p = (dat[0] << 16 | dat[1] << 8 | dat[2]) >> 4  # 0xF7~0xF9 20bit
    dat_t = (dat[3] << 16 | dat[4] << 8 | dat[5]) >> 4  # 0xFA~0xFC 20bit

    #補償計算
    tmp=bmp280_compensate_t(dat_t)
    prs=bmp280_compensate_p(dat_p)

    #表示
    print('t = ' + str(tmp))
    print('p = ' + str(prs))

スクリプトを全整理

測定結果を表示するスクリプトは抽出した補償用データを組み込んで以下のようにしました。

全スクリプト


bmp280_comp_measout_01b.py
from microbit import i2c
# 抽出した補償用リストデータを使う、ビットシフトで計算
# org bmp280_test06_mesout_shift.py
addr=0x76       # スレーブアドレス
#i2c.init(freq=100000, sda=pin20, scl=pin19)
i2c.init()      # I2C初期化
# compensation
t_fine = 0.0    # 補償計算で使う補償された温度データ
# 読み出した補償係数のデータ(先のスクリプトで抽出したデータ)
digT=[27981, 26705, -1000]
digP=[37717, -10811, 3024, 2557, 124, -7, 15500, -14600, 6000]
# 読み出すレジスタのアドレスをセットする
def r_reg(dat):
    buf=bytearray(1)
    buf[0]=dat
    i2c.write(addr,buf)
# レジスタに書込む レジスタアドレスと書込むデータ 計2bytes
def w_reg(reg,dat):
    buf=bytearray(2)
    buf[0]=reg
    buf[1]=dat
    i2c.write(addr,buf)
#BME280の設定
## 初期設定 ctr_meas(0xF4)'0x27'config(0xF5)'0x52'
def init_bme280():
    w_reg(0xF4,0x27)
    w_reg(0xF5,0x52)
# 補償計算
def bmp280_compensate_t(adc_T):
    global  t_fine
    var1  = ( ((adc_T>>3)-(digT[0]<<1)) * (digT[1]) ) >> 11
    var2  = (( ( ((adc_T>>4)-(digT[0])) * ((adc_T>>4)-(digT[0])) ) >> 12) \
             * (digT[2]))>> 14
    t_fine = var1 + var2 
    t = (t_fine * 5 + 128) >> 8
    t= t/100
    #print(var1,var2,t_fine)
    return t 
def bmp280_compensate_p(adc_P):
    global  t_fine
    var1 = (t_fine)-128000 
    var2 = var1 * var1 * digP[5] 
    var2 = var2 + ((var1*digP[4])<<17) 
    var2 = var2 + ((digP[3])<<35) var1 = ( (var1 * var1 * digP[2]) >>8 ) \ 
           + ( (var1 * digP[1]) <<12 ) 
    var1 = ( ( ((1)<<47) +var1 ) )*(digP[0])>>33

    if var1 == 0:
        return 0 # avoid exception caused by division by zero 

    p = 1048576 - adc_P 
    p = ( ( (p<<31)-var2 )*3125 )/var1  # p(float)
    #print('p',p,type(p)) # p 6.7116e+09 <class 'float'>
    p=int(p)             # float >> int
    #print('p',p,type(p)) # p 6711599104 <class 'int'>
    var1 = (digP[8] * (p>>13) * (p>>13)) >> 25
    var2 = (digP[7] * p) >>19 
    p = ( (p + var1 + var2) >>8) + (digP[6] <<4) p = p / 256 / 100 return p 

## 圧力、温度データ読込>>補償計算>>表示出力
def read_data():
    ## temp press data register
    ## 測定データ読み込み press_msb 0xF7~temp_xlsb 0xFC  3+3 6byte
    r_reg(0xF7)
    dat=i2c.read(addr,6)   # dig_P1~P9 6byte
    #print('r_mdata=',dat)
    #データ変換
    dat_p = (dat[0] << 16 | dat[1] << 8 | dat[2]) >> 4  # 0xF7~0xF9 20bit
    dat_t = (dat[3] << 16 | dat[4] << 8 | dat[5]) >> 4  # 0xFA~0xFC 20bit
    #補償計算
    tmp=bmp280_compensate_t(dat_t)
    prs=bmp280_compensate_p(dat_p)
    #表示
    print('t = ' + str(tmp))
    print('p = ' + str(prs))

## MAIN --------------------
#BME280の初期化
init_bme280()

#測定データ読込>>表示出力
read_data()

実行結果

実行結果です。Thonnyのshellに表示されます。


>>> %Run bmp280_comp_measout_01b.py
t = 25.66
p = 1015.43
>>> %Run bmp280_comp_measout_01b.py
t = 25.66
p = 1015.39
>>> 

日本気象株式会社のサイト「お天気ナビゲータ」で確認してみました。18:00が表示した時刻でそれ以後は予測です。気圧(圧力)はスクリプトで表示された結果とほぼ一致しています。温度は5℃くらい違いますが室温と外気温の差だろうと思います。
お天気ナビゲータ
※お天気ナビゲータでの表示を引用

まとめ

micro:bitのMicroPythonでGY-BMP280-3.3を使って気圧を測定することが出来ました。BMP280より高価ですがBME280なら湿度も測れます。ほぼ同じ方法で使えるので機会があれば使ってみようと思います。

参考にしたサイト

「初心者のためのセンサーと測定入門」サイトの記事「https://s-design-tokyo.com/use-bme280/」を参考にさせていただきました。