2020年12月5日土曜日

ド素人がM5StickCを用いたマルチ環境計を作ってみた Ver1.1 (CO2測定関連のプチコラム追加)

とりあえずVer.1としては完成したので、作り方を書き連ねていこうと思います~

※プログラミング素人が、教本すら読まずにネット上の知識のみで組み上げたものです。ガバガバでスパゲッティなコードだったり、変なところもあるかと思いますが温かい目で見てやってください。
※そのため説明が間違っている可能性が高いです。参考の一つになるかも怪しいのはあしからず。

※最下部のマルチ環境コードに訂正、M5StickC非公式日本語リファレンスさんによると、バッテリ電圧の取得はdouble vbat~の関数よりもfloat vbat = M5.Axp.GetBatVoltage();で取得したほうが良いそうです。よって該当部分を修正しました。主たる動作に変更はありません。
※また、番外編の心拍・SpO2センサ編へのリンクを貼りました。よろしければ見ていってください。
※あと、MH-Z19Bの下欄に調子乗ったこと書きました。CO2測定の云々を書いてあります。
※最新版→ド素人がM5StickCを用いたマルチ環境計を作ってみた Ver3.0(Ambientとの接続優先で30秒おきのデータ更新となります)

~今回作ったもの~

M5StickCを使用したマルチ環境計(環境メーター?環境センサー?)
(測定・表示できるのは温度・湿度・気圧・CO2濃度です)

使用したデバイス類

・M5StickC(ESP32搭載小型コンピュータ)
・ENV Ⅱ HAT(M5StickC公式の環境センサキット、温湿度・気圧・地磁気を測定可能)
・MH-Z19B(汎用のNDIR-非分散型赤外線吸収法-方式CO2センサ)
・Grove-メスピンヘッダケーブル(一部要加工)

~まずは~

 開発環境として、Arduino IDEをインストール。あといらないらしいけどCP210xというM5StickC用のドライバもインストールしました。
 そこからM5StickCのセットアップをするのですが、ド素人のわたしはそこでも苦戦しました。
 そのあたりを少し書き連ねさせていただきます。

 まず、Tools内のBoardsは、ESP32 Pico KitでもM5Stick-Cでも大丈夫でした。ただし前者のUpload Speedは115200が最大、後者は1500000が最大になります。地味に書き込み速度って変化を感じますよ。
 あと、ポート番号は自分のものを調べて覚えておきましょう。たぶんそう変わらないので一回確認すれば十分だと思います。

 あと、ExamplesのFactoryTestをそのまま書き込もうとすると、FastLED.hというライブラリが無いよと怒られるのでライブラリをインストールしましょう。
 さらに、”スケッチが大きすぎます”と怒られたのでToolsのPartition SchemeをNo OTA(Large APP)にして解決しました。もしかしたら一個下のLarge APPs with OTAでも良かったかもしれません。

 次にHello Worldを表示。ここまでできたらチュートリアルは完了って感じ…でいいんだよね?
Hello World 文字サイズ変更と画面の向きの変更はした

 さてここまででもいくつかのサイトを参考にさせていただいたのですが、そちらに関しては保存するのを忘れていたためなんとなくしか覚えていません…ここ(リンク:MSR合同会社さん)は残してあったんですが、他のエラーにあたったときはその場で調べてそのまま消してしまいまして…。あと公式のページは参考にしたかな??

~ENV Ⅱ HATのサンプルコードと小改造~

 M5Stack公式サイトから、ENV Ⅱ HATのドキュメントを探し、GithubにあるサンプルコードをそのままArduino IDEにコピペして書き込み。ちなみに1こずつタブ?に分けておかないと動きませんでした。(そういうことすら知らない)
サンプルコードまんまの状態
 下記のコードは、サンプルコードから地磁気を取得する部分を省き、また温度の後にCを付けて(℃はできないらしい?)、湿度を小数点以下一桁まで表示するようにしてその後ろに%を付けて、気圧をhPa表示にするという小改造を行ったものです。
 それ以外はまったくいじっていません。
下のコードで動いている様子

code:1 ENV II HAT KAI
/*
    note: need add library Adafruit_BMP280 from library manage
    Github: https://github.com/adafruit/Adafruit_BMP280_Library
*/

/*注意 私の理解できたところのみ説明が書かれています
 説明がないということは…そういうことです
 また、英語はガバガバ英語だったりDeepLに頼った英語だったりします。自己満です。 */

//Include Libraries ライブラリをインクルード
#include <M5StickC.h>
#include "SHT3X.h"
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>

//Define Valiable 変数を定義
SHT3X sht3x; 
Adafruit_BMP280 bme;

//Calculate temperature, humidity and air pressure in floating point? 浮動小数点で温湿度・気圧を計算?
float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;

void setup() {
  // put your setup code here, to run once:
  M5.begin(); //Initialize M5StickC M5StickCを初期化
  Wire.begin(0,26); //Initialize I2C of ENVHAT2 ENVHAT2のI2Cを初期化
  M5.Lcd.setRotation(3); //Set Lcd's Direction 画面を横向き(左上)に
  M5.Lcd.fillScreen(BLACK); //Fill Screen Black 背景色を黒に
  M5.Lcd.setCursor(0, 0, 2); //Set Cursor to (0,0) and Assign Fonts 2 カーソルを(0,0)へ、フォントは2を指定
  M5.Lcd.println("ENV TEST 2"); //Show sentences in "" on the screen 画面に””内の文章を表示

  if (!bme.begin(0x76)){  
      Serial.println("Could not find a valid BMP280 sensor, check wiring!");
      while (1); //Error message if the BMP280 sensor was not detected? BMP280センサが検出されなかった場合のエラー表示?
  }
}
uint8_t setup_flag = 1;

void loop() {
  // put your main code here, to run repeatedly:
  if(sht3x.get()==0){
    tmp = sht3x.cTemp;
    hum = sht3x.humidity; //Acquisition of temperature and humidity data from sht3x? sht3xからの温湿度データの取得?
  }

  
  M5.Lcd.setCursor(0, 20, 2); //Set Cursor to (0,20) and Assign Fonts 2 カーソルを(0,20)へ、フォントは2を指定
  M5.Lcd.printf("Temp: %2.1fC Humi: %2.1f%%", tmp, hum); //Display of degrees Celsius and humidity in one decimal place 摂氏度および湿度を小数点以下1桁で表示
  
  float pressure = bme.readPressure();
  M5.Lcd.setCursor(0, 40, 2); //Set Cursor to (0,40) and Assign Fonts 2 カーソルを(0,40)へ、フォントは2を指定
  M5.Lcd.printf("pressure: %4.3fhPa", pressure / 100); //Atmospheric pressure in units of hPa to three decimal places 大気圧をhPaの単位で小数点以下3桁まで表示 
  delay(100); //Wait 100msecs 100ミリ秒待機

  if (!bme.begin(0x76)){  
      Serial.println("Could not find a valid BMP280 sensor, check wiring!");
      while (1); //Error message if the BMP280 sensor was not detected? BMP280センサが検出されなかった場合のエラー表示?
  }
 }


~MH-Z19B~

 ここまでは公式にドキュメントだったりコードだったりが用意されていたので実装もかんたんだったんですが、次は汎用の規格を使ったものになります。まあ作例いっぱいあるから参考にすれば私ですらできたので大丈夫じゃないかな…
 ということで、NDIR式のCO2センサの中では安価で定番な、MH-Z19B(リンク:Amazonの検索結果)です。Amazonの商品画像とはちょっと裏の基盤のシルク印刷が違ってるけど、なんとかわかったからヨシ。(TxとRxに"d"がついて、VinがV+、GNDがV-になってて、ADTとSRが消えてる)(ADTとSRは使わないからどうでもいいけど)
表面 こっちは変化なし

裏面 基盤の色とかシルクとかちょっと違う

 そして、ピンヘッダを必要な部分にはんだ付けして

表面はんだ付け後
裏面はんだ付け後

 GroveケーブルでM5StickC本体と接続
 ちなみに、センサ側のTxdにG32、RxdにG33を接続して、V+に5V、V-にGNDを接続しました(TxdとRxdの端子を逆にしてみたら-1ppmという値が出たのでこの通り配線しないといけないようです)。
ペイント3分クォリティのMH-Z19B簡易配線図


Grove-メスピンヘッダケーブル
接続後 ピンが緩かったのでこのあとテープで補強した


下記のコードで動いている様子 シンプルでしょ?

code:2 MH-Z19B TEST
/*注意 私の理解できたところのみ説明が書かれています
 説明がないということは…そういうことです
 また、英語はガバガバ英語だったりDeepLに頼った英語だったりします。自己満です。 */
 
//Include Libraries ライブラリをインクルード
#include <M5StickC.h>
#include "MHZ19_uart.h"

//Define Valiable 変数を定義
MHZ19_uart mhz19;

void setup() {
  M5.begin(); //Initialize M5StickC M5StickCを初期化
  delay(50); //Wait 50msecs 50ミリ秒待機

  mhz19.begin(32,33); //Initialize I2C of MHZ19 MHZ19のI2Cを初期化
  mhz19.setAutoCalibration(false); //MHZ19's autoCalibration turn off MHZ19の自動校正をしない
  delay(3000); //Wait 3000msecs 3000ミリ秒待機
  M5.Lcd.setRotation(3); //Set Lcd's Direction 画面を横向き(左上)に
  M5.Axp.ScreenBreath(10); //Decrease Lcd's Brightness to 10 画面の輝度を10に下げる
  setCpuFrequencyMhz(80); //Set Cpu Frequency to 80MHz CPUの動作周波数を80MHzにする
  M5.Lcd.fillScreen(BLACK); //Fill Screen Black 背景色を黒に
  M5.Lcd.setCursor(0,0,4); //Set Cursor to (0,0) and Assign Fonts 4 カーソルを(0,0)へ、フォントは4を指定
}

void loop() {
  int co2,temp;
  int i;
  unsigned int t;
  co2 = mhz19.getPPM();
  temp = mhz19.getTemperature();
  Serial.printf("CO2=%d ppm / Temp=%d C\n",co2,temp);
  M5.Lcd.fillScreen(BLACK); //Fill Screen Black 背景色を黒に
  M5.Lcd.setCursor(0,20,4); //Set Cursor to (0,0) and Assign Fonts 4 カーソルを(0,0)へ、フォントは4を指定
  M5.Lcd.printf("%4dppm",co2); //CO2 concentration in integers in ppm CO2濃度をppmの単位で整数で表示

  
}
ここの参考文献は末尾※1

-プチコラム-

 今回のセンサで採用されているNDIR、非分散赤外線吸収法によるCO2測定について。
 まず、二酸化炭素は三分子原子で、対称・非対称・変角の3つの分子運動をしています。そのうち非対称運動が赤外線吸収を示します。
 その振動数が2349cm^-1(カイザー、cmで測った光の波長の逆数、つまりλ=約425μm)と特異な数値を示すため、単一波長の吸収のみ検出できる非分散方式での測定が可能で、単純な構造のためある程度の小型化と安価にすることができるのです。
 あと、その赤外線を吸収する、というところから想像できるかもしれませんが、CO2が温室効果ガスとして取り上げられているのも、CO2が赤外線を吸収し大気を暖めるためです。

 ちなみに、同じ赤外線吸収法による測定として、FT-IR(フーリエ変換赤外分光法)というものがあります。こちらは装置が大掛かりで高価だったりしますが、様々な成分を検出することができます。


~2センサの統合処理~

 さてここまで単体動作はなんとかなったのですが、私が作りたいのはマルチな環境計。温度と湿度、換気の目安ともなるCO2濃度(気圧はおまけ)を同時に表示させたかったのです。
 ここでプログラミングの知識がある人ならかんたんに上2つのコードを難なくまとめて統合したコードを書いちゃうんでしょうけど、もちろん私は苦戦しました。
 まずGPIOピンの配列がどうなってるのというところからスタートし、番外に乗っける予定の心拍・SpO2センサをついでに載っけようとしたり、(わかってる人にとっては当たり前なのかもしれないけど)同じGPIOピンを両方のセンサにつなげて動かねーってなったりと、色々と紆余曲折を経てとりあえず完成させました(とりあえず、というのはインターネットに接続するアップデートを行う予定があるから)。

 なにはともあれ完成形から。
マステでごちゃごちゃ留めてありますがこれが完成形です
ちなみに8の字というか∞風に線が走ってます

 ENV Ⅱ HATについて、これはM5StickCのHAT端子のうち必要なGND/G26/G0/3V3端子を延長して接続しました。配線はそのまま延長しただけなので公式に準じています(5V端子じゃなくて3V3端子だったのは意外だった)。

 そして、色々と試行錯誤しながらなんとか完成したコードがこちらです。Multi-Environmental-Sensor-Systemとか変な名前つけちゃってますが無視してあげてください。
 あと、上2つのぶんに加えてバッテリー電圧をちょこんとシステム名の下に表示してあります。バッテリ容量が小さすぎで電圧表示のほうが管理しやすいかなと思って、%ではなく電圧を表示してあります。まあ大体電源つないであれば問題ないんだけどね。

code:3 MultiSensingSketch
/*注意 私の理解できたところのみ説明が書かれています
 説明がないということは…そういうことです
 また、英語はガバガバ英語だったりDeepLに頼った英語だったりします。自己満です。 */

//Include Libraries ライブラリをインクルード
#include <M5StickC.h>
#include "SHT3X.h"
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#include "MHZ19_uart.h"

//Define Valiable 変数を定義
SHT3X sht3x;
Adafruit_BMP280 bme;
MHZ19_uart mhz19;

//Calculate temperature, humidity and air pressure in floating point? 浮動小数点で温湿度・気圧を計算?
float tmp = 0.0;
float hum = 0.0;
float pressure = 0.0;

void setup() {
  // put your setup code here, to run once:
  M5.begin(); //Initialize M5StickC M5StickCを初期化
  delay(50); //Wait 50msecs 50ミリ秒待機

  mhz19.begin(32,33); //Initialize I2C of MHZ19 MHZ19のI2Cを初期化
  mhz19.setAutoCalibration(false); //AutoCalibration turn off 自動校正をしない
  delay(3000); //Wait 3000msecs 3000ミリ秒待機
  
  Wire.begin(0,26); //Initialize I2C of ENVHAT2 ENVHAT2のI2Cを初期化
  M5.Axp.ScreenBreath(10); //Decrease Lcd's Brightness 画面の輝度を下げる
  setCpuFrequencyMhz(80); //Set Cpu Frequency to 160MHz CPUの動作周波数を80MHzに
  M5.Lcd.fillScreen(BLACK); //Fill Screen Black 背景色を黒に
  M5.Lcd.setRotation(3); //Set Lcd's Direction 画面を横向き(左上)に
  M5.Lcd.setCursor(0, 2, 1); //Set Cursor to (0,2) and Assign Fonts 1 カーソルを(0,2)へ、フォントは1を指定
  M5.Lcd.println("Multi-Environmental-Sensor-System"); //Show sentences in "" on the screen 画面に””内の文章を表示
  
  if (!bme.begin(0x76)){  
      Serial.println("Could not find a valid BMP280 sensor, check wiring!");
      while (1); //Error message if the BMP280 sensor was not detected? BMP280センサが検出されなかった場合のエラー表示?
  }

}

uint8_t setup_flag = 1;

void loop() {
  // put your main code here, to run repeatedly:
  if(sht3x.get()==0){
    tmp = sht3x.cTemp;
    hum = sht3x.humidity; //Acquisition of temperature and humidity data from sht3x? sht3xからの温湿度データの取得?
  }

  M5.Lcd.setCursor(0, 22, 2); //Set Cursor to (0,22) and Assign Fonts 2 カーソルを(0,22)へ、フォントは2を指定
  M5.Lcd.printf("Temp: %2.1fC Humi: %2.1f%%", tmp, hum); //Display of degrees Celsius and humidity in one decimal place 摂氏度および湿度を小数点以下1桁で表示
  
  float pressure = bme.readPressure(); //Calculating atmospheric pressure in floating point? 浮動小数点で大気圧を計算?
  M5.Lcd.setCursor(0, 42, 2); //Set Cursor to (0,42) and Assign Fonts 2 カーソルを(0,42)へ、フォントは2を指定
  M5.Lcd.printf("pressure: %4.2fhPa", pressure / 100); //Atmospheric pressure in units of hPa to three decimal places 大気圧をhPaの単位で小数点以下3桁まで表示 
  delay(100); //Wait 100msecs 100ミリ秒待機

  if (!bme.begin(0x76)){  
      Serial.println("Could not find a valid BMP280 sensor, check wiring!");
      while (1); //Error message if the BMP280 sensor was not detected? BMP280センサが検出されなかった場合のエラー表示?
  }

  int co2,temp; //Calculate CO2 concentration by integer? 整数でCO2濃度を計算?
  int i;
  unsigned int t;
  co2 = mhz19.getPPM(); //Datas read from MH-Z19MH-Z19からのデータ呼び出し?
  temp = mhz19.getTemperature();
  Serial.printf("CO2=%d ppm / Temp=%d C\n",co2,temp);
  M5.Lcd.setCursor(0,62,2); //Set Cursor to (0,62) and Assign Fonts 2 カーソルを(0,62)へ、フォントは2を指定
  M5.Lcd.printf("CO2: %4dppm",co2); //CO2 concentration in integers in ppm CO2濃度をppmの単位で整数で表示
  delay(100); //Wait 100msecs 100ミリ秒待機

  float vbat = M5.Axp.GetBatVoltage(); //Get the battery voltage of the M5StickC? M5StickCのバッテリー電圧を取得
  M5.Lcd.setCursor(62,10,1); //Set Cursor to (62,10) and Assign Fonts 1 カーソルを(62,10)へ、フォントは1を指定
  M5.Lcd.printf("vbat: %4.2fV",vbat); //Battery voltage displayed in 2 decimal places バッテリー電圧を小数点以下2桁で表示

}

 一応これらのコードは私の環境上で動作したものとなります。なんならこの記事を書くまでに1ヶ月以上寝かせてしまったので3つ目のコードに至っては約1ヶ月間の稼働実績もあります。まだクラウドに接続してないからログは取れないけどね…

~次回以降の展望~

 とりあえず、Ambientさんのサービスを利用してグラフ化はしてみたいと思ってます。
 あとはM5StickC自体をWebサーバー化して、ブラウザ経由で測定値を見れるようになったらなぁ、どっちも実装できたらなぁと画策しています。どっちもっていうのが成功しなさそうだけど。


~巻末の参考資料欄~

 ・電源管理(AXP192)(の、バッテリー電圧取得 GetBatVoltage())

※1
CO2測定(MH-Z19B・M5StickCPlus) - Qiita ←たぶん一番参考にしたのはここかな…
(Githubのライブラリは、どっちをメインで使ったか忘れたので2つ載せました。実際使用しているライブラリは片方のはずなのでどっちかは要らないです)
(リンク禁止のところなどがあれば教えて下さい…)

0 件のコメント:

コメントを投稿