LoginSignup
2
1

M5Dial + Bosch BME688で空気品質測定

Posted at

初投稿です。
M5Stack社のM5Dialが手に入ったので、これを使って机上に置くガジェットを作ろうと画策中。その機能の一つとして部屋の空気品質測定をしようとしていて、Bosch BME688の動作確認をしたのでその覚え書きです。

使用機器・環境

Bosch BME688について

Boschの資料はこちら。BME688で実現できる機能についてはこちらのページが参考になります。
ガスをセンシングして様々な分析ができる環境センサですが、ぶっちゃけどうやれば何ができるかわかっていません。資料によると以下の測定ができるとなっています。

  • 揮発性有機化合物(VOC)
  • 揮発性硫黄化合物(VSC)
  • 空気質指数(IAQ)
  • CO2濃度
  • 温度(-40~85℃)
  • 湿度(0~100%)
  • 気圧(300~1100hPa)

温度、湿度、気圧以外はガスセンサを利用して実現しているようです。細かい所は置いておいて、VOCやIAQの測定についてはBoschが提供しているBSEC2 Libraryを使うと取得することができるのでこれを利用しました。今後、特定のガス(火事、ペットの排泄物等)に反応する設定を試すかもしれません。

以下のソースコードを書き込んだM5Dialからの出力は以下のような感じです。
電源投入直後はガスセンサのスタビライゼーションが完了していないため、VOCやCO2の精度がイマイチですが、しばらく放置してaccuracyが3になるとちゃんとした出力になります。
bme688_bsec2_sample.png

M5Dial

  • 使用I2Cポート : PORT A(SCL: 15, SDA: 13)
  • BME688 I2Cアドレス : 0x77 (ENV ProではSDOに+3.3Vが印加されており0x77になる)
  • 使用ライブラリ
    • M5Unified
    • M5Dial
    • BME68x Sensor library
    • BSEC2 Software Library

ソースコード

基本的にはBoschのBSEC2 Software Libraryのサンプル(以下スクショにある'basic')をそのまま使っていますが、
Bsec2オブジェクトに設定するセンサリストにBSEC_OUTPUT_CO2_EQUIVALENTとBSEC_BREATH_VOC_EQUIVALENTを追記し、CO2濃度[ppm]とVOC[ppm]を取得できるようにしています。
vscode_library_bsec2.png

main.cpp
#include <Wire.h>
#include <M5Dial.h>
#include <bme68xLibrary.h>
#include <bsec2.h>

#define SERIAL_SPEED (115200)

#define PORTA_SCL_PIN (15)
#define PORTA_SDA_PIN (13)
#define PORTB_SCL_PIN (1)
#define PORTB_SDA_PIN (2)

#define BME688_ADDR (0x77)

Bsec2 envSensor;

bsecSensor sensorList[] = {
	BSEC_OUTPUT_IAQ,
	BSEC_OUTPUT_CO2_EQUIVALENT,
	BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
	BSEC_OUTPUT_RAW_TEMPERATURE,
	BSEC_OUTPUT_RAW_PRESSURE,
	BSEC_OUTPUT_RAW_HUMIDITY,
	BSEC_OUTPUT_RAW_GAS,
	BSEC_OUTPUT_STABILIZATION_STATUS,
	BSEC_OUTPUT_RUN_IN_STATUS};

void checkBsecStatus(Bsec2 bsec)
{
	if (bsec.status < BSEC_OK)
	{
		USBSerial.println("BSEC error code:" + String(bsec.status));
	}
	else if (bsec.status > BSEC_OK)
	{
		USBSerial.println("BSEC warning code:" + String(bsec.status));
	}

	if (bsec.sensor.status < BME68X_OK)
	{
		USBSerial.println("BME68X error code:" + String(bsec.sensor.status));
	}
	else if (bsec.sensor.status > BME68X_OK)
	{
		USBSerial.println("BME68X warning code:" + String(bsec.sensor.status));
	}
}

void newDataCallback(const bme68xData data, const bsecOutputs outputs, Bsec2 bsec)
{
	if (!outputs.nOutputs)
	{
		return;
	}

	USBSerial.println("BSEC outputs:\n\ttimestamp = " + String((int)(outputs.output[0].time_stamp / INT64_C(1000000))));
	for (uint8_t i = 0; i < outputs.nOutputs; i++)
	{
		const bsecData output = outputs.output[i];
		switch (output.sensor_id)
		{
		case BSEC_OUTPUT_IAQ:
			USBSerial.println("\tiaq = " + String(output.signal));
			USBSerial.println("\t\tiaq accuracy = " + String((int)output.accuracy));
			break;
		case BSEC_OUTPUT_CO2_EQUIVALENT:
			USBSerial.println("\tco2 = " + String(output.signal) + " ppm");
			USBSerial.println("\t\tco2 accuracy = " + String((int)output.accuracy));
			break;
		case BSEC_OUTPUT_BREATH_VOC_EQUIVALENT:
			USBSerial.println("\tbreath voc = " + String(output.signal) + " ppm");
			USBSerial.println("\t\tbreath voc accuracy = " + String((int)output.accuracy));
			break;
		case BSEC_OUTPUT_RAW_TEMPERATURE:
			USBSerial.println("\ttemperature = " + String(output.signal) + " degC");
			break;
		case BSEC_OUTPUT_RAW_PRESSURE:
			USBSerial.println("\tpressure = " + String(output.signal / 100.0) + " hPa");
			break;
		case BSEC_OUTPUT_RAW_HUMIDITY:
			USBSerial.println("\thumidity = " + String(output.signal) + " %RH");
			break;
		case BSEC_OUTPUT_RAW_GAS:
			USBSerial.println("\tgas resistance = " + String(output.signal));
			break;
		case BSEC_OUTPUT_STABILIZATION_STATUS:
			USBSerial.println("\tstabilization status = " + String(output.signal));
			break;
		case BSEC_OUTPUT_RUN_IN_STATUS:
			USBSerial.println("\trun in status = " + String(output.signal));
			break;
		default:
			break;
		}
	}
}

void setup()
{
	auto cfg = M5.config();
	M5Dial.begin(false, false);
	USBSerial.begin(SERIAL_SPEED);
	Wire.begin(PORTA_SDA_PIN, PORTA_SCL_PIN);

	if (!envSensor.begin(BME688_ADDR, Wire))
	{
		checkBsecStatus(envSensor);
	}
	if (!envSensor.updateSubscription(sensorList, ARRAY_LEN(sensorList), BSEC_SAMPLE_RATE_LP))
	{
		checkBsecStatus(envSensor);
	}

	envSensor.attachCallback(newDataCallback);

	USBSerial.println("BSEC library version " +
					  String(envSensor.version.major) + "." + String(envSensor.version.minor) + "." + String(envSensor.version.major_bugfix) + "." + String(envSensor.version.minor_bugfix));
}

void loop()
{
	if (!envSensor.run())
	{
		checkBsecStatus(envSensor);
	}
}

platformio.ini
[env:m5stack-stamps3]
platform = espressif32
board = m5stack-stamps3
framework = arduino
lib_deps = 
	m5stack/M5Dial@^1.0.2
	m5stack/M5Unified@^0.1.12
	boschsensortec/BME68x Sensor library@^1.1.40407
	boschsensortec/BSEC2 Software Library@^1.3.2200
platform_packages = framework-arduinoespressif32 @ https://github.com/bsergei/arduino-esp32.git#issue-8185
monitor_speed = 115200

参考資料

あとがき

M5Stackとそのツールチェーンを使えばRTOS、ライブラリを組み合わせて100行程度でこの機能を実現できるって素晴らしいですね。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1