編集履歴一覧に戻る
akira.keiのアイコン画像

akira.kei が 2026年07月02日16時35分34秒 に編集

コメント無し

本文の変更

[<前の記事](https://elchika.com/article/c10b45e3-2d5a-4a5b-856a-94913bf7d287/) : 次の記事> ## 悪名高きBME280を使う? AHT21B、SHT40i、SHT31ときて[BME280](https://akizukidenshi.com/catalog/g/g109421/)を使おうかなとGeminiやCopilotと相談してみた。ちなみに間違って買ったBMP280はBME280と同じ使い方で良いらしく、単純に湿度が0%になるだけだ。 ![AE-BME280](https://akizukidenshi.com/img/goods/L/109421.jpg) なぜBME280の悪名が高いのかというと、得られたデータの変換式が意味不明なためだ。 ![換算例](https://camo.elchika.com/d9e9500d5847f424333a5539f5b137009479a0f6/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f33363031626666382d633530632d343762382d613765362d3531663731393163323336642f63666534643361612d366365612d343339632d386534342d336233313631636135663332/) スケッチの方はこれまでと同じように単純だ。アドレスは0x76と0x77が選べるようになっている。 ```Arduino: #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ビットなのか、どうなってんの。 ```Arduino:

+

BME280特有のたくさんの補正パラメータが「private:」以下に示してある。どうも符号付きだか符号無しだか8ビットなのか16ビットなのか32ビットなのか、どうなってんの。 ```Arduino: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は適切なコマンドを送るだけなので特に難しくない。 ```Arduino: void BME280::begin(void) { delay(100); command(CONFIG, 0x00); command(CTRL_MEAS, 0x24); command(CTRL_HUM, 0x01); setCalibParm(); delay(1000); } ``` ただし、補正パラメータを取り出しておく必要がある。なんかシフトしすぎだよね。。。 ```Arduino: 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```を計算しておく。何をやっているのか本当に意味不明だ。 ```Arduino: 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```の中身も意味不明なままで、全く理解できない。 ```Arduino: 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```から求める。 ```Arduino: float BME280::temperature(void) { int32_t T = (t_fine * 5 + 128) >> 8; return (float)T/100.0; } ``` 最後に湿度を求める```humidity```は、「まぁ気圧よりはマシか」程度だが本当にどうにかならないのか。16384(=2^14)を15回右シフトとかどうなってのw。(設計意図を残す目的らしい) ```Arduino: 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```関数とか。 ```Arduino: void BME280::command(uint8_t v1, uint8_t v2) { Wire.beginTransmission(_adrs); Wire.write(v1); Wire.write(v2); Wire.endTransmission(); } ``` メモリアドレスを指定して指定バイト数を読みだす```readReg```とかを用意してある。 ```Arduino: 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を印加しないよう注意が必要だ。また、自己発熱が多めなのかファンで強制空冷しないと高目の温度が出たことがある。 --- [<前の記事](https://elchika.com/article/c10b45e3-2d5a-4a5b-856a-94913bf7d287/) : 次の記事>