M5Stackとダストセンサを使い、空気中のホコリの量を測定してみます。
Grove ダストセンサ
ホコリの量の測定にはSeeed Studio社製の「GROVEダストセンサ」というセンサを使います。このセンサは直径1マイクロメートル(μm)以上の粒子を検出し、粒子の濃度に対応したパルスを発生します。
ちなみに、大気汚染で注目されているPM2.5は2.5μmの粒子状物質、スギ花粉症の原因となるスギ花粉は30〜40μmの大きさとのことなので、このGrove ダストセンサはPM2.5やスギ花粉が検出可能です。
Grove ダストセンサをM5Stackに接続する
Grove ダストセンサには4ピンのGroveコネクタがついていて、各ピンはデジタル出力、未使用、5V、グランドです。
M5Stack BasicやGrayにはGroveポートがありますが、I2C接続用です。M5Stack内部でIP5306というI2Cで通信する電源制御用チップにもつながっているので、I2C以外の方法で使えません。つまり、GroveダストセンサのGroveコネクタはM5Stack BasicやGrayのGroveポートには挿せません。そこで表1のようにGroveダストセンサをM5StackのGPIO5、5V、GNDに接続しました。
Groveダストセンサ | M5Stack |
---|---|
デジタル出力 | GPIO2 |
未使用 | ー |
5V | 5V |
グランド | GND |
空気中のホコリの量と合わせて温度、湿度、気圧も測るように、「M5Stack用環境センサユニット」も接続しました。こちらはI2C接続ですので、M5StackのGroveポートに接続できます。
写真ではダストセンサーを水平に置いていますが、製造元のSeeed Studio社のサイトには垂直に立てて使うようにと書かれているので、設置するときは垂直に置きます。
プログラム
Grove ダストセンサにアクセスする
Grove ダストセンサは、測定時間中にパルスがLowレベルになった時間の比率が、空気中の粒子状物質の濃度に対応します。
Grove ダストセンサにアクセスしてホコリの粒子量を得るプログラムは、Seeed studio社のサンプルコードを使いました。
void loop() {
duration = pulseIn(pin, LOW); // パルスがLowである時間を測る
lowpulseoccupancy = lowpulseoccupancy+duration; // Lowである時間を積算する
if ((millis()-starttime) > sampletime_ms) { // 前回の測定から60秒経過したら
ratio = lowpulseoccupancy/(sampletime_ms*10.0); // 積算時間 / 60秒の比率の100倍を計算する
concentration = 1.1*pow(ratio,3)-3.8*pow(ratio,2)+520*ratio+0.62; // 比率から粒子量を計算する
}
}
pulseInはパルス幅を測るArduinoのシステム関数です。
pulseIn(pin, value[, timeout]);
pinで指定するピンがHIGHまたはLOWである時間を測り、結果をマイクロ秒で返します。timeoutを指定した場合、timeout μ秒経過すると0が返ります。
pulseIn関数でパルスがLowである時間を測り、lowpulseoccupancyという変数に積算していき、sampletime_ms秒経ったら比率(ratio)を計算します。
次の式で比率(ratio)から粒子の濃度(concentration)を計算しています。
concentration = 1.1 * ratio ^3 - 3.8 * ratio ^2 + 520 * ratio + 0.62
通常、1分に1回センサの値を測る場合、loop関数の中でセンサの値を取得して処理をおこない、delay関数で1分待つプログラムを書くことが多いです。
void loop() {
センサの値を取得して処理する;
delay(60 * 1000); // 1分待つ
}
センサの値を取得するのにかかる時間は、センサの種類によりますが、数ミリ秒程度です。センサをアクセスする時間はループ時間に対して非常に小さいので、このようなプログラムでも問題ありません。
ところが、Grove ダストセンサの場合、測定周期(今回は1分間)の間、繰り返しpulseIn関数でピンがLOWになっている時間を調べるので、delay関数で待つことができません。そこで、測定周期の開始時刻をstarttimeとして記録し、現在時刻がstarttime+測定周期(sampletime_ms)を超えたら周期処理をおこなうプログラムになっています。
環境センサユニットにアクセスする
温度、湿度、気圧の測定は「M5Stack用環境センサユニット」を使いました。この「環境センサユニット」はDHT12という温湿度センサと、BMP280という気圧センサを内蔵していて、次のようなスペックで温度、湿度、気圧を測定します。
環境センサユニットに内蔵されるBMP280用にライブラリがあります。ライブラリマネージャの検索窓に「grove bmp280」と入力すると、次の図のように「Grove - Barometer Sensor BMP280 by Seeed Studio」というライブラリが見つかるので、その最新版をインストールします。
環境センサユニットをアクセスする部分を抜き出したのが次のプログラムです。DHT12とBMP280をアクセスするオブジェクトを作り、setup関数でI2CとBMP280を初期化します。loop関数ではDHT12とBMP280のオブジェクトを使って温度、湿度、気圧を取得しています。
#include "DHT12.h"
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
DHT12 dht12; // DHT12のオブジェクトを生成
Adafruit_BMP280 bme; // BMP280のオブジェクトを生成
void setup() {
Wire.begin(); // I2Cを初期化する
while (!bme.begin(0x76)) { // BMP280を初期化する
M5.Lcd.println("BMP280 init fail");
}
}
void loop() {
float tmp = dht12.readTemperature(); // 温度を取得する
float humid = dht12.readHumidity(); // 湿度を取得する
float press = bme.readPressure() / 100.0; // 気圧を取得する
}
ホコリの量をLCDにグラフ表示する
取得したホコリの量(粒子濃度)をM5Stackの液晶画面(LCD)にグラフ表示してみましょう。
M5StackのLCDは横幅320ピクセルです。左右に10ピクセルの余白を取り、300ピクセルのエリアに150件のデータを表示することにします。150件のリングバッファを用意し、リングバッファに粒子濃度データを挿入するputData()関数と、リングバッファからデータを読み出し、グラフ描画するdrawChart()関数を用意しました。
loop関数で計算した粒子濃度(concentration)の値をputData関数でリングバッファに挿入し、drawChart関数でグラフを描きます。
drawChart関数ではリングバッファから隣接する2つの粒子濃度の値を取り出し、その値をmap関数を使ってY座標に変換し、M5.Lcd.drawLine関数で線を描いています。これをリングバッファ全体に繰り返すことで、データを折れ線グラフにしています。リングバッファとputData関数、drawChart関数は次のプログラムのようになります。
実験したところ、粒子濃度の値は0から1000ぐらいの範囲でしたので、
グラフのY軸の最大値を1000にしましたが、この値は環境に合わせて調整してください。
// 粒子濃度を入れるリングバッファ
#define NDATA 150
struct d {
bool valid;
float d1;
} data[NDATA];
int dataIndex = 0;
// リングバッファに粒子濃度を挿入する
void putData(float d1) {
if (++dataIndex >= NDATA) {
dataIndex = 0;
}
data[dataIndex].valid = true;
data[dataIndex].d1 = d1;
}
#define X0 10
#define Y0 10
#define MINC 0
#define MAXC 6000
// リングバッファから粒子濃度を読み、グラフ表示する
void drawChart() {
int HEIGHT = M5.Lcd.height();
for (int i = 0, j = dataIndex + 1; i < (NDATA - 1); i++, j++) {
if (data[j % NDATA].valid == false) continue;
int c0 = constrain(data[j % NDATA].d1, MINC, MAXC);
int c1 = constrain(data[(j + 1) % NDATA].d1, MINC, MAXC);
int y0 = map(c0, MINC, MAXC, HEIGHT - Y0, 0);
int y1 = map(c1, MINC, MAXC, HEIGHT - Y0, 0);
M5.Lcd.drawLine(i * 2 + X0, y0, (i + 1) * 2 + X0, y1, WHITE);
}
}
プログラム全体はGithubに公開しました。