Arduinoで作るMH-Z19Cを使ったCO2濃度計 (基本検討)
◆まえがき
新型コロナウィルス対策はまだまだ続けないといけないようです。対策の中で部屋の換気は重要で、その状態の確認にはCO2濃度計を使うと良いそうです。そう聞くと、電子工作好きとしてはCO2濃度計を作りたくなるのですが、世の中には色々なセンサーが売られていて、どれを使えば良いのか判らなくてそのままになっていました。
そんなところに、ともの技術メモさんのブログに、安価なCO2センサの比較というシリーズ記事が掲載されていて、それによると、今買うなら、MH-Z19CというNDIR方式のセンサが良さそう、ということが判りました。
そんなことで、早速そのセンサーを手に入れてCO2濃度が測定可能な状態まで立ち上げたので紹介します。
・外観

この記事で紹介するのはこんな物です。右は電源に使ったモバイルバッテリーです。
◆MH-Z19C
MH-Z19Cは秋月でも売られていますが、このご時世に秋葉原まで買いに行く気はしません。とものさんのブログ情報によると、AliExpressにこのセンサーのメーカーであるWinsenの直売店があり、そこで買えばFAKE品を掴まされることは無さそう、ということだったのでそこに注文しました。
・AliExpressのMH-Z19Cのページ(クリックでジャンプ)

(上記の真贋は不明です、購入先や仕様の選定は自己責任でお願いします。)
レンジは200-4000ppmで、コネクタタイプ(ピンヘッダでは無い物)を買いました。
・届いた

1個買ったのに、10個くらいは入りそうな箱で届きました。
・試験成績書

何と手書きの試験成績書で担当者と責任者のサイン入りのものが添付されていました。なお、400と2000ppmの精度確認結果が記入されていました。
◆回路図(図をクリックで別窓に拡大)
動作確認に使った回路は以下の通りです。

CPUモジュールにはArduino NANO を使いました。MH-Z19Cの電源電圧は5.0±0.1Vが必要みたいなので、NANOの+5V端子に外部から5Vを供給する方式にしました。ちなみに、NANOはUSBから電源を供給するとダイオード1段分電源電圧が下がってしまいます。なお、この使い方にはいろいろ注意事項がありますが、書いたら切りが無いので省略します。
Auto Zeroモード切替スイッチ(JP1)と、オフセット設定スイッチ(SW1)を設けておきました。
・ブレッドボード

表示には128x32画素のI2CインターフェイスのOLEDを使いました。JP-1は必要に応じジャンパー線で接続します。
◆ソフト
少し長いですが、全体が見える形で掲載します。
・このセンサー用のライブラリ(たしか、MHZ19.h) もあるようですが、簡単な操作だけなのでライブラリは使いませんでした。
・測定周期はタイマー割り込みを使って正確なインターバルで測定出来るようになっています。(ログの時間精度が良くなります)
・測定結果はOLEDに表示すると共にシリアルにも流すようになっています。
・オフセット補正モードはAuto/Manual設定のコマンドを送っているだけで、その動作確認まではやっていません。(1回の確認に24時間かかる)
・ブレッドボードとセンサー

センサーは60cmくらいのケーブルで接続しています。センサーのフィルターの窓から赤い光が扇形に漏れている様子が見えます。たぶんこの光(の赤外成分)を使ってCO2の吸収を測定しているのだと思います。
◆まとめ
とりあえず単体で測定結果を表示出来る状態になりました。人がいて換気が悪いとCO2濃度がすぐに上がるのが判って面白いです。
最終的にはケースに入れて仕上げるつもりです。でもその前に、このセンサーがちゃんとCO2を検出しているかどうか、もう少し実験で確認、というか遊んでみたいと思います。
新型コロナウィルス対策はまだまだ続けないといけないようです。対策の中で部屋の換気は重要で、その状態の確認にはCO2濃度計を使うと良いそうです。そう聞くと、電子工作好きとしてはCO2濃度計を作りたくなるのですが、世の中には色々なセンサーが売られていて、どれを使えば良いのか判らなくてそのままになっていました。
そんなところに、ともの技術メモさんのブログに、安価なCO2センサの比較というシリーズ記事が掲載されていて、それによると、今買うなら、MH-Z19CというNDIR方式のセンサが良さそう、ということが判りました。
そんなことで、早速そのセンサーを手に入れてCO2濃度が測定可能な状態まで立ち上げたので紹介します。
・外観

この記事で紹介するのはこんな物です。右は電源に使ったモバイルバッテリーです。
◆MH-Z19C
MH-Z19Cは秋月でも売られていますが、このご時世に秋葉原まで買いに行く気はしません。とものさんのブログ情報によると、AliExpressにこのセンサーのメーカーであるWinsenの直売店があり、そこで買えばFAKE品を掴まされることは無さそう、ということだったのでそこに注文しました。
・AliExpressのMH-Z19Cのページ(クリックでジャンプ)

(上記の真贋は不明です、購入先や仕様の選定は自己責任でお願いします。)
レンジは200-4000ppmで、コネクタタイプ(ピンヘッダでは無い物)を買いました。
・届いた

1個買ったのに、10個くらいは入りそうな箱で届きました。
・試験成績書

何と手書きの試験成績書で担当者と責任者のサイン入りのものが添付されていました。なお、400と2000ppmの精度確認結果が記入されていました。
◆回路図(図をクリックで別窓に拡大)
動作確認に使った回路は以下の通りです。

CPUモジュールにはArduino NANO を使いました。MH-Z19Cの電源電圧は5.0±0.1Vが必要みたいなので、NANOの+5V端子に外部から5Vを供給する方式にしました。ちなみに、NANOはUSBから電源を供給するとダイオード1段分電源電圧が下がってしまいます。なお、この使い方にはいろいろ注意事項がありますが、書いたら切りが無いので省略します。
Auto Zeroモード切替スイッチ(JP1)と、オフセット設定スイッチ(SW1)を設けておきました。
・ブレッドボード

表示には128x32画素のI2CインターフェイスのOLEDを使いました。JP-1は必要に応じジャンパー線で接続します。
◆ソフト
少し長いですが、全体が見える形で掲載します。
/* MH-Z19C CO2ガスモニタ 20210528_MH-Z19C_monitor V0.6・ネットにあるいろいろな方が書かれたコードを参考に作りました。
機能:測定結果をOLEDに表示、シリアルに送信、ゼロ調の自動/マニュアル切り替え
タイマー割り込みで測定インターバル設定
ラジオペンチ 2021/5/28
*/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SoftwareSerial.h>
#include <MsTimer2.h>
#define MODE_PIN 2
#define RX_PIN 10
#define TX_PIN 11
#define LED_PIN 13
#define BAUDRATE 9600 // MH-Z19の通信ボーレート(固定値)
#define INTERVAL 2000 // 測定インターバル(ms単位で指定、最小値は1100)
#define SCREEN_WIDTH 128 // OLED Xサイズ
#define SCREEN_HEIGHT 32 // OLED Yサイズ
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 OLED(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
uint16_t co2Val;
uint16_t calib;
byte ReadCO2[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
byte SCalOn[9] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6};
byte SCalOff[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86};
byte RetVal[9];
char chrBuff[6];
volatile boolean timerFlag = false; // タイマー割り込みフラグ
SoftwareSerial mySerial(RX_PIN, TX_PIN);
void setup() {
pinMode(MODE_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
pinMode(RX_PIN, INPUT);
pinMode(TX_PIN, OUTPUT);
Serial.begin(115200);
mySerial.begin(9600);
if (!OLED.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
Serial.println(F("SSD1306 error")); // OLEDが使えなければメッセージ出して
for (;;); // 停止
}
OLED.clearDisplay();
OLED.setTextColor(WHITE);
OLED.setCursor(10, 12);
OLED.print(F("CO2 monitor V0.6")); // 開始メッセージ
OLED.display();
if (digitalRead(MODE_PIN) == LOW) { // スタート時にモードピンがLOWだったら
calib = 0; // マニュアルモード(Autoゼロ調無し)
} else { // そうでなければ
calib = 1; // ゼロ調はAutoモード(デフォルト状態)
}
setCalMode(calib); // ゼロ調モードを設定 0:手動、1:自動
measure(); // 1回読み飛ばす
MsTimer2::set(INTERVAL, timer2_IRQ); // 測定周期を設定
MsTimer2::start();
}
void loop() {
while (timerFlag != true) { // タイマーフラグが立つまで待つ
}
timerFlag = false; // 次回割り込み用にフラグを戻しておく
digitalWrite(LED_PIN, HIGH);
measure(); // CO2濃度測定(エラーチェックは省略)
digitalWrite(LED_PIN, LOW);
disp(); // OLEDに表示
Serial.print(F(" , ")); // タイムスタンプ用のデリミッタ
Serial.println(co2Val); // シリアルにデーター流す
delay(1000);
}
void disp() { // OLED画面表示
OLED.clearDisplay();
OLED.setCursor(0, 0);
OLED.setTextSize(1); // 先頭行に小さな文字で、、
OLED.print(F("CO2:"));
OLED.setCursor(15 * 6, 0); // 右上にゼロ調モードを表示
if (calib == 0) {
OLED.print(F("Man. Z"));
} else {
OLED.print(F("Auto Z"));
}
OLED.setCursor(10, 9);
OLED.setTextSize(3); // 3倍角文字で
sprintf(chrBuff, "%4d", co2Val); // CO2の値を4桁で
OLED.print(chrBuff); // 表示
OLED.setCursor(12 + 4 * 6 * 3, 17); // 文字数(4),文字幅(6pix),サイズ(3x)分移動
OLED.setTextSize(2);
OLED.print(F("ppm")); // 単位表示
OLED.display(); // データー転送して画面に表示
}
int measure() { // MH-Z16CからCO2濃度の値を読む
int err;
mySerial.write(ReadCO2, sizeof ReadCO2); // 測定コマンド送信
memset(RetVal, 0x00, sizeof RetVal); // 受信バッファをクリアしておいて
mySerial.readBytes((char *)RetVal, sizeof RetVal); // 測定結果を受信
if (RetVal[0] == 0xff && RetVal[1] == 0x86) { // 正常に読めていたら
co2Val = RetVal[2] * 256 + RetVal[3]; // CO2濃度を計算
err = 0;
} else {
co2Val = 399; // 正常でなければとりあえずこの値を返しておく
err = 1;
}
return err;
}
void setCalMode(int m) { // ゼロ調モードを設定
if (m == 0) {
mySerial.write(SCalOff, sizeof SCalOff); // キャリブレーションOFF
}
if (m == 1) {
mySerial.write(SCalOn, sizeof SCalOn); // キャリブレーションON
}
mySerial.readBytes((char *)RetVal, sizeof RetVal); // ダミー受信。これが無いと電源ON後はresetしないと測定結果が読めない
delay(100);
}
void timer2_IRQ() { // MsTimer2割込み
timerFlag = true; // 割込みが入ったのでフラグをセット
}
・このセンサー用のライブラリ(たしか、MHZ19.h) もあるようですが、簡単な操作だけなのでライブラリは使いませんでした。
・測定周期はタイマー割り込みを使って正確なインターバルで測定出来るようになっています。(ログの時間精度が良くなります)
・測定結果はOLEDに表示すると共にシリアルにも流すようになっています。
・オフセット補正モードはAuto/Manual設定のコマンドを送っているだけで、その動作確認まではやっていません。(1回の確認に24時間かかる)
・ブレッドボードとセンサー

センサーは60cmくらいのケーブルで接続しています。センサーのフィルターの窓から赤い光が扇形に漏れている様子が見えます。たぶんこの光(の赤外成分)を使ってCO2の吸収を測定しているのだと思います。
◆まとめ
とりあえず単体で測定結果を表示出来る状態になりました。人がいて換気が悪いとCO2濃度がすぐに上がるのが判って面白いです。
最終的にはケースに入れて仕上げるつもりです。でもその前に、このセンサーがちゃんとCO2を検出しているかどうか、もう少し実験で確認、というか遊んでみたいと思います。