前回の続き。

</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);
}