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を利用したもの。
必要なパーツ
- WeMos D1 mini ProtoBoard Shield
- T6703との接続のため
- CO2センサー T6703 モジュール
- Telaire T6703
- Aliexpressで購入
- 6pin ピンヘッダ
- T6703モジュールに取り付ける
- 6pin ピンソケット
- CO2センサー T6703とPrototype Shieldを接続するため。ProtoBoard Shieldに取り付ける。
- 配線材料
- 少々
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のサイトから以下の資料にアクセスできる。
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に変換するレギュレータを追加する必要がある。