はじめに
今回はBME280を使用していこうと思います。BME280とは何ぞやというと【温度・気圧・湿度の複合センサー】でメーカーはドイツのBOSCH製になります。
本来はチップとして単体の製品ですが、様々なメーカーからデバイスとして販売されております。本記事で紹介するのはAmazonで売られていた基板上にレギュレータとプルアップの抵抗が既に実装されているタイプの物を使用しております。
レギュレーターがない裸のチップのまま使用する場合の定格電源電圧は【1.71V~3.6V】なので注意してください。
また、BM”P“280という似たような名前のチップが存在しますが、気圧オンリーのセンサなので買う時には注意してください。私も一回間違って購入の経験ありです。
通信インターフェースはI2CとSPの両方に対応しております。今回はI2Cタイプを購入しております。
本記事を書くにあたり日本語訳のデータシートを参照させていただきました。有志の皆様ありがとうございました。リンクは最後にまとめております。
なお、Arduinoでの使用ならば<Wire.hライブラリ>と<BME280ライブラリ>を使用すればすぐにデータを取り出すことが可能ですが、レジスタの機能から使用方法まで掘り下げていくので<Wire.hライブラリ>のみの使用になるのでご承知おきください。
ではどうぞ!
BME280概要
1.モード
BME280は【スリープ】【強制測定】【ノーマル】の3つのモードを保有しています。modeレジスタにより変更可能です。
- スリープ:測定は実行されない待機状態
- 強制測定:手動による単発測定
- ノーマル:待機と測定の繰り返し。待機時間は0.5ms~1000msで設定可能。後述のIIRフィルタ使用の際はノーマルモードを使用することで外乱を抑えることができる。
消費電力を抑える必要があるのであれば定期的な強制測定モードを行い、測定していないときはスリープモードにすると消費電力が抑えられると思われます。
2. 測定フロー
BME280の測定はオーバーサンプリングによるAD変換⇒IIRフィルタによるノイズ除去で構成されています。オーバーサンプリングというのは簡単に言うとサンプリングレートを早くしてノイズを広範囲に分散できるのでノイズ低減の効果が期待できる。落としきれなかったノイズは内蔵フィルタで処理するのですがBME280ではIIRフィルタというのを使用しているようです。
分解能については以下の通りです。
- 湿度:16bit
- 圧力:IIRフィルタ有効なら20bit。無効なら16+(osrs_p – 1)bit。
- 温度:IIRフィルタ有効なら20bit。無効なら16+(osrs_p – 1)bit。
BME280使用手順
制御レジスタ設定
温度測定の前に制御レジスタを設定する必要があります。レジスタ番号としてはF2h, F4h, F5hが該当します。
- F2h:湿度のオーバーサンプリング設定。この設定が有効になるタイミングはF4h(ctrl_meas)の書き込み後。
- F4h:ステータスレジスタ。温度と圧力のオーバーサンプリングの設定。及び、モードの設定を行う。
- F5h:デバイスの設定レジスタ。通常モードでの待機時間の設定とIIRフィルタの有効無効設定、及びフィルタ係数の設定を行います。0bit目ではSPI使用時の設定を行う。
今回設定するパラメータは【屋内環境のモニタリング】用に使用するのでデータシートを参考に以下のようなパラメータに設定します。
測定生データ読み取り
BME280は測定データをF7h~FEhの8byteに格納されています。データシートによると各レジスタに対して読み込みコマンドを送るのではなく連続して読み出した方が良いと記載されています。今回はそれにならってF7h~FEhを連続で読み取っていきます。読み取った後にbitシフトでデータを組み合わせていきます。実際のArduinoでの手順としてはWire.requestFromを使用して開始アドレスをF7hとして読み取りbyte数を8byteとします。
測定データ補償
読み取りの生データでは値を正確に読むことが不可能なのでBME280に内蔵されている補償パラメータにより補償することが必要です。補償パラメータはレジスタアドレス88h~E7hに格納されており、データシート中の表にまとめられております。
よってBME280を使用する場合には一旦補償パラメータを配列に読み出して格納する必要があります。補償式に関してもデータシート中にコードが載せられているのでそのまま使用します。
■温度補償式
1 2 3 4 5 6 7 8 9 10 11 |
BME280_S32_t t_fine; 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; } |
■圧力補償式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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; // ゼロ除算による例外を避ける。 } 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; } |
■湿度の補償式
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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); } |
サンプルコード
今までの設定を踏まえてサンプルコードを載せたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
#include <Wire.h> #define ADDRESS 0x76 //BME280アドレスGND接続 #define WAITE_TIME 500 //更新間隔 unsigned long int hum_raw, temp_raw, pres_raw; signed long int t_fine; //補償係数 uint16_t dig_T1, dig_P1; int16_t dig_T2, dig_T3, dig_P2,dig_P3,dig_P4,dig_P5,dig_P6, dig_P7,dig_P8,dig_P9,dig_H2,dig_H4,dig_H5; int8_t dig_H1,dig_H3,dig_H6; void readCompensation_factor() { uint8_t data[32], i = 0; Wire.beginTransmission(ADDRESS); Wire.write(0x88); Wire.endTransmission(); Wire.requestFrom(ADDRESS, 25); while (Wire.available()) { data[i] = Wire.read(); i++; } Wire.beginTransmission(ADDRESS); Wire.write(0xE1); Wire.endTransmission(); Wire.requestFrom(ADDRESS, 7); while (Wire.available()) { data[i] = Wire.read(); i++; } dig_T1 = (data[1] << 8) | data[0]; dig_T2 = (data[3] << 8) | data[2]; dig_T3 = (data[5] << 8) | data[4]; dig_P1 = (data[7] << 8) | data[6]; dig_P2 = (data[9] << 8) | data[8]; dig_P3 = (data[11] << 8) | data[10]; dig_P4 = (data[13] << 8) | data[12]; dig_P5 = (data[15] << 8) | data[14]; dig_P6 = (data[17] << 8) | data[16]; dig_P7 = (data[19] << 8) | data[18]; dig_P8 = (data[21] << 8) | data[20]; dig_P9 = (data[23] << 8) | data[22]; dig_H1 = data[24]; dig_H2 = (data[26] << 8) | data[25]; dig_H3 = data[27]; dig_H4 = (data[28] << 4) | (0x0F & data[29]); dig_H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F); dig_H6 = data[31]; } void writeBME280(uint8_t reg_address, uint8_t data) { Wire.beginTransmission(ADDRESS); Wire.write(reg_address); Wire.write(data); Wire.endTransmission(); } void readBME280() { int i = 0; uint32_t data[8]; Wire.beginTransmission(ADDRESS); Wire.write(0xF7); Wire.endTransmission(); Wire.requestFrom(ADDRESS, 8); while (Wire.available()) { data[i] = Wire.read(); i++; } pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4); temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); hum_raw = (data[6] << 8) | data[7]; } //分解能:0.01℃, 返り値は小数点2桁の整数で返されるので100で割っている。 signed long int BME280_compensate_T(signed long int adc_T) { signed long int var1, var2, T; double temp; var1 = ((((adc_T >> 3) - ((signed long int)dig_T1 << 1))) * ((signed long int)dig_T2)) >> 11; var2 = (((((adc_T >> 4) - ((signed long int)dig_T1)) * ((adc_T >> 4) - ((signed long int)dig_T1))) >> 12) * ((signed long int)dig_T3)) >> 14; t_fine = var1 + var2; T = ((t_fine * 5 + 128) >> 8); return T; } //分解能:0.01℃, 返り値は小数点2桁の整数で返されるので100で割っている。 unsigned long int BME280_compensate_P(signed long int adc_P) { signed long int var1, var2; unsigned long int P; var1 = (((signed long int)t_fine) >> 1) - (signed long int)64000; var2 = (((var1 >> 2) * (var1 >> 2)) >> 11) * ((signed long int)dig_P6); var2 = var2 + ((var1 * ((signed long int)dig_P5)) << 1); var2 = (var2 >> 2) + (((signed long int)dig_P4) << 16); var1 = (((dig_P3 * (((var1 >> 2) * (var1 >> 2)) >> 13)) >> 3) + ((((signed long int)dig_P2) * var1) >> 1)) >> 18; var1 = ((((32768 + var1)) * ((signed long int)dig_P1)) >> 15); if (var1 == 0) { return 0; } P = (((unsigned long int)(((signed long int)1048576) - adc_P) - (var2 >> 12))) * 3125; if (P < 0x80000000) { P = (P << 1) / ((unsigned long int) var1); } else { P = (P / (unsigned long int)var1) * 2; } var1 = (((signed long int)dig_P9) * ((signed long int)(((P >> 3) * (P >> 3)) >> 13))) >> 12; var2 = (((signed long int)(P >> 2)) * ((signed long int)dig_P8)) >> 13; P = (unsigned long int)((signed long int)P + ((var1 + var2 + dig_P7) >> 4)); return P; } unsigned long int BME280_compensate_H(signed long int adc_H) { signed long int v_x1; v_x1 = (t_fine - ((signed long int)76800)); v_x1 = (((((adc_H << 14) - (((signed long int)dig_H4) << 20) - (((signed long int)dig_H5) * v_x1)) + ((signed long int)16384)) >> 15) * (((((((v_x1 * ((signed long int)dig_H6)) >> 10) * (((v_x1 * ((signed long int)dig_H3)) >> 11) + ((signed long int) 32768))) >> 10) + (( signed long int)2097152)) * ((signed long int) dig_H2) + 8192) >> 14)); v_x1 = (v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * ((signed long int)dig_H1)) >> 4)); v_x1 = (v_x1 < 0 ? 0 : v_x1); v_x1 = (v_x1 > 419430400 ? 419430400 : v_x1); return (unsigned long int)(v_x1 >> 12); } void setup() { // BME280 initial uint8_t osrs_t = 0b010; //温度オーバーサンプリング x 2 uint8_t osrs_p = 0b101; //気圧オーバーサンプリング x 16 uint8_t osrs_h = 0b001; //湿度オーバーサンプリング x 1 uint8_t mode = 0b11; //00:スリープ,01:強制測定,11:通常測定 uint8_t t_sb = 0b000; //待機時間 0.5ms uint8_t filter = 0b100; //フィルタ係数16 uint8_t spi3w_en = 0; //I2C仕様なので関係ない uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode; uint8_t config_reg = (t_sb << 5) | (filter << 2) | spi3w_en; uint8_t ctrl_hum_reg = osrs_h; Serial.begin(9600); Wire.begin(); writeBME280(0xF2, ctrl_hum_reg); writeBME280(0xF4, ctrl_meas_reg); writeBME280(0xF5, config_reg); readCompensation_factor(); delay( 1000 ); } void loop() { double temp = 0.0, pres = 0.0, hum = 0.0; readBME280(); temp = (double)BME280_compensate_T(temp_raw) / 100.0; pres = (double)BME280_compensate_P(pres_raw) / 100.0; hum = (double)BME280_compensate_H(hum_raw) / 1024.0; Serial.print("温度 : ");Serial.print(temp);Serial.print("℃ "); Serial.print("気圧 : ");Serial.print(pres);Serial.print("hpa "); Serial.print("湿度 : ");Serial.print(hum);Serial.println("% "); delay(WAITE_TIME); } |
コードが長くなってしまっていますが、ほとんどは補償式と補償パラメータが占めております。基本的にBME280の制御設定を書き込んでレジスタに格納されている値を読み取り補償式により温度、気圧、湿度に変換しているだけです。
- Wire.beginTransmissionにより制御パラメータ設定(F2h, F4h, F5h)
- Wire.requestFromにより補償係数を引き出す(88h~E7h)
- Wire.requestFromにより値を連続で読み出す(F7h~FE)
- 補償式により読み取り値を計算
コード中で補償係数を読み取るのに25byte分と7byte分で分けて読み出しています。当初は32byte分一気に読みだしたのですがうまくいきませんでした。調べていませんが、 Wire.requestFromのバッファ量が25byteまでなのかもしれません。
また、読み取った値は配列に格納されてビットシフトとOR及び&により値を抽出・接合しています。下記記事にbit演算についてまとめてありますので役に立つかもしれません。
おわりに
今回はライブラリ不使用でのBME280使用を紹介しました。
参考のコードではノーマルモードでの一定間隔の読み取りを行いましたが、消費電力を抑えたり、そこまでリアルタイムの値が欲しくない場合は強制測定を一定間隔で回すのもありかと思います。例えばタイマ割り込みで強制測定を行い、それ以外はスリープモードにするなど。
せっかくなのでBME280を使用した電子工作をどこかでやれればと思います。
今回はこれで失礼いたします。
ではでは~
コメント