悪名高きBME280を使う?
AHT21B、SHT40i、SHT31ときてBME280を使おうかなとGeminiやCopilotと相談してみた。ちなみに間違って買ったBMP280はBME280と同じ使い方で良いらしく、単純に湿度が0%になるだけだ。
なぜBME280の悪名が高いのかというと、得られたデータの変換式が意味不明なためだ。
スケッチの方はこれまでと同じように単純だ。アドレスは0x76と0x77が選べるようになっている。
#include <Wire.h>
#include "BME280.h"
BME280 meas=BME280(0x77);
void setup() {
Serial.begin(115200);
Wire.begin();
meas.begin();
}
byte cnt=0;
void loop() {
meas.measure();
Serial.print(cnt++);
Serial.print(", BMP280, MEAS-T-H-P, ");
Serial.print(meas.temperature()); Serial.print(", ");
Serial.print(meas.humidity()); Serial.print(", ");
Serial.println(meas.pressure());
delay(1000);
}
ヘッダファイル「BME280.h」
BME280特有のたくさんの補正パラメータが「private:」以下に示してある。どうも符号付きだか符号無しだか8ビットなのか16ビットなのか32ビットなのか、どうなってんの。
BME280.h
#ifndef BME280_h
#define BME280_h
#include <inttypes.h>
#include "Arduino.h"
#include <Wire.h>
#define CONFIG 0xF5
#define CTRL_MEAS 0xF4
#define CTRL_HUM 0xF2
class BME280 {
public:
BME280(uint8_t adrs);
void begin(void);
void measure(void);
float temperature(void);
float humidity(void);
float pressure(void);
private:
void command(uint8_t reg, uint8_t val);
void readReg(uint8_t reg, uint8_t *buf, int n);
void setCalibParm(void);
uint16_t _T1; int16_t _T2, _T3;
uint16_t _P1; int16_t _P2, _P3, _P4, _P5, _P6, _P7, _P8, _P9;
uint8_t _H1; int16_t _H2; uint8_t _H3; int16_t _H4; int16_t _H5; int8_t _H6;
uint8_t dac[26];
int32_t t_fine;
int32_t adc_P, adc_T, adc_H;
uint8_t _adrs;
};
#endif
ライブラリ本体「BME280.cpp」
あまりにも補正式が複雑なので、AIに聞いて書いてもらった。beginは適切なコマンドを送るだけなので特に難しくない。
void BME280::begin(void) {
delay(100);
command(CONFIG, 0x00);
command(CTRL_MEAS, 0x24);
command(CTRL_HUM, 0x01);
setCalibParm();
delay(1000);
}
ただし、補正パラメータを取り出しておく必要がある。なんかシフトしすぎだよね。。。
void BME280::setCalibParm(void){
readReg(0x88,dac,26);
_T1 = ((uint16_t)((dac[1] << 8) | dac[0]));
_T2 = ((int16_t)((dac[3] << 8) | dac[2]));
_T3 = ((int16_t)((dac[5] << 8) | dac[4]));
_P1 = ((uint16_t)((dac[7] << 8) | dac[6]));
_P2 = ((int16_t)((dac[9] << 8) | dac[8]));
_P3 = ((int16_t)((dac[11] << 8) | dac[10]));
_P4 = ((int16_t)((dac[13] << 8) | dac[12]));
_P5 = ((int16_t)((dac[15] << 8) | dac[14]));
_P6 = ((int16_t)((dac[17] << 8) | dac[16]));
_P7 = ((int16_t)((dac[19] << 8) | dac[18]));
_P8 = ((int16_t)((dac[21] << 8) | dac[20]));
_P9 = ((int16_t)((dac[23] << 8) | dac[22]));
_H1 = ((uint8_t)(dac[25]));
readReg(0xE1,dac,7);
_H2 = ((int16_t)((dac[1] << 8) | dac[0]));
_H3 = ((uint8_t)(dac[2]));
_H4 = ((int16_t)((dac[3] << 4) + (dac[4] & 0x0F)));
_H5 = ((int16_t)((dac[5] << 4) + ((dac[4] >> 4) & 0x0F)));
_H6 = ((int8_t)dac[6]);
}
この後のmeasure関数では測定コマンド0x25を打って8バイトを読み出し、温度補正用t_fineを計算しておく。何をやっているのか本当に意味不明だ。
void BME280::measure(void) {
command(CTRL_MEAS, 0x25);
delay(10);//10msec待機
readReg(0xF7,dac,8);
adc_P = ((uint32_t)dac[0] << 12) | ((uint32_t)dac[1] << 4) | ((dac[2] >> 4) & 0x0F);
adc_T = ((uint32_t)dac[3] << 12) | ((uint32_t)dac[4] << 4) | ((dac[5] >> 4) & 0x0F);
adc_H = ((uint32_t)dac[6] << 8) | ((uint32_t)dac[7]);
int32_t var1, var2;
var1 = ((((adc_T>>3) - ((int32_t)_T1<<1))) * ((int32_t)_T2)) >> 11;
var2 = (((((adc_T>>4) - ((int32_t)_T1)) * ((adc_T>>4) - ((int32_t)_T1))) >> 12) * ((int32_t)_T3)) >> 14;
t_fine = var1 + var2;
}
気圧を補正するpressureの中身も意味不明なままで、全く理解できない。
float BME280::pressure(void) {
int32_t var1, var2;
uint32_t p;
var1 = (((int32_t)t_fine)>>1) - (int32_t)64000;
var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((int32_t)_P6);
var2 = var2 + ((var1*((int32_t)_P5))<<1);
var2 = (var2>>2)+(((int32_t)_P4)<<16);
var1 = (((_P3 * (((var1>>2) * (var1>>2)) >> 13 )) >> 3) + ((((int32_t)_P2) * var1)>>1))>>18;
var1 =((((32768+var1))*((int32_t)_P1))>>15);
if (var1 == 0) return 0; // avoid exception caused by division by zero
p = (((uint32_t)(((int32_t)1048576)-adc_P)-(var2>>12)))*3125;
if (p < 0x80000000) p = (p << 1) / ((uint32_t)var1);
else p = (p / (uint32_t)var1) * 2;
var1 = (((int32_t)_P9) * ((int32_t)(((p>>3) * (p>>3))>>13)))>>12;
var2 = (((int32_t)(p>>2)) * ((int32_t)_P8))>>13;
p = (uint32_t)((int32_t)p + ((var1 + var2 + _P7) >> 4));
return (float)p/100.0;
}
温度を求めるtemperature部分はすでに計算したt_fineから求める。
float BME280::temperature(void) {
int32_t T = (t_fine * 5 + 128) >> 8;
return (float)T/100.0;
}
最後に湿度を求めるhumidityは、「まぁ気圧よりはマシか」程度だが本当にどうにかならないのか。16384(=2^14)を15回右シフトとかどうなってのw。(設計意図を残す目的らしい)
float BME280::humidity(void) {
int32_t v_x1_u32r;
v_x1_u32r = (t_fine - ((int32_t)76800));
v_x1_u32r = (((((adc_H << 14) - (((int32_t)_H4) << 20) - (((int32_t)_H5) * v_x1_u32r)) + ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)_H6)) >> 10) * (((v_x1_u32r * ((int32_t)_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) * ((int32_t)_H2) + 8192) >> 14));
v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)_H1)) >> 4));
v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
return (float)((uint32_t)(v_x1_u32r>>12))/1024.0;
}
この他、コマンドを1バイト送りつけるcommand関数とか。
void BME280::command(uint8_t v1, uint8_t v2) {
Wire.beginTransmission(_adrs);
Wire.write(v1);
Wire.write(v2);
Wire.endTransmission();
}
メモリアドレスを指定して指定バイト数を読みだすreadRegとかを用意してある。
void BME280::readReg(uint8_t reg, uint8_t *buf, int n) {
Wire.beginTransmission(_adrs);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom((int)_adrs,n);
byte i=0;
while (Wire.available()) buf[i++] = Wire.read();
}
ちょっと今はそうちに組み込んじゃったので手元でライブラリを試せないが、SEIKOの温湿度計とほとんど同じ値を出していた。なお、電源電圧は3.6Vまでなので5Vを印加しないよう注意が必要だ。また、自己発熱が多めなのかファンで強制空冷しないと高目の温度が出たことがある。
投稿者の人気記事

-
akira.kei
さんが
2026/07/02
に
編集
をしました。
(メッセージ: 初版)
-
akira.kei
さんが
2026/07/02
に
編集
をしました。
-
akira.kei
さんが
前の金曜日の14:10
に
編集
をしました。
ログインしてコメントを投稿する