部品 · 読了まで15分

ArduinoでBME280を使うガイド:温度、湿度、気圧、高度

BME280やBMP280をArduinoにつなぐ方法。0x76/0x77のI2Cアドレス、Adafruitのライブラリ、海面気圧からの高度、3.3 Vの注意点、ESP32の気象ステーション、Picoまで解説します。

BME280は、Bosch製のデジタル環境センサーで、温度、相対湿度、気圧を1つの小さなチップで測ります。Arduinoで使うのに必要なのは4本の線(VCC、GND、SDA、SCL)とAdafruit BME280ライブラリだけで、数行のコードでシリアルモニタに°C、%、hPaの値が表示されます。気圧を測るので、海面からの高さ、つまり高度も計算できます。兄弟分のBMP280は見た目がほとんど同じですが、湿度は測りません。温度と気圧だけです。

このガイドでは、2つのセンサーの違い、I2Cアドレスが0x76のときと0x77のときがある理由、Arduino Uno、ESP32、Raspberry Pi Picoへの配線、高度の式の仕組みと正しい海面気圧の選び方を学びます。続いて、スマホで開けるESP32のミニ気象ステーションを作り、Picoでライブラリを使わないMicroPythonのドライバを動かし、1つのスケッチで、とてもよくある「BME280として売られているBMP280」の問題を見破る方法を見ていきます。

BME280温湿度・気圧センサーモジュール
BME280モジュール:金属のふたの小さなチップが温度、湿度、気圧を測り、I2CかSPIで接続します。

BME280とは?どう動くのか

BME280には3つの検知素子が入っています。気圧センサーは、薄いシリコンの膜が押し付ける空気でどれだけたわむかを、ピエゾ抵抗効果で測ります。湿度センサーは、空気中の水分を吸収すると静電容量が変わるポリマーの層です。温度センサーは周囲の温度を知らせるとともに、ほかの2つの測定値の補正にも使われます。どのチップも工場で1個ずつキャリブレーションされ、その係数が内部のメモリに保存されています。ライブラリは起動時にそれを読み、データシートの式で生の数値を実際の単位に変換します。金属のふたの小さな穴は空気をセンサーに届けるためのものなので、テープや接着剤で決してふさがないでください。

項目BME280BMP280
測るもの温度、湿度、気圧温度、気圧
温度-40…+85 °C、±1 °C(25 °Cで±0.5 °C)-40…+85 °C、±1 °C(25 °Cで±0.5 °C)
相対湿度0〜100 %、±3 %なし
気圧の範囲300〜1100 hPa300〜1100 hPa
気圧の精度絶対±1 hPa、相対±0.12 hPa(≈ ±1 m)絶対±1 hPa、相対±0.12 hPa(≈ ±1 m)
チップの電源電圧1.71〜3.6 V1.71〜3.6 V
電流(1秒に1回の測定)3.6 µA(3つの測定すべて)2.7 µA
スリープ時の電流0.1 µA0.1 µA
インターフェースI2C(最大3.4 MHz)とSPI(最大10 MHz)I2CとSPI
チップID(レジスタ0xD0)0x600x58
チップの大きさ2.5 × 2.5 mm(正方形)2.0 × 2.5 mm(長方形)

まとめると、BMP280は湿度センサーのない少し安いBME280で、気圧と温度の値は同じくらい優秀です。高度や気圧だけが必要ならBMP280で十分です。気象ステーション、温室、室内環境の監視のように湿度が重要なプロジェクトには、BME280を選んでください。ファミリーの1つ上のBME680/BME688には、空気中の揮発性有機化合物(VOC)を検知するガスセンサーが加わりますが、価格は数倍になります。

BME280とDHT11/DHT22のどちら?

項目DHT11DHT22BME280
温度の範囲と精度0〜50 °C、±2 °C-40…+80 °C、±0.5 °C-40…+85 °C、±1 °C
湿度の精度±5 %±2〜5 %±3 %
気圧と高度なしなしあり
最も速い読み取り1秒に1回2秒に1回1秒に何十回
接続データ線1本データ線1本I2CまたはSPI
電流(測定中)≈ 1 mA≈ 1.5 mA1 mA未満

DHTのセンサーは安くて簡単ですが、遅く、ときどき読み取りエラーを返し、気圧は測れません。BME280はディスプレイなどほかの機器と同じI2Cバスを共有でき、スリープ中はほとんど電流を消費せず、何か月も電池で動かすプロジェクトに最適です。DHT11の詳細はDHT11温湿度センサーで、すべての選択肢を並べて見るには温度センサーの比較を読んでください。

モジュールの種類と3.3 Vの注意点

チップ単体はとても小さいので、常にブレイクアウト基板に載せて使われます。一般的なのは2種類です。6ピンのモジュール(たいてい紫色で、GY-BME280-3.3やGY-BMP280-3.3として売られています)にはVCC、GND、SCL、SDA、CSB、SDOのピンがあります。ほとんどはレギュレーターがなく、名前の3.3がまさにそれを示しています。3.3 Vでだけ給電してください。4ピンのモジュール(VIN、GND、SCL、SDA)は、たいてい小さな3.3 Vのレギュレーターを載せていて、I2Cの線用のレベル変換トランジスタも付いていることが多いので、Arduino Unoの5Vピンにつなげます。どちらの種類かわからない場合は、基板に3本足の小さなレギュレーターのチップがあるか探してください。なければ3.3 Vを使います。

レギュレーターのない3.3 Vのモジュールを5Vピンにつなぐと、センサーが壊れることがあります。チップの絶対最大定格は4.25 Vです。壊れたセンサーが完全に動かなくなるとは限りません。温度が数度ずれたり、気圧の値がでたらめになったりすることもあり、故障に気づきにくくなります。

BME280の配線:Arduino Uno、ESP32、Pico

BME280のピンArduino UnoESP32Raspberry Pi Pico
VCC / VIN3.3V(6ピン)または5V(4ピン、レギュレーター付き)3V33V3(OUT)、36番ピン
GNDGNDGNDGND、38番ピン
SCLA5GPIO 22GP5、7番ピン
SDAA4GPIO 21GP4、6番ピン
CSB(6ピン)3.3V(I2Cを選ぶ)3V33V3
SDO(6ピン)開放かGND:0x76、3.3V:0x77開放かGND:0x76、3V3:0x77開放かGND:0x76、3V3:0x77

多くの人がつまずくのはアドレスです。チップのSDOピンがGNDならアドレスは0x76、3.3 Vなら0x77です。安いモジュールの多くはSDOを抵抗でGNDに引き下げているのでアドレスは0x76ですが、Adafruit自身の基板は0x77を使い、ライブラリも初期設定では0x77を試します。そのため、コードではアドレスをはっきり書いてください。電源投入時にCSBピンがGNDだと、チップはSPIモードになり、I2Cには応答しません。I2Cを使う場合は、CSBを3.3 Vにつなぐか、モジュールがすでに引き上げていることを確認してください。ボードのI2Cのピンはピン配置ツールで、I2Cバスの考え方はI2Cとは?で確認できます。

同じバスに2つのセンサーをつなぎたい場合は、片方のセンサーのSDOピンを3.3 Vにつなぎ、アドレスを0x77に変えます。4ピンのモジュールでは、たいてい裏のはんだジャンパーでこれを行います。センサーが3つ以上ならSPIのほうが向いています。SCLをSCK(Unoでは13)へ、SDAをMOSI(11)へ、SDOをMISO(12)へつなぎ、各センサーのCSBピンをそれぞれ別のデジタルピンへつなぎます。コードではオブジェクトをAdafruit_BME280 bme(10);と宣言するだけです。5 VのUnoはSPIの線を直接駆動するので、3.3 Vのモジュールとの間には必ずレベル変換モジュールを入れてください。

Arduino Unoは5 Vで動き、WireライブラリはA4とA5の内部プルアップ抵抗を5 Vにオンにします。3.3 Vの6ピンのモジュールでもたいていは動きます。モジュール上のプルアップ抵抗がバスを約3.3 Vに保つからです。ただし常設のプロジェクトでは、レギュレーター付きの4ピンのモジュールかレベル変換モジュールを使うのが最も安全です。ESP32とPicoはもともと3.3 Vで動くので、この問題はありません。

Adafruit BME280ライブラリのインストール

  1. Arduino IDEで「ツール」>「ライブラリを管理」を開き、Adafruit BME280を検索します。
  2. インストールをクリックしたら、依存ライブラリについて聞くウィンドウで「全てをインストール」を選び、Adafruit Unified SensorとAdafruit BusIOも一緒にインストールします。
  3. センサーがBMP280なら、同じ方法でAdafruit BMP280ライブラリをインストールします。BME280ライブラリはBMP280を認識しません。
  4. 必要なら「ファイル」>「スケッチ例」>「Adafruit BME280 Library」>「bme280test」を開いても構いませんが、begin()の行にアドレス(0x76)を入れるのを忘れないでください。

最初のスケッチ:BME280で温度、湿度、気圧を読む

C++ (Arduino IDE)
1#include <Wire.h>
2#include <Adafruit_Sensor.h>
3#include <Adafruit_BME280.h>
4
5#define DENIZ_SEVIYESI_HPA 1013.25   // bugünkü deniz seviyesi basıncı / today's sea-level pressure
6
7Adafruit_BME280 bme;
8
9void setup() {
10  Serial.begin(9600);
11  // Önce 0x76, olmazsa 0x77 dene / try 0x76 first, then 0x77
12  if (!bme.begin(0x76) && !bme.begin(0x77)) {
13    Serial.println(F("BME280 bulunamadi! Kablolari ve adresi kontrol et."));
14    Serial.print(F("Chip ID: 0x"));
15    Serial.println(bme.sensorID(), HEX);       // 0x58 ise bu bir BMP280 / 0x58 means BMP280
16    while (true) delay(10);
17  }
18  Serial.println(F("BME280 hazir"));
19}
20
21void loop() {
22  float sicaklik = bme.readTemperature();          // °C
23  float nem      = bme.readHumidity();             // % bağıl nem / % relative humidity
24  float basinc   = bme.readPressure() / 100.0F;    // Pa -> hPa
25  float irtifa   = bme.readAltitude(DENIZ_SEVIYESI_HPA);   // metre / meters
26
27  Serial.print(F("Sicaklik: ")); Serial.print(sicaklik, 1); Serial.println(F(" C"));
28  Serial.print(F("Nem:      ")); Serial.print(nem, 1);      Serial.println(F(" %"));
29  Serial.print(F("Basinc:   ")); Serial.print(basinc, 1);   Serial.println(F(" hPa"));
30  Serial.print(F("Irtifa:   ")); Serial.print(irtifa, 0);   Serial.println(F(" m"));
31  Serial.println();
32  delay(2000);
33}
関数役割
bme.begin(0x76)センサーを起動する。そのアドレスで機器が応答し、チップIDが0x60ならtrueを返す
readTemperature()温度、°C
readHumidity()相対湿度、%
readPressure()気圧、Pa(100で割るとhPa)
readAltitude(P0)海面気圧P0(hPa)を基準にした高さ、メートル
seaLevelForAltitude(altitude, P)わかっている高度から求めた海面気圧、hPa
sensorID()読み取ったチップID:0x60はBME280、0x58はBMP280
setTemperatureCompensation(x)すべての温度の値にx °Cを加える。湿度と気圧もそれに合わせて補正される

スケッチを書き込んでシリアルモニタを9600ボーで開くと、室内の条件ではおよそ23.4 °C、湿度45 %、1009.8 hPaといった値が表示されます。気圧は何時間もかけてゆっくり変わります。センサーに息を吹きかけると数秒で湿度が上がり、指で触れると温度が上がるのがわかります。初期設定では、ライブラリは最も高いオーバーサンプリング(x16)のノーマルモードで連続して測ります。最初のテストには最適ですが、下で見るように、長期の気象観測には別の設定のほうが正確な結果になります。

測定モード:フォースドモード、ノーマルモードと正しい設定

BME280には3つのモードがあります。スリープモードでは測定せず、0.1 µAしか消費しません。フォースドモードでは1回だけ測ってスリープに戻ります。新しい値のたびにtakeForcedMeasurement()を呼びます。ノーマルモードでは、設定した待機時間で自分から測り続けます。オーバーサンプリングは同じ値を何回か測って平均し、ノイズを減らします。IIRフィルターは、急な気圧のスパイク(ドアがバタンと閉まる、突風)をなめらかにします。Boschはデータシートで、用途に応じて次の設定を推奨しています。

用途モードオーバーサンプリング(気圧 / 温度 / 湿度)IIRフィルター平均電流
気象の観測フォースド、1分に1回x1 / x1 / x1オフ≈ 0.16 µA
湿度の検知フォースド、1秒に1回省略 / x1 / x1オフ≈ 2.9 µA
屋内ナビゲーション(階、エレベーター)ノーマル、待機0.5 msx16 / x2 / x116≈ 633 µA
センサーが連続して測ると、チップと基板がごくわずかに温まります。これを自己発熱と呼びます。室温や外気温を測る場合は、フォースドモードを使い、1分に数回の測定で満足してください。それでも信頼できる温度計より一定の量だけ高い場合は、bme.setTemperatureCompensation(-1.0);のような補正を加えられます。

海面気圧と高度の計算

気圧は高く上がるほど下がるので、測った気圧から高度を計算できます。ライブラリは次の気圧の式を使います。h = 44330 × (1 − (P / P0)^0.1903)です。ここでPは測った気圧、P0は現在の海面気圧です(どちらもhPa)。標準大気ではP0 = 1013.25 hPaとし、海面付近では1 hPaの差が約8.3メートルに相当します。

問題は、P0が一定ではないことです。天気によっておよそ990〜1035 hPaの間で変わります。今日の実際の値が1023 hPaなのにコードに1013.25を入れたままだと、センサーは約80メートル低い高度を示します。正確な結果を得るにはP0を最新に保つ必要があります。気象サービスが出す「海面気圧」の値か、最寄りの空港のMETARのQの値(たとえばQ1018は1018 hPaの意味)を使ってください。逆の方法もあります。自分のいる場所の高度がわかっていれば、seaLevelForAltitude(altitude, pressure)でP0を計算します。たとえばアンカラ(約938 m)でセンサーが905 hPaを示すなら、海面に換算した気圧は約1012.6 hPaで、これが天気アプリに表示される数値です。

絶対高度では、±1 hPaの絶対精度は約±8メートルの不確かさを意味します。しかし相対の測定では、センサーはずっと優秀で、±0.12 hPa ≈ ±1 mです。建物の1階分は約3メートル、つまり0.36 hPaなので、BME280は今何階にいるかを簡単に見分けたり、エレベーターの動きを検知したりできます。下のスケッチは起動時に20回の測定の平均を基準にし、その点からの相対的な高さと、わかっている高度から計算した海面気圧を表示します。

C++ (Arduino IDE)
1#include <Wire.h>
2#include <Adafruit_Sensor.h>
3#include <Adafruit_BME280.h>
4
5#define BILINEN_IRTIFA 938.0   // bulunduğun yerin rakımı (m), örnek Ankara / your altitude (m), e.g. Ankara
6
7Adafruit_BME280 bme;
8float referansHpa;             // açılıştaki basınç / pressure at start-up
9
10void setup() {
11  Serial.begin(9600);
12  if (!bme.begin(0x76)) {
13    Serial.println(F("BME280 bulunamadi!"));
14    while (true) delay(10);
15  }
16  // İç mekan navigasyonu ayarı: düşük gürültü / indoor navigation preset: low noise
17  bme.setSampling(Adafruit_BME280::MODE_NORMAL,
18                  Adafruit_BME280::SAMPLING_X2,    // sıcaklık / temperature
19                  Adafruit_BME280::SAMPLING_X16,   // basınç / pressure
20                  Adafruit_BME280::SAMPLING_X1,    // nem / humidity
21                  Adafruit_BME280::FILTER_X16,     // IIR filtre / IIR filter
22                  Adafruit_BME280::STANDBY_MS_0_5);
23  delay(1000);                                      // filtre otursun / let the filter settle
24
25  float toplam = 0;
26  for (int i = 0; i < 20; i++) {                   // 20 ölçümün ortalaması / average of 20 readings
27    toplam += bme.readPressure() / 100.0F;
28    delay(50);
29  }
30  referansHpa = toplam / 20;
31}
32
33void loop() {
34  float basinc = bme.readPressure() / 100.0F;                               // istasyon basıncı / station pressure
35  float denizSeviyesi = bme.seaLevelForAltitude(BILINEN_IRTIFA, basinc);    // hPa
36  float bagilYukseklik = bme.readAltitude(referansHpa);                     // başlangıca göre / relative to start
37
38  Serial.print(F("Istasyon basinci: "));   Serial.print(basinc, 2);         Serial.println(F(" hPa"));
39  Serial.print(F("Deniz seviyesi (QNH): ")); Serial.print(denizSeviyesi, 1); Serial.println(F(" hPa"));
40  Serial.print(F("Baslangica gore: "));    Serial.print(bagilYukseklik, 2); Serial.println(F(" m"));
41  Serial.println();
42  delay(1000);
43}

ボードを床から持ち上げて頭の上に掲げると、「Baslangica gore」(起動時からの差)の値が約2メートル上がるのがわかります。気圧は1日のうちに数hPa変わるので、この相対的な値は数時間で少しずつずれていきます。長期の使用では、ときどき基準を更新する必要があります。値をさらに落ち着かせるには、センサーデータのフィルタリングの移動平均を加えられます。

ESP32のミニ気象ステーション:スマホで開けるWebページ

ESP32のWi-Fiを使えば、同じネットワークのどのスマホやパソコンからでも、BME280の値をWebページで見られます。下のスケッチはESP32のコアに付属するWebServerライブラリを使うので、追加のライブラリは不要です。メインページは一度だけ読み込まれ、その中の小さなJavaScriptが5秒ごとに/veriからJSONを取得し、ページを再読み込みせずに値を更新します。センサーはフォースドモードで動くので、誰かがページを見ているときだけ測定し、気圧は天気予報と比べられるよう海面に換算して表示します。ESP32が初めてならESP32とは?から始めてください。

  1. 表のとおりにBME280を配線します。VCCを3V3に、SDAをGPIO 21に、SCLをGPIO 22につなぎます。
  2. コードのWIFI_ADI(ネットワーク名)とWIFI_SIFRE(パスワード)の欄にネットワークの情報を、RAKIM_Mに自分のいる場所の高度を入力します。
  3. ボードにESP32 Dev Moduleを選び、スケッチを書き込みます。
  4. シリアルモニタを115200ボーで開きます。接続されるとIPアドレスが表示されます。
  5. 同じWi-Fiネットワークにつないだスマホのブラウザにそのアドレス(たとえば192.168.1.42)を入力すると、温度、湿度、気圧のカードが表示されます。
C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WebServer.h>
3#include <Wire.h>
4#include <Adafruit_Sensor.h>
5#include <Adafruit_BME280.h>
6
7const char* WIFI_ADI   = "WiFiAdin";      // kendi ağ adın / your network name
8const char* WIFI_SIFRE = "WiFiSifren";    // kendi şifren / your password
9const float RAKIM_M    = 938.0;           // bulunduğun yerin rakımı / your local altitude
10
11Adafruit_BME280 bme;
12WebServer server(80);
13
14// Telefonda açılan sayfa, 5 sn'de bir /veri'yi okur / phone page, reads /veri every 5 s
15const char SAYFA[] PROGMEM = R"rawliteral(
16<!DOCTYPE html><html><head><meta charset="utf-8">
17<meta name="viewport" content="width=device-width,initial-scale=1">
18<title>Mini Hava İstasyonu</title>
19<style>
20body{font-family:sans-serif;text-align:center;background:#0f172a;color:#e2e8f0;margin:0;padding:16px}
21.k{display:inline-block;min-width:140px;margin:8px;padding:16px;border-radius:14px;background:#1e293b}
22.d{font-size:2.2em;font-weight:bold;margin:6px 0}
23</style></head><body>
24<h2>Mini Hava İstasyonu</h2>
25<div class="k">Sıcaklık<div class="d" id="t">--</div>°C</div>
26<div class="k">Nem<div class="d" id="h">--</div>%</div>
27<div class="k">Basınç<div class="d" id="p">--</div>hPa</div>
28<script>
29async function guncelle() {
30  const v = await (await fetch('/veri')).json();
31  document.getElementById('t').textContent = v.t.toFixed(1);
32  document.getElementById('h').textContent = v.h.toFixed(0);
33  document.getElementById('p').textContent = v.p.toFixed(1);
34}
35guncelle();
36setInterval(guncelle, 5000);
37</script>
38</body></html>
39)rawliteral";
40
41void anaSayfa() {
42  server.send(200, "text/html", SAYFA);
43}
44
45void veriGonder() {
46  bme.takeForcedMeasurement();                     // tek ölçüm al, sonra uyu / one measurement, then sleep
47  float p = bme.readPressure() / 100.0F;
48  String json = "{\"t\":" + String(bme.readTemperature(), 2) +
49                ",\"h\":" + String(bme.readHumidity(), 1) +
50                ",\"p\":" + String(bme.seaLevelForAltitude(RAKIM_M, p), 1) + "}";
51  server.send(200, "application/json", json);
52}
53
54void setup() {
55  Serial.begin(115200);
56  Wire.begin(21, 22);                              // SDA, SCL
57  if (!bme.begin(0x76)) {
58    Serial.println("BME280 bulunamadi!");
59    while (true) delay(10);
60  }
61  // Hava durumu izleme ayarı: kendi kendini ısıtmayı azaltır / weather monitoring preset: less self-heating
62  bme.setSampling(Adafruit_BME280::MODE_FORCED,
63                  Adafruit_BME280::SAMPLING_X1,    // sıcaklık / temperature
64                  Adafruit_BME280::SAMPLING_X1,    // basınç / pressure
65                  Adafruit_BME280::SAMPLING_X1,    // nem / humidity
66                  Adafruit_BME280::FILTER_OFF);
67
68  WiFi.mode(WIFI_STA);
69  WiFi.begin(WIFI_ADI, WIFI_SIFRE);
70  while (WiFi.status() != WL_CONNECTED) {
71    delay(500);
72    Serial.print(".");
73  }
74  Serial.print("\nTarayicida ac / open in browser: http://");
75  Serial.println(WiFi.localIP());
76
77  server.on("/", anaSayfa);
78  server.on("/veri", veriGonder);
79  server.begin();
80}
81
82void loop() {
83  server.handleClient();
84}
ESP32は動作中に数度温まるので、センサーをボードのすぐ横に置くと、その熱で値が狂います。BME280を10〜15 cmのケーブルでボードから離し、ケースの通気口の横に置き、日光が当たらないようにしてください。グラフやボタンでの操作を備えた、より高度なダッシュボードにはESP32のWebサーバーとWebSocketに進んでください。

Raspberry Pi PicoでBME280を使う(MicroPython)

MicroPythonにはBME280用の組み込みのモジュールがありません。ドライバのファイルをダウンロードする代わりに、下のコードは小さなドライバを中に持っています。キャリブレーションの係数を読み、フォースドモードでセンサーに1回測定させ、データシートの式で結果を計算します。チップIDを確認するので、BMP280でも動き、湿度の行は自動で省きます。センサーをGP4(SDA)、GP5(SCL)、3V3(OUT)につなぎ、Thonnyでコードをmain.pyとしてPicoに保存してください。Picoが初めてなら、Raspberry Pi Picoとは?とMicroPythonの基本を読むとわかりやすくなります。

MicroPython
1from machine import Pin, I2C
2import struct, time
3
4i2c = I2C(0, sda=Pin(4), scl=Pin(5), freq=400000)
5ADR = 0x76                                   # olmazsa 0x77 dene / try 0x77 if it fails
6P0 = 1013.25                                 # deniz seviyesi basıncı (hPa) / sea-level pressure (hPa)
7
8def s8(x):                                   # işaretli 8 bit / signed 8-bit
9    return x - 256 if x > 127 else x
10
11chip = i2c.readfrom_mem(ADR, 0xD0, 1)[0]
12BME = chip == 0x60                           # 0x60 BME280, 0x58 BMP280
13print("Chip ID:", hex(chip), "BME280" if BME else "BMP280 (nem yok / no humidity)")
14
15# Fabrika kalibrasyon katsayıları / factory calibration coefficients
16T1, T2, T3, P1, P2, P3, P4, P5, P6, P7, P8, P9 = struct.unpack("<HhhHhhhhhhhh", i2c.readfrom_mem(ADR, 0x88, 24))
17if BME:
18    H1 = i2c.readfrom_mem(ADR, 0xA1, 1)[0]
19    e = i2c.readfrom_mem(ADR, 0xE1, 7)
20    H2 = struct.unpack("<h", e[0:2])[0]
21    H3 = e[2]
22    H4 = (s8(e[3]) << 4) | (e[4] & 0x0F)
23    H5 = (s8(e[5]) << 4) | (e[4] >> 4)
24    H6 = s8(e[6])
25    i2c.writeto_mem(ADR, 0xF2, b"\x01")      # nem x1 (ctrl_meas'tan önce) / humidity x1 (before ctrl_meas)
26
27def oku():
28    i2c.writeto_mem(ADR, 0xF4, b"\x25")      # sıcaklık x1, basınç x1, forced mod / temp x1, press x1, forced mode
29    time.sleep_ms(15)                        # ölçümün bitmesini bekle / wait for the measurement
30    d = i2c.readfrom_mem(ADR, 0xF7, 8 if BME else 6)
31    adc_p = (d[0] << 12) | (d[1] << 4) | (d[2] >> 4)
32    adc_t = (d[3] << 12) | (d[4] << 4) | (d[5] >> 4)
33
34    # Sıcaklık (datasheet formülü) / temperature (datasheet formula)
35    v1 = (adc_t / 16384 - T1 / 1024) * T2
36    v2 = (adc_t / 131072 - T1 / 8192) ** 2 * T3
37    t_fine = v1 + v2
38    t = t_fine / 5120
39
40    # Basınç, Pa / pressure, Pa
41    v1 = t_fine / 2 - 64000
42    v2 = v1 * v1 * P6 / 32768 + v1 * P5 * 2
43    v2 = v2 / 4 + P4 * 65536
44    v1 = (P3 * v1 * v1 / 524288 + P2 * v1) / 524288
45    v1 = (1 + v1 / 32768) * P1
46    p = 1048576 - adc_p
47    p = (p - v2 / 4096) * 6250 / v1
48    p = p + (P9 * p * p / 2147483648 + p * P8 / 32768 + P7) / 16
49
50    h = None
51    if BME:                                  # nem, % / humidity, %
52        adc_h = (d[6] << 8) | d[7]
53        x = t_fine - 76800
54        x = (adc_h - (H4 * 64 + H5 / 16384 * x)) * (H2 / 65536 * (1 + H6 / 67108864 * x * (1 + H3 / 67108864 * x)))
55        x = x * (1 - H1 * x / 524288)
56        h = min(max(x, 0), 100)
57    return t, p / 100, h                     # °C, hPa, %
58
59while True:
60    t, p, h = oku()
61    irtifa = 44330 * (1 - (p / P0) ** 0.1903)    # metre / meters
62    print("Sicaklik: %.1f C  Basinc: %.1f hPa  Irtifa: %.0f m" % (t, p, irtifa))
63    if h is not None:
64        print("Nem: %.1f %%" % h)
65    time.sleep(2)

コードのいくつかの細かい点に注目してください。湿度の設定(レジスタ0xF2)は0xF4に書き込んだあとで初めて有効になるので、湿度の設定を先に、測定のコマンドをあとに送っています。0xF4に書き込む値0x25は「温度x1、気圧x1、フォースドモード」を意味します。この設定では1回の測定にかかるのは最大約10 msなので、15 ms待てば十分です。BoschのBMP280のデータシートにあるキャリブレーション値の例でテストすると、コードは25.08 °Cと1006.5 hPaを返し、計算がBoschの式とぴったり一致することがわかります。

BME280として売られているBMP280:手元のセンサーは本当はどれ?

どちらのチップも同じ基板の設計に載るので、BMP280のモジュールをBME280として売る販売店もあります。基板に「BME/BMP280」とだけ書かれていることもあります。症状はいつも同じです。Adafruit BME280ライブラリが"Could not find a valid BME280 sensor, check wiring, address, sensor ID!"(有効なBME280が見つからない)と表示し、そのすぐ下にSensorID was: 0x58と出ます。ほかのライブラリではエラーは出ませんが、湿度がいつも0かnanになります。配線には問題がありません。手元のチップは湿度を測らないBMP280なのです。

BME280
BME280
BMP280
BMP280
  • チップの形を見る:金属のふたが正方形(2.5 × 2.5 mm)ならBME280、はっきり長方形(2.0 × 2.5 mm)ならBMP280です。スマホのカメラで拡大すれば、違いは簡単にわかります。
  • チップIDを読む:最も確実な方法は下のスケッチです。チップの0xD0レジスタのIDを読み、どのセンサーかを教えてくれます。
  • 価格に注意する:本物のBME280はBMP280よりはっきり高いので、相場よりずっと安く売られている「BME280」のモジュールは疑ってください。
C++ (Arduino IDE)
1#include <Wire.h>
2
3void setup() {
4  Serial.begin(9600);
5  Wire.begin();
6  bool bulundu = false;
7
8  for (uint8_t adres = 0x76; adres <= 0x77; adres++) {
9    Wire.beginTransmission(adres);
10    if (Wire.endTransmission() != 0) continue;   // bu adreste cihaz yok / no device at this address
11
12    Wire.beginTransmission(adres);
13    Wire.write(0xD0);                             // chip ID register'ı / chip ID register
14    Wire.endTransmission();
15    Wire.requestFrom(adres, (uint8_t)1);
16    uint8_t id = Wire.read();
17    bulundu = true;
18
19    Serial.print(F("Adres 0x"));
20    Serial.print(adres, HEX);
21    Serial.print(F("  Chip ID 0x"));
22    Serial.print(id, HEX);
23    if (id == 0x60)                    Serial.println(F("  -> BME280 (sicaklik + nem + basinc)"));
24    else if (id >= 0x56 && id <= 0x58) Serial.println(F("  -> BMP280 (nem olcmez)"));
25    else if (id == 0x61)               Serial.println(F("  -> BME680 / BME688"));
26    else if (id == 0x55)               Serial.println(F("  -> BMP180"));
27    else                               Serial.println(F("  -> bilinmeyen yonga / unknown chip"));
28  }
29
30  if (!bulundu) Serial.println(F("0x76 ve 0x77'de sensor yok: kablolari kontrol et"));
31}
32
33void loop() {
34}

結果が0x58でも心配はいりません。BMP280の温度と気圧の値はBME280と同じくらい優秀です。Adafruit BMP280ライブラリをインストールして下のスケッチを使ってください。湿度が必要なら、プロジェクトに別のDHT22を加えるか、商品を返品してください。ときどきIDが0x56や0x57のBMP280に出会うこともあります。その場合はbeginの行をbmp.begin(0x76, 0x56)と書きます。

C++ (Arduino IDE)
1#include <Wire.h>
2#include <Adafruit_BMP280.h>
3
4Adafruit_BMP280 bmp;
5
6void setup() {
7  Serial.begin(9600);
8  if (!bmp.begin(0x76) && !bmp.begin(0x77)) {
9    Serial.println(F("BMP280 bulunamadi! Kablolari ve adresi kontrol et."));
10    while (true) delay(10);
11  }
12  // Datasheet'in önerdiği varsayılan ayarlar / default settings from the datasheet
13  bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
14                  Adafruit_BMP280::SAMPLING_X2,     // sıcaklık / temperature
15                  Adafruit_BMP280::SAMPLING_X16,    // basınç / pressure
16                  Adafruit_BMP280::FILTER_X16,      // IIR filtre / IIR filter
17                  Adafruit_BMP280::STANDBY_MS_500); // bekleme süresi / standby time
18}
19
20void loop() {
21  Serial.print(F("Sicaklik: ")); Serial.print(bmp.readTemperature(), 1);       Serial.println(F(" C"));
22  Serial.print(F("Basinc:   ")); Serial.print(bmp.readPressure() / 100.0F, 1); Serial.println(F(" hPa"));
23  Serial.print(F("Irtifa:   ")); Serial.print(bmp.readAltitude(1013.25), 0);   Serial.println(F(" m"));
24  Serial.println();
25  delay(2000);
26}

BME280のプロジェクトアイデア

  • 卓上の気象ステーション:同じI2Cバスを共有するOLEDディスプレイかI2C LCDに、温度、湿度、気圧を表示します。
  • 気圧による天気予報:1時間ごとに気圧を記録します。3時間で2 hPaを超えて下がるのはたいてい天気が崩れる兆しで、着実に上がるのは晴れのしるしです。
  • 露点とカビの警告:Magnusの式(a = 17.62、b = 243.12 °C)で露点を計算し、壁の温度がそれに近づいたら警告を出します。
  • スマート温室:湿度と温度に応じてファンやミストのポンプを動かします。詳しくはArduinoのスマート温室プロジェクトにあります。
  • データロガー:測定値をSDカードモジュールに、ESP32ならGoogleスプレッドシートに書き込み、1週間のグラフを描きます。
  • スマートホームのセンサー:ESP32から値をMQTTでHome Assistantに送ります。
  • 電池で動く屋外ステーション:10分ごとにESP32をディープスリープから起こします。BME280はスリープ中0.1 µAしか消費しません。
  • 階数カウンター:相対高度のスケッチを使い、エレベーターや階段で何階に着いたかを表示します。

よくある間違いと解決法

症状考えられる原因解決法
"Could not find a valid BME280 sensor"(センサーが見つからない)、SensorIDが0x0か0xFFアドレスの間違いか配線の不具合begin(0x76)を使い、SDAとSCLが逆になっていないか、GNDがつながっているかを確認し、I2Cスキャナーを動かす
同じエラーで、SensorIDが0x58チップが実はBMP280Adafruit BMP280ライブラリを使い、湿度には別のセンサーを加える
fatal error: Adafruit_Sensor.h: No such file or directory(ファイルが見つからない)依存ライブラリがインストールされていないAdafruit Unified Sensorライブラリをインストールする
値がnanになるフォースドモードで新しい測定を要求していない、またはbegin()が失敗している各読み取りの前にtakeForcedMeasurement()を呼び、begin()の結果を確認する
温度が1〜3 °C高い連続測定による自己発熱、近くのESP32やレギュレーターの熱フォースドモードに切り替え、センサーをボードから離し、必要ならsetTemperatureCompensation()を使う
気圧が900 hPaほどに低いこれは現地気圧で、高度が上がると下がる予報と比べるには、seaLevelForAltitude()で海面気圧に換算する
高度が何十メートルもずれる海面気圧が1013.25のまま現在のP0(QNH)を入力するか、わかっている高度で校正する
長い配線でランダムに固まるI2Cバスが長く、ノイズが多いケーブルを50 cm未満にし、I2Cの速度を下げるかSPIを使う
センサーを密閉した箱に入れないでください。湿度と気圧の測定には、空気がチップに届く必要があります。屋外では、下が開いていて雨と直射日光を防ぐよろい戸状のケース(気象観測所の百葉箱のようなもの)が最も正確な結果になります。結露でセンサーがぬれると、しばらく湿度100 %を示すことがありますが、乾けば元に戻ります。

BME280は、気象ステーションの基本的な測定をすべて1枚の小さな基板でこなします。まずアドレスとチップIDを確認し、次に目的に合わせて測定モードを選び、高度には海面気圧を最新に保ちましょう。BlueGraysアプリには、DHT11とDHT22での温湿度の測定、ESP32の天気表示、温度ロガー、ESP32のWebサーバーなど、このガイドと組み合わせられるプロジェクトが、ステップごとの回路図とコード付きで揃っています。

よくある質問

BME280とBMP280の違いは?

BME280は温度、相対湿度、気圧を測ります。BMP280は温度と気圧だけを測り、湿度センサーはありません。気圧と温度の精度は同じです。チップにも違いがあり、BME280は正方形(2.5 × 2.5 mm)、BMP280は長方形(2.0 × 2.5 mm)で、チップIDは0x60と0x58です。

BME280のI2Cアドレスは0x76と0x77のどちら?

SDOピンをGNDにつなぐと0x76、3.3 Vにつなぐと0x77です。安いモジュールの多くは0x76、Adafruitの基板は0x77を使います。Adafruitのライブラリは初期設定で0x77を試すので、コードにbme.begin(0x76)と書く必要があるかもしれません。わからなければ、I2Cスキャナーのスケッチを動かしてください。

BME280は5Vで動く?

チップ自体は最大3.6 Vで動きます。VINと書かれた4ピンのモジュールの多くはレギュレーターを持っているので、5Vにつなげます。レギュレーターのない6ピンの3.3 Vのモジュールは3.3 Vでだけ給電してください。5 Vではセンサーが壊れることがあります。

「Could not find a valid BME280 sensor」を直すには?

まず、エラーの下に表示されるSensorIDの値を確認してください。0x58なら、センサーはBMP280なので、Adafruit BMP280ライブラリを使います。0x0か0xFFなら、アドレスか配線の問題です。begin(0x76)を試し、SDAとSCLが逆になっていないか、CSBピンが3.3 Vになっているかを確認してください。

BME280の高度の測定はどれくらい正確?

相対的な高さの変化は約±1 mで測れ、階の移動を検知するには十分です。絶対高度は入力した海面気圧で決まり、1 hPaの誤差で結果が約8 mずれます。現在のQNHの値を入力するか、わかっている高度で校正すれば、数メートル以内に収められます。

BME280の温度が高く出るのはなぜ?

最もよくある原因は、連続測定による自己発熱と、ESP32やレギュレーターのような近くの部品の熱です。フォースドモードに切り替え、1分に数回測り、ケーブルでセンサーをボードから離してください。一定のずれが残る場合は、信頼できる温度計と比べてsetTemperatureCompensation()で補正してください。

全プロジェクトを見る
DHT11で温度と湿度を測定
#55中級

DHT11で温度と湿度を測定

部屋の温度は何度で、空気は湿っているでしょうか、それとも乾燥しているでしょうか?DHT11センサーが温度と湿度を測定し、Arduinoが2行のLCDディスプレイに値を表示して、数秒ごとに更新します。センサーに異常があればエラーメッセージが表示されます。自分だけの小さな気象観測装置を作る第一歩です。

4.7(47)
73
ESP32 DHT22温湿度計(LCDディスプレイ付き)
#117中級

ESP32 DHT22温湿度計(LCDディスプレイ付き)

DHT22センサーで周囲の温度と湿度を測定し、16x2 I2C LCDにリアルタイムで表示するプロジェクトです。Adafruit DHTライブラリを使い、1本の信号線を使うプロトコルでセンサーからデータを読み取ります。IoT気象観測装置やスマートホームの自動化、温室の環境監視システムの基礎となります。ESP32のGPIO 4ピンを介してDHT22と通信します。

4.8(38)
56
ドットマトリクス時計・カレンダー・気象ステーション(MAX7219、DS1307)
#85中級
プレミアム

ドットマトリクス時計・カレンダー・気象ステーション(MAX7219、DS1307)

机や壁に置いて、時刻、日付、室内の温湿度を順番に表示する、文字がスクロールする表示板です。8x32のLEDディスプレイには、まず摂氏と華氏の温度、次に湿度、そして2つの点が1秒ごとに点滅する時計が表示されます。分が変わると、曜日と日付がスクロール表示されます。時計モジュールが時刻を保持し、DHT22センサーが温度と湿度を測定します。

5.0(50)
66
ESP32温度データロガー(DHT22 + DS1307 RTC + microSD + OLED)
#144上級
プレミアム

ESP32温度データロガー(DHT22 + DS1307 RTC + microSD + OLED)

ESP32で日時付きの温度・湿度記録システムを作ります!DHT22で1分ごとに温度と湿度を読み取り、DS1307 RTCから取得した日時とともにmicroSDカードへCSV形式で記録します。OLEDには最新の温度を大きな文字で表示し、湿度のパーセント値、日時、総記録数も表示します。SDカードの/temp_log.csvファイルはExcel、Google Sheets、Pythonのpandasで開いて分析できます。温室の監視、家や部屋の温度管理、実験室のデータ収集、食品の保管環境や養蜂箱の監視に適しています。

4.7(7)
12
ESP32 HTTPClient+天気API(SSD1306 OLED付き)
#134上級
プレミアム

ESP32 HTTPClient+天気API(SSD1306 OLED付き)

このプロジェクトでは、ESP32でHTTPClient.hを使い、インターネット経由で天気API(wttr.in)をREST APIとして呼び出します。前のI2C LCD版とは異なり、SSD1306 OLED(128×64)ディスプレイを使うため、大きな文字やベクターアイコンで、1つの画面に豊かな表現ができます。天気を表す7種類のベクターアイコン(晴れ、晴れ時々曇り、曇り、雨、雪、嵐、霧)を実行時に描画するので、ビットマップファイルは不要です。気温は中央に3倍の大きさの文字で表示します。

4.3(10)
19
ESP32非同期Webサーバー(LCDにIPアドレスを表示)
#129上級

ESP32非同期Webサーバー(LCDにIPアドレスを表示)

このプロジェクトでは、ESP32を同じWi-Fiネットワーク上のスマートフォンやパソコンから操作できる非同期Webサーバーにします。ESPAsyncWebServerライブラリを使うと、従来のWebServer.hのように1つのリクエストで処理がブロックされず、複数のリクエストを同時に処理できます。ブラウザーでESP32のIPアドレスを開き、ON/OFFボタン(/onと/offのルート)を押すと、GPIO 5に接続したLEDを点灯・消灯できます。

4.8(16)
26
ESP32 HTTPClient+天気API(LCD付き)
#133上級
プレミアム

ESP32 HTTPClient+天気API(LCD付き)

このプロジェクトでは、ESP32でHTTPClient.hを使い、インターネット経由で天気API(wttr.in)をREST APIとして呼び出します。返されたJSONレスポンスをArduinoJson v7で解析し、都市名、気温、湿度、天気、風の情報をI2C LCDディスプレイにトルコ語と英語で表示します。wttr.inは無料で、APIキーも不要です。都市名を入力するだけで、その都市の現在の天気を取得できます。30分ごとに自動更新し、表示画面ごとに異なるLEDの光り方で状態を知らせます。

4.7(13)
20
ESP32 I2C LCDディスプレイ(16x2)
#119中級
プレミアム

ESP32 I2C LCDディスプレイ(16x2)

このプロジェクトでは、16x2文字のLCDをI2CでESP32に接続して文字を表示します。I2Cならデータ用の2本のピン(SDAとSCL)だけで制御でき、従来のパラレル接続のように6本以上のピンを使わずに済みます。コードでは1行目に固定メッセージ、2行目に毎秒増えるカウンターを表示します。また、起動時にI2Cスキャナーを実行し、ディスプレイのアドレス(通常は0x27または0x3F)をシリアルモニタに表示します。

4.9(23)
40