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に送る で利用したコードと異なる点である。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
#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に結果を格納する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
#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に変換するレギュレータを追加する必要がある。