懲りずにまたCO2センサーを購入

最近CO2センサーを3つ購入したので、今回はその内容になります。

3.3V駆動のCO2センサーは少ない

左からELT Sensor社のD-300、Senseair社のSunriseLP8となります。

最初にD-300を購入し、Arduino Duemilanoveで動作確認したところ、正常に動きました。
次に本命のArduino MKR WAN 1310に繋いだところ、初期値の500ppmしか取得できませんでした。
あくまでも推測ですが、Arduino内部のプルアップ抵抗が悪さをしている気がします。

どうしても解決できなかったので諦めていたところ、Senseair社を知りました。
CO2センサー界隈では有名なメーカーだそうです。
そして見つけたのがSunrise!
光源にLEDを使用しているため消費電力が低い画期的な商品です。
値段も6000円ちょっとだったのですぐにポチリました。

しかし、購入後に400ppm以上しか計測できないことに気づくorz
この失敗、2度目です😭
慌ててキャンセルできないか購入元のマルツオンラインさんに確認したけど、取り寄せ商品なためキャンセルできなかった💸

結局、Sunriseは一度も使うことなくお蔵入りに・・・

もうここで完全に心が折れていたわけですが、逆にどうでもよくなってLP8を購入(笑)
OEM製品ですがAmazonで購入することができました!


まずはブレッドボードで動作確認をしました。
Arduino DuemilanoveとLP8の間には39Ωの抵抗と、0.47Fのコンデンサーを挟んでいます。
メーカー推奨のコンデンサーは「Eaton Bussman PM-5R0H474-R」なのですが、入手性が悪かったので「PB-5R0V474-R」を購入しました。
コンデンサー1個をわざわざアメリカから空輸してもらうと、謎の背徳感があります(笑)
佐川さん、ごめんなさい🙇

回路とスケッチはCO2Meter.comというサイトを参考にしました。


LP8の情報はとても少なく、ましてArduinoに接続している情報は皆無だったので、凄く助かりました!!
Arduino Duemilanoveで正常に動作したので、次にArduino MKR WAN 1310で試したところ、こちらも問題なく動作しました。
スケッチは以下になります。


// RDY:センサーの状態とArduinoとの通信準備を同期する
int RDY_PIN = 0;
// EN_VBB:VBBを制御し、LP8を起動、停止する
// High=LP8起動、Low=LP8停止
int EN_VBB_PIN = 1;  
// !RESET 
int RESET_PIN = 4;
// LP8との伝送速度
int DEVICE_BAUD_RATE = 9600;
// LP8 Address
byte DEVICE_ADDRESS = 0xfe;
// 
byte WRITE_COMMAND = 0x41;
// 
byte READ_COMMAND = 0x44;
// LP8のRAMメモリアドレスの上位バイト
byte DEVICE_HIGH_BYTE_RAM_ADDRESS = 0x00;

// 測定期間の動作モード : 0x10
// 以前のセンサー状態が利用できない場合にのみ使用する
// 新たな起動後、または再キャリブレーション後など
uint8_t INITIAL_MEASUREMENT = 0x10;

// 測定期間の動作モード : 0x20(デフォルト)
// 以前のセンサー状態をノイズ抑制に再利用し、
// バックグラウンドのキャリブレーション基準を渡す
uint8_t SEQUENTIAL_MEASUREMENT = 0x20;

uint8_t LastMeasurementResult[31];

byte co2Response[49];
int crc_result = 0;
bool isFirstRun = true;

void setup() {
  Serial.begin(115200);            // PC ⇔ Arduino
  while(!Serial){}
  Serial1.begin(DEVICE_BAUD_RATE); // Arduino ⇔ LP8
  delay(1000);
  pinMode(RDY_PIN, INPUT);
  pinMode(EN_VBB_PIN, OUTPUT);
  pinMode(RESET_PIN, OUTPUT);

}

void loop() { 

  // LP8起動
  digitalWrite(EN_VBB_PIN, HIGH);

  // RDYがLowになるまで待機
  while (digitalRead(RDY_PIN) == HIGH) {
    delay(100);
    Serial.println("waiting for rdy to go high");
  }

  if(isFirstRun){
    deviceReset();
    byte cmd[] = { DEVICE_ADDRESS,
                   WRITE_COMMAND,
                   DEVICE_HIGH_BYTE_RAM_ADDRESS, 
                   0x80, // LP8のRAMメモリアドレスの下位バイト
                   0x01, // 送信バイト数(1byte)
                   INITIAL_MEASUREMENT, // 計算制御バイト
                   0x28, // CRCの下位バイト
                   0x7e  // CRCの上位バイト
                 };
    sendRequest(cmd, 8, 4);
    isFirstRun = false;
  }
  else{
    // 前回の測定結果をLP8へ送信
    LastMeasurementResult[0] = DEVICE_ADDRESS;
    LastMeasurementResult[1] = WRITE_COMMAND;
    LastMeasurementResult[2] = DEVICE_HIGH_BYTE_RAM_ADDRESS;
    LastMeasurementResult[3] = 0x80;
    LastMeasurementResult[4] = 0x18;
    LastMeasurementResult[5] = SEQUENTIAL_MEASUREMENT;
    
    for (int i = 6; i < 31; i++) {
      LastMeasurementResult[i] = co2Response[i-3];
    }
  
    crc_result = modbusCrc(LastMeasurementResult, 29);
    LastMeasurementResult[29] = crc_result & 0xff;
    LastMeasurementResult[30] = (crc_result >> 8) & 0xff;
    
    sendRequest(LastMeasurementResult, 31, 4);
  }
 
  // RDYがHighになるまで待機
  while (digitalRead(RDY_PIN) == LOW) {
    delay(100);
    Serial.println("waiting for rdy to go high");
  }

  // CO2を測定
  byte cmd[] = { DEVICE_ADDRESS,
                 READ_COMMAND,
                 DEVICE_HIGH_BYTE_RAM_ADDRESS,
                 0x80,
                 0x20,
                 0x79,
                 0x3C
               };
  sendRequest(cmd, 7, 37);

  // LP8停止
  digitalWrite(EN_VBB_PIN, LOW);

  int co2 = 256 * co2Response[29] + co2Response[30];
  Serial.print("CO2:");
  Serial.print(co2);
  Serial.println("ppm");
  delay(20000); // 20秒待ってから次のセンサー読み取り
}

bool deviceReset(){
  
  // LP8をリセット
  digitalWrite(RESET_PIN, LOW);
  delay(500);
  digitalWrite(RESET_PIN, HIGH);

  while (digitalRead(RDY_PIN) == HIGH) {
    delay(100);
    Serial.println("waiting for rdy to go low");
  }

  return true;
}

// packetにはModBUSコマンドバイトが含まれ、mはModbusコマンドのバイト数、nはセンサーから返されたバイト数です。
void sendRequest(byte packet[], int m, int n) {

  // 応答を得るまでリクエストを送信し続ける
  while (!Serial1.available()) {
    Serial.println("waiting for serial port availability");
    Serial1.write(packet, m);
    delay(1000); // 応答の一貫したロードを取得するために必要
  }
  
  int timeout = 0; //set a timeout counter

  // nバイトの応答を待つ
  while (Serial1.available() < n ) {
    timeout++;
    // 時間がかかりすぎる場合は、おそらくエラーが発生しています
    if (timeout > 10) {
      while (Serial1.available()) // 私たちが持っているものは何でも洗い流す
      Serial1.read();
      break; // 終了して再試行
    }
    
    delay(50);
  }
  
  for (int i = 0; i < n; i++) {
    co2Response[i] = Serial1.read();
    Serial.print("response[");
    Serial.print(i);
    Serial.print("] = ");
    Serial.println(co2Response[i], HEX);
  }
  
  Serial.print("\n\n");
  Serial.flush();
}


// Modbus 16ビットCRCの計算
int modbusCrc(byte sensorData[], byte n1) {

  uint16_t crc = 0xFFFF;
  
  for (int pos = 0; pos < n1; pos++) {
    crc ^= (uint16_t)sensorData[pos]; // 最小sigへのXORバイト

    // 各ビットをループ
    for (int i = 8; i != 0; i--) {
      // LSBが設定されている場合
      if ((crc & 0x0001) != 0) {
        crc >>= 1; // 右シフトとXOR 0xA001
        crc ^= 0xA001;
      } else { 
        crc >>= 1; // LSBが未設定の場合は右にシフトするだけ
      }
    }
  } 

  return crc;
}

上記のスケッチが完成するまで、何回も心が折れかけています😓
誰かの参考になれば幸いです。

RSコンポーネンツについて

今までIoTの部品を購入するときは主にマルツオンラインを使用していました。
しかし、以下の理由から今後はRSコンポーネンツになりそうです。
  • 品ぞろえがDigi-Key並みに凄い
  • 発送が早く、空輸なためすぐに届く
  • 国内に在庫が無く、アメリカからの空輸になっても送料はそのまま
実際に今回、メーカー推奨のコンデンサーである「M-5R0H474-R」をDigi-Keyで購入しようとしたところ、アメリカからの発送なため送料が2000円くらいでした(笑)
絶対に買いたくなかったので、RSコンポーネンツで「PB-5R0V474-R」を購入しました。
案の定、Digi-Keyと同様に国内の在庫がないため、アメリカからの発送となりました。
しかし、送料は国内発送の時と同じ、450円でした。
抵抗もRSコンポーネンツで購入したのですが、こちらも佐川さんの飛脚で届けてくれるため、直ぐに届きました。

まじでRSコンポーネンツは神✨

コメント

このブログの人気の投稿

Visual StudioでAngularの開発をする

ArduinoにUARTのPINを追加する

Arduino MKR WAN 1310を購入!