空気品質センサ CCS811 で二酸化炭素濃度などを測定してみる

 最近オフィス内の人数も増えて来て、換気が不十分でオフィス内の空気が悪いと感じるメンバーが増えて来たこともあり、試しに二酸化炭素濃度などを測ってみようと下記センサーを買ってみました。

www.switch-science.com

 今回はとりあえず下記チュートリアルに従って、正しくセンサーを動作させることができるのか、どんな値が取れるのかを確認してみたいと思います。

CCS811 Air Quality Breakout Hookup Guide - learn.sparkfun.com

ピンヘッダ実装

 モジュールにはピンヘッダが実装されていないので、まずはピンヘッダを下記写真のように半田付けします。半田の量がまちまちなのはご愛嬌ということで。

f:id:akanuma-hiroaki:20180610212741j:plain

 NTCピンにNTCサーミスタを実装すれば温度を測定して空気測定値の補正に利用できるらしいのですが、今回はまだNTCサーミスタは入手していないので、とりあえずなしで使ってみます。

シンプルな計測サンプル

 まずは最もシンプルな構成で空気品質を測定してみたいと思います。実際は Seeeduino を利用したのですが、 Seeeduino の Fritzing のデータでは I2C の SCL と SDA を接続しているピンが不足していたので、回路図では Arduino を使用しています。また、 Seeeduino では動作電圧をスイッチで 5V か 3.3V に切り替えられますが、今回は CCS811 の動作電圧が 3.3V なので、 3.3V で動作させています。

f:id:akanuma-hiroaki:20180610213103p:plain

 では次にファームウェアの実装です。 CCS811 の Arduino 用ライブラリが SparkFun から提供されていますのでこれを使用します。

github.com

 Arduino Web Editor を使っていれば上記ライブラリはダウンロードやインポートをしなくてもそのまま利用できます。

f:id:akanuma-hiroaki:20180610213121p:plain

 コードはチュートリアルとほぼ同様ですが、下記のような実装になります。

#include <SparkFunCCS811.h>

#define CCS811_ADDR 0x5B

CCS811 mySensor(CCS811_ADDR);

void setup() {
  Serial.begin(115200);
  Serial.println("CCS811 Basic Example");
  
  CCS811Core::status returnCode = mySensor.begin();
  if (returnCode != CCS811Core::SENSOR_SUCCESS)
  {
    Serial.println(".begin() returned with an error.");
    while (1);
  }
}

void loop() {
  if (mySensor.dataAvailable())
  {
    mySensor.readAlgorithmResults();
    
    Serial.print("CO2[");
    Serial.print(mySensor.getCO2());
    Serial.print("] tVOC[");
    Serial.print(mySensor.getTVOC());
    Serial.print("] millis[");
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
  }
  
  delay(10);
}

 setup() の中で mySensor.begin() することでセンサーを初期化し、失敗した場合はそこでスタックさせています。

 loop() の中では mySensor.dataAvailable() でまだ読み取っていない新しいデータが存在するかをチェックし、存在する場合は mySensor.readAlgorithmResults() で TVOC と eCO2 のレベルを計算させ、 mySensor.getCO2() と mySensor.getTVOC() でそれぞれの値を取得しています。

 また、このセンサーでは48時間のエージングと、20分のコンディショニングが推奨されていて、初めて実行した場合は下記のように有効な値が取得されません。

CO2[400] tVOC[0] millis[5996]
 tVOC[0] millis[6991]
CCS811 Basic Example
CO2[0] tVOC[0] millis[2017]
CO2[0] tVOC[0] millis[3011]
CO2[0] tVOC[0] millis[4006]
CO2[400] tVOC[0] millis[5002]
CO2[400] tVOC[0] millis[5996]
CO2[400] tVOC[0] millis[6991]
CO2[400] tVOC[0] millis[7987]
CO2[400] tVOC[0] millis[8981]
CO2[400] tVOC[0] millis[9976]
CO2[400] tVOC[0] millis[10972]
CO2[400] tVOC[0] millis[11966]
CO2[400] tVOC[0] millis[12961]
CO2[400] tVOC[0] millis[13957]
CO2[400] tVOC[0] millis[14952]

 48時間のエージングはまだ実行できていませんが、とりあえず20分のコンディショニング後は下記のように値が取れ始めました。

CO2[408] tVOC[1] millis[1224601]
CO2[408] tVOC[1] millis[1225595]
CO2[408] tVOC[1] millis[1226591]
CO2[408] tVOC[1] millis[1227586]
CO2[413] tVOC[1] millis[1228581]
CO2[415] tVOC[2] millis[1229577]
CO2[415] tVOC[2] millis[1230572]
CO2[415] tVOC[2] millis[1231566]
CO2[415] tVOC[2] millis[1232562]
CO2[415] tVOC[2] millis[1233557]
CO2[415] tVOC[2] millis[1234552]
CO2[418] tVOC[2] millis[1235548]
CO2[418] tVOC[2] millis[1236533]
CO2[413] tVOC[1] millis[1237528]
CO2[408] tVOC[1] millis[1238523]
CO2[408] tVOC[1] millis[1239519]
CO2[408] tVOC[1] millis[1240513]
CO2[408] tVOC[1] millis[1241508]
CO2[408] tVOC[1] millis[1242504]
CO2[405] tVOC[0] millis[1243499]

 二酸化炭素濃度なので、息を吹きかけると下記のように値が上昇するのがわかります。

CO2[817] tVOC[63] millis[1319103]
CO2[905] tVOC[76] millis[1320087]
CO2[1090] tVOC[105] millis[1321082]
CO2[1044] tVOC[98] millis[1322078]
CO2[1380] tVOC[149] millis[1323073]
CO2[967] tVOC[86] millis[1324068]
CO2[1113] tVOC[108] millis[1325064]
CO2[1316] tVOC[139] millis[1326059]
CO2[844] tVOC[67] millis[1327054]
CO2[1095] tVOC[105] millis[1328050]
CO2[825] tVOC[64] millis[1329045]
CO2[1215] tVOC[124] millis[1330040]
CO2[1349] tVOC[144] millis[1331036]
CO2[770] tVOC[56] millis[1332031]
CO2[1380] tVOC[149] millis[1333026]

 一度電源をOFFにしてまた起動すると下記のように初回起動と同様の状態になったので、コンディショニングは起動時に毎回必要なようです。

CCS811 Basic Example
CO2[0] tVOC[0] millis[2017]
CO2[0] tVOC[0] millis[3011]
CO2[0] tVOC[0] millis[4006]
CO2[400] tVOC[0] millis[5002]
CO2[400] tVOC[0] millis[5998]
CO2[400] tVOC[0] millis[6982]
CO2[400] tVOC[0] millis[7977]
CO2[400] tVOC[0] millis[8972]
CO2[400] tVOC[0] millis[9967]
CO2[400] tVOC[0] millis[10962]
CO2[400] tVOC[0] millis[11957]
CO2[400] tVOC[0] millis[12952]
CO2[400] tVOC[0] millis[13947]
CO2[400] tVOC[0] millis[14943]
CO2[400] tVOC[0] millis[15937]
CO2[400] tVOC[0] millis[16932]
CO2[400] tVOC[0] millis[17928]

Interrupt と Wake を使ったサンプル

 もう1つのサンプルとして、 Interrupt と Wake を使った例を試してみます。 CCS811 ではセンサーをスリープさせることで消費電力を抑え、新しいデータが取得された時にスリープを解除して値を取得することができます。回路図としては下記のように、先程までのものに加えて NOT WAKE ピンと NOT INT ピンへの接続を追加しています。チュートリアルでは動作電圧が 5V のマイコンを使っているため、 NOT WAKE ピンへの接続は電圧が 3.3V 以下になるように抵抗の接続などで電圧を下げていますが、今回は Seeeduino を 3.3V で動作させているので、直接接続しています。

f:id:akanuma-hiroaki:20180610213603p:plain

 ファームウェアの実装としては下記のようになります。 setup() の中で myCCS811.begin() するのは先程と同じですが、 myCCS811.setDriveMode(2) でデータ取得間隔を 10 秒に変更しています。また、 myCCS811.enableInterrupts() でデータ取得時の Interrupt を有効にしています。 pinMode() で NOT INT ピンをインプット用、 NOT WAKE ピンをアウトプット用として設定し、 NOT WAKE ピンは High 、つまりスリープ状態にしておきます。

 新しいデータが取得された時は NOT INT ピンが Low になるので、 loop() の中では NOT INT ピンの状態をチェックし、 Low になった(新しいデータが取得された)時に NOT WAKE ピンを Low にしてスリープ状態を解除し、 myCCS811.readAlgorithmResults() でレベルを計算させてからそれぞれの値を取得しています。

 NOT WAKE ピンの状態の切り替えには 20μs〜50μs かかるので、切り替え時には 1ms の delay を挟んでいます。

#include <SparkFunCCS811.h>

#define CCS811_ADDR 0x5B

#define PIN_NOT_WAKE 5
#define PIN_NOT_INT 6

CCS811 myCCS811(CCS811_ADDR);

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("...");

  CCS811Core::status returnCode;
  
  returnCode = myCCS811.begin();
  Serial.print("CCS811 begin exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  returnCode = myCCS811.setDriveMode(2);
  Serial.print("Mode request exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_INT, INPUT_PULLUP);
  returnCode = myCCS811.enableInterrupts();
  Serial.print("Interrupt configuation exited with: ");
  printDriverError( returnCode );
  Serial.println();
  
  pinMode(PIN_NOT_WAKE, OUTPUT);
  digitalWrite(PIN_NOT_WAKE, 1);
}

void loop() {
  if (digitalRead(PIN_NOT_INT) == 0)
  {
    digitalWrite(PIN_NOT_WAKE, 0);
    delay(1);
    myCCS811.readAlgorithmResults();
    
    Serial.print("CO2[");
    Serial.print(myCCS811.getCO2());
    Serial.print("] tVOC[");
    Serial.print(myCCS811.getTVOC());
    Serial.print("] millis[");
    Serial.print(millis());
    Serial.print("]");
    Serial.println();
    
    digitalWrite(PIN_NOT_WAKE, 1);
    delay(1);
  }
}

void printDriverError( CCS811Core::status errorCode )
{
  switch ( errorCode )
  {
    case CCS811Core::SENSOR_SUCCESS:
      Serial.print("SUCCESS");
      break;
    case CCS811Core::SENSOR_ID_ERROR:
      Serial.print("ID_ERROR");
      break;
    case CCS811Core::SENSOR_I2C_ERROR:
      Serial.print("I2C_ERROR");
      break;
    case CCS811Core::SENSOR_INTERNAL_ERROR:
      Serial.print("INTERNAL_ERROR");
      break;
    case CCS811Core::SENSOR_GENERIC_ERROR:
      Serial.print("GENERIC_ERROR");
      break;
    default:
      Serial.print("Unspecified error.");
  }
}

 これを実行すると下記のように値が取得されます。 CO2 が 1,000 を超えている部分は息を吹きかけてみたところです。

...
CCS811 begin exited with: SUCCESS
Mode request exited with: SUCCESS
20
28
Interrupt configuation exited with: SUCCESS
CO2[0] tVOC[0] millis[11563]
CO2[0] tVOC[0] millis[21618]
CO2[0] tVOC[0] millis[31675]
CO2[400] tVOC[0] millis[41733]
CO2[400] tVOC[0] millis[51789]
CO2[402] tVOC[0] millis[61845]
CO2[407] tVOC[1] millis[71902]
CO2[407] tVOC[1] millis[81959]
CO2[402] tVOC[0] millis[92016]
CO2[410] tVOC[1] millis[102073]
CO2[1228] tVOC[126] millis[112129]
CO2[1118] tVOC[109] millis[122182]
CO2[1245] tVOC[128] millis[132231]

まとめ

 今回はとりあえず値を取得してみるところまででしたが、エージングやコンディショニングに時間がかかるのが少々ネックではあるものの、値の取得自体は簡単にできたので、次回は値が一定値を超えた場合に何かしらの処理をしたり、ネットワーク通信などを取り入れて、オフィスの二酸化炭素濃度が一定値を超えたら通知するような物を作ってみようかと思います。