ESP-WROOM-02とMQ-135でCO2濃度を測定する

電子工作をやりはじめてから作りたい物ばっかりになってしまって全然追いつかないerukitiです、ごきげんよう。

さて、皆さんのところでは、CO2の濃度は大丈夫でしょうか？気密性の高い建物の中だと寒さ・暑さには強くても空気の循環が滞ってしまって頭がぼーっとするなどの症状が出ることがあります。[チームラボオフィスの空気環境(CO2)を測定して改善している話:tks(高須 正和)のブロマガ - ブロマガ](http://ch.nicovideo.jp/tks/blomaga/ar932992)という記事ではチームラボのtksさんが職場のCO2状況を改善するというお話があって、僕はとても感動しました。

今は僕は休職中なのと、今のオフィスはそんなでもないような気がするんですが、過去のオフィスでは頭がぼーっとする、空気が滞ってる感じがあるなどの症状が出る事もままありました。CO2を測定して是非労務環境(住居環境)を改善しましょう。と言っても、実はCO2の計測はそれなりに大変なのです。Amazonで適当に二酸化炭素を検出できる機械を探すと1万円以上するんですよね。じゃぁ電子工作だ！と言っても、日本で売ってるCO2センサー、妙に高い。業界人な友人曰く「なんかCO2センサーって高いんだよねー」とのこと。

ところが、[ねとけん](https://www.facebook.com/netoken.jp/)でこんな情報がありました。「MQ-135だったら一個5.50EURじゃね？」と。どうも製造元の通販?っぽいので[SNS-MQ135](https://www.olimex.com/Products/Components/Sensors/SNS-MQ135/)が5.50EURで売ってたので、早速買ってみました。ユーロなので少しレート的には高くなる＆送料もかかるとは言え、一個あたり1000円以下なら他より圧倒的に安いじゃないですか。

このMQ-135は正確にはCO2のセンサーではありません。NH3, NOx, アルコール, ベンゼン, 煙, CO2など複合的なセンサーのようです。ただまぁ、正確なCO2の濃度がわからないにしても「有害な空気」を検出する能力があるという事でしょうからまぁいいのではないかと。

## ESP-WROOM-02とつなぐ

MQ-135はAO(analog output)とDO(digital output)があって、DO使えばいいのでは？と思うんですが、何故か公式含めAOを使った情報ばかりなので、AOを使ってみました。アナログ信号はADCでデジタルに変換する必要があります。ESP-WROOM-02はピンの名前で言うところのtoutがADCの機能を持っています。ただ、ArduinoのADCならば0V〜5Vの1024階調のADCなんですが、ESP-WROOM-02は0V〜1Vの1024階調のADCになります。MQ-135のAOはどうも0V〜5Vで出力されるっぽいので、1Vに落とす必要があります。いくつか検索して見つかったMP-135の使用例ではAOからGNDに10k〜22k程度の抵抗を入れているので、これを使って分圧をしてみました。

![mq-135.png](https://qiita-image-store.s3.amazonaws.com/0/3134/00479cdc-5978-6103-115c-905f6765f260.png)

回路図ではESP-WROOM-02のTOUT以外のピンの接続は省略しています。またMQ-135は1から順に、AO, DO, GND, VCCとなっています。(EagleでPIN HEADERを流用したのだけど1,2,3,4をリネームする方法がわからなかった…。)

スケッチは[empierre/arduino: arduino scripts for MySensors IoT](https://github.com/empierre/arduino)をほぼコピペしてESP-WROOM-02対応しました。

```c
// https://github.com/empierre/arduino/blob/master/AirQuality-MQ135.ino
/*
  Arduino MQ135
  connect the sensor as follows :
  A H A   >>> 5V
  B       >>> A0
  H       >>> GND
  B       >>> 10K ohm >>> GND
 
  Contribution: epierre
  Based on David Gironi http://davidegironi.blogspot.fr/2014/01/cheap-co2-meter-using-mq135-sensor-with.html
  http://skylink.dl.sourceforge.net/project/davidegironi/avr-lib/avr_lib_mq135_01.zip
 
*/

extern "C" {
#include "user_interface.h"
}

#define MQ135_DEFAULTPPM 399 //default ppm of CO2 for calibration
#define MQ135_DEFAULTRO 68550 //default Ro for MQ135_DEFAULTPPM ppm of CO2
#define MQ135_SCALINGFACTOR 116.6020682 //CO2 gas value
#define MQ135_EXPONENT -2.769034857 //CO2 gas value
#define MQ135_MAXRSRO 2.428 //for CO2
#define MQ135_MINRSRO 0.358 //for CO2

#define RO 2000.0

//VARIABLES
float mq135_ro = RO;
int val = 0;           // variable to store the value coming from the sensor
float valAIQ =0.0;
float lastAIQ =0.0;

void setup()  
{
  Serial.begin(9600);
}

/*
 * get the calibrated ro based upon read resistance, and a know ppm
 */
long mq135_getro(long resvalue, double ppm) {
  return (long)(resvalue * exp(log(MQ135_SCALINGFACTOR / ppm) / MQ135_EXPONENT));
}

/*
 * get the ppm concentration
 */
double mq135_getppm(long resvalue, long ro) {
  double ret = 0;
  double validinterval = resvalue / (double)ro;
  if (validinterval < MQ135_MAXRSRO && validinterval > MQ135_MINRSRO) {
    ret = (double)MQ135_SCALINGFACTOR * pow(((double)resvalue / ro), MQ135_EXPONENT);
  }
  return ret;
}

void loop()
{
  uint16_t valr = system_adc_read();
  // uint16_t val = ((float)22000 * (1023 - valr) / valr); 
  // これたぶん http://davidegironi.blogspot.jp/2014/01/cheap-co2-meter-using-mq135-sensor-with.html の22kΩが元になってるので、2000に変更

  uint16_t val = (RO * (1023 - valr) / valr);
  mq135_ro = mq135_getro(val, MQ135_DEFAULTPPM);
  //convert to ppm (using default ro)
  valAIQ = mq135_getppm(val, MQ135_DEFAULTRO);

  Serial.print ( "Vrl / Rs / ratio:");
  Serial.print ( val);
  Serial.print ( " / ");
  Serial.print ( mq135_ro);
  Serial.print ( " / ");
  Serial.print ( valAIQ);
  Serial.print("ppm [");
  Serial.print(valr);
  Serial.println("] ");

  delay(3000); 
}
```

### ESP-WROOM-02でアナログを読み込む

[Arduino - ちょと裏技っぽいけど、ESP8266 でAD変換やる方法 - Qiita](http://qiita.com/azusa9/items/26e74e4e0d5773ce9c41)によると、Arduinoの[analogRead](https://www.arduino.cc/en/Reference/AnalogRead)は使えず、代わりに`system_adc_read()`を使うようです。

```
extern "C" {
#include "user_interface.h"
}
```

```
int hoge = system_adc_read();
```

元ソースでは[MySensors - MySensors Arduino Library (v1.4)](https://www.mysensors.org/download/sensor_api_14)を使っていますが、これを使わないようにして、`analogRead`を`system_adc_read`に置き換えました。

### ESP8266でlibmを使うとエラーが出る

[Stable version libm.a compilation error · Issue #612 · esp8266/Arduino](https://github.com/esp8266/Arduino/issues/612) というやつです。途中の発言にあった https://files.gitter.im/esp8266/Arduino/Abqa/libm.a.tbz でlib.mを置き換えるとどうやら直るようなのでそうしました。（適当すぎる…）

## 実際に動かしてみた

```
$ platformio serialports monitor -b 9600 --port /dev/ttyAMA0 
--- Miniterm on /dev/ttyAMA0  9600,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
Vrl / Rs / ratio:52816 / 82358.00 / 240.04ppm [17] 
Vrl / Rs / ratio:60339 / 94089.00 / 166.01ppm [16] 
Vrl / Rs / ratio:52816 / 82358.00 / 240.04ppm [17] 
Vrl / Rs / ratio:52816 / 82358.00 / 240.04ppm [17] 
Vrl / Rs / ratio:60339 / 94089.00 / 166.01ppm [16] 
Vrl / Rs / ratio:52816 / 82358.00 / 240.04ppm [17] 
Vrl / Rs / ratio:46714 / 72843.00 / 337.22ppm [42] 
Vrl / Rs / ratio:25280 / 39420.00 / 1846.44ppm [75] 
Vrl / Rs / ratio:21517 / 33552.00 / 0.00ppm [87] 
Vrl / Rs / ratio:36603 / 57076.00 / 662.59ppm [53] 
Vrl / Rs / ratio:54833 / 85503.00 / 216.37ppm [36] 
Vrl / Rs / ratio:3015 / 4701.00 / 0.00ppm [29] 
Vrl / Rs / ratio:8241 / 12850.00 / 0.00ppm [27] 
Vrl / Rs / ratio:14304 / 22304.00 / 0.00ppm [25] 
Vrl / Rs / ratio:25464 / 39707.00 / 1809.74ppm [22] 
Vrl / Rs / ratio:29892 / 46612.00 / 1160.95ppm [21] 
Vrl / Rs / ratio:34764 / 54209.00 / 764.25ppm [20] 
Vrl / Rs / ratio:34764 / 54209.00 / 764.25ppm [20] 
Vrl / Rs / ratio:34764 / 54209.00 / 764.25ppm [20] 
Vrl / Rs / ratio:40148 / 62604.00 / 512.95ppm [19] 
Vrl / Rs / ratio:46130 / 71932.00 / 349.18ppm [18] 
```

角括弧の中は、実際にアナログ読み出しした数値(0〜1023)です。今うちの部屋は平均的に200〜300pps位の濃度らしいですが、途中一気に上昇した時はわざと息を吹きかけた時です。0.00ppmという結果が出ちゃうのは計算式の都合でしょうか、over の時も under の時も出ちゃうっぽいので、うまく改良したいところです。

### 課題

そもそも取得される数値を見る限り息を吹きかけても最大で87とかしか取得されてないって事を考えると、実は分圧いらなかったのでは？と思っています。何らかのクリッピング回路だけ入れて1Vを越えないように保護さえできれば良かったのでは…。

あと、参考にしたサイトでは10kΩのパターンと22kΩのパターンがありましたが、22kΩ入れた方が精度でたりしたんでしょうかね。そこらへんも追試、というかまじめにソースや文章を読んでみないといけないなぁと思っています。[Davide Gironi: Cheap CO2 meter using the MQ135 sensor with AVR ATmega](http://davidegironi.blogspot.jp/2014/01/cheap-co2-meter-using-mq135-sensor-with.html#.VqoCaFOLTUI)とかちゃんと読み込んでみたいところです。

根本的な問題として、本当に正しい数字なのかどうかは別の測定器か何かが無いとわからない気がします…。たとえば抵抗を20kにしてみたら、平均が400ppm前後と出てました。計算式を何か間違えて書き換えてしまったかもしれません。誰か測定器を持ってるかた、いい感じに調整してみて頂けると助かります！(もしくは[wishlist](http://www.amazon.co.jp/gp/registry/wishlist/JQ28X5KI9FRY))

## ということで

課題は残るもののCO2っぽいものを測定することができました。今後はWiFiを使ってサーバーにREST HTTPかMQTTあたりで測定情報を定期的に飛ばす事を考えています。

ESP-WROOM-02にしてもMQ-135にしてもちょっとアナログとして扱うにはクセのある感じですが、大変安価でCO2計測ができるというのはいいことだと思います。

それではみなさん、ごきげんよう。