Raspberry piでセンサーデータを取得する(BME280)

7 min read読了の目安(約7100字

https://zenn.dev/false/articles/0001-a9d5d3903afed72a06bc
の続き。

前回扱ったCO₂センサが、気圧とかが取れそうと期待したのに取れなかったので、今回は気温/湿度/気圧にチャレンジです。

BME280 で気温/湿度/気圧を取得

気圧が測れるセンサで検索して、以下のサイトを見つけました。

https://deviceplus.jp/hobby/raspberrypi_entry_039/

BME280 で検索すると、以下がありました。(AliExpress にもありますが、時間がかかるので)

https://akizukidenshi.com/catalog/g/gK-09421/
https://www.switch-science.com/catalog/2236/

いずれもピンヘッダの半田付けが必要ですが、スイッチサイエンスの方は+330円でピンヘッダ実装済みのものがあります。

https://www.switch-science.com/catalog/2323/

調べたときはピンヘッダ実装済みが在庫0で入荷未定だったので、秋月のものを買ったのですが、今みたら在庫多数になっていました。

面白いのは、秋月の方は直角に曲がったピンヘッダが付属しているのに対し、スイッチサイエンスの方はまっすぐなピンヘッダがついています。
ブレッドボードに挿す場合、秋月の方は基板が立ち、スイッチサイエンスの方は基板が寝るようになります。どちらが良いのでしょうね?

前回の MH-Z19C は Raspberry Pi とジャンパワイヤで直接繋いだのですが、センサが二つケーブルで宙に浮くのも収まりが悪いと思い、今回はブレッドボードに挿すことにしました。
MH-Z19Cはピンが2列あり、そこそこ距離が空いていて、手元にある普通のブレッドボード(5穴×2)には刺さりません。
探してみると、電源を片方だけにして6穴×2にしたものがあったのでこれも購入しました。

https://akizukidenshi.com/catalog/g/gP-12366/

後は、Raspberry Pi とブレッドボードを繋ぐためにオス-メスのジャンパワイヤも購入しました。

結線


届いた BME280基板

恐ろしく小さくないですか?こんなのはんだ付けするんですか?

老眼鏡かけて泣きながらやりました。


結果

取扱説明書を見ながら結線方法を調べます。

Pi BME280
1(3.3v) 1.VDD, 3.CSB
3(SDA.1) 4.SDI
5(SCL.1) 6.SCK
6(0v) 2.GND, 5.SDO
  • 今回は直結ではなくブレッドボードを使うので、GND は MH-Z19C と共通で Raspberry Pi の6番ピンから取りましたが、直結する場合や BME280だけ使う場合は9番ピンでも構いません。
  • 実際には、3.3v と 0v はブレッドボードの +, - に入れて、そこからジャンパワイヤでセンサのピンの列に繋いでいます。
  • 説明書には、I²Cを使う場合は J3 のハンダジャンパを使うとありますが、これは 1.VDD と 3.CSB のジャンパなので、3.CSB に 3.3v を入れています。
  • 5.SDO は I²C の場合はアドレス選択で、GND で 0x76, VDD で 0x77 になります。

新しいブレッドボードにジャンパワイヤを配線して、いざ MH-Z19C を刺そうと思ったら、・・・刺さりません。
ちゃんとピン間の距離を調べてから発注すれば良かった。
悩んだ結果、2枚のブレッドボードを並べるとその間に MH-Z19C が刺せることがわかったので、ちょっと邪魔ですがそのように結線しました。


結線図

手元にあるジャンパワイヤはあまり色が選べなかったので、実際の配線は色が違います。


実際

ブレッドボードの左が BME280、右が MH-Z19C です。

I²Cを有効にする

raspi-config を使うか、/boot/config.txt の以下の行を有効にして I²C を有効にします。

config.txt
dtparam=i2c_arm=on

I²C のデバイスファイルは /dev/i2c-1 でした。(Raspberry Pi のモデルによっては、1の部分が変わるらしいです)

% ls -l /dev/i2c-1
crw-rw---- 1 root i2c 89, 1  5月 20 10:17 /dev/i2c-1

グループは i2c なので、一般ユーザでアクセスしたい場合はそのユーザを i2c グループに参加させます。

以下のコマンドで i2c-tools をインストールします。

% sudo apt install i2c-tools

結線が成功していれば、i2cdetectで 0x76 が表示されます。(1の部分は I²C のバス番号なので、モデルによっては他の数字になります)

% sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

データの取得

ネットにある情報は、ほとんどがスイッチサイエンスのサンプルを参照しています。

しかし、このソースは python2 系用なので、そのまま使うのはちょっとためらわれます。
またスイッチサイエンスの製品ページからリンクが貼られている使い方ページも not found になっています。 →復活していました!

Python3 用に修正されている方もいます。

https://qiita.com/yukataoka/items/8f9046587c978e91f689

しかし、これらをそのまま使うのもどうかと思ったので、原典を当たることにしました。

リビジョンの違いはあれど、レジスタに書き込むコマンドや、読み値の calibration のコードは同じようです。
ただ、スイッチサイエンスのサンプルとは微妙に calibration のコードが違うようなので、今回はこちらを python に移植してみることにしました。

パラメータ

データシートには、推奨される動作モードと言うところがあり、いくつかの推奨設定が載っています。(以下抜粋)

  • 天気の監視の推奨設定
    • センサー・モード: 強制モード
    • オーバーサンプリング: 圧力 ×1, 温度 ×1, 湿度 ×1
    • IIRフィルタ: オフ
    • 消費電力小
  • 屋内のナビゲーションの推奨設定
    • センサー・モード: 通常モード(tStandby 0.5[ms])
    • オーバーサンプリング: 圧力 ×16, 温度 ×2, 湿度 ×1
    • IIRフィルタ: フィルタ係数 16
    • 消費電力多め

これに対して、スイッチサイエンスのサンプルは以下のようになっています。

  • スイッチサイエンスのサンプル
    • センサー・モード: 通常モード(tStandby 1,000[ms])
    • オーバーサンプリング: 圧力 ×1, 温度 ×1, 湿度 ×1
    • IIRフィルタ: オフ

私の使い方は、1分に1回測定してデータを残すだけなので、天気の監視の設定がはまるのかと思ったのですが、CO₂ センサが結構ノイズがあるので、安定するように屋内のナビゲーションの設定を使うことにしました。

I²C ライブラリ

python から I²C にアクセスするのに、以下の二つのライブラリがあります。

  • smbus
    • C で書かれている
    • i2c-tools の一部
    • pypi のサイトには使い方が何も書いてない
  • smbus2
    • python で書かれている
    • smbus 互換
    • 説明がある

スイッチサイエンスのサンプルは smbus2 を使っています。
ちょっと悩んだのですが、smbus の方を使うことにしました。

実装

  • calibration データは工場出荷時に書き込まれるので、一度読み込んだら繰り返し読み直す必要はありません。
  • control レジスタの書き込みも電源が切られるまでは保持されるので、毎回書く必要はありません。
  • calibration アルゴリスムはデータシートのものを使いました。
    • Pa → hPa の変換で、100で割るところを1000で割って計算が合わないと悩みました。
    • 同じセンサの読み値でも、スイッチサイエンス版とは微妙に値が違うようです。
  • データの読み出しは、1byte ずつではなく read_i2c_block_data() を使って一括でやるようにしました。
  • 読み込んだ byte データを short / unsigned short に変換するのは、python の struct.unpack を使いました。

例えば、calibration データの読み込みは以下のようになります。

    def init_calibration(self) -> None:
        """calibrationデータの初期化."""
        calib0: bytes = bytes(self.bus.read_i2c_block_data(self.i2c_address, 0x88, 26))
        calib1: bytes = bytes(self.bus.read_i2c_block_data(self.i2c_address, 0xE1, 7))
        self.dig_T: typ.List[int] = list(struct.unpack("<Hhh", calib0[0:6]))
        self.dig_P: typ.List[int] = list(struct.unpack("<Hhhhhhhhh", calib0[6:24]))
        self.dig_H: typ.List[int] = [*struct.unpack("B", calib0[-1:]), *struct.unpack("<hBbHb", calib1)]
        self.dig_H[3] = (self.dig_H[3] << 4) + (self.dig_H[4] & 0xF)
        self.dig_H[4] = self.dig_H[4] >> 4

ソースは、https://github.com/false-git/power-consumption0b4f3e... のあたり。bme280 関連のコードは bme280.py です。
リポジトリには、test ディレクトリの下にデータシートから抜き出した検算用プログラムを置いてあります。
何故か、データシートのPDFからコピペすると、改行文字が消えたり、-(\u002d HYPEHN-MINUS)が(\u2013 EN DASH)になっていたりしました。
本当は、ちゃんとしたテストプログラムを書いて、各センサの読み値20bitを全網羅する比較試験を書けば良かったのですが、面倒なのでやめました。

こんなグラフになりました。

追記

別件でデータシートを眺めていたら、Appendix に 8.1 Compensation formulas in double precision floating point と言う節を発見しました。
どうも、スイッチサイエンスのサンプルはこのコードを元にしているようで、

Compensating the measurement value with double precision gives the best possible accuracy but is only recommended for PC applications.

とか書いてあるので、Raspberry Pi で使うならこちらの方が正解のようです。
bme280.cc を修正して 32bit int 版と double 版を両方載せて、比較できるようにしてあります。
面倒なので、pythonの方は整数版のままとしました。

続きはこちら

https://zenn.dev/false/articles/0003-3152d9c309f426a5a441