- MH-Z19C
- MH-Z19B
- Raspberry Piとの接続
- Raspberry PiのUART設定
- 二酸化炭素濃度の読みとり
- キャリブレーション
- 外気の二酸化炭素濃度
- 実際の配線
- 実測値
- 測定が止まる
- 気温
- まとめ
MH-Z19C
追記: 2021/09/18
以下で紹介しているAliExpressで買ったMH-Z19Bの調子が悪かったので 秋月電子通商でMH-Z19Cを買って取り替えました。
追記ここまで
MH-Z19B
MH-Z19B ndir CO2 sensor for indoor air quality monitoring–Winsen
MH-Z19Bは二酸化炭素センサーモジュールで、Raspberry Piなどの小型PC と繋げて使う二酸化炭素センサーモジュールとしては一番良く例が出てくるものでした。
最近はMH-Z19Cという新しいバージョンのものもあるので注意してください。
追記: 2021/08/22
Cの方が新しいからスペックが良い、と思いこんでたのか見間違えてましたが、 MH-Z19Bが0~5000ppm、MH-Z19Cが400~5000ppmになってます。
上のAmazonのタイトルがそもそもそうなってて、上限も2000ではなく5000ですね。。。
追記ここまで
一部信号が違うのでよくあるMH-Z19B用に作られたライブラリとかが使えない可能性があります。(ただ、データシートを見ると、一番使う二酸化炭素濃度の読み取りコマンドは同じなのでそれだけなら問題ないとは思いますが。)
MH-Z19Cではキャリブレーション信号が無くなってますがそれ以外は基本的に同じです。(キャリブレーションはHDピン-GNDピンを短絡して行う。)
MH-Z19、とB/Cがついてない古いバージョンもありますが(MH-Z19 データシート、基本的にはMH-Z19Bとほとんど同じ仕様ですし、売っているものは実はBだったりもするようですが、一応きちんとBが付いてるものを買ったほうが間違いないです。
MH-Z19Bに関しては、Amazonでも3000円~4000円ほどで売ってますが、 AliExpressというサイトで安いもので2000円位で売ってるので今回はそちらで買いました。
MH Z19B MH Z19 CO2二酸化炭素ガスセンサーシリアル出力非分散型赤外線 infrared gas sensor infrared sensorinfrared co2 sensor - AliExpress
他の人を見てもこれに関してはAliExpressで購入してる人が多いようでした。
買う時にちょっと注意したほうが良いのは、ものによって、端子にピンが付いてたり 付いてなかったりするので自分の用途を考えて使いやすいものを選んだほうが良いです。
まあ、写真と違うものが来る可能性がゼロではないんですが。
今回は年末に頼んだこともありますが、12月30日位に頼んで届いたのが1月24日とかでした。
軽く忘れかけてた頃に届いた感じです。 この辺考慮すると、多少高くてもAmazonで買ってしまっても良かったかも、と思ってます。
年末ころはあまりMH-Z19Cを見なかった気がするんですが、今見るとMH-Z19Cも大分安く売ってるので、 データシート見て色々できそうだと思ったらそっちを買っても良いかも。
Raspberry Piとの接続
使っているのはRaspberry Pi Zero WHです。
Raspberry PiのGPIOとの接続は4箇所。
- MH-Z19BのV+と5V power (e.g. 4番)
- MH-Z19BのV-とGround (e.g. 6番)
- MH-Z19BのRxdとGPIO14 (TXD) (e.g. 8番)
- MH-Z19BのTxdとGPIO15 (RXD) (e.g. 10番)
ちょうど外側の2番から5番目までを埋める感じで揃えることができます。 TXDとRXDはMH-Z19BとRaspberry Pi側で逆のものを接続する様にするところを注意。
追記: 2022/01/06
上のTXD/RXDが逆になっていました。 訂正前はGIPO14側(8番)をRXDと書いていましたがTXD。 これに従ってMH-Z19B側も逆に書き直してあります。
MH-Z19BとRaspberry Pi側で逆のものを繋ぐのは正しい。
追記ここまで
Raspberry PiのUART設定
MH-Z19BとはUART(Universal Asynchronous Receiver Transmitter)という機器を使った 非同期式のリシアル通信を使って喋ります。
上で接続したTXDとRXDがそれぞれ送信データ(Transmit Data)、受信データ(Receive Data)を表す信号名になります。 (なのでMH-Z19BのTXD(送信側)とRaspberry Pi側のRXD(受信側)をつなぐ。)
細かいUARTの説明は他に譲って、Raspberry Piではどの様に使われているかというと 公式にドキュメントがあります。
Raspberry Piの種類によって違っていて、Raspberry Pi Zero WHだと初期状態で
Name | Type | Configuration |
---|---|---|
UART0 | PL011(PrimeCell UART) (/dev/ttyAMA0 ) |
secondary (Bluetooth) (/dev/serial1 ) |
UART1 | mini UART (/dev/ttyS0 ) |
primary |
になっています。
Typeの違い、としては、PL011に比べmini UARTの方は一部機能が制限されたUARTといった感じです。
初期状態だと、UART0の方はBluetoothモジュールに繋がれています。
これが
/dev/serial1
がUART0の/dev/ttyAMA0
のシンボリックリンクになっている状態。
pi@raspberrypi:~ $ ls -l /dev/serial*
lrwxrwxrwx 1 root root x xxx xx xx:xx /dev/serial1 -> ttyAMA0
今回使いたい
GPIO14 (RXD), GPIO15 (TXD)は/dev/serial0
にあたりますが、
これは
初期状態だとシリアルコンソールのために使われるようになっています。
(UARTにはつながってない。)
UARTを使うためには、
- UART1でUART通信を行う
- Bluetoothを無効にして、UART0でUART通信を行う
- UART0でUART通信を行い、UART1でBluetoothを使う
などが考えられます。
UART1の方がmini UARTなので、そちらで使う方はパフォーマンスが落ちたり 問題が起きやすかったりするわけですが、 現状Bluetoothを頻繁に使うこともないので1
- UART0でUART通信を行い、UART1でBluetoothを使う
という方法にしました。
この辺りは下のページのまとめが分かりやすかったです。
pi@raspberrypi:~ $ ls -l /dev/serial*
lrwxrwxrwx 1 root root x xxx xx xx:xx /dev/serial1 -> ttyAMA0
pi@raspberrypi:~ $ vcgencmd measure_clock core
frequency(1)=250000000
pi@raspberrypi:~ $
この状態から、/boo/config.txtに
dtoverlay=pi3-miniuart-bt
を加えてから再起動。
再起動後
pi@raspberrypizero:~ $ ls -l /dev/serial*
lrwxrwxrwx 1 root root x xxx x xx:xx /dev/serial0 -> ttyAMA0
lrwxrwxrwx 1 root root x xxx x xx:xx /dev/serial1 -> ttyS0
pi@raspberrypizero:~ $ vcgencmd measure_clock core
frequency(1)=400000000
pi@raspberrypizero:~ $
ttyAMA0(PL011)がserial0に、ttyS0(mini UART)がserial1(Bluetooth)になっていることが分かります。
コアクロックも上のリンク内の説明にあるように400MHzに変わっています。
追記: 2021/03/22
この状態だとBluetoothが有効になっていませんでした。
Bluetoothを有効にするには/boo/config.txtに
dtoverlay=pi3-miniuart-bt
core_freq=250
とcore_freq
の設定も入れる必要があります2。
この状態で再起動すると、
pi@raspberrypizero:~ $ vcgencmd measure_clock core
frequency(1)=250000000
と、コアクロックが元の250MHzと同じになります。
この状態だとBluetoothが使えます。
追記ここまで
二酸化炭素濃度の読みとり
データシートにあるコマンドを使って二酸化炭素濃度を読みとります。
Python
Python(3)で行うには、 pySerial パッケージをインストールして、
$ pip3 install pyserial
以下の様なスクリプトを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
上にも書いたようにアクセスするデバイスは/dev/serial0。
後はデータシートに従って、ボーレートを9600などに設定して通信を繋げます。
書き込みとしては0x86 Read CO2 concertrationのセクションにある 8バイト分の数字を送ります。
その後9バイト分読み込んでいますが、 成功すると、
- 1バイト目:
0xff
- 2バイト目: 送ったコマンド(3バイト目、つまり
0x86
)
が入っているはずなのでそれをチェック。
3バイト目、4バイト目に二酸化炭素濃度の情報が入っていて、 2バイト分で濃度を表す数字がppm単位で入っています。
- 3バイト目: 二酸化炭素濃度を表す数字の上位バイト
- 4バイト目: 二酸化炭素濃度を表す数字の下位バイト
なので
256 * 3バイト目 + 4バイト目
とすることでppm(百万分率)として計算できます。
実行する際には/dev/serial0へのアクセスがrootユーザーのみになってるので、
$ sudo python3 ./mhz19.py
482 ppm
の様にsudo
する必要があります。
こんな感じで簡単なスクリプトで読み取ることもできますが、 先人たちが作ってくれたライブラリとかもあるのでそれを使うとさらに簡単に読めます。
このパッケージに関しては、 Wikiにも有用な情報が載っているのでPython以外の言語で読み取る場合にも MH-Z19Bを使う場合には一通り見ておくと理解が深まります。
このパッケージを(Python3もなかったらともに)、
$ sudo apt install python3-pip
$ sudo pip3 install mh-z19
としてインストールして
$ sudo python3 -m mh_z19
{'co2': 482}
といった感じで読み取ることが出来ます。
1 2 3 4 5 6 7 |
|
とかいうスクリプトを作れば
$ sudo python3 ./mhz19_module.py
482 ppm
の様に簡単に読み取れます。
最初のスクリプトのところで、9バイト分読み込んでるにも関わらず最初の4バイト分だけ使っていますが、
mh-z19では、read_all()
という関数を呼ぶことでその他の情報も返してくれます。
>>> import mh_z19
>>> print(mh_z19.read_all())
{'co2': 482, 'temperature': 26, 'TT': 66, 'SS': 0, 'UhUl': 0}
ただし、二酸化炭素濃度以外はデータシートには載っていませんが、 以下のサイトに5バイト目以降の説明があります。
- 5バイト目(TT): 摂氏+40
- 6バイト目(SS): 何らかのステータス値
- 7バイト目(Uh): 気圧に関する値?の上位バイト?
- 8バイト目(Ul): 気圧に関する値?の下位バイト?
- 9バイト目(CS): checksum (これはデータシートにある値)
5バイト目に関しては気温を表し、40を引くと摂氏になるようです。
華氏 = 1.8 * 摂氏 + 32
なわけですが、ちょうど摂氏10℃の時に
華氏 = 1.8 * 10 + 32 = 10 + 40
となるので、簡易計算した華氏的な値、という感じです。
ただ、この値が実際にセンサーがある周りの気温なのか何なのか、というのは不明なところで、 人によって気温よりも高く出ていたり低く出ていたりといった報告があり、 これをそのまま気温センサーとして使うことはやめた方が良さそうです。
6バイト目のステータス値については今の所0以外を見たことは無いです。
7/8バイト目のUh/Ulの値は、10500位の値が出る、とのことなんですが、 これも今の所全部0。
最後のChecksumに関しては上のスクリプトだとチェックしてませんが、 ちゃんとチェックしたいなら、 2~8バイト目の合計の下位2バイトを取って反転させて1を足したもの、 を返してくれるので、
if len(s) == 9 and s[0] == 0xff and s[1] == 0x86 \
and (0xff - sum(s) % 0x100 + 1):
print(f'{s[2]*256 + s[3]} ppm')
みたいにするとチェックが出来ます。
ただ、通常最初の2つが正しく帰ってくるかどうかのチェックで十分なため それだけで済ましている人が多い様でした。
実際、最初の2つのチェックを通っておかしな値が返ってくることは今の所ありません。 (偶然最初の2つの数字が合ってしまう、という可能性はあるわけですが。)
mh-z19パッケージの中でも 最初の2つをチェックしているだけになっています。
このmh-z19を使ってMH-Z19を定期的にGoogle Spreadsheetsに記録するスクリプトを作って 記録しています。
以下の記事ではBME280で測定する気温などを記録していますが、 基本的には同じことで、測定値の取得の部分がmh-z19パッケージを使って簡単に作れます。
C++
C++に関しては以下のページが参考になります。
co2UART - Measuring CO2 with an Raspberry PI and the MHZ19(B)
簡単な例としてはこんな感じで読み込めます。
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 |
|
これを以下のようなMakefileを作って
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 |
|
$ make
して、
$ sudo ./mhz19
482 ppm
Checksumに関してはC用例がデータシートに載っていたのでそれを(ちょっといじったもの) を付けておきました。
この辺を参考にして、Raspberry Pi上ではBlynkを使った測定値表示のために使っています。
以下はBME280とMH-Z19Bの測定値をBlynkで見れるようにアップデートしたコード。
Go
Goは今回直接使ってはないので詳しくははぶきますが、ちょっとチェックとかに使ったので。
以下のものとかを直接使えば一番簡単にすぐsudo mhz19
するだけで読み取ることが出来ます。
kebhr/mhz19: Read CO2 concentration from MH-Z19 on Raspberry Pi
キャリブレーション
MH-Z19Bには二酸化炭素濃度の測定値をキャリブレーションする機能が付いています。
基本的には400ppmをゼロポイントとしてキャリブレーションします。 外気の二酸化炭素濃度が大体400ppm(後述)なので、換気を良くした状態の家の中か もしくは外に置いて20分ほどしてからキャリブレーションする、とかします。
それ以外にも適当な濃度の中で値をキャリブレーションすることも出来ますが、 普通の家で適当な二酸化炭素濃度を作ることはまず無理なのでやることはないと思います。
ゼロポイントキャリブレーションの方は、出荷状態で定期的に実行されるように設定されています。 起動後、24時間おきに良しなに行う、と、データシートには書いてありますが、 実際にどの様に行われるかはよくわかりません。
自分で行うことも出来て、 400ppmの環境に20分ほど置いた後に、 データシートにあるとおりにコマンドを送るか、 HDピンをGNDと7秒以上直結させるとその時点での値が400ppmとしてキャリブレーションされます。
コマンドは、以下の9バイトを送ります。
{0xff, 0x01, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78}
Pythonパッケージのmh-z19 をインストールしてあるなら、
$ python3 -m mh_z19 --zero_point_calibration
で出来ます。
外気の二酸化炭素濃度
二酸化炭素がどの程度の濃度なのか、というのは昔どこかで習ったかもしれませんが、 実際あまり感覚がありませんでした。
まあ、最近たまにテレビとかでお店の換気とかの話でやってるのでなんとなく 数値を見てる人もいるかと思いますが、気象庁のページによると 400ppmちょっと、というのが現在の二酸化炭素濃度だそうです。
測定値が綾里(りょうり、岩手)、南鳥島、与那国島という、空気がきれいそうなとこだけにあって、 できるだけ人為的な影響がない環境下での濃度を測っているようです。
また、過去2年分位が測定値となっていて、きちんとした値を出すには色々と時間がかかるようです (なんでそんなに時間がかかるのだろうか?)。
数値を見ると夏場に下がって冬場に上がる感じで1年でも10ppm位の差があります。 が、年毎に見るときれいに上がっていて、1987年には350ppm位だったものが、 2020年には410ppm位になっています。
これを数字改めて見るとすごいですね。。。
以上はきれいな空気のところ、の濃度でしたが、 東京の二酸化炭素濃度に関してこんなのがありました。
江東区の測定で、2006年までしかありませんが、綾里と比べて大体70ppm位高かったようですが、 10年で20ppm位の差が増えてるようにも見えるので、今は90~100ppm位高い可能性があります。
なので現在で500ppm位あるのかもしれません。
いくつかのサイトに、通常の大気中の濃度は360ppmで、新宿路上だと450ppm、とかいう記述がありましたが(コロナ後に書いてる感じのものも結構あった)、 おそらくこの資料が発端かな、と思うのですが(この資料自体が古いものを参照して作られた比較的最近のものな感じはありますが)、 大気中の濃度が360ppmなのは1990年頃の話なので今だとやはり新宿では500ppmとかは余裕で超えてそうです。
埼玉でのもの(比較的都市部に近いが観測値はそれなりに人里離れたところでやっている模様)。
やはり人の影響があるところだと100ppmとかかなり大きく振れる様です。
ですので、MH-Z19Bのゼロポイントキャリブレーションとして、外に置いておいたとしても400ppmで合わせる、というのは実は少し低く合わせすぎているという可能性があります。
実際の配線
Raspberry Pi ZeroにはBME280が接続されていましたが、 MH-Z19Bと共存が可能です。
配線はこんな感じ。
追記: 2022/01/06
絵の中のTxdとRxdが逆でした。直してあります。 配線は位置はそのまま正しいです。(左上から二番目のRxdとRaspberry Piの8番をつなぎ、三番目のTxdを10番とつなぐぐ。
追記ここまで
実際にはこんな感じ。。。
BME280は未だにブレッドボードに付けたままなんですが、 そこにMH-Z19Bも付けようとしたら付属していたピンの幅が合わずに挿せませんでした。
(片側の4つ、5つの近いもの同士のピン幅は挿さるピッチ幅なんですが、 両側のピンの幅が合わず。実際他の人も合わない、という人が居たのでそういういものなのかと。
ユニバーサル基板などにはんだ付けしてつける場合には少しピンを斜めにして挿してしまう、とかしているようです。
ブレッドボードに無理やり挿そうとして斜めにするとうまく挿ささらないので、 ペンチとかで途中で曲げるとかしないと、という感じでしょうか? (ただ、今持ってるブレッドボードだとそもそも幅が狭すぎて使えない。)
この状態だと、むしろBME280側も直接つなげれば良いじゃん、という状態なんですが、 単にメスオスのケーブルが無いだけです。。。
そのうち。
実測値
上の様につないで実際に測ってみました。
2月6日の朝から測定値があります。
動き始めてから最初のうちは1000ppm付近の値で、計測の仕方を間違えているのか、 それとも部屋の中が結構空気悪いのか、心配になりました。
一旦換気してみよう、ということで窓全開にしてその部屋には人が居ないようにして 1時間ほど放置してみました。 それがすぐに750位まで落ちてるところです。
窓際に置いてあることもあるので、外と同じ程度になっていたとは思ったんですが、 換気が十分でないのか、そもそも外気がそんなものなのか。
で、その後また戻ったのでやはり普段は空気悪いのか、と。
一応人がいる環境で1000ppmくらいまでなら許容範囲(ただし、 オフィスとかでたくさん人が集まる場所でそのくらいになる、という話なんですが)、 ということでこんなものなのかな、と思ってました。
で、次の日、ちょうど前日スタートし始めたころにガクッと下がってます。
おそらくここでキャリブレーションが走った模様。
なので正解は500ppmくらいか?という感じなのですが、せっかくなので ちゃんとキャリブレーションしようと思って外に置いてしばらくしてから ゼロポイントキャリブレーションを走らせてみました。
モバイルバッテリーを使って外で起動させました3。 Wi-Fiも届いたようで良かったです。
7日の分だけを拡大したのがこちら。
外に出したタイミングは夕方17:00頃ですが、 上の図でいうと、直角に下がっている部分があるかと思いますが、 その直前です。
家の中での測定が520ppm付近だったものが、外に出して起動したところ550ppm位を示していました。
このとき、気温の測定値を見てみると、それまで25度位で安定していたのが21度位に下がったので こちらの方はむしろ動作している感じでした(絶対値が正しいかどうかは別として)。
が、数分後、急激に下がって400ppmの下限値になりました。 それが上の図で直角に落ちているところ。 このとき気温も15度位まで落ちてます。
キャリブレーションが走ったのか?
しばらく外に置いたまま放置してましたが、400ppmのまま変わらず。
1時間ほど放置した後、ソフトウェアでゼロポイントキャリブレーションを実行してみました。
Pythonのmh_z19パッケージを使って
$ sudo python3
>>> import mh_z19
>>> mh_z19.zero_point_calibration()
これを実行した後、値が445ppmになり、その後はその付近での値を示すようになりました。
しばらくして家の中に移動させましたが、その移動させた後の測定が700ppm位まで跳ね上がっているところです。 入れて再起動した直後は550ppmとかで数分後に700ppm近くまで上がりました。
ただ、その後また600ppm位まで落ちて落ち着いています。
最初の図を見てもらうと、その後600ppmくらいで落ち着いていたんですが、 10日ころに一度ガクッと下がってます。
その前の再起動のタイミング的に、1日一回のキャリブレーションが走った感じかも。
その後、換気をしたり色々しながら見ていますが、ざっと見てる感じでは 1000ppmよりは十分小さいのかな、という感じなので一安心といったところです。
測定が止まる
11日付近で線が真っ直ぐになっているところは測定できてなかったところです。
気づいてRaspberry Piを再起動してみたものの測定が開始出来ませんでした。
そこで一旦MH-Z19Bにつながったコードを外して戻してみると測定が開始されました。 おそらく5Vにつながっているコードを一旦外せばよいのだと思います。
上の期間のあとも2度ほどあったので今のところで1週間に1度くらい起こっているペースです。
それらのときもやはり Raspberry Piの再起動ではうまくいかず、コードの抜き差しで治りました。
接触の問題なのか?個体が悪いのか…?
気温
気温も一応測れるということで記録しています。
気温はRaspberry Pi上でBME280を使って測定したものと、 Nature Remo miniを使って測定したものが記録されています。
気温測定に関してNature Remo miniとBME280で測定したものとの比較。
それらと日毎の平均値を比べたものがこれ。
Nature Remo mini (NR AVG)とBME280 (BME AVG)は0.1度位の範囲の差におさまっていますが、 MH-Z19の測定(MH-Z19 AVG)はこれらに対して大体4.5度ほど高くずれてます。
ただ意外にも安定している感じで、下駄が正しくないですが、よくわからない数字を出している、というわけではなくちゃんと室温に関連した温度を出してくれている様です。
まとめ
ということでとりあえず室内の二酸化炭素濃度が測れる様になりました。
ちょっと機材的に不安定な所が見られますが、参考程度にはなるかなと思っています。
費用的にはRaspberry Pi Zero + MH-Z19Bで3000円位になるので、 単に二酸化炭素濃度を見たい、というだけであれば 安いのであれば二酸化炭素濃度系が3000円ちょっとで 買えるのでそっちの方が良いかも。
ただ記録したりなんだかんだするのはやはり面白いので、 Raspberry Piとかで遊んでる人はMH-Z19Bとか買って二酸化炭素を見えるようにするのは 比較的簡単にできますし おすすめです。
-
最近までRaspberry PiからBluetoothでSwitchBotに接続して操作、ということをしていましたが、
SwichBotカーテンと一緒にSwitchBot Hub miniを手に入れたので そちらから操作するようにしました。 (これだとパスワードを設定できるので。パスワードを設定してしまうとRaspberry Piからは接続できなくなってしまう。)
-
enable_uart=1 does NOT set core_freq=250 - Raspberry Pi Forums ↩
-
Raspberry Pi Zeroなら10000mAhのモバイルバッテリーで60時間ほど稼働させることが出来るようです。
Raspberry Piの消費電力測定&モバイルバッテリー稼働時間の計算 (RPZ-PowerMGR) – Indoor Corgi ↩