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)が違うだけでロジックは同じです