勝手な電子工作・・

勝手なオリジナル電子工作に関する記事を書きます

室内CO2濃度の簡単なロガー

f:id:a-tomi:20200720200916j:plain

前回の「室内炭酸ガスの濃度測定」で少しご紹介したロガーの製作についてです。前の記事から少し間があいてしまいましたため、ここで急いで簡単にご紹介することにします。

ロガーでは、センサーが空気と接するよう、上の写真のようにケースの右側にとりつけてセンサーの前面に小さな穴を設けておきました。換気している部屋の机の隅において、窓を閉めたり全開したりすれば、次のような測定値が記録できます。

f:id:a-tomi:20200720201642j:plain

前回は、ロガーをブレッドボードで試作したところまででしたが、その後すぐに基板に組んで1号機を作りました。次の写真の下の基板です。

f:id:a-tomi:20200720202046j:plain

このロガーの回路は前回のとほぼ変わりませんが、都合のよいようにピンを変えたところがありますので、ここに改めて掲載します。

f:id:a-tomi:20200720202322j:plain

Arduinoプログラムはこの記事の最後の方につけておきます。

最初の基板では電源スイッチをつけましたが、ケースに収めにくいので、「不要なものはとにかく外す」という主義で他の位置関係などを含めて少し改善をしたのが次の元図。

これを作るには、過去の工作で作った部品ごとのパターンを切り貼りしてまずは部品面の図を作り、左右逆転します。毎度ながら時間節約のため、ランドなどは勝手に省略する基板です。基板の大きさは105mm x 78mm です。

f:id:a-tomi:20200720202557j:plain

いつものことですが、ツールパス・データに変換してCNCルーターで削り20分程でできます。

f:id:a-tomi:20200720203016j:plain

次が基板です。

f:id:a-tomi:20200720203058j:plain

加工の前にまずはジャンパー線で補完、今回は10本と少し多めですが。グラウンドは0.6φスズメッキ線5本、その他は細い銅線5本です。

f:id:a-tomi:20200720203620j:plain

最初に3V用レギュレータだけを配線して、センサーの予定電圧に正確に合わせておきます。

f:id:a-tomi:20200720203759j:plain

多回転ポテンシオメーターなので上図のように精密に合わせられます。また生活の知恵として他の抵抗器を混ぜないので、温度補正が不要になり常に安定した電圧になります。

ここまで行けば後はさっさと部品を付けて完成。

f:id:a-tomi:20200720204203j:plain

ブレークアウトモジュールは試作用にL型ヘダーピンが付いたものが多いですが、無理に外して付け替えずにソケットで対応します。なにせハンダを吸い取る場合はパターンを傷つけてしまうことが多いので、必要悪ですね。ハンダ吸い取り線だと時間がかかるし、「はんだシュッ太郎」ではパターンを剥がしやすく、ヒートガンではパーツにダメージを与えることがあるので、とにかくそのまま使うようにしています。

センサー部品(SMD)を直接使うほうが小さくて良いですが、購入数量が少ないと、ブレークアウトモジュールのほうが圧倒的に安いうえ扱いやすい。もしピンヘダーが予め着いてなければ言うことないのですが、そういうものは多くはありません。

上の例では上部のマイクロSDライターと、下部のTM1637はブレークアウトモジュールそのままです。左にあるRTC(リアルタイムクロック)DS3231モジュールは、空いているI2C用4穴に新たな平型ヘダーピンをつけて使います(L型ピンは整形し樹脂粘土をかぶせて脚にしているがカットしてもよい)。中央のロジックレベルコンバーターはコストも安いので必要な穴にだけスズメッキ線を通して直接ハンダづけします(底部がジャンパー線に当たらないよう少し浮かせてつけます)。

組み立て上がりを銅箔面から見ると次です。

f:id:a-tomi:20200720205036j:plain

1号機と並べると次です。下が今回の2号機。大して変わりませんが。

f:id:a-tomi:20200720205217j:plain

さて、2つ用意した目的は、もちろん長時間ロギングした値の変化を比較しようというものです。なぜなら、48時間のバーンインの最中に値が上昇する状況を詳しく見たかったからです。そのため、CCS811モジュールを改めて2個入手し、最初から行いました。

バーンイン24時間後時点で再スタートし、残りの24時間の途中で次のように値の上昇がみられます。

f:id:a-tomi:20200720205936j:plain

驚くべきことに2つの独立したセンサーの出す異常値は同じ変化をしています。つまり環境の僅かな変化が増幅されて値に反映されているように見えます。このセンサーの内蔵ソフトウェアはいったいどのような処理をしているのでしょうか。

とにかくそれからしばらくすると何事もなかったかのように落ち着きますから、このセンサーは凡人の理解を超えています(・・?。

バーンインの終わった状態でケースにとりつけました。このケースはだいぶ前に秋月で購入したポリカーボネートの丈夫なケースで、加工も簡単です。

f:id:a-tomi:20200720210457j:plain

センサー取り付け部分は、後から側面を適当に加工したため次の写真のように少し曲がってとりつけてしまいましたが、まあよしとします。

f:id:a-tomi:20200720210620j:plain

これで室内を記録した結果、換気状態に応じた値を敏感に示し異常なく動作します。なお、始まりの20分間はこのセンサーの「Run-in時間」とされ、測定値を無視すべき時間です(下のグラフでは09:22以降が測定値として有効です)。

f:id:a-tomi:20200720211006j:plain

今のところ1秒単位で計測・表示し、SDへは分単位で(分替わりの最初の測定結果)1つを記録していますが、記録としては秒単位の移動平均をとればスムーズかもしれません。あるいは複数回の平均をとるのもよいかもしれません。とはいえセンサーの提供する個々の値も既に多数回測定し平均した結果だろうとは思いますが。

また、この測定は秒単位の「モード1」なのでセンサーを構成しているヒーターは常時オンになっています。オンオフするモードでどうなるかも今後調べたいところです、時間のあるときにマイペースでなのですが。

しばらく使いこんだら「ベースライン設定」、つまりキャリブレーションが望ましいとのこと。少し手間のかかるセンサーですが、それほど正確なのでしょうか。マニュアルは次のサイトにまとめてリストアップされています。

www.sciosense.com

今回のプログラムは次です。毎度のことですが、Arduino-UnoにIDEで書き込み、ATmega328pの単体を取り出して使います。いつもそういう使い方をするため、UNOの本体には無圧(Zero Pressure)ソケットを装填しておきます。

/* *********************************************************
   CO2 Logger V03                    (c)2020 Akira Tominaga
   Function:
    Measure CO2 and TVOC using CCS811, with RTC & TM1637.
   Revisions:
    V00: Original devloped on 6/25/'20 
    V02: Show sensor error status on display        6/28/'20
    V03: SD-close-switch from D2 to D4              7/05/'20
 *  ********************************************************/
#include "Wire.h"           // I2C for CCS811 and RTC-DS3231 
#include "ccs811.h"         // CCS811 Air quality (CO2) sensor
#include "SPI.h"            // SPI for SD drive interface
#include "SD.h"             // Micro SD drive
#include "TM1637Display.h"  // TM1637 4dig LED Display
// *** for Air quality sensor CCS811
#define AQadr 0x5A          // AQ sensor adr (0x5B alternative)
CCS811 AQ;                  // AQ as air quality sensor symbol
uint16_t CO2;               // CO2 ppm(400-8192), or AQ err_status
uint16_t TVOC;              // TVOC ppb(0-1187)
char strCT[10];             // editing area for CO2 and TVOC
// *** for micro SD writer
File aqLog;                 // aqLog as SD file symbol 
#define Cs  10              // SD Chip-select pin is D10
//  MOSI=11, MISO=12, CLK=13
#define SDsw 4              // SD-file closing button (L=on)
// *** for TM1637 display
#define TMdio 6             // DIO for TM1637
#define TMclk 7             // CLK for TM1637
TM1637Display disp(TMclk, TMdio); // disp as TM1637 symbol
byte Data[] = { 0x3F, 0x3F, 0x3F, 0x3F }; // data for TM1637 init 0000
// *** for Real Time Clock DS3231
byte  vR[8];                // values in RTC registers
int vI[8] = { 0, 0, 0, 0, 0, 0, 0, 0};  // integers for RTC data
#define RTC_addr 0x68
#define mdI 0               // Initial mode
#define mds 1               // ss
#define mdm 2               // mm
#define mdh 3               // hh
#define mdW 4               // WW=Day of Week
#define mdD 5               // DD
#define mdM 6               // MM
#define mdY 7               // YY
#define sS 0
#define mM 1
#define hH 2
char  strMDhm[11];          // edit area for calendar and clock
uint8_t mmSv;               // minute-llvalue save area
#define erCd CO2            // error code instead of CO2, when error 

void setup() { // ***** Arduino setup *****
  Serial.begin(9600);
  Wire.begin();
  pinMode(SDsw, INPUT_PULLUP);
  disp.setBrightness(0x0A);
  if (!SD.begin(Cs)) {
    //Serial.println("SD E");
    Data[2] = disp.encodeDigit(0x02); // 0020=SD Error
    disp.setSegments(Data);
    while (1) {}
  }
  if (!AQ.begin()) {
    //Serial.println("AQ E");
    Data[2] = disp.encodeDigit(0x03); // 0030=AQ Error
    disp.setSegments(Data);
    while (1) {}
  }
  if ( !AQ.start(CCS811_MODE_1SEC) ) {
    //Serial.println("Am E");
    Data[2] = disp.encodeDigit(0x04); // 0040=AQ mode-set Error
    disp.setSegments(Data);
    while (1) {}
  }
  // set SD file name MMDDhhmm
  getTime();
  String s = strMDhm;
  s.concat(".csv");      // complete Comma-separated-value file name
  aqLog = SD.open(s, FILE_WRITE);
   aqLog.println("MMDDhhmm,CO2m,VOCb"); // write column hdr
  mmSv = vI[mdm];        // save current minute value
  edDisp();              // edit Data and display (0000)
  delay(2000);           // enough time for AQ
}

void loop() { // ****** Arduino Loop *****
  getTime();
  // *** measure air quality and display it
  uint16_t errSt, raw;
  AQ.read(&CO2, &TVOC, &errSt, &raw);
  if ( errSt == CCS811_ERRSTAT_OK ) {
    // for serial plotter 
    Serial.print("400,");Serial.print(CO2); Serial.println(",1000"); 
   } else if ( errSt == CCS811_ERRSTAT_OK_NODATA ) { // if no data
    erCd = 9000;          // show waiting-data
  } else if ( errSt & CCS811_ERRSTAT_I2CFAIL ) {    // if I2C error
    erCd = 9100;          // show I2C-error
  } else {                // for all other erros,
    erCd = 9200 + errSt;  // add error status to erCd 92ee
  }
  edDisp();               // edit Data and display

  // *** Write SD data every minute
  if (vI[mdm] != mmSv) {  // if minute changed
    mmSv = vI[mdm];       // save new minute
    String s = strMDhm;   // set date-time
    s.concat(",") ;       // set comma
    s.concat(strCT);      // set data
    aqLog.println(s);     // write a record
    //Serial.println(s);  // this is for debug only
  }
  // 1000mS delay, with checking SD-close-button every 100mS
  for (int i = 0; i < 10; i++) {
    if (digitalRead(SDsw) == LOW) { // if SD sw to close
      aqLog.close();
      disp.clear();
      while (1) {}
    }
    delay(100);
  }
}

/**************************************
      User defined functions
* *********************************** */
// *** Edit and display values
void edDisp(void) {
  // edit CO2 and TVOC value into strCT
  sprintf(strCT, "%04d,%04d", CO2, TVOC);
  // display CO2 value via Data
  for (int i = 0; i < 4; i++) {
    Data[i] = disp.encodeDigit(strCT[i]);
  }
  disp.setSegments(Data);
}

// *** get MMDD & hhmm into strMDhm ***
void getTime(void) {
  rRTC();      // read RTC device
  cR2I();      // convert RTC-format to Integers
  sprintf(strMDhm, "%02d%02d%02d%02d", vI[mdM], vI[mdD], vI[mdh], vI[mdm]);
}

// *** read Real-Time-Clock ***
void rRTC(void) {
  Wire.beginTransmission(RTC_addr);
  Wire.write(0x00);
  Wire.endTransmission();
  Wire.requestFrom(RTC_addr, 7);
  while (Wire.available() < 7) {}   // wait for data ready
  for (int i = 1; i < 8; i++) {
    vR[i] = Wire.read();
  }
  Wire.endTransmission();
}

// *** convert RTC-format to Integers ***
void cR2I(void) {
  vI[mds] = ((vR[mds] & B01110000) / 16) * 10 + (vR[mds] & B00001111);
  vI[mdm] = ((vR[mdm] & B01110000) / 16) * 10 + (vR[mdm] & B00001111);
  vI[mdh] = ((vR[mdh] & B00100000) / 32) * 20 + ((vR[mdh] & B00010000) / 16) * 10 + (vR[mdh] & B00001111);
  vI[mdW] = vR[mdW];
  vI[mdD] = ((vR[mdD] & B01110000) / 16) * 10 + (vR[mdD] & B00001111);
  vI[mdM] = ((vR[mdM] & B00010000) / 16) * 10 + (vR[mdM] & B00001111);
  vI[mdY] = ((vR[mdY] & B11110000) / 16) * 10 + (vR[mdY] & B00001111);
}
/* End of program */

 Arsuino-Unoにどのように無圧ソケットをつけて使うかは次の記事に詳しく書いてありますので、必要な方は次のリンクをクリックしてご参照ください。

Zoom用かんたん操作ボタンをArduino-UNOで作る(その1) - 勝手な電子工作・・

 

さて、思い出したのでついでに書いておきます。この測定値を従来のNDIRセンサーと比較してみたいところです。最近世界中へ多く出ているのは中国製の次のものらしい。取り寄せたらすぐに来ました。

f:id:a-tomi:20200720212053j:plain

試しにUART接続で簡単に使ってみると次のように安定して測定されます。記録は分単位で、ピーク2つは息をかけたところです、だいたい2分後(!)に応答します。長時間の記録にも安定して使えるようにみえます。走り出しの2分間はRun-inタイムです。

f:id:a-tomi:20200720212239j:plain

赤外線吸収タイプなので価格は15ドルほどしますが、ずいぶん低価格になったものです。微細なほこりなどが入らないように空気の出入り口にはフィルターがありますから、反応もそのぶんは遅いでしょうね。

こちらのセンサーも長時間の間にはキャリブレーションを要しますがその操作は簡単です。そのうちにこちらを試してみようかと思いますが、うまく行けば半導体センサーと併用する測定がよいのではないかとふと感じます。

 

では今回はこの辺で。

 

©2020 Akira Tominaga, All rights reserved.