知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

中華製の安価なCO2センサー「MH-Z19B」を買ったので「SCD30」と比較してみた

スイスのSENSIRION社製、非分散赤外線吸収法(NDIR)のCO2センサー「SCD30」の記事を以前に書きました。

このセンサー、I2Cインターフェースで電源も3.3V~5.5Vまで対応していて使いやすいのですが、値段がちとお高いのが難点でした。

そんな時に「MH-Z19」という安いCO2センサーがあると教えてもらったので、早速AliExpressで検索してみたら、送料込みで1,900円ぐらいのお手頃なものを見つけることができたんです。これもSCD30と同じくNDIRらしいんですよ。

※2021年6月7日追記

貼り付けていたMH-Z19Bのリンクが変更されていたのでリンクを削除しました。

今は秋月電子通商で後継のMH-Z19Cが2,480円で購入できます。

香港民主化デモ発生以来、中国からの郵便物の配達遅延が目立っていて、それまでは10日ぐらいで着いていたのですが今回は24日ぐらいかかりました。

で、届いたセンサーにピンヘッダーを付けたのがこれです!

f:id:IntellectualCuriosity:20200102144404p:plain

この写真のツッコミポイントは、ブレッドボードを使っているのになんでピンヘッダーが上向きやねん!ってところだと思います。

ピンヘッダーの間隔はブレッドボード用に2.54mmになっているのですが、センサーの基盤の幅がですね、ブレッドボードに合っていないんですよ。で、仕方なくピンヘッダーを上向きに付けて、ダイソーで買った粘着タック(水色の物体)でブレッドボードに貼り付けた次第です。

データシートにはこのように書いてありました。

f:id:IntellectualCuriosity:20200102152703p:plain

この「29.5mm」幅は何用なんでしょう?

各ピンの定義はこうなっています。(赤枠は今回使用するピンです)

f:id:IntellectualCuriosity:20200102160623p:plain

検出した値の出力方法にはUART・DAC(Analog)・PWMの3種類が用意されていますが、UARTだと各種コマンドの送信をすることもできるためお勧めです。

ここに一緒に書いておいてくれればいいと思うんですが、別の場所に電圧のことが書いてあります。

f:id:IntellectualCuriosity:20200102161100p:plain

なので、Vinには3.3Vではなく5V突っ込む必要がありますが、UARTは3.3VのためESP8266とかRaspberry PiのGPIOにそのまま繋げることができます。

また、CO2の検出範囲をコマンドで指示できるようになっていて、こんな感じになっています。

f:id:IntellectualCuriosity:20200102185018p:plain

 

ESP8266との配線

ESP8266との配線例はこんな感じです。(電源はMicro USB端子)

f:id:IntellectualCuriosity:20200102182503j:plain

ただし、秋月電子のESP8266ボード「AE-ESP-WROOM-02-DEV」の説明書には「5V0」の説明として「USBバスパワー5Vの出力としては使用しないでください。」と書いてあるので、「5V0」からセンサーの電源を取ることを推奨していないと思います。

また、ボードのUART端子であるTXDとRXDを使っていないのは、プログラムのログをデフォルトのUARTの出力しているためです。

 

MH-Z19Bのコマンド

UARTで送信できるコマンドは次の5種類があります。

f:id:IntellectualCuriosity:20200102190816p:plain

それぞれ簡単に説明すると、次のようになっています。

・Read CO2 Concentration : CO2値の取得

・Calibrate Zero Point (Zero) : 400ppmで校正する(20分以上安定後)

・Calibrate Span Point (SPAN) : 1000ppm以上の値で構成する(20分以上安定後)

・ON/OFF Self-calibration function for zero point : 自己校正機能のON/OFF

・Detection range setting : 検出範囲の設定

ここで注意が必要なのが自己校正機能だと思います。この機能の説明が次のように書いてありました。

「After the module works for some time, it can judge the zero point intelligently and do the zero calibration automatically. The calibration cycle is every 24 hours since the module is power on. The zero point is 400ppm. This method is suitable for office and home environment, not suitable for agriculture greenhouse, farm, refrigerator, etc.. If the module is used in latter environment, please turn off this function.」

電源が入ってから24時間ごとに自己校正する機能で、オフィスや家庭に適しており、農場や冷蔵庫などの環境には向きませんとなっています。なぜオフィスや家庭だといいのかは、人がいないときに換気のおかげでzero point(400ppm)になるからではないかと推測しています。逆に密閉空間やzero pointにならないようなところでは、自己校正が正常に行えないのではと思うんです。

そう思う理由はSCD30の自己校正機能が1日1時間の新鮮な空気を必要としていて、部屋にずっと設置していたセンサーを外に出したら400ppmよりも数百ppm高い値になったからなんです。

外気でしっかり換気ができる環境でないと自己校正機能は正常に働かないみたいなんです。ぼくはこの寒い時期に部屋の窓を1時間も開ける気にはならないので、自己校正機能はオフにしています。

※2021年6月7日追記

最近は自己校正機能(セルフキャリブレーション)をオンで使っています。

 

MA-Z19BとSCD30の比較

外(ベランダ)に30分間ほど放置した後、MA-Z19Bを400ppmでSCD30を415ppmで校正後に部屋のCO2濃度を6時間計測したグラフです。

415ppmは気象庁が発表している2018年12月の平均CO2濃度で、MA-Z19Bもその値にしたかったのですが、400ppmでしか校正できないので妥協しました。

f:id:IntellectualCuriosity:20200102203518p:plain

まず、2つのセンサーの特徴として、MA-Z19Bは濃度変化に対して緩慢に反応するのに対して、SCD30は機敏に反応します。SCD30のグラフの方がギザギザしているのと、最後に急激に上がっているのは機敏な反応のためです。

また、電源オン後にMA-Z19Bは少しの間、値が410ppmになる待機時間が必要なのに対して、SCD30は直ぐに値が表示されます。

結果的に初めの校正で発生した15ppmの違いを気にする必要もないほど、2つのセンサーの値は異なっています。どっちが正しいか?ということよりも、それぞれのセンサーの特性が結果に反映されている気がします。

 

使用したBlynkプロジェクト

今回使用したBlynkのプロジェクトはこんな感じです。

MH-Z19とSCD30とでそれぞれESP8266を使用して、1つのプロジェクトで2つのデバイスを使う設定にしています。

f:id:IntellectualCuriosity:20200102212122j:plain

グラフの下にはCARIBRATION(校正)ボタンを用意して、任意のタイミングで校正を実行できるようにしました。

 

スケッチ(プログラム)

最後にMH-Z19BとSCD30で使用したスケッチを載せて終わりにします。

※2020年1月19日追記

ボードマネージャーでインストールするESP8266のバージョンを最新の2.6.xにするとセンサーの情報が正常に取得できなくなります。バージョンを2.5.2にすると正常に動作するようになります。

※2021年6月7日追記

ESP8266のボードパッケージは最新版の3.0.0で動きました。

f:id:IntellectualCuriosity:20210607175318p:plain

確認したEspSortwareSerialのバージョンは最新版の6.12.6です。

f:id:IntellectualCuriosity:20210607175600p:plain

 

※2022年8月7日追記

今更ながらセルフキャリブレーションONのコマンドのチェックサム(最後のバイト)が間違っていたので修正しました。

MH-Z19Bスケッチ

/*
 * IoT CO2 monitor using ESP8266 and MH-Z19B
 * January 1, 2020
 * By Hiroyuki ITO
 * http://intellectualcuriosity.hatenablog.com/  
 * MIT Licensed.
 */
#define BLYNK_PRINT Serial // Comment this out to disable prints and save space
#include <BlynkSimpleEsp8266.h>
#include <SoftwareSerial.h>

// Blynk Auth Token and WiFi credentials
#define AUTH_TOKEN "YourMH-Z19BAuthToken"
#define WIFI_SSID  "YourNetworkName"
#define WIFI_PASS  "YourPassword"

// Define ESP8266 TX RX pin
#define ESP8266_TX 12
#define ESP8266_RX 13

// MH-Z19 Commands
byte cmd_read_co2[9] = {0xFF,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
byte cmd_calibrate_zero[9] = {0xFF,0x01,0x87,0x00,0x00,0x00,0x00,0x00,0x78};
byte cmd_Self_calibration_off[9] = {0xFF,0x01,0x79,0x00,0x00,0x00,0x00,0x00,0x86}; // Self-calibration OFF
byte cmd_Self_calibration_on[9] = {0xFF,0x01,0x79,0xA0,0x00,0x00,0x00,0x00,0xE6};  // Self-calibration ON
byte cmd_detection_range_2000[9] = {0xFF,0x01,0x99,0x00,0x00,0x00,0x07,0xD0,0x8F}; // 0~2000ppm
byte cmd_detection_range_5000[9] = {0xFF,0x01,0x99,0x00,0x00,0x00,0x13,0x88,0xCB}; // 0~5000ppm

// Global Objects
SoftwareSerial co2Serial(ESP8266_RX, ESP8266_TX);
BlynkTimer timer;

/*
 * Calibration
 */
BLYNK_WRITE(V2) {
  if (param.asInt()) {
    co2Serial.write(cmd_calibrate_zero, 9);
  }
}

/*
 * Send sensor data to Blynk
 */
void sendSensor() {
  unsigned char response[9];        // for answer
  co2Serial.write(cmd_read_co2, 9); // request PPM CO2
  co2Serial.readBytes(response, 9);
  if (response[0] == 0xFF && response[1] == 0x86){
    unsigned int responseHigh = (unsigned int) response[2];
    unsigned int responseLow = (unsigned int) response[3];
    int ppm = (256 * responseHigh) + responseLow;
    Blynk.virtualWrite(V1, ppm);
    Serial.println(String("CO2: ") + ppm);
  } else {
    Serial.println("CO2: read error");
  }
}

void setup() {
  Serial.begin(74880);
  co2Serial.begin(9600);

  // Initialize MH-Z19
  co2Serial.write(cmd_Self_calibration_on, 9);
  co2Serial.write(cmd_detection_range_5000, 9);

  // Initialize Blynk
  Blynk.begin(AUTH_TOKEN, WIFI_SSID, WIFI_PASS);

  // Initialize BlynkTimer
  timer.setInterval(2000, sendSensor); // every 2 seconds
}

void loop() {
  Blynk.run();
  timer.run();
}

SCD30スケッチ

/*
 * IoT CO2 monitor using ESP8266 and SCD30
 * January 1, 2020
 * By Hiroyuki ITO
 * http://intellectualcuriosity.hatenablog.com/  
 * MIT Licensed.
 */
#include <BlynkSimpleEsp8266.h>
#include <SparkFun_SCD30_Arduino_Library.h> 

// Blynk Auth Token and WiFi credentials
#define AUTH_TOKEN "YourSCD30AuthToken"
#define WIFI_SSID  "YourNetworkName"
#define WIFI_PASS  "YourPassword"

// Global Objects
SCD30 sensor;
BlynkTimer timer;

/*
 * Calibration
 */
BLYNK_WRITE(V2) {
  if (param.asInt()) {
    sensor.setForcedRecalibrationFactor(415); // Calibrate at 415ppm
  }
}

/*
 * Send sensor data to Blynk
 */
void sendSensor() {
  while(!sensor.dataAvailable()) {
    delay(100);
  }
  int co2 = sensor.getCO2();
  Blynk.virtualWrite(V1, co2);
  Serial.println(String("CO2: ") + co2);
}

void setup() {
  Serial.begin(74880);

  // Initialize SCD30
  sensor.begin();
  sensor.setMeasurementInterval(2); //Change number of seconds between measurements: 2 to 1800 (30 minutes)
  sensor.setAutoSelfCalibration(false);

  // Initialize Blynk
  Blynk.begin(AUTH_TOKEN, WIFI_SSID, WIFI_PASS);

  // Initialize BlynkTimer
  timer.setInterval(2000, sendSensor); // every 2 seconds
}

void loop() {
  Blynk.run();
  timer.run();
}


誰かのお役に立てれば幸いです!