Aliexpress に チープな CO2センサーのお勧めが出てきたので、買って試してみた。NDIR デュアルチャンネル CO2センサーが安く販売されていた。以前、CM1106 と思われるチープなCO2センサーを売っていた販売店である。
販売ページを見ると、ジャンク品と思われるような、酷く汚れていたり、ピンが曲がったりしている写真が載っていて動作するかはっきりしない感じであった。1個 約7ドルで、2個で送料が4ドル弱だったので動作すれば儲けものと思って買って試してみた。販売ページには、前回と異なり、型番がCM1107 と書かれていた。
目次
届いたセンサー
届いたセンサーは、小さな傷などはあるが、販売ページの写真ほどひどくはない外観で、ピンも曲がっているところはなかった。
以下の写真の上段が以前購入したCM1106と思われるセンサーで、下段が今回のCM1107である。写真では照明の関係でCM1107は暗くなっているが、CM1106と同じ金属メッキがされている。
動作確認
動作させるために「チープなCO2センサー」で作った M5Atom のものを使おうとしたが、センサーの厚さが以前のCM1106よりも大きく、ATOM Hub プロトキットには収まらなかった。
とりあえず、UARTでの動作だけを確認すると、Netatmo が 648 ppm の際に 621 ppm を示しており、正常に動作しているように思えた。
M5Stack Base26 Proto
ATOM Hub プロトキットには納まらなかったので、「M5Stackでのオゾン濃度測定」と同様に M5Stack Gray と M5Stack Base 26 Proto を使って収めることにした。
ピンの対応
ピンは以下のように対応させた。 CM1107の R/T 端子からGNDの途中に、ジャンパーピンを入れて、UARTとI2C を切り替えて試せるようにした。
CM1107 CON5 (5pin側) | M5Stack-Gray |
pin 1 – 3.3V | open |
pin 2 – RX/SDA | GPIO16 – RXD2 |
pin 3 – TX/SCL | GPIO17 – TXD2 |
pin 4 – R/T | GND for I2C, open for UART |
pin 5 – CA | open |
CM1107 CON4 (4pin側) | M5Stack-Gray |
pin 1 – 5V | 5v |
pin 2 – GND | GND |
pin 3 – Alarming | open |
pin 4 – PWM | open |
基板
M5Stack プロトタイプ基板に通常のピンソケットを使ったところ、CM1107センサーの厚みのために、うまく納まらなかった。少し薄い CM1106 を試したところ、CM1106は大丈夫であった。
ピンソケットにlow profileのものを使ったプロトボードを作ったところ、CM1107もBase26 Protoに納まるようになった。次に作成したボードの写真を示す。
動作確認プログラム
CUBIC CM1106 I2C library を利用して I2C での動作確認をしたところ、CM1107は、I2Cで動作することがわかった。
ちなみに、先日買ったチープで型番が明示されていなかったCM1106 と思われるセンサーはI2Cでは動作しなかったが、念のために今回のボードで動作するか調べてみた。やはり、動作しなかった。そもそもI2Cスキャナーのプログラムで調べてもセンサーが見つからない。
開発ツールには、PlatformIO IDE for VSCode を利用した。
CM1106_I2C.ino
ライブラリ CUBIC CM1106 I2C library のサンプルプログラムを基にして TFT_Terminal.h を使って、表示するようにした。
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 |
#include <M5Stack.h> #include <cm1106_i2c.h> #include "TFT_Terminal.h" int bus = 0; int scl = 16; int sda = 17; CM1106_I2C cm1106_i2c; TwoWire i2cPort(bus); #define CM1107 void setup() { // void M5Stack::begin(bool LCDEnable, bool SDEnable, bool SerialEnable, bool I2CEnable) M5.begin(true, false, true, true); termInit("CO2: CM1107 I2C"); i2cPort.begin(sda, scl); cm1106_i2c.begin(i2cPort); Serial.begin(115200); delay(1000); cm1106_i2c.read_serial_number(); delay(1000); cm1106_i2c.check_sw_version(); delay(1000); Serial.println("setup done."); } void loop() { uint8_t ret = cm1106_i2c.measure_result(); Serial.print("loop ret="); Serial.println(ret); if (ret == 0) { Serial.print("Co2 : "); Serial.println(cm1106_i2c.co2); Serial.println("Status >>"); char buf[256]; long now = millis(); long sec = now / 1000; sprintf(buf, "CO2: %d ppm, at %d\n\r", cm1106_i2c.co2, sec); termPrintString(buf); #if defined(CM1107) if (cm1106_i2c.status & (1 << CM1106_I2C_STATUS_CM1107_PREHEATING)) { Serial.println("Preheating"); } else { Serial.println("Preheat complete"); } if (cm1106_i2c.status & (1 << CM1106_I2C_STATUS_CM1107_OPERATING_NORMAL)) { Serial.println("Sensor Error"); } else { Serial.println("Operating normal"); } if (cm1106_i2c.status & (1 << CM1106_I2C_STATUS_CM1107_OVER_MEASUREMENT_RANGE)) { Serial.println("Over Measurement Range"); } else if (cm1106_i2c.status & (1 << CM1106_I2C_STATUS_CM1107_LESS_THAN_MEASUREMENT_RANGE)) { Serial.println("Less than Measurement Range"); } else { Serial.println("Normal Measurement Range"); } if (cm1106_i2c.status & (1 << CM1106_I2C_STATUS_CM1107_CALIBRATED)) { Serial.println("Non-calibrated"); } else { Serial.println("Calibrated"); } if (cm1106_i2c.status & (1 << CM1106_I2C_STATUS_CM1107_LIGHT_AGING)) { Serial.println("Light Aging"); } else { Serial.println("Light Normal"); } if (cm1106_i2c.status & (1 << CM1106_I2C_STATUS_CM1107_DRIFT)) { Serial.println("Drift"); } else { Serial.println("Non-Drift"); } #else switch (cm1106_i2c.status) { case CM1106_I2C_STATUS_PREHEATING: { Serial.println("Preheating"); break; } case CM1106_I2C_STATUS_NORMAL_OPERATION: { Serial.println("Normal operation"); break; } case CM1106_I2C_STATUS_OPERATING_TROUBLE: { Serial.println("Operating trouble"); break; } case CM1106_I2C_STATUS_OUT_OF_FS: { Serial.println("Out of FS"); break; } case CM1106_I2C_STATUS_NON_CALIBRATED: { Serial.println("Non calibrated"); break; } } #endif } delay(5000); } |
TFT_Terminal.h
「M5Stackでのオゾン濃度測定」のTFT_Terminal.cpp で外部から利用する関数の宣言を TFT_Terminal.h としてまとめた。
1 2 3 |
extern void termPutchar(char data); extern void termPrintString(const char *str); extern void termInit(const char *title); |
TFT_Terminal.cpp
「M5Stackでのオゾン濃度測定」のTFT_Terminal.cppと基本的には同じ。
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 |
/************************************************************* This sketch implements a simple serial receive terminal program for monitoring serial debug messages from another board. Connect GND to target board GND Connect RX line to TX line of target board Make sure the target and terminal have the same baud rate and serial stettings! The sketch works with the ILI9341 TFT 240x320 display and the called up libraries. The sketch uses the hardware scrolling feature of the display. Modification of this sketch may lead to problems unless the ILI9341 data sheet has been understood! Updated by Bodmer 21/12/16 for TFT_eSPI library: https://github.com/Bodmer/TFT_eSPI BSD license applies, all text above must be included in any redistribution *************************************************************/ /* This file is based on https://github.com/m5stack/M5Stack/blob/master/examples/Advanced/Display/TFT_Terminal/TFT_Terminal.ino This file is modified by epi */ #include <M5Stack.h> #include "TFT_Terminal.h" // The scrolling area must be a integral multiple of TEXT_HEIGHT #define TEXT_HEIGHT 24 // Height of text to be printed and scrolled #define TOP_FIXED_AREA 24 // Number of lines in top fixed area (lines counted from top of screen) #define BOT_FIXED_AREA 0 // Number of lines in bottom fixed area (lines counted from bottom of screen) #define YMAX 240 // Bottom of screen area #define XMAX 320 // The initial y coordinate of the top of the scrolling area uint16_t yStart = TOP_FIXED_AREA; // yArea must be a integral multiple of TEXT_HEIGHT uint16_t yArea = YMAX - TOP_FIXED_AREA - BOT_FIXED_AREA; // The initial y coordinate of the top of the bottom text line uint16_t yDraw = YMAX - BOT_FIXED_AREA - TEXT_HEIGHT; //uint16_t yDraw = 0; // Keep track of the drawing x coordinate uint16_t xPos = 0; // For the byte we read from the serial port byte data = 0; // A few test variables used during debugging boolean change_colour = 1; boolean selected = 1; // font type // 1: 8 pixel ASCII // 2: 16 pixel ASCII // 4: 24 pixel ASCII uint16_t fontType = 4; // We have to blank the top line each time the display is scrolled, but this takes up to 13 milliseconds // for a full width line, meanwhile the serial buffer may be filling... and overflowing // We can speed up scrolling of short text lines by just blanking the character we drew //int blank[19]; // We keep all the strings pixel lengths to optimise the speed of the top line blanking // ############################################################################################## // Setup the vertical scrolling start address pointer // ############################################################################################## void scrollAddress(uint16_t vsp) { M5.Lcd.writecommand(ILI9341_VSCRSADD); // Vertical scrolling pointer M5.Lcd.writedata(vsp >> 8); M5.Lcd.writedata(vsp); } // ############################################################################################## // Call this function to scroll the display one text line // ############################################################################################## int scroll_line() { int yTemp = yStart; // Store the old yStart, this is where we draw the next line // Use the record of line lengths to optimise the rectangle size we need to erase the top line // M5.Lcd.fillRect(0,yStart,blank[(yStart-TOP_FIXED_AREA)/TEXT_HEIGHT],TEXT_HEIGHT, TFT_BLACK); M5.Lcd.fillRect(0, yStart, XMAX, TEXT_HEIGHT, TFT_BLACK); // Change the top of the scroll area yStart += TEXT_HEIGHT; // The value must wrap around as the screen memory is a circular buffer if (yStart >= YMAX - BOT_FIXED_AREA) yStart = TOP_FIXED_AREA + (yStart - YMAX + BOT_FIXED_AREA); //if (yStart >= YMAX) yStart = 0; // Now we can scroll the display scrollAddress(yStart); return yTemp; } // ############################################################################################## // Setup a portion of the screen for vertical scrolling // ############################################################################################## // We are using a hardware feature of the display, so we can only scroll in portrait orientation void setupScrollArea(uint16_t tfa, uint16_t bfa) { M5.Lcd.writecommand(ILI9341_VSCRDEF); // Vertical scroll definition M5.Lcd.writedata(tfa >> 8); // Top Fixed Area line count M5.Lcd.writedata(tfa); M5.Lcd.writedata((YMAX - tfa - bfa) >> 8); // Vertical Scrolling Area line count M5.Lcd.writedata(YMAX - tfa - bfa); M5.Lcd.writedata(bfa >> 8); // Bottom Fixed Area line count M5.Lcd.writedata(bfa); } void termPutchar(char data) { // while (Serial.available()) { // data = Serial.read(); // If it is a CR or we are near end of line then scroll one line if (data == '\r' || xPos > 311) { xPos = 0; yDraw = scroll_line(); // It can take 13ms to scroll and blank 16 pixel lines } if (data > 31 && data < 128) { xPos += M5.Lcd.drawChar(data, xPos, yDraw, fontType); //blank[(18+(yStart-TOP_FIXED_AREA)/textHeight)%19]=xPos; // Keep a record of line lengths } //change_colour = 1; // Line to indicate buffer is being emptied } } void termPrintString(const char *str) { while (*str) termPutchar(*str++); } void termInit(const char *title) { // Setup the TFT display // M5.Lcd.setRotation(5); // Must be setRotation(0) for this sketch to work correctly M5.Lcd.fillScreen(TFT_BLACK); M5.Lcd.setTextColor(TFT_WHITE, TFT_BLUE); M5.Lcd.fillRect(0, 0, XMAX, TEXT_HEIGHT, TFT_BLUE); M5.Lcd.drawCentreString(title, XMAX / 2, 0, fontType); // Change colour for scrolling zone text M5.Lcd.setTextColor(TFT_WHITE, TFT_BLACK); // Setup scroll area setupScrollArea(TOP_FIXED_AREA, BOT_FIXED_AREA); //setupScrollArea(0, 0); // Zero the array //for (byte i = 0; i<18; i++) blank[i]=0; } |
platformio.ini
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library storages ; Advanced options: extra scripting ; ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:m5stack] platform = espressif32 board = m5stack-grey framework = arduino lib_deps = # RECOMMENDED # Accept new functionality in a backwards compatible manner and patches neosarchizo/CM1106 I2C @ ^1.0.2 ; Serial Monitor options monitor_speed = 115200 |
動作状況
起動したばかりの時には550ppmを示すが、その後変化していく。センサーのインタフェースに I2C と UART のどちらでも使える点は良いと思った。
精度は正直言ってはっきりしないが、CM1107が示すCO2濃度とNetatmo Weather Station の二酸化炭素濃度との差は、おおむね 100ppm 以内になっている。センサーの Dual Channel NDIR の効果がどの程度あるかはわからない。