スポンサーリンク

SGP30の比較用にMH-Z19センサHATを作ってM5StickCでCO2を測定してみる

スポンサーリンク

スポンサーリンク

SGP30のCO2の値が正しいのか知りたい

以前の実験で、SGP30センサで測定したCO2の値が、TVOCの値に影響されているようで、正しい値なのか疑問を持ちました。

そこで、以前mbedで実験したCO2専用のセンサMH-Z19を使って、CO2を測定する環境を用意しようと思います。

MH-Z19 CO2センサ

MH-Z19はNDIRという赤外線分光を使ったガス検出センサです。ガスの中を通った光は、そのガス固有の特定の波長の光だけが大きく減衰します。この原理を利用したセンサです。

センサの仕組みはこうです。光を発生するライトと、測定したいガス(例えばCO2)固有の特定の波長のみに感度がある光センサを用意して、ライトとセンサの間にガスを通します。通したガスが特定のガス(例えばCO2)の場合だけ、光センサが受光する光の量が減少します。この、「特定の波長の光がどれだけ減衰したか」から特定のガス(例えばCO2)の濃度が測定できるというわけです。この方式のセンサは、簡単な仕組みの割には、特定のガスのみを比較的精度よく測定できるのが特徴です。

ということで、この原理を利用したMH-Z19は、原理的にはそれなりに性能が高いので、このセンサでCO2を測定して、SPG30の測定結果と比較しようと思います。

スポンサーリンク

プログラムを作る

MH-Z19はArduinoのライブラリがあったのですが、私のセンサではうまく測定値が得られませんでした。そこで、データシートを見ながら自分でプログラムを作りました。

測定プログラム

M5SticKCのG0とG26でMH-Z19センサとシリアル通信します。作ったプログラムがこちら。

#include <M5StickC.h>

//MHZ19センサ用
unsigned long getDataTimer = 0; //時間保持用
const unsigned long getDataPeriod = 1000; //測定周期[ms]
int CO2 = 400;
int temperature = 20;

//画面の明るさ
bool lcdOn = false;

void setup() {
  M5.begin();
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setRotation(0);

  //MH-Z19用シリアル
  Serial1.begin(9600, SERIAL_8N1, 0, 26);

  //デバッグ用シリアル
  Serial.begin(115200);
  Serial.println("MH-Z19 test");

  //測定レンジ変更
  setDetectionDange(5000);
}

void loop() {
  M5.update();

  //Aボタンを押すと、画面の明るさをON,OFFする
  if( M5.BtnA.pressedFor(1) ){
    if( lcdOn == true )
    {
      lcdOn = false;
      M5.Axp.ScreenBreath( 0 );
    }
    else
    {
      lcdOn = true;
      M5.Axp.ScreenBreath( 15 );
    }
  }

  //Bボタンを押すと、キャリブレーション
  if( M5.BtnB.pressedFor(1000) ){
    setZeropoint();
    Serial.println(" calibrated");
    delay( 4000 );
  }

  //測定
  if( getGasConcentration( &CO2 , &temperature ) )
  {
    Serial.println("CO2[ppm]: " + String(CO2) + "\tTemperature['C]: " + String(temperature));      
  }

  //測定結果の表示
  int yLocation = 0;
  M5.Lcd.setCursor(0, yLocation, 2);
  M5.Lcd.println("CO2"); yLocation+=16;
  String str = "      " + (String)CO2;
  M5.Lcd.drawRightString(str, M5.Lcd.width(), yLocation,4); yLocation+=24;
  M5.Lcd.drawRightString("[ppm] ", M5.Lcd.width(), yLocation,2); yLocation+=16;
  
  yLocation+=8;
  M5.Lcd.setCursor(0, yLocation , 2);
  M5.Lcd.println("Temperature"); yLocation+=16;
  str = "      " + (String)(temperature);
  M5.Lcd.drawRightString(str, M5.Lcd.width(), yLocation,4); yLocation+=24;
  M5.Lcd.drawRightString("['C] ", M5.Lcd.width(), yLocation,2); yLocation+=16;

  delay(1000);

}

/*--- MH-Z19用 ---*/
#define MHZ19_DATA_LEN 9

//CO2,温度を取得する
bool getGasConcentration(int *CO2 , int *temperature )
{
  byte command[MHZ19_DATA_LEN] = {0xff,0x01,0x86,0x00,0x00,0x00,0x00,0x00,0x79};
  sendCommand( command , sizeof(command) );
  
  byte response[MHZ19_DATA_LEN];
  recieveResponse( response );


  //コマンドチェック
  if( response[1] != 0x86 )
  {
    Serial.printf("Response Error '%x'\n",response[1]);
    return false; //timeOut
  }

  int CO2Temp = (int)response[2] * 256 + (int)response[3];
  int temperatureTemp = (int)response[4] -40;

  *CO2 = CO2Temp;
  *temperature = temperatureTemp;

  return true;
}

//ゼロポイントキャリブレーション
bool setZeropoint()
{
  byte command[9] = {0xff,0x01,0x87,0x00,0x00,0x00,0x00,0x00,0x78};
  sendCommand( command , sizeof(command) );

  byte response[MHZ19_DATA_LEN];
  recieveResponse( response );

  //コマンドチェック
  if( response[1] != 0x87 )
  {
    Serial.printf("Response Error '%x'\n",response[1]);
    Serial.printf("reaponse %x %x %x %x %x %x %x %x %x\n", response[0], response[1], response[2], response[3], response[4], response[5], response[6], response[7], response[8]);
    return false; //timeOut
  }

  return true;

}

//測定レンジ変更
bool setDetectionDange(int range)
{
  if( range != 2000 && range != 5000 )
  {
     Serial.printf("invalid range. Please set 2000 or 5000 \n");
     return false;
  }
  
  byte command[9] = {0xff,0x01,0x99,0x00,0x00,0x00,0x00,0x00,0x00};  
  command[3] = (byte)(range/256);
  command[4] = (byte)(range%256);
  command[8] = calcCheckSum(command);
  
  sendCommand( command , sizeof(command) );

  byte response[MHZ19_DATA_LEN];
  recieveResponse( response );

  //コマンドチェック
  if( response[1] != 0x99 )
  {
    Serial.printf("Response Error '%x'\n",response[1]);
    Serial.printf("reaponse %x %x %x %x %x %x %x %x %x\n", response[0], response[1], response[2], response[3], response[4], response[5], response[6], response[7], response[8]);
    return false; //timeOut
  }

  return true;

}


//コマンドの送信
void sendCommand(byte command[], int length)
{
  //Serial.printf("command %x %x %x %x %x %x %x %x %x\n", command[0], command[1], command[2], command[3], command[4], command[5], command[6], command[7], command[8]);
  for( int i=0 ; i<length ; i++ )
    Serial1.write(command[i]);
}

//チェックサムの計算
byte calcCheckSum( byte data[] )
{
  byte checkSum = 0;

  for (int x = 1; x<MHZ19_DATA_LEN-1; x++)
  {
    checkSum += data[x];
  }
  checkSum = 255 - checkSum;
  checkSum++;

  return checkSum;
}

//センサから応答を受信する
bool recieveResponse( byte response[] )
{
  unsigned long timeStamp = millis();

  //返答の先頭の0xFFが来るのを待つ
  while(1){
    if( Serial1.available() )
    {
      byte res;
      Serial1.readBytes(&res, 1);
      if( res == 0xff )
        break;
    }
    if (millis() - timeStamp >= 2000)
    {
      Serial.println("0xFF wait Time Out");
      return false; //timeOut
    }

    //millisがオーバーフローしたらgetDataTimerをリセット
    if( timeStamp > millis() )
      timeStamp = millis();

  }

  //8バイトたまるのを待つ
  timeStamp = millis();
  while (Serial1.available() < MHZ19_DATA_LEN-1)
  {
    if (millis() - timeStamp >= 2000)
    {
      Serial.println("Recieve Time Out");
      return false; //timeOut
    }

    //millisがオーバーフローしたらgetDataTimerをリセット
    if( timeStamp > millis() )
      timeStamp = millis();
  }

  //データの読み出し
  response[0] = 0xff;
  Serial1.readBytes(&(response[1]), MHZ19_DATA_LEN-1);
  //Serial.printf("reaponse %x %x %x %x %x %x %x %x %x\n", response[0], response[1], response[2], response[3], response[4], response[5], response[6], response[7], response[8]);

  //チェックサムチェック
  if( response[8] != calcCheckSum( response ) )
  {
    Serial.printf("Check Sum Error read'%x' correct'%x'\n", response[8], calcCheckSum(response ) );
    Serial.printf("reaponse %x %x %x %x %x %x %x %x %x\n", response[0], response[1], response[2], response[3], response[4], response[5], response[6], response[7], response[8]);
    return false;
  }
  return true;
}

測定値を得るコマンドと、ゼロキャリブレーション、レンジ変更コマンドだけを実装しました。

Aボタンを長押しすると表示のON,OFFができ、Bボタンを長押しでゼロキャリブレーションできます。

1秒ごとに、CO2とセンサ温度を液晶画面に表示してくれます。

センサのTXとM5StickCのG0の間には、220Ωの抵抗を接続します。これがないと、プログラムの書き込みができませんでした。

いい感じに、CO2が測定できています。SPG30よりも値がぶれずに安定して測定できているような気がします。

スポンサーリンク

MH-Z19 HATを作る

無事動くようになったので、HATにします。といっても、ピンヘッダと、センサをつなぐだけです。

ポイントとしては、センサのTxとG0との間に220Ωの抵抗を挟むということです。これがないと、プログラムを書き込むことができません。これ以上大きな値だと、今度は通信がうまくいきません。220Ωがちょうどいいようです。

あとM5StickCは5V出力があるので、5Vのセンサが接続できて便利ですね。

配線が終わったらショートしないように、ポリイミドテープを貼って絶縁しておきます。

M5StickC用 MH-Z19 HATの出来上がり。

さて、単体で測定できるようになったので、次回はSGP30の時のようにambientに送信して記録できるようにしたいと思います。

電子工作
スポンサーリンク
スポンサーリンク

関連記事

kohacraftのblog

コメント