Arduino NanoとBME280を使って温度・湿度・気圧を計測する

![implemented](https://qiita-image-store.s3.amazonaws.com/0/30370/12051c65-95a0-de1a-4951-0f3de63673b6.jpeg)

Arduino と温湿度気圧センサ BME280 を接続し、温度・湿度・気圧を測定します。
ここでは Arduino Nano と、BME280 を使いやすくした AE-BME280 を使用し、I<sup>2</sup>C と SPI 接続両方について解説します。
**回路とプログラムは Arduino Uno にも対応しています。**

Arduino 自体の使い方については他の記事をご参照ください。

# 用意するもの

|部品名|画像|はんだ付け|入手先 (参考)|
|---|:--:|---|---|
|**Arduino Nano**<br/>(Arduino UNO でも可)|<img width="240" alt="Arduino Nano" src="https://qiita-image-store.s3.amazonaws.com/0/30370/af28fa91-7ae2-1f5c-fada-d6f532bceee3.jpeg">|必要なし|[秋月電子](https://akizukidenshi.com/catalog/g/g109059/) など |
|**AE-BME280**<br/>温湿度・気圧センサモジュール|<img width="240" alt="AE-BME280" src="https://qiita-image-store.s3.amazonaws.com/0/30370/f108a5dc-4e63-24a8-7b52-05be0370f388.jpeg">|必要|[秋月電子](https://akizukidenshi.com/catalog/g/g109421/)
|**AE-PCA9306**<br/>I<sup>2</sup>Cバス用双方向電圧レベル変換モジュール<br />※I<sup>2</sup>C接続時のみ|<img width="240" alt="AE-PCA9306" src="https://qiita-image-store.s3.amazonaws.com/0/30370/bfa1e1d3-eddf-803b-7af8-18d0ee8078dd.jpeg">|必要|[秋月電子](https://akizukidenshi.com/catalog/g/g105452)
|**AE-LCNV4-MOSFET**<br/>4ビット双方向ロジック変換モジュール<br />※SPI接続時のみ|<img width="240" alt="AE-LCNV4-MOSFET" src="https://qiita-image-store.s3.amazonaws.com/0/30370/03914e72-47ee-6f37-8bba-9c6cbc6bc530.jpeg">|必要|[秋月電子](https://akizukidenshi.com/catalog/g/g113837/)

Arduino Nano は 5V で駆動し、ロジックレベル（IOピンの電圧レベル）も 5V のため、3.3V で駆動する BME280 に**直接接続できません**。
3.3V は Arduino Nano に搭載されているレギュレータから供給しますが、5V <-> 3.3V のロジックレベル変換回路は別途用意しなくてはなりません。

各部品は 1 つずつ必要ですが、ロジックレベル変換モジュールは接続方法に合わせてご用意ください。配線の難易度や部品の配置を考えると I<sup>2</sup>C 接続が SPI 接続よりも容易ですが、AE-BME280 に少なくとも1箇所にはんだジャンパを作る必要があります。

ブレッドボード、ジャンパ、ケーブル、はんだ用具などは別途、お好きなものをご用意ください。

## AE-BME280のはんだジャンパについて

![solder_jumper](https://qiita-image-store.s3.amazonaws.com/0/30370/81e2aeda-5f01-7f8b-0a4e-9ca514f812b5.jpeg)

I<sup>2</sup>C で接続する場合は **J3** に、さらに SDA, SCL をともにプルアップする必要がある場合は **J1, J2** にもはんだジャンパを行います (画像左)。

ただし、前述した **I<sup>2</sup>Cバス用双方向電圧レベル変換モジュールにはプルアップ抵抗が内蔵されている** ため、これを使う場合は **J3 のみ**、はんだジャンパを行ってください (画像中央)。
以降、I<sup>2</sup>C 接続する場合は **J3 のみ**、はんだジャンパがされているものとして回路図を示します。

SPI で接続する場合は、はんだジャンパは **しません** (画像右)。


# 回路

|接続方式|回路図|実装例|配線例|
|:--:|:--:|---|---|
|**I<sup>2</sup>C**|![schematic_i2c.png](https://qiita-image-store.s3.amazonaws.com/0/30370/37630dd5-1e03-0c4b-f1c4-dd3c3498cab9.png)|![i2c_implemented](https://qiita-image-store.s3.amazonaws.com/0/30370/12051c65-95a0-de1a-4951-0f3de63673b6.jpeg)|![i2c_wiring.jpg](https://qiita-image-store.s3.amazonaws.com/0/30370/537102fe-9755-dbee-1e68-b3a79c8d0057.jpeg)|
|**SPI**|![schematic_spi.png](https://qiita-image-store.s3.amazonaws.com/0/30370/d84634c2-e2de-d77f-e78d-8e0e0e6f581f.png)|![spi_implemented.jpg](https://qiita-image-store.s3.amazonaws.com/0/30370/438172aa-a8a0-9624-23f5-0c6809b868dd.jpeg)|![spi_wiring.jpg](https://qiita-image-store.s3.amazonaws.com/0/30370/bee4dafe-4085-c769-82f8-d844e6674f5a.jpeg)|

前述の通り、配線量は I<sup>2</sup>C 接続のほうが少ないです。

## I<sup>2</sup>C 接続の場合

|Arduino Nano側ピン| AE-PCA9306 ピン | AE-BME280 ピン |
|---|---|---|
| 5V | VREF1 | |
| 3V3 | VREF2 | VDD |
| GND | GND | GND |
| A4 (SDA) | SDA1 -> SDA2 | SDI |
| A5 (SCL) | SCL1 -> SCL2 | SCK |

各モジュール間の配線は上表の通りです。
AE-PCA9306 の VPU、AE-BME280 の CSB ピンは接続しません。SDO ピンには 3.3V を印加します（詳しくは後述）。

## SPI 接続の場合

|Arduino Nano側ピン| AE-LCNV4-MOSFET ピン | AE-BME280 ピン |
|---|---|---|
| 5V | HV | |
| 3V3 | LV | VDD |
| GND | GND | GND |
| D11 (MOSI) | HV1 -> LV1 | SDI |
| D12 (MISO) | HV2 -> LV2 | SDO |
| D13 (SCK) | HV3 -> LV3 | SCK |
| D10 (/SS) | HV4 -> LV4 | CSB |

各モジュール間の配線は上表の通りです。
HVx -> LVx の組み合わせは一例です。Arduino と AE-BME280 の接続が対応さえしていれば、1～4 のどの組み合わせで接続してもかまいません。

# スケッチ

I<sup>2</sup>C と SPI 接続のどちらも、初期化方法が異なるだけで、値の取得方法に差異はありません。

予め [**SparkFun BME280 Arduino Library**](https://github.com/sparkfun/SparkFun_BME280_Arduino_Library) をダウンロードし、コンパイラが認識できる場所に配置してください。また、 `SparkFunBME280.h` を必ずインクルードしてください。

```cpp
#include "SparkFunBME280.h"
```

このライブラリはその名の通り、[SparkFun の BME280 モジュール](https://www.sparkfun.com/products/14348)用に設計されていますが、AE-BME280 でも問題なく使用できます。

## I<sup>2</sup>C 接続の場合

`Wire.h` を必ずインクルードします。`beginI2C` 関数を引数なしで呼び出すと、`Wire.h` 内で宣言された既定の `Wire` を使うためです。

```cpp:bme280_i2c.ino
#include <Wire.h>
#include "SparkFunBME280.h"

BME280 sensor;

void setup() {
  Serial.begin(115200);
  Wire.begin();
  sensor.beginI2C();  // Wire を用いて I2C 接続開始
}

void loop() {
  Serial.print("Temp: ");
  Serial.print(sensor.readTempC(), 2);

  Serial.print(" °C, Humidity: ");
  Serial.print(sensor.readFloatHumidity(), 2);

  Serial.print(" %, Pressure: ");
  Serial.print(sensor.readFloatPressure() / 100.0, 1);
  Serial.println(" hPa");

  delay(5000);
}
```

## SPI 接続の場合

`beginSPI` 関数には SPI の CS ピン番号を指定します。

```cpp:bme280_spi.ino
#include "SparkFunBME280.h"

const int SPI_CS_PIN = 10;
BME280 sensor;

void setup() {
  Serial.begin(115200);
  sensor.beginSPI(SPI_CS_PIN);
}

void loop() {
  Serial.print("Temp: ");
  Serial.print(sensor.readTempC(), 2);

  Serial.print(" °C, Humidity: ");
  Serial.print(sensor.readFloatHumidity(), 2);

  Serial.print(" %, Pressure: ");
  Serial.print(sensor.readFloatPressure() / 100.0, 1);
  Serial.println(" hPa");

  delay(5000);
}
```

# 計測

![measuring](https://qiita-image-store.s3.amazonaws.com/0/30370/68c815c6-0c54-0b3f-5c7d-4b749d149b5e.gif)

プログラムを書き込んだあと、シリアル通信を開くと 温度、湿度、気圧 が 5 秒ごとに出力されます。
値がすべて 0 になってしまう場合は配線を誤っている可能性がありますので、電源を抜いてから回路を再確認してください。

## 電源投入直後の計測値に注意

```
[Starting] Opening the serial port - COM4
[Info] Opened the serial port - COM4
Temp: 23.04 °C, Humidity: 38.33 %, Pressure: 666.4 hPa
Temp: 23.30 °C, Humidity: 50.86 %, Pressure: 1003.8 hPa
Temp: 23.24 °C, Humidity: 51.48 %, Pressure: 1003.8 hPa
Temp: 23.14 °C, Humidity: 47.17 %, Pressure: 1003.8 hPa
```
電源投入直後、気圧が極端に低く計測されたり、湿度が数％高く計測されることがあります。センサが安定するまで、十数秒〜数分は計測値を捨てる（無視）などして対応します。

稀に、この異常な数値が保持されて数分以上継続することがあります。この場合は電源を投入し直すことで解決します。いずれにせよ、計測値が想定の範囲内であるかは確認してください。


# 発展

## I<sup>2</sup>Cアドレスを変更する

SparkFunBME280 ライブラリのデフォルトのI<sup>2</sup>Cアドレスは `0x77` ですが、`beginI2C` 関数を呼び出す前に `0x76` へ変更ができます。

```cpp
Wire.begin();
sensor.setI2CAddress(0x76);
sensor.beginI2C();
```

ただし、BME280 側にもアドレスの変更を認識させるために、SDOピンの配線を変える必要があります。

| SDOへの入力 | アドレス | 回路図 |
|:---:|:---:|:---:|
| 3.3V | 0x77 |[<img width="240" alt="I2C 0x77" src="https://qiita-image-store.s3.amazonaws.com/0/30370/d1401058-23c5-b58e-f327-2a7958329ba1.png">](https://qiita-image-store.s3.amazonaws.com/0/30370/d1401058-23c5-b58e-f327-2a7958329ba1.png)|
| GND | 0x76 |[<img width="240" alt="I2C 0x76" src="https://qiita-image-store.s3.amazonaws.com/0/30370/b0f5fd4d-95e4-1c1c-a617-66d08106815a.png">](https://qiita-image-store.s3.amazonaws.com/0/30370/b0f5fd4d-95e4-1c1c-a617-66d08106815a.png)|

なお、BME280 がどのアドレスになっているか不明な場合でも、`beginI2C` 関数の返り値で判定ができます。これを応用して、自動的にアドレスを決定することもできます。

```cpp
void setupBME280() {
  Wire.begin();
  sensor.setI2CAddress(0x76);

  if (sensor.beginI2C()) {
    Serial.println("I2C address: 0x76");
    return;
  }

  sensor.setI2CAddress(0x77);

  if (sensor.beginI2C()) {
    Serial.println("I2C address: 0x77");
    return;
  }

  Serial.println("Sensor connect failed");
  while(1) { }
}
```

## フィルタとオーバーサンプリング設定

BME280には以下のモードが存在します。

- **SLEEP**: 電源投入直後の、計測をせず設定を行うためのモード
- **FORCED**: 1回のみ計測を行うモード。計測終了後は SLEEP モードに自動で遷移
- **NORMAL**: 繰り返し計測を行うモード

begin 系関数を呼び出すと、暗黙的に NORMAL モードに遷移します。
オーバーサンプリング設定によって値の精度を高めることができますが、正常な値が取得できるまでの時間が伸びるなどのデメリットも存在します。

```cpp
void setup() {
  Serial.begin(115200);
  Wire.begin();
  sensor.beginI2C();

  sensor.setFilter(1);              // フィルタ係数: 2
  sensor.setStandbyTime(0);         // スタンバイ時間: 0.5 ms
  sensor.setTempOverSample(1);      // オーバーサンプリング x1
  sensor.setPressureOverSample(1);  // オーバーサンプリング x1
  sensor.setHumidityOverSample(1);  // オーバーサンプリング x1
}
```

フィルタ係数、スタンバイ時間、オーバーサンプリングの詳細については [**こちら**](https://trac.switch-science.com/wiki/BME280) で詳しく解説されているのでご参照ください。

## 消費電力を抑える

前述した通り、通常は NORMAL モードで繰り返し計測を行うため、値を取得していない間も計測を行っています。FORCED モードにより、必要なときだけ計測を行い、消費電力を最小限にできます。

`setMode` 関数でモードの変更ができるので、初期化直後に SLEEP モードに遷移させ、計測前に FORCED モードにするだけです。計測後は自動的に SLEEP モードに遷移します。
注意が必要なのは、すぐに値を読み取ると計測途中のために正しい値が取得できないことです。`isMeasuring` 関数で計測中かどうかを判定できるので、計測中であれば待ちます。

```cpp
void setup() {
  Serial.begin(115200);
  Wire.begin();
  sensor.beginI2C();
  sensor.setMode(MODE_SLEEP);
}

void loop() {
  sensor.setMode(MODE_FORCED);
  while (!sensor.isMeasuring()) delay(1);  // 計測開始前ならば待ち
  while ( sensor.isMeasuring()) delay(1);  // 計測中ならば待ち

  Serial.print("Temp: ");
  Serial.print(sensor.readTempC(), 2);

  // 以下略
}
```

## 高度を求める

`readFloatAltitudeMeters` 関数で高度（標高）を求めることができます。気圧から計算をしているため、ある程度の誤差があります。単位はメートル (m) ですが、`readFloatAltitudeFeet` 関数でフィート (ft) を求められます。

```cpp
Serial.print("Altitude: ");
Serial.print(sensor.readFloatAltitudeMeters(), 0);
Serial.println(" m");
```

## 露点温度を求める

`dewPointC` 関数で露点温度を求めることができます。内部で温度と湿度を計測しています。単位は摂氏 (°C) ですが、`dewPointF` 関数で華氏 (°F) を求められます。

```cpp
Serial.print("Dew point: ");
Serial.print(sensor.dewPointC(), 2);
Serial.println(" °C");
```

## 不快指数を求める

不快指数を求める関数は SparkFunBME280 ライブラリには存在しないため、定義から求めます。

乾球温度 $T_d$ (°C), 湿度 $H$ (%) とすると不快指数 $\textrm{DI}$ は、

```math
\textrm{DI} = 0.81 T_d + 0.01 H (0.99 T_d - 14.3) + 46.3
```

と求められるので、これを計算して値を得ます。

```cpp
double temp = sensor.readTempC();
double humi = sensor.readFloatHumidity();
double di = 0.81 * temp + 0.01 * humi * (0.99 * temp - 14.3) + 46.3;

Serial.print("Discomfort index: ");
Serial.println(di, 1);
```

---

**参考文献**

- **[ＢＭＥ２８０使用　温湿度・気圧センサモジュールキット - 秋月電子通商](https://akizukidenshi.com/catalog/g/g109421/)**<br />
  https://akizukidenshi.com/catalog/g/g109421/
- **[[I2C] 温湿度大気圧センサー (BME280)](https://ht-deko.com/arduino/i2c_bme280.html)**<br />
  https://ht-deko.com/arduino/i2c_bme280.html
- **[BME280 - スイッチサイエンス](https://trac.switch-science.com/wiki/BME280)**<br />
  https://trac.switch-science.com/wiki/BME280
- **[SparkFun BME280 Arduino Library - GitHub](https://github.com/sparkfun/SparkFun_BME280_Arduino_Library)**<br />
  https://github.com/sparkfun/SparkFun_BME280_Arduino_Library