ガスセンサー MQ-135を使ったCO2濃度測定(M5StickC Plus編)

最近テレワークで作業していると、頭がぼーっつとしてくることが多いと感じていました。
冬になり部屋を締め切っていることが多いので、室内のCO2濃度を測定しようとCO2センサーを探していたら、aitendoでMQ-135搭載のモジュール基板が199円で販売されているのを発見、購入して手持ちのM５StickC Plusと組み合わせて動かしてみました。
ESP32系で動かした情報があまりないので、今後のためにも整理しておきます。

#ガスセンサー MQ-135

##使用したモジュール
センサモジュールはaitendoにて特売商品(数量限定、2021年2月時点)で1個199円（税別)で販売されている**MQ135P2**を使用しました。
https://www.aitendo.com/product/19187
![MQ-135センサ](https://www.aitendo.com/data/aitendo/product/20200626_f39253.png)

##MQ-135のデータシート
aitendoの製品ページのデータシートはリンク切れとなっているのですが、同じセンサを使用した別商品からデータシートを入手することができます。
http://aitendo3.sakura.ne.jp/aitendo_data/product_img/sensor/MQ-135/MQ-135_e.pdf

**Specification**によると電源電圧は５V、内部のヒーターで加熱すると検出するガスの濃度によって抵抗値Rs(Sensing Resistance)が変化することで検出する仕組みのようです。
![SPECIFICATIONS](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120903/61096fca-5181-5ee1-b416-5068fc7f1dbe.png)

データシートには各種ガスに対する**感度特性カーブ**も載っています。基準となる濃度での抵抗値（Ro)と測定値(Rs)の比率からガス濃度を計算で求めることができそうです。
![感度特性カーブ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120903/28dea865-1651-a2b3-e73f-cbbb6d44bff1.png)

なお、製造元とおもわれる中国の**郑州炜盛电子科技有限公司(Zhengzhou Winsen Electronics Technology Co., Ltd.)**のサイトからは少し詳しいデータシートが入手できます。ただし、CO2のカーブは載っていませんでした。
https://www.winsen-sensor.com/d/files/PDF/Semiconductor%20Gas%20Sensor/MQ135%20(Ver1.4)%20-%20Manual.pdf

##MQ135P2モジュールの回路図
実際のモジュールを確認したところ、aitendoの製品ページ記載の回路図は間違っていることがわかったので、改めて回路図を作成しました。
![MQ135P2の回路図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120903/5e8f2c61-2f61-55dc-5bc0-9107bd559426.png)

ガスの濃度に応じてMQ-135の内部抵抗値とRLで分割された電圧が**AO**に出力されます。CO2濃度の測定にはこちらを使用します。
OPAMP(LM393)はコンパレータとして使われていて、AOの電圧が可変抵抗RPで設定した電圧を超えると出力がH→Lに変化するので、ある基準濃度を超えたかどうかの判定にのみにしか使えないことがわかります。

なお、購入したモジュールではMQ-135の負荷抵抗R2（データシートのRL)には1kΩがついていますが、上記の感度特性の測定条件（RL=20kΩ)および後述するライブラリの条件に合わせて、**22ｋΩに交換**しました。(実際にRL=1kΩで測定したのですが信号レベルが小さく値が正常に取れないので改造することをお勧めします。)
![抵抗の変更](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120903/1f8001a2-95ee-4478-e409-d149936a65ad.png)

#M5StickC Plusとの接続
MQ135P2とM5StickC Plusの結線は以下になります。

|MQ132P2|M5StickC Plus|
|:-:|:-:|
|VCC|5V|
|GND|GND|
|DO|NC|
|AO|G36|

AOはアナログ出力なので、M5StickC Plusの上部ピンソケットの[G36/G25]に接続して**G36(ESP32のADC1_CH0)**で取り込みます。
ESP32の入力は3.3Vトレラントなのですが、AOは抵抗分割出力で実用的な出力電圧範囲としては問題ないので直結で使用します。
DO端子はOPAMPの出力に接続されており通常はH(約5V)なのでNC(未接続)とします。

![connect.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120903/60b00707-80f2-9cff-dbac-f7832b63965b.jpeg)

#Arduinoのスケッチ
今回作成したスケッチはgithubで公開しています。
https://github.com/tomorrow56/M5Stack_CO2Monitor

##使用したライブラリ
Georg Krocker氏が公開している**Arduino library for the MQ135**を使用しました。
https://github.com/GeorgK/MQ135

M5StickC Plus(ESP32)のADC入力レンジ(3.3V/12bit)およびMQ-135の個体ばらつきの補正のためにライブラリ自体の修正が必要となりますので、**スケッチと同じフォルダに置いて**使用します。

##ADC入力レンジ(3.3V/12bit)対応
オリジナルのライブラリは5V/10bitのADC想定ですので、3.3V/12bitにあわせてADCの入力値を使用する**getResistance()**関数を修正します。
####ADCのデータからセンサの抵抗値を取得する計算式
~~~
Vout  =3.3*(readVal/4095)
Iload =Vout/RLOAD
Rsense=(5-Vout)/Iload
      =(5-Vout)/(Vout/RLOAD)
      =(5/Vout-1)*RLOAD
      =(5/(3.3*(readVal/4095))-1)*RLOAD
      =((4095/readVal)*(5/3.3)-1)*RLOAD
~~~

この計算にあわせてMQ135.cppの**getResistance()**関数を以下のように修正します。

~~~MQ135.cpp
/**************************************************************************/
/*!
@brief  Get the resistance of the sensor, ie. the measurement value

@return The sensor resistance in kOhm
*/
/**************************************************************************/
float MQ135::getResistance() {
  int val = analogRead(_pin);
  //return ((1023./(float)val) * 5. - 1.)*RLOAD;
  return ((4095./(float)val) * (5. / 3.3) - 1.)*RLOAD;
}
~~~

##MQ-135の個体バラツキの補正
使用する環境のCO2濃度とセンサの抵抗値を測定してMQ135.hのパラメータを修正します。
最初に十分に換気された環境で[Rzero測定用スケッチ](https://github.com/tomorrow56/M5Stack_CO2Monitor/tree/main/M5StickCPlus_MQ135_Rzero)を実行してセンサの抵抗値を取得して記録しておきます。

~~~M5StickCPlus_MQ135_Rzero.ino
#include <M5StickCPlus.h>
#include "MQ135.h"

// MQ135 Pin config
#define AO_IN        36
#define DO_IN        26

MQ135 gasSensor = MQ135(AO_IN); // Attach sensor to pin AO_IN

void setup (){
  // void begin(bool LCDEnable=true, bool PowerEnable=true, bool SerialEnable=true);
  M5.begin();
  // Wire.begin(32,33);
  M5.Lcd.setRotation(3);

  // SET INPUT PINS
  pinMode(AO_IN, INPUT);
  pinMode(DO_IN, INPUT);
}
void loop() {
  float rzero = gasSensor.getRZero();
  Serial.println (rzero);
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(GREEN);
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(0, 0);
  M5.Lcd.print("RZERO: ");
  M5.Lcd.println(rzero);

  delay(1000);
}
~~~

別にCO2濃度計がある場合は、その時のCO2濃度も記録しておきます。

私の環境でのセンサ抵抗実測値は**126.17kΩ**、CO2濃度はCO2濃度計を持っていないため、[気象庁の二酸化炭素濃度の月平均値](https://ds.data.jma.go.jp/ghg/kanshi/obs/co2_monthave_ryo.html)を参考に**420ppm**として測定を進めます。

上記で測定した値をそれぞれMQ135.hの**RZERO**,**ATMOCO2**に反映します。**RLOAD**はMQ-135の負荷抵抗にあわせて22kΩに修正します。

~~~MQ135.h
/// The load resistance on the board
//#define RLOAD 10.0
#define RLOAD 22.0
/// Calibration resistance at atmospheric CO2 level
//#define RZERO 76.63
#define RZERO 126.17
/// Parameters for calculating ppm of CO2 from sensor resistance
#define PARA 116.6020682
#define PARB 2.769034857

/// Parameters to model temperature and humidity dependence
#define CORA 0.00035
#define CORB 0.02718
#define CORC 1.39538
#define CORD 0.0018

/// Atmospheric CO2 level for calibration purposes
//#define ATMOCO2 397.13
#define ATMOCO2 420
~~~

##CO2濃度の測定
ライブラリの修正を行い、[CO2濃度測定用スケッチ](https://github.com/tomorrow56/M5Stack_CO2Monitor/tree/main/M5StickCPlus_MQ135_CO2)を作成しました。以下はスケッチの測定部分の抜粋です。
1秒毎にCO2濃度を測定し、5分毎に[ambient](https://ambidata.io/)にデータを送ってグラフ化するようにしています。部屋の環境がわかりやすいように測定値に応じてデータの文字色も変更するようにしています。

~~~M5StickCPlus_MQ135_CO2.ino
#include <M5StickCPlus.h>
#include "MQ135.h"
〜省略〜
uint32_t interval = 1000;
uint32_t interval_ambient = 1000 * 60 * 5;

// MQ135 Pin config
#define AO_IN        36
float ppm = 0;

MQ135 gasSensor = MQ135(AO_IN);

void setup(void) {
  〜省略〜
  // SET INPUT PINS
  pinMode(AO_IN, INPUT);
  〜省略〜
  //1回目のデータは空読みして捨てる
  ppm = gasSensor.getPPM();
  delay(1000);
}

void loop() {
  〜省略〜
  if(millis() > update_time){
    update_time = millis() + interval;

    ppm = gasSensor.getPPM();
    Serial.print ("ppm: ");
    Serial.println (ppm);

    if(ppm <= 1000){
      M5.Lcd.setTextColor(GREEN);
    }else if(ppm <= 1500){
      M5.Lcd.setTextColor(YELLOW);
    }else{
      M5.Lcd.setTextColor(RED);
    }
    printData(ppm);
  }

  if(millis() > update_time_ambient){
    update_time_ambient = millis() + interval_ambient;
    // 二酸化炭素濃度をAmbientに送信する
    ambient.set(1, String(ppm).c_str());
    ambient.send();
 
    Serial.printf("ambient: %.2f\r\n", ppm);
  }
}
~~~

このスケッチを使って自宅の作業スペースのCO2濃度を測定した結果が以下です。CO2濃度はだいぶ高めですが、それなりの数字が測定できています。
![measure.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120903/6f144a5c-32ae-607f-ff2d-5b23b9ec7353.jpeg)

ambientでは以下のようにCO2濃度の変化がグラフで表示されているのを確認しました。
![ambient.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/120903/eb49f435-5fc8-4feb-ebc0-8366c7038932.png)

###コールドスタート時の異常データについて
MQ-135は内蔵のヒーターが十分温まるまでの期間(大まかな目安は1分以上)は異常データ(非常に大きな値)を出力します。
実際の使用時には最初の数秒の検出で一定値以上のデータ(目途は3000ppmくらい)が来た場合はそこから1分程度のエージング時間を取るような処理を追加した方がよさそうです。(現在のサンプルプログラムにはエージング処理は入れていません)

#まとめ
199円で購入したセンサモジュールでCO2濃度の変化の測定ができました。絶対値の測定精度は期待できないですが、自宅でのテレワークの作業環境の換気の目安には使えそうです。

#参考文献・サイト
[MQ135 Arduino Library(github)](https://github.com/GeorgK/MQ135)
[Measuring CO2 Concentration in Air using Arduino and MQ-135 Sensor](https://circuitdigest.com/microcontroller-projects/interfacing-mq135-gas-sensor-with-arduino-to-measure-co2-levels-in-ppm)