001 BME688 BSECライブラリ 備忘録

# 1.はじめに
　赤ちゃんが泣いているときに、まずオムツ交換が必要かどうかを確認するのですが、毎回肌着の股の部分のボタンを外して目視で確認するのをどうにかしたいと思いました。何か良い方法がないかと考えていたのですが、Bosch社のBME688というセンサが良さそうだったので、まずはこれを使ってみようと思います。

https://www.switch-science.com/products/9274

# 2.BME688 データシート
　まずはデータシートを読んでみました。データシートの3章～5章を読めば大体のことが書かれています。センサのメモリマップや測定結果の補正サンプルプログラムなども記載されていますが、Bosch社よりBSECライブラリが提供されているので、まずはこれを使って動かしてみることにします。なお、4章にライブラリに関する説明が記載されているので、簡単に抜粋します。

　BME688自体はForced/Parallel/Sleepの3つの動作モードを有していますが、BSECライブラリは以下の表でまとめられている5種類の動作モードを有しています。
| BSEC power mode | Update rate | Average current consumption |
|:-:|:-:|:-:|
| Standard gas scan mode (scan) | 10.8s | 3.96mA |
| Ultra-low power mode (ULP) | 300s |  0.09A  |
| quick Ultra-low power mode (q-ULP) | 3s(T,P,H)/300s(IAQ) | 0.1mA |
| Low power mode (LP)  | 3s | 0.9mA |
| Continuous mode (for testing purposes only) | 1s | 12mA |

　また、BSECライブラリを使うことで、センサの直値だけではなく、以下のような出力を得ることができます。`IAQ(Index of Air Quality)` や `CO2推定値` などを取得できるようです。
 
| Output | Description |
|:-:|:-:|
| Raw pressure          | Raw data from sensor API bypassed to BSEC output |
| Raw temperature       | Raw data from sensor API bypassed to BSEC output |
| Raw relative humidity | Raw data from sensor API bypassed to BSEC output |
| Raw gas resistance    | Raw data from sensor API bypassed to BSEC output |
| Sensor-compensated temperature (℃) | Temperature which is compensated for internal cross-influences caused by the BME sensor |
| Sensor-compensated relative humidity (%) | Relative humidity which is compensated for internal cross-influences caused by the BME sensor |
| Sensor-compensated gas resistance(Ohm) | Raw gas resistance compensated by temperature and humidity influences. |
| Ambient temperature (℃) | Ambient temperature after compensating the influence of device (where BME688 is integrated in) heatsources. |
| Ambient relative humidity (%) | Ambient relative humidity after compensating the influence of device (where BME688 is integrated in) heatsources. |
| IAQ (0-500) | Index for Air Quality, especially recommended for mobile devices, since the auto-trim algorithm automatically adopts to different environments. |
| Static IAC ("s-IAQ") | "Static" Index for Air Quality, especially recommended for stationary devices (w/o auto-trimming algorithm) |
| CO2 equivalents (ppm) | Estimation of the CO2 level in ppm. The sensor does not directly measure CO2, but derives this from the average correlation between VOCs and CO2 in human's exhaled breath. |
| b-VOC equivalents (ppm) | Conversion into breath-VOC equivalents in ppm concentration. The scaling is derived from lab tests with the b-VOC gas mixture descrived in Table 7. |
| Accuracy status (0-3) | Accuracy status of IAQ |
| Stabilization time status | Indicates if the sensor is undergoing initial stabilization during its first use after production. |
| Run in status | Indicates when the sensor is ready after after switch-on |
| Gas (%) | Alternative indicator for air pollution which rates the current raw gas resistance value based on the individual sensor history: 0%="lowest air pollution ever measured" 100%="highest air pollution level ever measured" |
| Gas scan result(%) | The gas scan result is given in % for each of the used classes. In standard scan mode, the probability of H2S and non H2S class is provided by the variables GAS_ESTIMATE_1 & GAS_ESTIMATE_2 respectively. A maximum of 4 classes can be used by configuring using BME AI-Studio. |

# 3.BSEC2.x ライブラリ
　以下から`BSEC2.X`ライブラリをダウンロードして、Arduino IDEにインポートしておきます。ダウンロード時にメールアドレスの入力などが必要になります。

https://www.bosch-sensortec.com/software-tools/software/BME688-software/

　BME688センサはSPIとI2Cで接続が可能なのですが、I2C接続で使う場合のサンプルは`examples/genelic_examples` 以下のソースコードが参考になりました。

　またZipファイル内には、`keywords.txt` が含まれており、関数名や動作モードの設定パラメータ定義名、出力値の定義名などが記載されています。
 

# ソースコード
　BSEC2ライブラリを用いて、BME688の動作確認を実施した最低限のコードを以下に示します。なおコントローラには `M5StickC Plus` を使用しました。

また、注意点は以下の通りです。
* BSEC2ライブラリより取得したいパラメータを`sensorList[]`に指定する
* 動作モードは`updateSubscription()`で指定する
* いずれも設定値は`keywords.txt`参照

``` c:BME688_test.ino
#include <M5StickCPlus.h>
#include <Wire.h>
#include <bsec2.h>

/* PORT DEFINITION */
#define G32_SDA 32
#define G33_SCL 33

/* Macros used */
#define PANIC_LED   10
#define ERROR_DUR   1000

/* プロトタイプ宣言 */
void errLeds(void);
void checkBsecStatus(Bsec2 bsec);
void newDataCallback(const bme68xData data, const bsecOutputs outputs, Bsec2 bsec);

Bsec2 envSensor;

void setup() {
  M5.begin();

  /* LED */
  pinMode(PANIC_LED, OUTPUT);

  /* Internal I2C : IMU(MPU6886)/PMIC(AXP192)/RTC(BM8563) */
  // PMIC(AXP192)
  M5.Axp.EnableCoulombcounter();


  /* HY2.0-4P(Grove) */
  Wire.begin(G32_SDA, G33_SCL);
  Wire.setClock(100000);
  
  // BME688
  // BSEC2ライブラリより出力させたい項目を指定する。設定値はkeywords.txtを参照。
  bsecSensor sensorList[] = {
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_STABILIZATION_STATUS,
    BSEC_OUTPUT_RUN_IN_STATUS
  };
  
  /* Initialize BME688 library */
  if(!envSensor.begin(BME68X_I2C_ADDR_HIGH, Wire))
  {
    checkBsecStatus(envSensor);
  }

  /* Subsribe to the desired BSEC2 outputs */
  // "BSEC_SAMPLE_RATE_***"で動作モードを指定する。設定値はkeywords.txtを参照。
  if (!envSensor.updateSubscription(sensorList, ARRAY_LEN(sensorList), BSEC_SAMPLE_RATE_LP))
  {
    checkBsecStatus(envSensor);
  }

  /* Whenever new data is available call the newDataCallback function */
  envSensor.attachCallback(newDataCallback);

  /* USB Serial */
  Serial.begin(115200);

Serial.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);
  }

}

void errLeds(void)
{
    while(1)
    {
        digitalWrite(PANIC_LED, HIGH);
        delay(ERROR_DUR);
        digitalWrite(PANIC_LED, LOW);
        delay(ERROR_DUR);
    }
}

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

    Serial.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:
                Serial.println("\tiaq = " + String(output.signal));
                Serial.println("\tiaq accuracy = " + String((int) output.accuracy));
                break;
            case BSEC_OUTPUT_RAW_TEMPERATURE:
                Serial.println("\ttemperature = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RAW_PRESSURE:
                Serial.println("\tpressure = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RAW_HUMIDITY:
                Serial.println("\thumidity = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RAW_GAS:
                Serial.println("\tgas resistance = " + String(output.signal));
                break;
            case BSEC_OUTPUT_STABILIZATION_STATUS:
                Serial.println("\tstabilization status = " + String(output.signal));
                break;
            case BSEC_OUTPUT_RUN_IN_STATUS:
                Serial.println("\trun in status = " + String(output.signal));
                break;
            case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE:
                Serial.println("\tcompensated_temperature = " + String(output.signal));
                break;
            case BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY:
                Serial.println("\tcompensated_humidity = " + String(output.signal));
                break;
            default:
                break;
        }
    }
}

void checkBsecStatus(Bsec2 bsec)
{
    if (bsec.status < BSEC_OK)
    {
        Serial.println("BSEC error code : " + String(bsec.status));
        errLeds(); /* Halt in case of failure */
    }
    else if (bsec.status > BSEC_OK)
    {
        Serial.println("BSEC warning code : " + String(bsec.status));
    }

    if (bsec.sensor.status < BME68X_OK)
    {
        Serial.println("BME68X error code : " + String(bsec.sensor.status));
        errLeds(); /* Halt in case of failure */
    }
    else if (bsec.sensor.status > BME68X_OK)
    {
        Serial.println("BME68X warning code : " + String(bsec.sensor.status));
    }
}
```


# 動作確認

　シリアルモニタに以下のように動作結果が表示されます。

 ![BME688_output_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186520/3add22b8-0928-516d-51e1-c18947a19a63.png)