CCS811でeCO2(二酸化炭素)とVOC(揮発性有機化合物)の測定 | そう備忘録

CCS811でeCO2(二酸化炭素)とVOC(揮発性有機化合物)の測定

CCS811

ESP32 と CCS811 モジュールでeCO2(二酸化炭素・同等)とTVOC(総揮発性有機化合物)を測定してOLEDディスプレイに表示した時の事を記事にしておく。

CCS811 モジュールは自分はスイッチサイエンス社の製品を購入したがアマゾン等で同様の製品を購入する事もできる。

eCO2とは

CCS811 で測定可能な CO2(二酸化炭素)は eCO2(equivalent CO2)と呼ばれるもので CO2 と全く同じものでは無い(equivalent:同等)

こちらのサイトに以下の記述がある。

“It’s important to recognize that the CCS811 cannot measure CO2 and that the “equivalent CO2″ being reported by the CCS811 has nothing to do with actual CO2 present in the area.”

→ CCS811 は CO2 を測定できないので、CCS811が報告している「同等CO2」は、その地域に存在する実際のCO2とは関係がないことを認識することが重要です。(DeepLによる翻訳)

試しにマジックペンのキャップをあけてセンサーに近づけた所、市販の CO2測定器(NDIR方式で1.8万円程度の製品)の CO2 濃度は変化しなかったが CCS811 は eCO2 の値が上昇した。

周辺にガスが発生していない状態で測定をすると市販の CO2 測定器と同等の数値を示したので、”周囲にガスがない” 状態であれば CO2 の測定として使えるが、CCS811 の eCO2 は揮発性のガスにも反応して数値が上昇する様だ。

ただ「室内の換気が必要か、否か」の視点で見ると eCO2 でも問題が無く、むしろ二酸化炭素の他に揮発性のガスにも反応してくれる分、使い勝手は良いのかも知れない。

純粋に CO2 濃度だけを測定したいのであれば以前の記事に書いた MH-Z19C(NDIR方式)の方が良いと思う。

TVOCとは

TVOC(Total Volatile Organic Compounds)はで大気中で気体になる有機化合物(化学物質)の総称だ。

特に明確な定義がある訳では無いが、塗料やインク、ガソリンなどの気体に反応して数値が上昇したので “空気の汚れ” を検知する指標としては有効だと思う。

スペック

主なスペックは以下の通り。

  • eCO2:400 〜 8,192 ppm(100万分の1)
  • TVOC(総揮発性有機化合物): 0 〜 1,187 ppb(10億分の1)
  • I2C 通信:アドレスは 0x5A or 0x5B
  • 電圧:3.3V
  • コンディショニング時間:起動後、20分以上経過すると有効な数値が出力される

外観

ピンは上から、

  • RST:ハードウェアリセットに使用する
  • INT:測定終了やしきい値を超えた時に出力(今回未使用)
  • WAK:省電力の為の起動/休止の切り替えに使用する。High(休止)、Low(起動)
  • SCL:SCL
  • SDA:SDA
  • 3.3V:ESP32 より電源供給
  • GND:GND

中央下に NTCサーミスタ接続用の端子がある(後述)

CCS811 表面

裏面の画像は以下の通り。

中央やや下にあるのが I2Cアドレス制御用の端子で、開放で 0x5B(デフォルト)、短絡で 0x5A と I2C アドレスを変更できる。

中央下にあるのがプルアップ抵抗で通常は短絡のままでOK。

開放するとプルアップ抵抗が無効になる。

CCS811 裏面

NTCサーミスタ

尚、オプションでNTCサーミスタをはんだ付けで追加して周辺の温度でガスの濃度を補正する機能があるようだが、現在は温度保証はサポートされなくなった様だ(コネクタは残っている)

NTC(Negative Thermal Coefficient)では無く、BME280 などの外部センサーを推奨するとあった。

ベースライン適用時間

センサーの製造上のばらつきや経時変化に対応して抵抗値を自動で補正する機能がある。

24時間で補正するとの事なので、前述のコンディショニング時間(20分)の他に24時間以上経過後以降に補正された正しい値が出力されてくると考えたほうが良いようだ。

その他のパーツ

その他、用意した主なパーツは以下の通り。

ESP32モジュール

ESP32 を使ったモジュールはスイッチサイエンスや秋月電子などから色々な製品が発売されているが、今回使用した ESP-WROOM-32 のモジュールは以下の製品だ。

ピン配置はESP32 DEVKIT V1(30ピン)のピン配置となっている。

OLEDモジュール

0.96 インチの OLED(Organic Light Emitting Diode)のディスプレイを用意した。

主なスペックは以下の通り。

  • サイズ:0.96 インチ
  • 電圧:3.3V ~ 5V(3.3V で問題なく動作した)
  • 解像度:128 × 64
  • I2C 接続:0x3C(デフォルト) or 0x3D
  • SSD1306 互換

ライブラリーのインストール

開発環境は Arduino IDE(Ver 1.8.19)で行った。

事前に必要なライブラリーのインストールを行う。

CCS811

sparkfun の Github よりライブラリーをダウンロードしてArduino IDE からスケッチー>ライブラリーをインクルードー>.ZIP形式のライブラリーインストールでライブラリーを追加する。

SSD1306

OLED ディスプレイの SSD1306ライブラリーをインストールする。

Arduino IDE からツールー>ライブラリーを管理で検索欄で “SSD1306” で検索をして Adafruit SSD1306 をインストールした。

Adafruit SSD1306

配線図

配線は以下の通り。

INT は今回は未接続、RST と WAK は使用したが、省電力を考慮せずに単に値を取得して表示するだけであれば、接続は必須では無い。

GNDと3.3V、SDA、SCLの接続だけで値を取得する事は可能だ。

CCS811 配線図

スケッチ

約 60秒間隔で eCO2 と TVOC を測定してディスプレイに表示するスケッチだ。

ccs811_oled.ino

/*
 * Created on Sun Feb 27 21:46:24 2022
 *  
 * CCS811 Co2 Sensor & OLED Display
 * 
 * @author: Souichirou Kikuchi
 */
 
#include <Wire.h> // I2C
#include <SparkFunCCS811.h> // CCS811
#include <Adafruit_SSD1306.h> // OLEDディスプレイ

#define CCS811_ADDRESS 0x5B // CCS811 I2C Address
#define SCREEN_ADDRESS 0x3C // OLED Display Address

// I2C Pin
constexpr short int SDA_PIN = 21;
constexpr short int SCL_PIN = 22;

constexpr short int RESET_PIN = 5;
constexpr short int WAKE_UP_PIN = 4;

// OLED Display
constexpr short int SCREEN_WIDTH = 128; // OLED display width, in pixels
constexpr short int SCREEN_HEIGHT = 64; // OLED display height, in pixels
constexpr short int OLED_RESET = 4; // Reset pin # (or -1 if sharing Arduino reset pin)

CCS811 eco2_sensor(CCS811_ADDRESS); // eCo2 Sensor 
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // OLEDディスプレイ

void ccs811_hw_reset() { // CCS811 Hardware Reset
    pinMode(RESET_PIN, OUTPUT);
    digitalWrite(RESET_PIN, LOW);
    delay(10);
    digitalWrite(RESET_PIN, HIGH);
}

void ccs811_wake() { // Wake up
    digitalWrite(WAKE_UP_PIN, LOW);
    delay(10);
}

void ccs811_sleep() { // Sleep 消費電力を下げる
    digitalWrite(WAKE_UP_PIN, HIGH);
}

void setup()
{
    int cnt; // 接続試行回数
    const short int MAX_RETRY = 10; // 最大リトライ回数

    Serial.begin(115200);
    Serial.println("Program Start");

    pinMode(WAKE_UP_PIN, OUTPUT);
    ccs811_hw_reset();
    ccs811_wake();

    // I2C
    Wire.begin(SDA_PIN, SCL_PIN);

    // CCS811初期化
    cnt = 0; // 接続試行回数
    while ((eco2_sensor.begin() == false) and (cnt < MAX_RETRY)) { // MAX_RETRY回数接続をリトライする 
        Serial.print("CCS811 initialize attempt");
        Serial.println(cnt);
        delay(1000);
        cnt++;
    }
    if (cnt >= MAX_RETRY){ // 接続回数オーバー
        Serial.println("CCS811 initialize Error.");
    } else {
        Serial.println("CCS811 Initialized.");
    }
    ccs811_sleep();

    // OLEDディスプレイ初期化
    if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
        Serial.println(F("SSD1306 allocation failed"));
    }
    // OLEDディスプレイ設定
    display.setTextSize(1); // size 1~5
    display.setTextColor(SSD1306_WHITE);
    display.display(); // 初期表示はAdafruitのスプラッシュロゴ
    delay(10000);
}

void loop()
{
    short int co2_ppm;
    short int voc_ppb;
    int co2_ttl; // 合計計算用
    int voc_ttl;
    std::vector<int> co2List;
    std::vector<int> vocList;
    constexpr short int DATA_COUNT = 5; // データ取得件数

    ccs811_wake(); // CCS811起動
    co2List.clear(); // リストクリア
    vocList.clear();
    // DATA_COUNT数だけ取得して最大値と最小値は切り捨てて平均を取得する
    if (eco2_sensor.dataAvailable()) {
        for (int i = 0; i < DATA_COUNT; i++) { // データの数だけ繰り返す
            eco2_sensor.readAlgorithmResults();
            co2List.push_back(eco2_sensor.getCO2());
            vocList.push_back(eco2_sensor.getTVOC());
            delay(1000);
        }
    } else {
        Serial.println("CCS811 not Available");
    }
    ccs811_sleep(); // CCS811休止(省電力)

    std::sort(co2List.begin(), co2List.end()); // sortする
    std::sort(vocList.begin(), vocList.end());
    co2List.pop_back(); // 末尾(最大値)のデータを削除 
    vocList.pop_back();
    co2List.erase(co2List.begin()); // 先頭(最小値)の行を削除
    vocList.erase(vocList.begin());

    // 最大値、最小値を除いた値の平均を求める
    co2_ttl = 0;
    voc_ttl = 0;
    // for (int i = 0; i < DATA_COUNT-2; i++) {
    for (int i = 0; i < co2List.size(); i++) {

        Serial.println(co2List[i]);
        
        co2_ttl += co2List[i];
        voc_ttl += vocList[i];
    }
    co2_ppm = co2_ttl / co2List.size();
    voc_ppb = voc_ttl / vocList.size();

    display.clearDisplay(); // Buffer Clear
    display.setCursor(0, 0); // X Y
    display.println("eCO2:" + String(co2_ppm) + " ppm");
    display.setCursor(0, 30); // X Y
    display.println("VOC:" + String(voc_ppb) + " ppb");
    display.display();

    delay(60000); // 60秒
}

補足説明

コード中にコメントを入れているが簡単に補足をしておく。

ccs811_hw_reset

CCS811 をハードウェアリセットする関数。

リセットピンに対して Low、High を送ってリセットしている。

ccs811_wake

CCS811 を起動する関数。

Wake up ピンに対して Low を送っている。

ccs811_sleep

CCS811 を省電力モードにする関数。

Wake up ピンに対して High を送っている。

setup

初期処理。

CCS811 のハードウェアリセットや初期化、OLED ディスプレイの初期化などを行っている。

loop

CCS811 だが1秒毎に連続して値を取得してみた所、毎回若干の差異がある事が分かった。

また急に大きな測定値が検出される事があり、その1秒後には元の数値前後に戻る事も確認された。

例) 447 → 449 → 680 → 445 → 450 ・・・

この為、1回の測定だけで、その値を正とするのは問題があると考え、何回か連続して値を取得して最大値と最小値を切り捨てて、切り捨てられた値以外の平均をとることしている。

プログラムでは 5回連続で値を取得して最大と最小を切り捨てているので中間の 3件のデータの平均となっている。

恐らくもう少しスマートな書き方があるとは思うが C++ はイマイチ分かっていないので、大目に見て欲しい。

実行の様子

実行の様子は以下の通り。

60 秒間隔で eCo2 濃度と TVOC を測定してディスプレイに表示している。

実行結果

以上で今回の記事は終了とする。

最後に

この記事が何処かで誰かの役に立つことを願っている。

尚、当記事中の商品へのリンクはAmazonアソシエイトへのリンクが含まれています。Amazonのアソシエイトとして、当メディアは適格販売により収入を得ていますのでご了承ください。

souichirou

やった事を忘れない為の備忘録 同じような事をやりたい人の参考になればと思ってブログにしてます。 主にレゴ、AWS(Amazon Web Services)、WordPress、Deep Learning、RaspberryPiに関するブログを書いています。 仕事では工場に協働ロボットの導入や中小企業へのAI/IoT導入のアドバイザーをやっています。 2019年7月にJDLA(一般社団法人 日本デイープラーニング協会)Deep Learning for GENERALに合格しました。 質問は記事一番下にあるコメントかメニュー上部の問い合わせからお願いします。

質問やコメントや励ましの言葉などを残す

名前、メール、サイト欄は任意です。
またメールアドレスは公開されません。