Skip to content

Instantly share code, notes, and snippets.

@mizdra
Last active November 23, 2021 08:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mizdra/955199c3ae4faad148c36ba0e6eba4e3 to your computer and use it in GitHub Desktop.
Save mizdra/955199c3ae4faad148c36ba0e6eba4e3 to your computer and use it in GitHub Desktop.
二酸化炭素濃度測定機 (M5StickC Plus + MH-Z19C)
// based https://gist.github.com/YujiSoftware/9274366a93f1ac7f9208bd4abf096527
#include <M5StickCPlus.h>
#include "MHZ19.h"
#define RX_PIN 36 // Rx pin which the MHZ19 Tx pin is attached to
#define TX_PIN 0 // Tx pin which the MHZ19 Rx pin is attached to
#define BAUDRATE 9600 // Device to MH-Z19 Serial baudrate (should not be changed)
#define LCD_MODE_DIGIT 0
#define LCD_MODE_GRAPH 1
#define BRIGHTNESS 8
#define ALERT_OK 0
#define ALERT_WARNING 1
#define ALERT_CRITICAL 2
MHZ19 myMHZ19; // Constructor for library
HardwareSerial mySerial(1); // (ESP32 Example) create device to MH-Z19 serial
int lcdMode = LCD_MODE_DIGIT;
auto alertLevel = ALERT_OK;
unsigned long lastBeepOperationTime = 0;
// 最後に CO2 濃度を測定した時刻
unsigned long lastMeasureTime = 0;
// CO2 濃度の測定間隔
unsigned long measureInterval = 60 * 1000;
// 今回の B ボタンの押下でキャリブレーション済みかどうか。
bool calibratedAtCurrentPressing = false;
// 画面幅 (240) 分だけ計測値をストックできるようにしておく
int history[240] = {};
int historyPos = 0;
// ダブルバッファリング用のバッファを用意
TFT_eSprite sprite(&M5.Lcd);
auto green = TFT_GREEN;
auto orange = TFT_ORANGE;
auto red = TFT_RED;
auto purple = TFT_PURPLE;
void setup() {
M5.begin();
M5.Axp.ScreenBreath(BRIGHTNESS);
Serial.begin(9600); // Device to serial monitor feedback
mySerial.begin(BAUDRATE, SERIAL_8N1, RX_PIN, TX_PIN); // (ESP32 Example) device to MH-Z19 serial start
myMHZ19.begin(mySerial); // *Serial(Stream) refence must be passed to library begin().
myMHZ19.autoCalibration(false);
// ビープ音の設定
M5.Beep.begin();
M5.Beep.setBeep(8000, 100);
digitalWrite(M5_LED, HIGH);
M5.Lcd.setRotation(3);
// ダブルバッファリング用のバッファを初期化
sprite.createSprite(M5.Lcd.width(), M5.Lcd.height());
render();
pinMode(M5_LED, OUTPUT);
}
void loop() {
auto now = millis();
M5.update();
M5.Beep.update();
// Aボタン: モードを切り替える
if ( M5.BtnA.wasPressed() ) {
lcdMode = (lcdMode + 1) % 2;
render();
}
// Bボタン: 3秒間長押しされたらゼロキャリブレーションする
// NOTE: 4秒、5秒と押し続けた時に何度もゼロキャリブレーションされないよう、
// calibratedAtCurrentPressing でフラグ管理している。
if (!calibratedAtCurrentPressing && M5.BtnB.pressedFor(3000)) {
Serial.print("calibrateZero\n");
M5.Beep.tone(2000);
delay(500);
M5.Beep.mute();
myMHZ19.calibrateZero();
calibratedAtCurrentPressing = true;
}
if (M5.BtnB.wasReleased()) {
calibratedAtCurrentPressing = false;
}
if (alertLevel >= ALERT_WARNING && now - lastBeepOperationTime >= 5 * 60 * 1000) {
lastBeepOperationTime = now;
M5.Beep.tone(150);
delay(100);
M5.Beep.mute();
delay(100);
M5.Beep.tone(150);
delay(100);
M5.Beep.mute();
}
if (now - lastMeasureTime >= measureInterval) {
/* note: getCO2() default is command "CO2 Unlimited". This returns the correct CO2 reading even
if below background CO2 levels or above range (useful to validate sensor). You can use the
usual documented command with getCO2(false) */
int CO2 = myMHZ19.getCO2(); // Request CO2 (as ppm)
int8_t temp = myMHZ19.getTemperature(false, true); // Request Temperature (as Celsius)
Serial.print("CO2 (ppm): ");
Serial.print(CO2);
Serial.print(", Temperature (C): ");
Serial.println(temp);
alertLevel = CO2 >= 1500 ? ALERT_CRITICAL : CO2 >= 1200 ? ALERT_WARNING : ALERT_OK;
// 測定結果の表示
historyPos = (historyPos + 1) % (sizeof(history) / sizeof(int));
history[historyPos] = CO2;
render();
lastMeasureTime = now;
}
}
void render() {
// Clear
int height = sprite.height();
int width = sprite.width();
sprite.fillRect(0, 0, width, height, BLACK);
switch (lcdMode) {
case LCD_MODE_DIGIT:
sprite.setTextSize(1);
sprite.drawString("CO2 [ppm]", 12, 0, 2);
sprite.setTextSize(2);
sprite.drawRightString((String)history[historyPos], width, 24, 7);
break;
case LCD_MODE_GRAPH:
int len = sizeof(history) / sizeof(int);
for (int i = 0; i < len; i++) {
auto value = history[(historyPos + 1 + i) % len];
auto cuttedValue = max(0, value - 400);
auto y = min(height, (int)(cuttedValue * (height / 1200.0)));
auto color = value < 1000 ? green : value < 1200 ? orange : value < 1500 ? red : purple;
sprite.drawLine(i, height - y, i, height, color);
}
break;
}
sprite.pushSprite(0, 0); // バッファの内容を画面に反映
}