センサ(CCS811)で、CO2濃度表示する(2/2 M5StickC plus編)

目次
前回の続き。
</674/>
M5StickC は素晴らしいが、今回、不満があった。
CCS811 の基板は、M5StickC の上部にちょうどいい大きさなのに、 G36 はREAD ONLYのPINで、I2Cに利用できない。
そのため、PIN配置そのままに、M5StickC に差すことができない。
でも、大丈夫!新製品 M5StickC PLUS がある。
M5StickC Plus なら、G36 の位置にINPUT/OUTPUT可能な G25 が配置され、こんな感じでぴったり。
- GND - GND
- VCC - TV
- SCL - G26
- SDA - G25
- INT 接続なし
- RST 接続なし
- WAKE - GNDとジャンパ接続

。
仕様追加
boot melody
せっかくBeepがついているが、音が小さくて警告音にならないので、boot時にmelodyを演奏。
画面の大きさ変更対応
LCDの解像度が、M5StickC の 80x16 から、 M5StickC plus の135x240 に変わったので対応
warrm up カウントダウン時もグラフ表示
20分のwarm up 中もグラフを表示

リセットボタン対応
長時間の運用に対応するため! ボタンBをリセットボタンにした。
ボタンAを押すとLED点灯
ハングした時の確認用に、ボタンAを押すとLEDが点灯するようにした。
code
M5StickC 版からチマチマブラッシュアップした。
Beep Melody用に、別途以下の pitches.h ファイルが必要。
https://www.arduino.cc/en/Tutorial/BuiltInExamples/toneMelody
ccs811_plus.ino · Gistcpp
#include <Wire.h>
#include <M5StickCPlus.h>
#include "pitches.h"
// LCD 135 * 240
#include "SparkFunCCS811.h" //Click here to get the library: http://librarymanager/All#SparkFun_CCS811
//#define CCS811_ADDR 0x5B //Default I2C Address
#define CCS811_ADDR 0x5A //Alternate I2C Address
#define PIN_LED 10
CCS811 mySensor(CCS811_ADDR);
uint16_t co2Buff[TFT_HEIGHT];
uint8_t co2Cursor;
// boot melody
int melody[] = {
// NOTE_A3, NOTE_A3, NOTE_B3, NOTE_A3, NOTE_A3,NOTE_B3,
NOTE_E3, NOTE_F3, NOTE_B3, NOTE_A3, NOTE_F3, NOTE_E3
};
int noteDurations[] = {
// 4, 4, 2, 4, 4, 2,
4, 4, 8, 8, 4, 1
};
int checkTime = millis();
int brightTime = checkTime + 30000; // 30sec
void boot_melody()
{
for (int thisNote = 0; thisNote < sizeof(melody)/sizeof(melody[0]); thisNote++) {
int noteDuration = 1000 / noteDurations[thisNote];
M5.Beep.tone(melody[thisNote]);
delay(noteDuration);
int pauseBetweenNotes = noteDuration * 1.30;
M5.Beep.mute();
delay(pauseBetweenNotes);
}
}
void lcd_init() {
// display values on LCD
M5.Lcd.setTextColor(DARKGREEN);
M5.Lcd.fillRect(0, 0, TFT_HEIGHT, TFT_WIDTH, BLACK);
// equivalent CO2 (eCO2)
// from 400ppm to 8192ppm
M5.Lcd.setTextSize(3);
M5.Lcd.setCursor(2, 4);
M5.Lcd.printf("CO2 :");
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(192, 1+4);
M5.Lcd.printf("ppm");
// Total Volatile Organic Compound (TVOC)
// from 0ppb to 1187ppb
M5.Lcd.setTextSize(3);
M5.Lcd.setCursor(2, 4 * 8);
M5.Lcd.printf("TVOC:");
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(192, 4 * 8+4);
M5.Lcd.printf("ppb");
}
//
// setup
//
void setup()
{
Serial.begin(115200);
CCS811Core::CCS811_Status_e returnCode;
M5.begin();
// PIN setup
gpio_pulldown_dis(GPIO_NUM_36); // use pin25, so free pin36
gpio_pullup_dis(GPIO_NUM_36);
pinMode(PIN_LED, OUTPUT); // LED
digitalWrite(PIN_LED, HIGH);
// display starting
M5.Axp.ScreenBreath(10);
M5.Lcd.setRotation(3); // displayを左を上にする
M5.Lcd.setTextSize(1);
M5.Lcd.fillScreen(BLACK);
M5.Lcd.setTextColor(DARKGREY);
delay(500);
M5.Axp.ScreenBreath(10); // LCD を最大限に明るくする
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(2, 4);
M5.Lcd.printf("CCS811 starting ");
//
// I2C and Sensor start
//
Wire.begin(GPIO_NUM_25, GPIO_NUM_26); //Inialize I2C Hardware
while (mySensor.begin() == false)
{
Serial.println("CCS811 error. Please check wiring. Freezing...");
M5.Lcd.printf(".");
delay(5000);
}
// Mode 1: Constant power mode, IAQ measurement every second
returnCode = mySensor.setDriveMode(1);
Serial.print("Mode request exited with: ");
Serial.println(mySensor.statusString(returnCode));
// beep melody
boot_melody();
// CO2 Graph Buffer clear
memset(co2Buff, 0, sizeof(co2Buff));
co2Cursor = 0;
// LCD初期表示
lcd_init();
}
void serial_output(int eCO2, int TVOC) {
Serial.print("CO2[");
Serial.print(eCO2);
Serial.print("] tVOC[");
Serial.print(TVOC);
Serial.print("] millis[");
Serial.print(millis());
Serial.print("]");
Serial.println();
}
void lcd_output(int eCO2, int TVOC) {
if (millis() % 10*1000 == 0)
lcd_init(); // たまにLCDの表示を初期化
M5.Lcd.setTextColor(DARKGREEN);
M5.Lcd.setTextSize(3);
M5.Lcd.fillRect(98, 4, TFT_HEIGHT-148, 30, BLACK);
M5.Lcd.setCursor(98, 4);
M5.Lcd.printf("%4d", eCO2);
M5.Lcd.fillRect(98, 3 * 8 +7, TFT_HEIGHT-148, 30, BLACK);
M5.Lcd.setCursor(98, 3 * 8 + 6);
M5.Lcd.printf("%4d", TVOC);
// graph TFT_HEIGHTx
co2Buff[co2Cursor] = eCO2;
int backcolor = DARKCYAN;
if (eCO2 > 1000) {
// 1000 ppm
backcolor = MAGENTA;
brightTime = millis() + 60*1000; // 1min
} else if (eCO2 > 2000) {
// 2000 ppm 危険!
backcolor = RED;
brightTime = millis() + 3600*1000; // 1hour
}
M5.Lcd.fillRect(0, 10 * 6, TFT_HEIGHT, TFT_WIDTH, backcolor);
for (int x = 0; x < TFT_HEIGHT; x++) {
int cursorx = co2Cursor - x;
if (cursorx < 0) {
cursorx = TFT_HEIGHT-1;
}
// value is 400ppm to 8192ppm
uint16_t value = co2Buff[cursorx];
if (value < 400) {
value = 400;
}
uint8_t height = (( value-400 ) /20/*graph height factor*/);
int line_color = WHITE;
M5.Lcd.drawLine(x, TFT_WIDTH-height, x, TFT_WIDTH, line_color);
}
if (co2Cursor++ >= TFT_HEIGHT) {
co2Cursor = 0;
}
// power on after 20minutes, stabilising
if (millis() / 1000 < 20*60) {
M5.Lcd.setTextColor(WHITE);
M5.Lcd.setTextSize(2);
M5.Lcd.setCursor(8, 7 * 10);
M5.Lcd.printf("warn up until", 20*60 - (millis() / 1000));
M5.Lcd.setTextColor(BLACK);
M5.Lcd.setTextSize(3);
M5.Lcd.setCursor(20, 10 * 10+4);
int nsec = 20*60 - (millis() / 1000);
M5.Lcd.printf("%2dm %2ds\r\n", nsec/60, nsec%60);
}
}
void sensor_output(int eCO2, int TVOC) {
serial_output(eCO2, TVOC);
lcd_output(eCO2, TVOC);
}
//
// loop
//
void loop()
{
static int sensor_catch_time = millis();
int nowtime = millis();
// pinの状態など更新
M5.update();
// TFT を明るく
int pressed = M5.BtnA.wasPressed();
if (pressed) {
brightTime = nowtime + 10000; // 10sec
// LED ON
digitalWrite(PIN_LED, LOW);
delay(100);
digitalWrite(PIN_LED, HIGH);
}
// REST BUTTON
if (M5.BtnB.wasPressed()){
esp_restart();
}
if (brightTime > nowtime) {
M5.Axp.ScreenBreath(10);
} else {
M5.Axp.ScreenBreath(7);
}
// CCS118 のチェック
if (!mySensor.dataAvailable()) {
delay(100);
return;
}
// 2秒に1回のセンサーチェックタイム
if (sensor_catch_time < nowtime) {
mySensor.readAlgorithmResults();
int eCO2 = mySensor.getCO2();
int TVOC = mySensor.getTVOC();
sensor_output(eCO2, TVOC);
sensor_catch_time = nowtime + 2000; // 1time/2sec
}
delay(100);
}