稼働中

Raspberry Pi Pico(d_10)BMP280 大気圧センサー

BMP280  (I2C)

BMP280はDIGITAL PRESSURE SENSORです。I2C通信で制御します。Raspberry Pi PicoでBMP280搭載のモジュールを使ってみました。
補償計算(Compensation formula)はデータシートの資料を焼き直しています。
計算の中身はよく分かりませんので、詳細はデータシートを参照ください。
※micro:bitの記事e_43からPiPicoで使えるように追記、修正等をしています。

外観

BMP280モジュールの外観写真です。(以後はBMP280と記載します)
BMP280

接続例

BMP280は3.3Vで動作するのでRaspberry Pi Picoと直接接続しました。
BMP280は設定によってI2C、SPI通信で動作が可能です。今回はI2C通信で制御します。そのためCSB(chip select)をVCCに接続しました。
SOD(serial data out)をGNDに接続したのでスレーブアドレスは0x76になります。VCCに接続すると0x77になります。
I2C通信にはI2C1のSDA(GP10)SCL(GP11)を使いました。
BMP280接続図
接続出来たらI2Cデバイスのスレーブアドレスを調べてみます。
ThonnyのShellで確認します。BMP280のスレーブアドレスは以下のように確認できました。


>>> from machine import I2C,Pin
>>> i2c=I2C(1, scl=Pin(11), sda=Pin(10), freq=100_000)
>>> i2c.scan()
[118]
>>> hex(118)
'0x76'
>>> 

BMP280の説明

データシートから抜粋してBMP280の説明します。(「###」印は下方のスクリプトに対応する部分です。)
Memory map は下図のようになっています。レジスタ名とアドレスが分かります。
Memory map
※BOSCH社のBMP280のデータシートから抜粋

■temp、press
温度のデータはtemp_mlsb(0xfa),temp_lsb(0xfb),temp_xlsb(0xfc)の20bitです。
気圧のデータはpress_mlsb(0xf7),press_lsb(0xf8),press_xlsb(0xf9)の20bitです。
スクリプトでは以下のように、レジスタ0xF7から6bytesを読み、各々20bitデータに整理しています。
r_reg(0xF7)
dat=i2c.readfrom(addr,6)
dat_p = (dat[0] << 16 | dat[1] << 8 | dat[2]) >> 4
dat_t = (dat[3] << 16 | dat[4] << 8 | dat[5]) >> 4

■config(0xf5) ### def init_bmp280()
デバイスを設定します。
t_sb[2:0]でstandby time、filter[2:0]でフィルタ、spi3w_en[0]でSPIの可否を設定します。
スクリプトでは、
t_sb 101 1000msec(1sec)  standby time
filter 000 IIR filter Filter無
spi(en) 0 spi not use
にしました。
そのため、config(0xF5)は0b1010000(‘0xa0’)としました。

■ctrl_meas(0xf4) ### def init_bmp280()
測定の条件を設定します。
osrs_t[2:0]で温度測定のoversampling、osrs_p[2:0]で気圧測定のoversampling、mode[1:0]でパワーモードを設定します。
スクリプトでは、
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’)としました。

■calib25…calib00 ### def para_bmp280(),def sig(dat)
Compensation parameter(dig_T1,dig_T2…dig_P1,dig_T2)はデータタイプがあります。signed typeは符号計算が必要です。
スクリプトでは各々を読み出し、符号計算の有無を行い、
その結果データを補償用データ配列 digT = []、digP = [] に保管しています。

Compensation parameter
※BOSCH社のBMP280のデータシートから抜粋

補償計算をする

温度、気圧の値を得るには、各々20bitの温度データ、気圧データ、及び補償用配列データから補償計算をする必要があります。

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

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

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

スクリプト

以上をまとめてBMP280のスクリプトは以下のようにしました。
BMP280のID値、補償用データ配列、温度・気圧をThonnyのshell部に表示します。
※ThonnyでMicroPythonをRaspberry Pi Pico with RP2040にインストールして使っています。


pico_bmp280_readmes_03b.py
#!/usr/bin/env python
# -*- coding: utf-8 -*
from machine import I2C,Pin
import time

## BMP280 DIGITAL PRESSURE SENSOR / BOSCH
#slave address
addr=0x76    # [118] 0x76
#i2c_init
i2c=I2C(1, scl=Pin(11), sda=Pin(10), freq=100_000)
#Compensation data
digT = [] # 補償用データ配列に保管
digP = []

#t_fine carries fine temperature as global value
t_fine = 0.0

## read out
# read start register set (yomidasu register address set)
def r_reg(dat):
    buf=bytearray(1)
    buf[0]=dat
    i2c.writeto(addr,buf)
# data write resister/data
def w_reg(reg,dat):
    buf=bytearray(2)
    buf[0]=reg
    buf[1]=dat
    i2c.writeto(addr,buf)   
# signed data fugo keisan 16bit
def sig(dat):
    if dat >= (2**15):
        dat=dat-(2**16)
    return dat

# compensation parameter 補正データ読み込み 
def para_bmp280():    
    global digT
    global digP
    ## dig_T1~T3 0x88~0x8d 6byte
    r_reg(0x88)
    com_t=i2c.readfrom(addr,6)   # dig_T1~T3 6byte
    
    # 16bitデータに変換
    for i in range(0,6,2):
        digT.append((com_t[i+1] <<8) | (com_t[i])) # digT[0] unsigned # dig_T2,T3(digT1,2) signed data->fugou keisan
    for i in range(1,3):
        digT[i]=sig(digT[i])
    
    ## dig_P1~P9 0x8E~0x9f 18byte
    r_reg(0x8E)
    com_p=i2c.readfrom(addr,18)   # dig_P1~P9 6byte
    # 16bitデータに変換
    for i in range(0,18,2):
        digP.append((com_p[i+1] <<8) | (com_p[i])) # digP[0] unsigned # dig_P2~P9(digP1~P8) signed data->fugou keisan 
    for i in range(1,9):
        digP[i]=sig(digP[i])
    

## init BME280の測定条件設定
def init_bmp280():
    w_reg(0xF4,0x27)   #Register 0xF4 “ctrl_meas”
    w_reg(0xF5,0x52)   #Register 0xF5 “config” 
    time.sleep_ms(10)  


# sleep
def sleep_bmp280():
    w_reg(0xF4,0x24) #ctr_meas(0xF4) sleep 0b_00100100'0x24'
    time.sleep_ms(2)
    
# reset    
def reset_bmp280():
    w_reg(0xe0,0xb6) #Register 0xE0 “reset”    
    time.sleep_ms(2)
    
# compensation formula
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 measure_bmp280():
    ## temp press data register
    ## 測定データ読み込み press_msb 0xF7~temp_xlsb 0xFC  3+3 6byte
    r_reg(0xF7)
    dat=i2c.readfrom(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)
    return tmp,prs

# BMP280_ID
def id_bmp280():
    r_reg(0xD0) #Register 0xD0 “id” 
    dat=i2c.readfrom(addr,1)   # <class 'bytes'>
    #print('ID=',hex(dat[0]))
    return dat

## MAIN --------------------
## bmp280_ID register IDを表示
id=id_bmp280()
print('ID=',hex(id[0]))

## calibration data(Trimming parameter) を表示
para_bmp280()
print('digT',digT)
print('digP',digP)

## reset BMP280 リセット
reset_bmp280() # added line 

#BME280の初期化
init_bmp280()

## pressure and temperature calculating out
#測定データ読込>>値を表示出力
t,p= measure_bmp280()
print('t=',t)
print('p=',p)

実行結果

結果は以下のようになりました。※Thonnyのshellに表示されます。


pico_bmp280_readmes_03b.py
>>> %Run -c $EDITOR_CONTENT
ID= 0x58
digT [27981, 26705, -1000]
digP [37717, -10811, 3024, 2557, 124, -7, 15500, -14600, 6000]
t= 20.27
p= 1023.004

一度、実行するとThonnyのShellでも関数が使えます。
>>> measure_bmp280()
(20.26, 1022.943)
>>> 

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

まとめ

Raspberry Pi PicoでBMP280を使って温度、気圧の測定ができました。