温度・気圧センサーBMP280を使ってみる

概要

BOSCH社製の温度・気圧センサー BMP280 を用いた、GY-BMP280という製品をESP-WROOM-02に接続して使ってみた。アマゾンで230円 (中国広東省からの送料込)。

1カ月ほど前から、BME280というセンサーモジュール (こちらは湿度も測れる) を用いた秋月電子の製品(AE-BME280) を使って室内データの測定も行っているが、今回は別のブレッドボードに回路を組んで、データをしばらく比較してみた。

この安価なセンサーモジュールに載っているBMP280と、湿度も測れるBME280およびHDC1000を使うためのスケッチなどを掲載。

GY-BMP280

6本足のピンポストをハンダ付けしてブレッドボードにさしてみた。中央上よりの銀色の四角いデバイスがBMP280で、小さな穴が開いているのが分かる。このモジュールについての資料がなかなか見つからなかったので、等価と思われる回路図も起こしてみた。

GY-BMP280 表側
GY-BMP280 表側 (左端のピンが#1)
GY-BMP280 裏側
GY-BMP280 裏側。”P”側がマークされている。

実は、湿度も測れるBME280が載ったモジュールと勘違いしてポチっとしてしまったのだけど、I2Cを使ったArduinoやWROOM-02への接続や測定のためのスケッチはBMEもBMPもほとんど同じだった。

ae-bme-280

秋月電子のAE-BME280 (上図) には、SPIとI2Cのどちらを使うのかを選択したり、I2C使用時のアドレスを設定するためのジャンパエリアがあったのだけど、GY-BMP280にはそんな気の利いたものはなく、BMP280自体のピンは以下のように結線されているようだ。

gy-bmp280等価回路

  • SDO : 10kΩでプルダウン (I2Cアドレス 0x76に固定)
  • CSB : 10kΩでプルアップ (I2C接続モードを指定。I2C時は通信には使わない)
  • SDI : 10kΩでプルアップ (I2CのSDAライン)
  • SCK : 10kΩでプルアップ (I2CのSCLライン)
  • VIO (VDDIO)はデジタルインタフェース専用の電源、VDDはそれ以外のアナログ部分も使う電源とのことで、シビアな測定に使う用途では分けて与えるのだろう。

※ BMP280のデータシートによれば、I2C接続を用いるときにはCSBはプルアップではなく、VDDIOに直結すべきとのこと。そうしないと、起動時にSPIになってしまうかもね、と書いてある。たしかに、AE-BME280ではプルアップ抵抗は入っていなかった。

ESP-WROOM02との接続

特筆すべきことはなくて、GY-BMP280のVDDに+3.3Vを与え、SDAをWROOM-02のIO4に、SCLをIO5に接続することで、Wireライブラリを使ったI2C通信が可能になる。

AE-BME280とAE-HDC1000を載せている方のボードについては、こちらを参照してください。今回BMP280を載せたボードも、ほとんど同じになっている(ただし、電池駆動ではない)。

スケッチ

センサーを使った測定を行う部分については、I2C接続専用だがBME280とBMP280で共通に使えるC++のクラスとして実装した。ついでにHDC1000を使った温度/湿度の測定を行う部分も含んでいる。このファイルをメインのスケッチで #include し、インスタンスを作ってやることで、初期化と測定の2つのメソッドだけを意識すればよくなる。

このファイルには、3つのクラスが含まれている。センサーを使いたいスケッチのディレクトリに置いておけば、Arduino IDEに編集ファイルのタブも追加されるので、手直しも簡単。BMP(E)280のデータシートに含まれている補正用の関数を含んでいるため、しょうしょう長くなった。

#include <stdint.h>
#include "Wire.h"
class i2c_support
{
private:
  byte i2c_address;
public:
  i2c_support(byte address) {
    i2c_address = address;
    Wire.begin();
  }
// read 8bit data from pointer.
  uint8_t i2c_read8(byte pointer) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)1);
    return (uint8_t)Wire.read();
  }
// read 16bit data from addr/pointer. 1st MSB, 2nd LSB.
  uint16_t i2c_read16(byte pointer) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)2);
    unsigned short data = Wire.read();
    data <<= 8;
    data |= Wire.read();
    return data;
  }
// read 16bit data from addr/pointer. 1st LSB, 2ns MSB.
  uint16_t i2c_read16_swab(byte pointer) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)2);
    uint16_t lsb = (uint16_t)Wire.read();
    uint16_t msb = (uint16_t)(Wire.read() << 8);
    return msb | lsb;
  }

// for HDC1000
  uint32_t i2c_read32(byte pointer, int delay_ms = 0) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);  // このタイミングでA/D変換が開始する。
    Wire.endTransmission();
    delay(delay_ms);
    Wire.requestFrom(i2c_address,  (byte)4);
    unsigned int data = Wire.read();
    data <<= 8;
    data |= Wire.read();
    data <<= 8;
    data |= Wire.read();
    data <<= 8;
    data |= Wire.read();
    return data;
  }

  void i2c_read_burst(byte pointer, int byte_count, byte* p) {
    Wire.beginTransmission(i2c_address);
    Wire.write(pointer);
    Wire.endTransmission();
    Wire.requestFrom(i2c_address,  (byte)byte_count);
    while(Wire.available() && byte_count > 0) {
      *p++ = (byte)Wire.read();
      byte_count--;
    }
  }

  void i2c_write_reg(byte reg, byte data) {
    Wire.beginTransmission(i2c_address);
    Wire.write(reg);
    Wire.write(data);
    Wire.endTransmission();
  }
};

class hdc1000 : public i2c_support
{
public:
  hdc1000() : i2c_support(0x40) {
    temp = 0.0;
    humi = 0.0;
  }
  enum { reg_measure = 0, reg_config = 2, reg_id = 0xff };
 
  bool init() {
    i2c_write_reg(reg_config, 0x90);  // 10000001B = RESET, 32bit MODE(temp << 16 + humi)
    return (i2c_read16(reg_id) == 0x1000); // check device id.
  }
  void measure() {
    uint32_t data = i2c_read32(reg_measure, 15);
    temp = (float)((data >> 16) / 65536.0) * 165.0 - 40.0;
    humi = (float)((data & 0xffff) / 65536.0) * 100.0;
  }
  float temp;
  float humi;
};

#ifndef BME280_S32_t
#define BME280_S32_t  int32_t
#endif
#ifndef BME280_U32_t
#define BME280_U32_t  uint32_t
#endif
#ifndef BME280_S64_t
#define BME280_S64_t  int64_t
#endif

class bme280 : public i2c_support
{
  bool bme280_flag;  // true : bme280, false: bmp280
public:
  bme280(int address) : i2c_support(address) {
    temp = 0.0;
    humi = 0.0;
    press = 0.0;
  }
  // results.
  float temp;
  float humi;
  float press;
  enum { Tmeasure = 10 };  // oversamplinkg, x1 x1 x1
/// registers.
  enum { reg_id = 0xd0, reg_reset = 0xe0, reg_ctrl_hum = 0xf2, reg_ctrl_meas = 0xf4, reg_config = 0xf5 };
  enum { reg_p_result = 0xf7, reg_t_result = 0xfa, reg_h_result = 0xfd };

// IIR Filter disable.
  bool init() {
    uint8_t id = i2c_read8(reg_id); // check device-id.
    if (id == 0x60)
      bme280_flag = true;
    else if (id == 0x58)
      bme280_flag = false;
    else
       return false;

    load_bme280_compensation_params();
    i2c_write_reg(reg_config, 0); //  no stand-by, no IIR filter, no SPI.
    return true;
  }

//  TEMP resolution = 16 + (osrs_t ? 1) bit, HUM resolution = 16 + (osrs_h ? 1) bit,
//  select forced mode.
  void measure()  {
    if (bme280_flag)
      i2c_write_reg(reg_ctrl_hum, 1);   // humi measurement control register.00000011  (osrs_h = 1);
    i2c_write_reg(reg_ctrl_meas, 0x25); // measurement control register. 00100101 (osrs_p = 1, osrs_t = 1, Forced mode);
    delay(Tmeasure);
    byte buffer[10];
    memset(buffer, 0, sizeof(buffer));
    i2c_read_burst(reg_p_result, 8, buffer);
    uint32_t raw_press = ((uint32_t)buffer[0] << 12) | ((uint32_t)buffer[1] << 4) | (((uint32_t)buffer[2] >> 4) & 0xf);
    uint32_t raw_temp = ((uint32_t)buffer[3] << 12) | ((uint32_t)buffer[4] << 4) | (((uint32_t)buffer[5] >> 4) & 0xf);
    uint16_t raw_humi = ((uint16_t)buffer[6] << 8) | (uint16_t)buffer[7];
    // uint16_t raw_humi = i2c_read16(reg_h_result);
#if 0
    Serial.print("raw_press = ");
    Serial.println(raw_press, HEX);
    Serial.print("raw_temp = ");
    Serial.println(raw_temp, HEX);
    Serial.print("raw_humi = ");
    Serial.println(raw_humi, HEX);
#endif
    temp = BME280_compensate_T_int32(raw_temp) / 100.0;
    press = BME280_compensate_P_int64(raw_press) / 25600.0;
    if (bme280_flag)
      humi = bme280_compensate_H_int32(raw_humi) / 1024.0;
    // reset.
  //  i2c_write_reg(reg_reset, 0xb6);
  }

// from BST-BME280_DS001-10.pdf Chapter4.2.3 (rev.1.1)
private:
  BME280_S32_t t_fine;

// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
  BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T) {
    BME280_S32_t var1, var2, T;
    var1 = ((((adc_T>>3) - ((BME280_S32_t)dig_T1<<1))) * ((BME280_S32_t)dig_T2)) >> 11;
    var2 = (((((adc_T>>4) - ((BME280_S32_t)dig_T1)) * ((adc_T>>4) - ((BME280_S32_t)dig_T1))) >> 12) * ((BME280_S32_t)dig_T3)) >> 14;
    t_fine = var1 + var2;
    T = (t_fine * 5 + 128) >> 8;
    return T;
  }

// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
  BME280_U32_t BME280_compensate_P_int64(BME280_S32_t adc_P)  {
    BME280_S64_t var1, var2, p;
    var1 = ((BME280_S64_t)t_fine) - 128000;
    var2 = var1 * var1 * (BME280_S64_t)dig_P6;
    var2 = var2 + ((var1*(BME280_S64_t)dig_P5)<<17);
    var2 = var2 + (((BME280_S64_t)dig_P4)<<35);
    var1 = ((var1 * var1 * (BME280_S64_t)dig_P3)>>8) + ((var1 * (BME280_S64_t)dig_P2)<<12);
    var1 = (((((BME280_S64_t)1)<<47)+var1))*((BME280_S64_t)dig_P1)>>33;
    if (var1 == 0) {
      return 0; // avoid exception caused by division by zero
    }
    p = 1048576 - adc_P;
    p = (((p<<31) - var2)*3125) / var1;
    var1 = (((BME280_S64_t)dig_P9) * (p>>13) * (p>>13)) >> 25;
    var2 = (((BME280_S64_t)dig_P8) * p) >> 19;
    p = ((p + var1 + var2) >> 8) + (((BME280_S64_t)dig_P7)<<4);
    return (BME280_U32_t)p;
  }

// Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format (22 integer and 10 fractional bits).
// Output value of “47445” represents 47445/1024 = 46.333 %RH
  BME280_U32_t bme280_compensate_H_int32(BME280_S32_t adc_H)  {
    BME280_S32_t v_x1_u32r;

    v_x1_u32r = (t_fine - ((BME280_S32_t)76800));
    v_x1_u32r = (((((adc_H << 14) - (((BME280_S32_t)dig_H4) << 20) - (((BME280_S32_t)dig_H5) * v_x1_u32r)) +
      ((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r * 
      ((BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) * 
      ((BME280_S32_t)dig_H2) + 8192) >> 14));
    v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_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 (BME280_U32_t)(v_x1_u32r>>12);
  }

// Chapter4.2.2. Trimming parameters.
  uint16_t  dig_T1;
  int16_t dig_T2;
  int16_t dig_T3;

  uint16_t  dig_P1;
  int16_t dig_P2;
  int16_t dig_P3;
  int16_t dig_P4;
  int16_t dig_P5;
  int16_t dig_P6;
  int16_t dig_P7;
  int16_t dig_P8;
  int16_t dig_P9;

  uint8_t dig_H1;
  int16_t dig_H2;
  uint8_t dig_H3;
  int16_t dig_H4;
  int16_t dig_H5;
  uint8_t dig_H6;

  void load_bme280_compensation_params() {
    dig_T1 = i2c_read16_swab(0x88);
    dig_T2 = (int16_t)i2c_read16_swab( 0x8a);
    dig_T3 = (int16_t)i2c_read16_swab(0x8c);

    dig_P1 = i2c_read16_swab(0x8e);
    dig_P2 = (int16_t)i2c_read16_swab(0x90);
    dig_P3 = (int16_t)i2c_read16_swab(0x92);
    dig_P4 = (int16_t)i2c_read16_swab(0x94);
    dig_P5 = (int16_t)i2c_read16_swab(0x96);
    dig_P6 = (int16_t)i2c_read16_swab(0x98);
    dig_P7 = (int16_t)i2c_read16_swab(0x9a);
    dig_P8 = (int16_t)i2c_read16_swab(0x9c);
    dig_P9 = (int16_t)i2c_read16_swab(0x9e);

    if (bme280_flag) {
      dig_H1 = i2c_read8(0xa1);
      dig_H2 = (int16_t)i2c_read16_swab(0xe1);
      dig_H3 = i2c_read8(0xe3);
      uint16_t _e4 = (uint16_t)i2c_read8(0xe4);
      uint16_t _e5 = (uint16_t)i2c_read8(0xe5);
      uint16_t _e6 = (uint16_t)i2c_read8(0xe6);
      dig_H4 = (int16_t)((_e4 << 4) + (_e5 & 0xf));
      dig_H5 = (int16_t)((_e6 << 4) + ((_e5 >> 4) & 0xf));
      dig_H6 = (int16_t)i2c_read8(0xe7);
    }
  }
};

class i2c_support

Wireライブラリを使ってI2Cでのデータ通信を行う。I2C接続するセンサーモジュールを使うときに共通となる部分をまとめており基本クラスとして用いることを意図した。stdint.h をインクルードし、uint8_tやuint16_t といったバイト数を意識したデータ型を使っている。

class hdc1000

HDC1000とのインタフェースをとるためのクラスで、i2c_support のサブクラスとしている。I2C用のレジスタの構成やモジュールの使い方については、秋月電子の取扱説明書やTI社のデータシートを参考にした。

init()

CONFIGレジスタに 0x90 を書き込むことで、

  • リセット
  • 温度、湿度ともに測定分解能14ビット
  • 温度、湿度一括測定、一括受信 (mode = 1)を指定
  • デバイスIDをチェック

を実行。意図通りのデバイスIDが得られれば true を返す。

measure()

温度と湿度の測定を行い、結果を格納する。

測定開始と結果の読出しは、基本クラスの i2c_read32() 内で行っている。測定は温度レジスタの指定 ( Wire.write(pointer) ) で開始するが、14ビット分解能での測定には温度が6.35msec、湿度が6.5msecかかる。なので結果の読出し ( Wire.read() ) までに15msecの delay() を置いている。delay() の後、4バイトを立て続けに読み出して32ビットの uint32_t の値に組み立ててからリターンしている。

得られた32ビット値の上位16ビット分が温度、下位16ビット分が湿度になるので、おのおのをデータシート指定の方法で変換したのちに変数に格納している。

HDC1000の電源投入時のスタートアップ時間は最大15msecとなっている。メインのスケッチの setup(); の最初に適宜 delay() を入れておけばよいだろう。また、HDC1000は電源投入時からスリープモードに入り、測定中のみ測定モードとなる。そして測定が終了して結果をレジスタに格納すると、ふたたびスリープモードに戻る。スリープモード中も、I2Cを介したレジスタの読出しや書込みは可能とのこと。

HDC1000の測定精度は、温度が±0.3℃、相対湿度が±3%である。

class bme280

BME(P)280とのインタフェースをとるためのクラスで、やはり i2c_support のサブクラスとしている。さまざまな項目について Bosch Sensortec社の BME280
Combined humidity and pressure sensor (rev1.1, BST-BME280-DS001-10)
  および BMP280
Digital Pressure Sensor (rev1.15, BST-BMP280-DS001-12)  を参考にした。

これらのセンサーモジュールでは、I2Cを介して得られる生データを、データシートに記載されているややこしい補正関数にかけて測定結果を得る必要がある。init() において補正関数の実行のために必要な定数群を load_bme280_compensation_params() の呼出しにより読み出している。補正関数については、BME280のデータシートの Chapter 4.2.3 Compensation formulas に記載されている64ビット整数を使った固定小数点バージョンをそのまま(コピペで)利用することとし、その中で使われているデータ型についてもマクロ (#define) で定義した。

BMP280のデータシートと見比べた限りでは、BMP側に湿度関連の項目がない以外の相違が見られなかったので、BME280のデータシートに基づいて作成した。見落としがあるかもしれないが。

コンストラクタ  bme280( int address )

I2Cアドレスを変更可能なので引数としてアドレスをとるようにした。

init()

BME(P)280のスタートアップ時間は2msecなので、必要ならばsetup() の最初にdelay() を置くことになるが、HDC1000に比べると十分に短い。

まずはデバイスが返すID値を読み取り、BMPとBMEの切り分けを行っている。いずれでもなければ配線ミスか、デバイスがないということで、falseをリターンする。

次に、温度、気圧、湿度を補正するための定数をデバイスから読み出している。デバイスIDおよび補正用定数の読出しは、デバイスがスリープモード(パワーオン後のスタートアップ時間経過後)であっても可能とのこと。

そして、CONFIGレジスタ(0xf5) に0を書くことで、測定間のスタンバイ無し(単発モード)、IIRフィルターも無しとしている。この時点では、まだ測定は開始していない。

measure()

温度、気圧と湿度(BMEのみ)の測定を行い、結果を格納する。まずは測定内容を設定するために以下のようなことをやっている。

  • BMEならば、湿度測定コントロールレジスタ (0xf2 ) に1を書くことで、湿度測定時のサンプリング回数を1回に。
  • 測定コントロールレジスタ(0xf4 ) に0x25 ( 00100101B) を書くことで、温度と気圧のサンプリング回数を各1回とし、測定モードを単発の forced modeに。この書込みにより、測定(サンプルとA/D変換)が開始する。

デバイスは、内部でA/D変換が終了すると結果をデータレジスタに格納しスリープ状態に戻る(データの読出しは可能)。このメソッド内では、サンプリング回数に基づいて得られる測定時間 (Tmeasure ) だけ delay() をおいている。

Tmeasure(max) = 1.25 + [2.3 ×T_oversampling] + [2.3×P_oversampling + 0.575] + [2.3×H_oversampling + 0.575]

3種類の測定対象のオーバーサンプリング値として001B(1)を設定しているから、 1.25  + (2.3 × 1) + (2.3 × 1 + 0.575) + (2.3 × 1 + 0.575) =  9.3msec (max)  となる(BME280の場合)。BMP280では、温度と気圧を測定して最大6.4msecと明記されており、上式の湿度の項を無しにしたのと同じ。

delay()の後にi2c_read_burst() を使って結果を読み出す。圧力(20ビット)、温度(20ビット)、湿度(16ビット)の各変換結果は、 0xf7で示されるレジスタから8バイト連続して格納されているので、一度に読み出してから変数に分け、補正用の関数にかけて測定結果として変数に格納している。

補正用の関数

  • BME280_compensate_T_int32()
  • BME280_compensate_P_int64()
  • bme280_compensate_H_int32()

これらにについては、データシートに記載されている内容をそのままコピペして使っている。たぶんうまく動いているのだろう。ただ最初に温度の補正を行わないと、気圧および湿度の補正で使っている t_fine が不定となることに注意。

なお、各測定精度は、

  • 温度 : ±1.0℃
  • 気圧 : ±1.0hPA
  • 湿度 : ±3% (BME280)

とのこと。

メイン側のスケッチ

以前、HDC1000を使ったときのコードとほとんど同じなので、概要のみを示す。

#include <ESP8266WiFi.h>
#include "hdc1000_bme280.h"
extern "C" {
  #include "user_interface.h"
}
#define START_MSG "\n" + String(__FILE__) + " start."
#define LED1 15
#define MEASURE_INTERVAL_SECONDS  300

bme280 bmp280(0x76);

// wifi
const char* ssid = "ssid";
const char* password = "password";
const char* remote_host = "192.168.1.1";

void die(int msec, const char* cp) {
// LEDを点滅させる無限ループ
}

void deep_sleep(int seconds) {
  ESP.deepSleep(seconds * 1000 * 1000, WAKE_RF_DEFAULT);
  delay(10);
}
bool ap_connect() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password); 
//
// ステータスを見て接続するまで待つか、タイムアウトしたらfalseを返す。
//
  return true;
}

void ap_disconnect() {
  WiFi.disconnect();
}

// httpリクエスト(GET)で測定データを投げる。
bool wifi_send_data(float temp, float humi, float bpress) {
  WiFiClient client;
  if (!client.connect(remote_host, 80))
    return false;
  String request = "/data/store_room_data.php?point_id=" + WiFi.macAddress() + "&T=" + String(temp) + "&H=" + String(humi) + 
      "&P=" + String(bpress);
  String req_line = "GET " + request + " HTTP/1.1\r\nHost: " + String(remote_host) + "\r\nConnection: close\r\n\r\n";
  client.print(req_line);
// ここで、リモートホストからの応答を待つ。
  return true;  
}

void setup() {
  delay(50);
  Serial.begin(115200);
  Serial.println(START_MSG);
  
  pinMode(LED1, OUTPUT);
  digitalWrite(LED1, 1);
  if (!bmp280.init())
    die(500, "FATAL: BMP280 init failed.");
}

void loop() {
  if (!ap_connect()) {
      deep_sleep(30);  // restart after a  while.
      return;
  }
  bmp280.measure();
  Serial.println("\nPa = " + String(bmp280.press) + ", Temp = " + String(bmp280.temp) );
  wifi_send_data(bmp280.temp, 0, bmp280.press);
  ap_disconnect();
  deep_sleep(MEASURE_INTERVAL_SECONDS);
  delay(5000);
}

処理の流れ

  • ESP-WROOM-02に電源が与えられるかリセットがアサートされるかしてプログラムが開始すると、オブジェクトの生成~setup()が行われ、すぐに測定が実施される。
  • 測定データをWEBサーバーに投げたら、すぐにディープスリープを開始する。
  • ディープスリープタイマーにより、指定時刻後にリセットがかかり、最初に戻る。現在のところ、5分間おきに測定している。

bme280 bmp280(0x76);

I2Cアドレスを指定し、bmp280とインタフェースするためのオブジェクトを生成する。

wifi_send_data()

温度、湿度、気圧の各測定値を、GETリクエストでインターネット上のWEBサーバーに投げる。サーバー側ではPHPプログラムで受け取り、MySQLに格納している。MySQLにINSERTを行うとき、測定データと共にサーバー時刻を記録している。

BMP280とBME280のデータの比較

ほぼ同じ場所に、以前作ったBME280を使ったボードと、今回のBMP280のボードを並べ、数日間の測定結果を蓄積した。それぞれが約5分間隔でデータの計測と送信を行うが、必ずしも一致したタイミングにはならないので、SQL文を工夫してだいたい同じ時系列のデータが並ぶようにした。ESP8266EXのディープスリープタイマーは、実時間よりわずかに早いような感じで、個体によるバラつきもあるような印象。

以下に3日間ほどの測定データをグラフ化したものを示すが、いずれもサーバーからjsonデータを取り出してgoogle charts を使って描画している。そのあたりの話も以前に書いたので省略。

温度データ

chart_t

温度データは、BME280、HDC1000 (この2つが同じボード上)、BMP280の3つのセンサーで得ている。青がBME、オレンジがHDC、赤がBMPである。同じような傾向での温度変化を示しているが、別のボードに載っているせいか、BMPのデータは平滑化されているかのように見える。これは、センサーデバイスの上面がブレッドボードに対して垂直なAE-BME280と、水平なGY-BMP280の形状の違いによるものかもしれない。空気の動きに対する敏感さとか?

BMEとHDCの示す温度差の平均は約0.47℃なのに対し、BMEとBMPの温度差は平均で0.75℃と大きい。もっとも、BME/BMP280の温度測定精度は±1℃なので、誤差の範囲内に思える。こういう曲線を見せられると、なんとなくHDC1000に一票入れたくなってしまうが。

気圧データ

chart_p

気圧データはBME280(青)およびBMP280(赤)から得たもの。温度よりはよく一致しているよう見えるし、データの相違の平均値は0.40hPaで、やはり測定誤差の範囲内に収まっている。各センサーが計測している温度が異なることで生じている違いもあるだろう。

気圧データを見るうえで注意すべきことは、測定場所の標高や温度により、気象庁のアメダスなんかで参照できる気圧と、思いのほかずれてしまうことだろう。たとえば、アメダスには東京の気圧は1020hPaと書いてあるのに、センサーの出力は1013hPaだったりして、あれ?とか思ってしまった。

アメダスの気圧データには、ごく一部を除いて海面気圧(標高0m) に換算した値が掲示されており、今回の測定を行った地点の標高が約60m程度だったことを思い出して腑に落ちた。海面気圧データと標高、気温から現在地の気圧を求める際には、カシオ計算機の計算サイトが便利。

きょうのまとめ

  • 居間のエアコンをESP-WROOM-02とIrLEDでコントロールしようとしているのだけど、温度センサーとして使う予定のGY-BMP280をしばらく試していた。今回は、そのまとめ的な内容になった。
  • 広東省からのお取り寄せで230円 (送料込) というのはビックリである。安いからといってたくさん買うようなもんじゃないし、製品や販社に対する信頼度を重視するならば国内のお店を選びたいと思っているが、安さに負けることもあるだろう。
  • 今回載せたセンサー用のクラスは、10月の初めに作ったHDC1000とBME280を載せたボードで使っているものをBMP280でも使えるように手直ししたもの。できれば、以前のボードのスケッチも入れ替えたいのだけど、電池があがって動かなくなるまでは触らないことにした。

追記

ESP-WROOM-02とBME280を組合せた温度、相対湿度等の測定について、この投稿を踏まえた改訂版 (WROOM-02とBME280 電池駆動で温度測定の改訂版  ) を掲載しました。おもに、電池のモチを改善する方法などが中心となっています。