CO2センサーT6703の値をAWS IoTに送る

CO2センサーMH-z19の値をAWS IoTに送る に続いてI2Cインタフェースを持つCO2センサー T6703 の値をAWS IoTに送るようにしてみた。

[2018年8月27日補足] Fritzing ファイルを追加しました。

ハードウェア

使用したハードウェアは以下の通り。

  • MH-ET Live Minikit ESP32
    • ESP32 ボード
    • WeMos D1 mini 互換のピン配列
  • WeMos D1 mini OLED Shield
    • CO2濃度などを表示するため
    • なくてもAWS IoTへのデータ送信には影響しない
  • WeMos D1 mini Dual Base
    • MH-ET Live Minikit ESP32 と CO2センサーボードを接続するため
    • 現在 Aliexpress の Lolin のショップで販売されている Dual Base は Dual Base V2.0.0 for LOLIN (WEMOS) D1 mini (Tripler Base と同様に、ユニバーサル基板のような穴とランドがある)であるが、WeMosのDual Base V1よりもシールド間の間隔が狭くなっていて、MH-ET Live Minikit ESP32と他のシールドを並べることが難しくなっている。
  • CO2センサーT6703 ボード
    • 後で説明する。

ハードウェア全体のブレッドボード図

T6703モジュールの上に配線があるなど実際とは異なる点がある。

図からはわかりづらいが、WeMos D1 mini Dual Base の上に、CO2センサーT6703 ボード (下図では左側)と MH-ET Live Minikit ESP32 が載っている。さらに、MH-ET Live Minikit ESP32 の上に WeMos D1 mini OLED Shield を載せている。

ハードウェア全体の回路図

Fritzingファイル

上記の図ではわかりづらい場合には、Fritzingのファイルをダウンロードして参照していただきたい。

CO2センサーT6703ボード

小型でI2Cで通信が可能なCO2センサー Amphenol Telaire T6703を利用したもの。

必要なパーツ

Amphenol Telaire T6703

非分散赤外線吸収法(Non-dispersive Infrared absorption method – NDIR)による小型な (30 mm X 15.6 mm X 8.6 mm) CO2センサーモジュール。

T6703 はシングルビーム(チャネル)の安価な(精度の低い)センサー。サイトによってはT6703の記載がないところがあるので、廃盤になっている可能性もある。

精度の高いものとしてT6713 がある。T6713は販売されている。

デュアルビームのT6715 も計画されているようであるが、販売はされていない模様。

I2Cインターフェスを有しており、ピン数の少ないESP8266などで複数のセンサーを利用する際には使いやすい。ESP32でも配線が少なくて済むのでUARTを利用する MH-z19 よりは便利だと思う。

後述の技術資料のマニュアルによれば、電源は5V (4.5V~5.5V)だが、インタフェースは3.3Vと5Vの両方に対応しているとのこと。3.3VインタフェースのESP32と接続する際にレベルコンバーターなしで良いことになる。

I2C通信を行うためには、ピン6をGNDに接続すれば良いようだが、マニュアルには10~100kΩでGNDにpullとも書いてある。ここに示すものでは、Frtizing の回路図通り GND とピン6を(抵抗を使うことなく)直接接続している。

データシート

データシートは Amphenol Advanced Sensors のサイトからアクセス可能。

https://www.amphenol-sensors.com/en/telaire/co2/525-co2-sensor-modules/3215-t6700

サンプルコード

サンプルコードは次のGitHubからアクセス可能。

Arduino Code to communicate and measure AAS Telaire Sensors

https://github.com/AmphenolAdvancedSensors/Telaire/tree/T6700_Series

技術資料

技術資料は、なぜかしらAmphenol のサイトからはアクセスできない。

CO2Meter.com のサイトからの方が詳細な情報が得られる。t6713とt6703は精度などを除けば同じもののようである。

https://www.co2meter.com/collections/0-1-co2/products/t6713-miniature-co2-sensor

マニュアルは http://www.co2meters.com/Documentation/Manuals/Manual-AMP-0002-T6713-Sensor.pdf でアクセスできる。

もしくは、Mouserのサイトから以下の資料にアクセスできる。

https://www.mouser.com/catalog/additional/Telaire_T6700_Series_CO2_sensor_Module_Application_Note.pdf

T6703ボードのブレッドボード図

(ブレッドボードは使っていないが、ブレッドボード図とする)

配線がT6703の上に表示されていたり、T6703と配線の結合箇所が同じなど、実際とは異なるが、線の対応は以下の通り。

実際には、配線はPrototype board上にあり、一番下の列には6ピンソケットがある(配線は下から2列目の穴を通して、裏面でピンソケットの端子に接続)。

T6703モジュールには6ピンヘッダが下向きに取り付けてあり、6ピンソケットの上で接続している。

T6703ボードの回路図

ソフトウェア

センサーの操作部分以外は基本的に、CO2センサーMH-z19の値をAWS IoTに送る で利用したソフトウェアと同じ。以下では異なるファイルのみ示す。

開発環境は、PlatformIO IDE for VSCode である。開発環境については必要に応じて、PlatformIO IDE for VSCode でESP32のプログラム開発 を参照してください。

main.cpp

下記でハイライトしてある行が CO2センサーMH-z19の値をAWS IoTに送る で利用したコードと異なる点である。

#include <Arduino.h>
#include <Stream.h>
#include <ArduinoLog.h> // https://github.com/thijse/Arduino-Log/
#include <ArduinoJson.h>

#define LOGLEVEL LOG_LEVEL_NOTICE
//#define LOGLEVEL LOG_LEVEL_WARNING

#define USE_HARD_RESET 1

#define OLED_DISPLAY_ARGS "CO2:\n %d", (int)jsonObject["CO2"]

#define DELAY_INTERVAL (15 * 1000)
#define RETRY_INTERVAL (1 * 1000)

#define MAX_JSON_SIZE 512
#define MAX_DEVICE_NAME 128

#ifdef ARDUINO_ESP8266_WEMOS_D1MINI
#define STOP_WIFI 1
void nothing(...)
{
}

int nothingJson(JsonObject &jsonObject)
{
}

#define publishMessage(arg) nothing(arg)
#define publishJson(arg) nothingJson(arg)
#define setupWiFi() nothing()
#define setupMQTT() nothing()
#endif

#ifndef STOP_WIFI
extern void setupWiFi();
extern void setupMQTT();
extern void loopClient();
extern int loopMQTT();
extern int publishJson(JsonObject &jsonObject);
#endif // STOP_WIFI

#ifdef USE_HARD_RESET
// wiring is required to use hardReset()
extern void hardReset();
#endif // USE_HARD_RESET
extern void setupHardReset();

extern void stopWiFi();

extern void setupT6703(char deviceNameBuffer[]);
extern int updateT6703(JsonObject &jsonObject);

extern void setupOLED(int size);
extern void displayOLED(const char *format, ...);

extern void setEventHandler(void (*handler)(JsonObject &objec));
extern void setResetFunc(void (*func)(void));

char  deviceNameBuffer[MAX_DEVICE_NAME];


void resetESP()
{
#ifdef ESP8266
  ESP.reset();
#endif // ESP8266
#ifdef ESP32
  ESP.restart();
#endif // ESP32
}
void softReset()
{
  StaticJsonBuffer<MAX_JSON_SIZE> errorJsonBuffer;
  JsonObject &errorJsonObject = errorJsonBuffer.createObject();

  Log.notice("softReset()\n");
  errorJsonObject["Reset"] = "soft";
  publishJson(errorJsonObject);
  resetESP();
}

void controlEventHandler(JsonObject &object)
{
  Log.notice(">controlEventHandler\n");

  const char *resetMethod = (const char *)object["Reset"];

  if (resetMethod != NULL)
  {
    if (strcmp(resetMethod, "hard") == 0)
    {
      Log.notice("hardReset\n");
#ifdef USE_HARD_RESET
      hardReset();
#endif // USE_HARD_RESET
    }
    else if (strcmp(resetMethod, "soft") == 0)
    {
      Log.notice("softReset\n");
      softReset();
    }
  }
  Log.notice("<controlEventHandler\n");
}

void setupDevices(char deviceNameBuffer[])
{
  setupT6703(deviceNameBuffer);
}

char* getDeviceName()
{
  return deviceNameBuffer;
}

void setup()
{
#ifdef STOP_WIFI
  stopWiFi();
#endif // STOP_WIFI

  Serial.begin(115200);
  Log.begin(LOGLEVEL, &Serial);
  delay(2000);

#ifdef USE_HARD_RESET
  setupHardReset();
#else
  setResetFunc(softReset);
#endif  // USE_HARD_RESET
  setupOLED(1); // USE_SPARKFUN_LIBRARIES medium (6 columns, 3 rows worth of characters)
  displayOLED("OLED initialized.");

  setupDevices(deviceNameBuffer);
  displayOLED("Device setup done.");

#ifndef STOP_WIFI
  setupWiFi();
  displayOLED("WiFi setup done.");
  
  setupMQTT();
  displayOLED("MQTT setup done.");

  setEventHandler(controlEventHandler);
  setResetFunc(softReset);
#endif

  delay(5000);
}

unsigned long lasttime;

void loop()
{
  unsigned long time = millis();

  if ((time - lasttime) < DELAY_INTERVAL)
  {
#ifndef STOP_WIFI
    loopClient();
#endif
    delay(1000);
    return;
  }
  else
  {
    lasttime = time;
  }
  Log.notice("freesize=%d\n", ESP.getFreeHeap());

  StaticJsonBuffer<MAX_JSON_SIZE> jsonBuffer;
  JsonObject &jsonObject = jsonBuffer.createObject();

  // update
  while (updateT6703(jsonObject) != 0)
  {
    delay(RETRY_INTERVAL);
  }
  displayOLED(OLED_DISPLAY_ARGS);

#ifndef STOP_WIFI
  // MQTT
  int rc;
  rc = loopMQTT();
  if (rc == 0)
  {
    publishJson(jsonObject);
  }
  else
  {
    displayOLED("MQTT error=%d", rc);
    Log.error("MQTT error=%d\n", rc);
  }
#endif // STOP_WIFI
}

t6703-i2c.cpp

T6703のI2C制御を行う。

また、取得したCO2のデータを 関数 updateT6703 でArduinoJsonのオブジェクトに設定する。

  • int readCO2()
    • CO2濃度(ppm)を取得する
  • void getT6700status()
    • 状態を取得して、正常時以外にはLog.errorで状態を表示する
  • void setT6700ABCLogic()
    • 自動較正を有効にする
  • void startT6700SinglePointCalibration()
    • 手動較正を行う
  • void setupT6703(char deviceNameBuffer[])
    • T6703の初期化を行う
  • int updateT6703(JsonObject &jsonObject)
    • T6703からCO2濃度を取得し、jsonObjectに結果を格納する
#include <ArduinoLog.h>
#include <ArduinoJson.h>

#include <Wire.h>

#define ADDR_6700 0x15 // default I2C slave address
#define DEVICE_NAME "T6703"

#define T6703_DATA_SIZE 4
#define T6703_STATUS_SIZE 4
#define T6703_REPORT_SIZE 5

#define MINIMUM_UPDATE_INTERVAL 5000

#define MAX_DATA_ERRORS 10 // max of errors, reset after them
/*
#define STARTUP_DELAY 3000  // start up delay (ms)
#define RESET_DELAY   2000  // delay before reset (ms)
*/

extern void setupDeviceName(char deviceNameBuffer[], const char *deviceName);

byte T6703Error;
byte T6703Connected = 0;

// read CO2 ppm value
int readCO2()
{
    int ppm;
    byte data[T6703_DATA_SIZE];

    byte command[] = { 0x04, 0x13, 0x8B, 0x00, 0x01};

    // start I2C
    Wire.writeTransmission(ADDR_6700, command, sizeof(command));

    // read report of current gas measurement in ppm
    delay(1);
    Wire.requestFrom(ADDR_6700, T6703_DATA_SIZE); // request 4 bytes from slave device
    delay(10);
    data[0] = Wire.read();
    data[1] = Wire.read();
    data[2] = Wire.read();
    data[3] = Wire.read();

    ppm = ((data[2] & 0x3F) << 8) | (data[3] & 0xff);
    return ppm;
}

void getT6700status()
{
    byte result[T6703_STATUS_SIZE];

    byte command[] = { 0x04, 0x13, 0x8A, 0x00, 0x01};

    // start I2C
    Wire.writeTransmission(ADDR_6700, command, sizeof(command));

    // read report of current gas measurement in ppm
    delay(1);
    Wire.requestFrom(ADDR_6700, T6703_STATUS_SIZE); // request 4 bytes from slave device
    result[0] = Wire.read();
    result[1] = Wire.read();
    result[2] = Wire.read();
    result[3] = Wire.read();

    unsigned int status = ((result[2] & 0x3F) << 8) | (result[3] & 0xff);

    if (status)
    {
        Log.error("getT6700status: 0x%x", status);
        Log.error("%s %s %s %s %s %s %s %s\n",
                  ((status & 0x0001) ? "Error codition" : ""),
                  ((status & 0x0002) ? "Flash error" : ""),
                  ((status & 0x0004) ? "Calibration error" : ""),
                  ((status & 0x0100) ? "RS-232" : ""),
                  ((status & 0x0200) ? "RS-485" : ""),
                  ((status & 0x0400) ? "I2C" : ""),
                  ((status & 0x0800) ? "Warm-up" : ""),
                  ((status & 0x8000) ? "Single point calibration" : ""));
    }
}

void setT6700ABCLogic()
{
    byte report[T6703_REPORT_SIZE];

    byte command[] = { 0x05, 0x03, 0xEE, 0xFF, 0x00};

    // start I2C
    Wire.writeTransmission(ADDR_6700, command, sizeof(command));

    // read report
    delay(1);
    Wire.requestFrom(ADDR_6700, T6703_REPORT_SIZE); // request 5 bytes from slave device
    report[0] = Wire.read();
    report[1] = Wire.read();
    report[2] = Wire.read();
    report[3] = Wire.read();
    report[4] = Wire.read();

    int func_code = report[0];
    int byte_count = report[1];

    Log.notice("setT6700ABCLogic func code: 0x%x, byte count: 0x%x, MSB: 0x%x, LSB: 0x%x\n",
               func_code, byte_count, report[2], report[3]);
}

void startT6700SinglePointCalibration()
{
    byte data[T6703_DATA_SIZE];

    byte command[] = { 0x05, 0x03, 0xEC, 0xFF, 0x00};

    // start I2C
    Wire.writeTransmission(ADDR_6700, command, sizeof(command));

    // read report
    delay(1);
    Wire.requestFrom(ADDR_6700, 6); // request 4 bytes from slave device
    data[0] = Wire.read();
    data[1] = Wire.read();
    data[2] = Wire.read();
    data[3] = Wire.read();

    int func_code = data[0];
    int byte_count = data[1];

    Log.notice("startT6700SinglePointCalibration func code: 0x%x, byte count: 0x%x, MSB: 0x%x, LSB: 0x%x\n",
               func_code, byte_count, data[2], data[3]);
}

void setupT6703(char deviceNameBuffer[])
{
    Log.notice("+setupT6703\n");

    // set device name
    setupDeviceName(deviceNameBuffer, DEVICE_NAME);

    Wire.beginTransmission(ADDR_6700); // check if T6700 series is present
    T6703Error = Wire.endTransmission();
    if (T6703Error == 0)
    {
        Log.notice("T6700 Detected");
        T6703Connected = 1;
    }
    else
    {
        Log.error("T6703Error=0x%x", T6703Error);
    }
    getT6700status();

    // set ABC logic
    setT6700ABCLogic();

    Log.notice("-setupT6703\n");
}

long previousMillis = 0;
int errorCount = 0;

int updateT6703(JsonObject &jsonObject)
{
    long currentMillis = millis();
    if (currentMillis - previousMillis < MINIMUM_UPDATE_INTERVAL)
        return -1;
    previousMillis = currentMillis;

    getT6700status();

    int ppm = readCO2();
    Log.notice("PPM = %d\n", ppm);

    jsonObject["CO2"] = ppm;

    return 0;
}

実行結果

T6703の測定結果が安定するまでには長い時間が必要なことがある。

自動較正の Automatic Background Logic (ABC Logic)を上記のプログラムの setT6700ABCLogic を呼び出して有効化しても、最初に通電してからは数日の単位で待たないと、換気した部屋でもっともらしい値(400ppm~1000ppm)にならないことがあった。安定するまでは、2桁の数値や5桁の数値が得られることもあった。

実行時の風景

T6703の内部が定期的に光っていることがわかる。

AWS IoT コンソールでのメッセージ

AWS IoT Consoleのテストで iot/[MACアドレス]/update を subscribe すると、ESP32からのMQTTメッセージを正しく受信できていれば、次のように確認できる。

電源に関する失敗

最初テストしている際に、Brownout detector was triggered メッセージが出てハード的にリセットされることが頻繁に起きた。T6703の電源には5Vを接続する必要があったが、3.3Vに誤接続をしているためであった。Brownout が発生したのは 3.3Vの電源容量不足と思われる。

MH-ET Live ESP32 Minikitでは、ME6211-3.3Vがレギュレータとして使われているが、最大出力電流が500mAになっている。T6703はピーク電流が200mAとなっている。WiFi通信時にはESP32の消費電力が大きくなるので、Brownout が発生するようだ。

T6703の電源電圧が4.5~5.5Vと書いてあり、電源を5Vに接続することでBrownout は発生しなくなったが、3.3Vで消費電力の大きなものを接続する場合には注意が必要である。T6703のインタフェースは5Vでも3.3Vでも動作するとのことなので、I2C接続時にレベルコンバータなどは必要ない。

補助電源としてLolin(WeMos)のBattery Shield  (最大1A) やDC Power Shield (最大1A)などを使うことで5Vの出力電流を増やすことは可能だが、3.3Vの出力電流は増えない。3.3Vの電源容量が不足する場合には追加デバイスごとに3.3Vに変換するレギュレータを追加する必要がある。