ゆるく楽しむプログラミング

BMP280 (気温・気圧センサー)

使用しているのは、Seeeduino ビギナーキット for Arduino に付属の 気温・気圧センサー BMP280 です。
同シリーズの 湿度・気温・気圧センサー BME280 の方がメジャーなようです。

▼のBME280は3.3V~3Vで動作しますが、秋月電子やスイッチサイエンスで売られているBME280は3.3V用で
Arduinoで使用する場合にはレベルシフターが必要ですので注意。

※GROVEのBME280ユニットは3.3V~5Vです、ビックリするほど高いですけど。
※M5StackのBMP280ユニット(SKU:U090)はリーザナブルなお値段です。
※マルツオンラインだと600円位で3.3V~5V動作品の扱いがあります。(Mouser 部品番号 426-SEN0372 )



動作モードの設定

動作動作モード、各レジスタの詳細は BMP280のスペックシート を参照
※BME280ですが、日本語訳のスペックシートも参考になります。

I2Cアドレスは0x77か0x76に設定できるようです。

■BMP280のレジスタ
レジスタ名 アドレス bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0
気温XLSB 0xFC 気温XLSB<7:4> 0 0 0 0
気温LSB 0xFB 気温LSB<7:0>
気温MSB 0xFA 気温MSB<7:0>
気圧XLSB 0xF9 気圧XLSB<7:4> 0 0 0 0
気圧LSB 0xF8 気圧LSB<7:0>
気圧MSB 0xF7 気圧MSB<7:0>
CONFIG 0xF5
CNTL_MEAS 0xF4 気温オーバーサンプリング[2:0] 気圧オーバーサンプリング[2:0] 動作モード[1:0]
STATUS 0xF3 計測状態 レジスタ状態
RESET 0xE0 リセット[7:0]
ID 0xD0 チップID[7:0]
補正値 0xA1~0x88
補正値データ 24Byte(12Word)


■リセット
 0xB6を書き込むとリセット。 0xB6以外の値を書き込んでもリセットしない。
 再起動には2m秒かかる。

■ オーバーサンプリング
 センサーの読み取り値を平均化する事で、突発的な変動(イリーガルデータ)の影響を抑える働きをすると思われます。(自信なし)
温度
設定値 
オーバーサンプリング  準備時間
[m秒] 
 000  計測しない
 (計測結果には0x80000 が返る)
 0.5
 001  x1  62.5
 010  x2  125
 011  x4  250
 100  x8  500
 101  x16  1000
 110  x16  2000
 111  x16  4000

気圧
設定値 
気圧
フィルター係数 
 処理応答の75%以上を
達成するためのサンプル数
 000  計測しない
 (計測結果には0x80000 が返る)
 - 
 001   OFF  1
 010  2  2
 011  4  5
 100  8  11
 101/110/111 16  22

■動作モード
 
設定値  動作モード
 00 スリープモード ※消費電力が少ない
 01/10 Forcedモード  ※一度だけ計測、計測後スリープモードに移行
 11 Normalモード  ※オーバーサンプリングを行う場合はNormalモードにする

気温・湿度の読み取り

気温・気圧は20bitの数値として取得できますが、読み込んだ値はそのままでは気温・気圧として使用できません。
データの補正が必要です。

補正に必要なパラメタは アドレス 0xA1~0x88 のレジスタから読みだして使用します。
※リトルインディアンで格納されているのでArduino/RaspberruPi Picoで使用する場合にはビックインディアンに変換する必要があります。

レジスタアドレス
LSB/MSB 
レジスタ名  データの型 
 0x88/0x89  dig_T1  unsigned short
 0x8A/0x8B  dig_T2  signed shot
 0x8C/0x8D  dig_T3  signed shot
 0x8E/0x8F  dig_P1  unsigned short
 0x90/0x91  dig_P2  signed shot
 0x92/0x93  dig_P3  signed shot
 0x94/0x95  dig_P4  signed shot
 0x96/0x97  dig_P5  signed shot
 0x98/0x99  dig_P6  signed shot
 0x9A/0x9B  dig_P7  signed shot
 0x9C/0x9D  dig_P8  signed shot
 0x9E/0x9F  dig_P9  signed shot

※ 補正式はデータシートに記載されています。データシートを参照してください。
変数のサイズと符号に注意
  BMP280_S32_t は符号付きの32Bit変数(int32_t) 、BMP280_U32_t は符号なしの32Bit変数(uint32_t)
  BMP280_S64_t 符号付きの64Bit変数(int64_t)、BMP280_U64_tは符号なしの64Bit変数(uint64_t)です。
 


読み込みテスト

BMP280-TEST1.zip
※ <>を全角<>に置き換えています。コピペの際は注意してください。
#include <Wire.h>

#define BMP280_ADDR     0x77
#define BMP280_ID       0xD0
#define BMP280_RESET    0xE0
#define BMP280_CONTROL  0xF4
#define BMP280_PRESSURE 0xF7
#define BMP280_TEMP     0xFA

#define BMP280_DIG_T1    0x88
#define BMP280_DIG_T2    0x8A
#define BMP280_DIG_T3    0x8C
#define BMP280_DIG_P1    0x8E
#define BMP280_DIG_P2    0x90
#define BMP280_DIG_P3    0x92
#define BMP280_DIG_P4    0x94
#define BMP280_DIG_P5    0x96
#define BMP280_DIG_P6    0x98
#define BMP280_DIG_P7    0x9A
#define BMP280_DIG_P8    0x9C
#define BMP280_DIG_P9    0x9E

#define BMP280_T1 bmp280DIG[0]
#define BMP280_T2 bmp280DIG[1]
#define BMP280_T3 bmp280DIG[2]
#define BMP280_P1 bmp280DIG[3]
#define BMP280_P2 bmp280DIG[4]
#define BMP280_P3 bmp280DIG[5]
#define BMP280_P4 bmp280DIG[6]
#define BMP280_P5 bmp280DIG[7]
#define BMP280_P6 bmp280DIG[8]
#define BMP280_P7 bmp280DIG[9]
#define BMP280_P8 bmp280DIG[10]
#define BMP280_P9 bmp280DIG[11]

uint16_t bmp280DIG[12] ; // 0-3 : T1-T3 / 3-11 : P1-P9
int32_t t_fine;

char str[120] ;

// 1バイトコマンドを送信
void sendBMP280(uint8_t addr , uint8_t command) {
  Wire.beginTransmission(BMP280_ADDR);
  Wire.write(addr);
  Wire.write(command);
  Wire.endTransmission();
}

// センサー読み取り
uint32_t readBMP32b20(uint8_t addr) {
  uint32_t sensorData = 0 ;
  uint8_t readData ;

  // Read Address Set
  Wire.beginTransmission(BMP280_ADDR);
  Wire.write(addr);
  Wire.endTransmission();
  
  // 3Byte Read
  Wire.requestFrom(BMP280_ADDR, 3);
  while(Wire.available()<3);
  readData = Wire.read();
  sensorData = readData ;
  readData = Wire.read();
  sensorData = (sensorData << 8) | readData ;
  readData = Wire.read();
  sensorData = (sensorData << 8) | readData ;
  sensorData >>= 4 ;

  return sensorData ;
}

// 補正値読み取り
uint16_t readBMP32b16LE(uint8_t addr) {
  uint16_t readData ;
  // Read Address Set
  Wire.beginTransmission(BMP280_ADDR);
  Wire.write(addr);
  Wire.endTransmission();
  
  // 2Byte Read
  Wire.requestFrom(BMP280_ADDR, 2);
  while(Wire.available()<2);
  uint8_t d1 = Wire.read();
  uint8_t d2 = Wire.read();
  readData = d2 << 8 | d1 ;
  // ▼ こんな書き方も出来るけど…あまりお勧めしない
  // ▼ 何をやってるのか直感的に分かりにくいから
  // ((uint8_t *)&readData)[0] = d1 ;
  // ((uint8_t *)&readData)[1] = d2 ;

  return readData ;
}

// Register読み取り
uint8_t readBMP32b8(uint8_t addr) {
  uint8_t readData ;
  // Read Address Set
  Wire.beginTransmission(BMP280_ADDR);
  Wire.write(addr);
  Wire.endTransmission();
  
  // 1Byte Read
  Wire.requestFrom(BMP280_ADDR, 1);
  while(Wire.available()<1);
  readData = Wire.read();

  return readData ;
}

// 温度読み取り
int32_t readBMP32Tmp() {
  uint32_t temData = readBMP32b20(BMP280_TEMP) ;

  // ----- DATA 補正 -----
  int32_t var1, var2;
  uint16_t t1 = BMP280_T1 ;
  int16_t t2 = (int16_t)BMP280_T2,t3=(int16_t)BMP280_T3 ;
  var1 = (((temData >> 3) - ((int32_t)(t1 << 1))) * ((int32_t)t2)) >> 11;
  var2 = (((((temData >> 4) - ((int32_t)t1)) * ((temData >> 4) - ((int32_t)t1))) >> 12) * ((int32_t)t3)) >> 14;

  t_fine = var1 + var2;
  temData = (t_fine * 5 + 128) >> 8;

  return temData ;
}

// 気圧読み取り
int32_t readBMP32Pre() {
  readBMP32Tmp(); // 気温センサー読み取り
  uint32_t preData = readBMP32b20(BMP280_PRESSURE) ; // 気圧センサー読み取り

  // ----- DATA 補正 -----
  int64_t var1, var2 , p;
  uint16_t p1 = BMP280_P1 ;
  int16_t p2 = (int16_t)BMP280_P2,p3=(int16_t)BMP280_P3 ;
  int16_t p4 = (int16_t)BMP280_P4,p5=(int16_t)BMP280_P5 ;
  int16_t p6 = (int16_t)BMP280_P6,p7=(int16_t)BMP280_P7 ;
  int16_t p8 = (int16_t)BMP280_P8,p9=(int16_t)BMP280_P9 ;

  var1 = ((int64_t)t_fine) - 128000;
  var2 = var1 * var1 * (int64_t)p6;
  var2 = var2 + ((var1*(int64_t)p5)<<17);
  var2 = var2 + (((int64_t)p4)<<35);
  var1 = ((var1 * var1 * (int64_t)p3)>>8) + ((var1 * (int64_t)p2)<<12);
  var1 = (((((int64_t)1)<<47)+var1))*((int64_t)p1)>>33;
  if (var1 == 0) {
    return 0; // avoid exception caused by division by zero
  }

  p = 1048576-preData;
  p = (((p<<31)-var2)*3125)/var1;
  var1 = (((int64_t)p9) * (p>>13) * (p>>13)) >> 25;
  var2 = (((int64_t)p8) * p) >> 19;
  p = ((p + var1 + var2) >> 8) + (((int64_t)p7)<<4);

  preData = (uint32_t)p /256 ;

  return preData ;
}


void setup() {
  Serial.begin(9600);
  Wire.begin();

   Serial.println("START");
  // BMP280リセット
  sendBMP280(BMP280_RESET,0xB6) ;
  delay(4) ;  // 通常軌道に2m秒を要するので、多めに4m秒待ってみる

  if (readBMP32b8(BMP280_ID) != 0x58) { // デバイスIDチェック
     Serial.println("Device is not BMP280"); // チェックする必要はない気がする、間違う奴はおらんやろう
  }
   Serial.println("CHECK ID - OK");

  // 補正データ読み出し
  BMP280_T1 = readBMP32b16LE(BMP280_DIG_T1) ;
  BMP280_T2 = readBMP32b16LE(BMP280_DIG_T2) ;
  BMP280_T3 = readBMP32b16LE(BMP280_DIG_T3) ;
  sprintf(str,"dig_T1=%d",BMP280_T1) ;  Serial.println(str) ;
  sprintf(str,"dig_T2=%d",BMP280_T2) ;  Serial.println(str) ;
  sprintf(str,"dig_T3=%d",BMP280_T3) ;  Serial.println(str) ;

  BMP280_P1 = readBMP32b16LE(BMP280_DIG_P1) ;
  BMP280_P2 = readBMP32b16LE(BMP280_DIG_P2) ;
  BMP280_P3 = readBMP32b16LE(BMP280_DIG_P3) ;
  BMP280_P4 = readBMP32b16LE(BMP280_DIG_P4) ;
  BMP280_P5 = readBMP32b16LE(BMP280_DIG_P5) ;
  BMP280_P6 = readBMP32b16LE(BMP280_DIG_P6) ;
  BMP280_P7 = readBMP32b16LE(BMP280_DIG_P7) ;
  BMP280_P8 = readBMP32b16LE(BMP280_DIG_P8) ;
  BMP280_P9 = readBMP32b16LE(BMP280_DIG_P9) ;

  // 動作モード設定
  sendBMP280(BMP280_CONTROL,0b00111111) ; // 001:温度の解像度 111:気圧の解像度 11:動作モード(ノーマルモード)
}

void loop() {
  long tmp = readBMP32Tmp() ;
  int tn=tmp/100 ;
  int td=tmp%100 ;
  sprintf(str,"%d.%02d℃  ",tn,td) ;
  Serial.print(str) ;

  long pre = readBMP32Pre() ;
  sprintf(str,"%dhPa",pre/100) ;
  Serial.println(str) ;

  delay(3000) ;
  
}

気温・気圧計DEMO

気温・気圧計のデモプログラム
I2C接続のSSD1306またはSSD1315有機LEモニターを使用してくだい。

BMP280-DEMO.zip ※Arduino用
BMP280-DEMO-Pico.zip ※RaspberryPi Pico用
 フォントデータの置き場所の問題でプログラムを分けています。
 フォントデータの定義(static or const)が違うだけでロジックは同じです





解説動画