Какво е BMЕ680
BMЕ680 е сензор за налягане, температура, влажност и въздушно качество, разработен от Bosch Sensortec. Той предоставя прецизни измервания на тези параметри и е използван в различни области като метеорология, промишлени приложения и IoT (Интернет на нещата). BMP680 е възможно да се използва за измерване на налягане от околната среда, както и за разработване на различни видове устройства, които изискват прецизни измервания на атмосферните условия. Днес ние ще видим, как може да си направим метеорологична станция с него в комбинация с ESP32 борд. Повече информация за сензора може да се намери тук. Сензора, който ще ползваме е този и ESP32 борда е NodeMCU-32S – този (версия с micro usb).
Какъв е плана?
BME680 поддържа I2C и SPI комуникация. Плана е да свържем сензора с ESP32 борда и да четем измерванията на определен интервал. След като ги прочетем да ги покажем на серийната конзола и да разберем измерванията.
Какво е I2C
I2C (Inter-Integrated Circuit) е комуникационен протокол, който се използва за свързване на различни компоненти в електронни устройства. Той позволява на тези компоненти да обменят данни и команди помежду си чрез двуправна серийна връзка, като използва две жици: данни (SDA – Serial Data) и часовник (SCL – Serial Clock).
I2C е много популярен за връзката на микроконтролери, сензори, актуатори и други периферни устройства в системи, където се изисква комуникация между различни компоненти. Едно от предимствата на I2C е, че позволява свързването на множество устройства към една и съща линия, като използва различни адреси за идентификация на всяко от тях. Това прави I2C подходящ за изграждане на сложни системи с множество периферни устройства.
Свързване
Нека да проверим сензора и да намерим SDA и SCL пиновете. Обикновено те са написани на сензора. Както може да видим от картината и надписите на сензора, това са 3тия и 4тия пин от ляво на дясно.

Сега трябва да намерим тези пинове и на ESP32 борда. Това може да видим в описанието на конкретния борд или от диаграмата му за пиновете. Нека намерим тези на тази диаграма.

Това са 3-тия и 6-тия пин от горе в дясно. Към тези входове трябва да свържем сензора. За да работи, на BME680 му трябва и захранване. Това можем да вземем от 3.3 V от 1рвия пин от горе в ляво и земя 1 пин в дясно. Нека ги навържем.

Да конфигурираме Arduino IDE 1
За да можем да четем от сензора, трябва първо да конфигурираме средата за програмиране. Затова трябва да инсталираме първо някои библиотеки.
Нека да отидем на Sketch -> Include Library -> Manage Libraries

И нека там потърсим „adafruit bme680“, изберем и инсталираме.

След това трябва да инсталираме и сензорните библиотеки.
Нека да отидем пак на Sketch -> Include Library -> Manage Libraries, да потърсим “Adafruit Unified Sensor” и да го инсталираме.

Ако сте ползвали тази статия за конфигуриране на Arduino IDE-то, трябва да сменим борда, защото в момента ползваме NodeMCU-32S. Нека отидем на Tools -> Board -> Boards manager и нека изберем NodeMCU-32S. За да имаме това, трябва преди това да семе инсталирали esp32 от Espressif Systems библиотеката (показана на по-следващата снимка).


Сега вече сме готови да напишем малко код.
Кода
Кодът, който използваме е взет от тук.
/***
Read Our Complete Guide: https://RandomNerdTutorials.com/esp32-bme680-sensor-arduino/
Designed specifically to work with the Adafruit BME680 Breakout ----> http://www.adafruit.com/products/3660 These sensors use I2C or SPI to communicate, 2 or 4 pins are required to interface. Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! Written by Limor Fried & Kevin Townsend for Adafruit Industries. BSD license, all text above must be included in any redistribution
***/
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
/*#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println(F("BME680 async test"));
if (!bme.begin()) {
Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
while (1);
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
}
void loop() {
// Tell BME680 to begin measurement.
unsigned long endTime = bme.beginReading();
if (endTime == 0) {
Serial.println(F("Failed to begin reading :("));
return;
}
Serial.print(F("Reading started at "));
Serial.print(millis());
Serial.print(F(" and will finish at "));
Serial.println(endTime);
Serial.println(F("You can do other work during BME680 measurement."));
delay(50); // This represents parallel work.
// There's no need to delay() until millis() >= endTime: bme.endReading()
// takes care of that. It's okay for parallel work to take longer than
// BME680's measurement time.
// Obtain measurement results from BME680. Note that this operation isn't
// instantaneous even if milli() >= endTime due to I2C/SPI latency.
if (!bme.endReading()) {
Serial.println(F("Failed to complete reading :("));
return;
}
Serial.print(F("Reading completed at "));
Serial.println(millis());
Serial.print(F("Temperature = "));
Serial.print(bme.temperature);
Serial.println(F(" *C"));
Serial.print(F("Pressure = "));
Serial.print(bme.pressure / 100.0);
Serial.println(F(" hPa"));
Serial.print(F("Humidity = "));
Serial.print(bme.humidity);
Serial.println(F(" %"));
Serial.print(F("Gas = "));
Serial.print(bme.gas_resistance / 1000.0);
Serial.println(F(" KOhms"));
Serial.print(F("Approx. Altitude = "));
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.println(F(" m"));
Serial.println();
delay(2000);
}
След като компилираме кода и го запишем на бода, може да отворим серийната конзола и ще видим следното:

По този начин можем да прочетем стойностите на сензора. Повече подробности за кода можете да прочетете в оригиналната статия, от която той е взет, тук. Но нека обърнем малко повече внимание на стойностите.
Стойностите
Както видяхме сензора може да измери температура, надморско налягане, влажност и газ.
Сензора мери доста точно температурното изменение, но стойността на температурата не е точна. Ако прочетем някои от оригиналните документи на производителя, примерно този тук, ще намерим следното описание:
„2.7 Температурни отмествания, дължащи се на източници на топлина върху платката
Нека да разгледаме стойностите на температурата и влажността, които получаваме от платката. Температура от над тридесет градуса и толкова ниска относителна влажност изглеждат нередни. Поглеждайки към еталонен термометър, можем да видим, че
температурата ни наистина е с няколко градуса по-висока. Означава ли това, че температурният сензор в BME680 е неточен?
Всъщност не, той много точно измерва температурата точно там, където е разположен, на платката. Нашата платка, както повечето устройства, съдържа някои източници на топлина (например MCU, WiFi чип, дисплей, …). Това означава, че температурата на там всъщност е по-висока от температурата на околната среда. Тъй като абсолютното количество на вода във въздуха е приблизително постоянно, това води и до по-ниска относителна влажност на борда, отколкото на на друго място в помещението.“
Това за нас означава, че трябва да вземем референтна стойност с друг термометър и да настроим отместването на температурата спрямо него. Това можем да направим като извадим или съберем отместването към градусите в програмата. Същото важи и за влажността.
Газ и VOC
Когато четем стойността на газ, ние получаваме стойност на съпротивление на сензора, но какво означава това и какво е VOC.
VOC стои за „волатилни органични съединения“ (Volatile Organic Compounds) и се използва за описване на различни химични съединения, които имат висока паропропускливост и се лесно изпаряват при обикновени условия на температура и налягане. Те често се свързват с промишлени процеси, боядисване, лепене, почистване и други дейности, които могат да предизвикат замърсяване на въздуха и да имат вредно въздействие върху здравето на хората и околната среда.
Когато VOC се свързват с газове, съпротивлението на сензора се увеличава, което намалява електропроводимостта, а когато VOC намаляват, съпротивлението намалява, което увеличава електропроводимостта. Така чрез измерване на съпротивлението можем да открием VOC-газовете. Накратко, когато съпротивлението се увеличава и наличието на газове се увеличава, а когато намалява и газовете намаляват.
Но за да можем да покажем разбираеми стойности, ще използваме библиотеката bsec.
BSEC
Нека я инсталираме. Нека да отидем пак на Sketch -> Include Library -> Manage Libraries, да потърсим “bsec” и да го инсталираме.

Нека да копираме следният код, който може да намерим тук с повече описание.
#include <EEPROM.h>
#include "bsec.h"
/* Configure the BSEC library with information about the sensor
18v/33v = Voltage at Vdd. 1.8V or 3.3V
3s/300s = BSEC operating mode, BSEC_SAMPLE_RATE_LP or BSEC_SAMPLE_RATE_ULP
4d/28d = Operating age of the sensor in days
generic_18v_3s_4d
generic_18v_3s_28d
generic_18v_300s_4d
generic_18v_300s_28d
generic_33v_3s_4d
generic_33v_3s_28d
generic_33v_300s_4d
generic_33v_300s_28d
*/
const uint8_t bsec_config_iaq[] = {
#include "config/generic_33v_3s_4d/bsec_iaq.txt"
};
#define BME68X_I2C_ADDR 0x77
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // 360 minutes - 4 times a day
// Helper functions declarations
void checkIaqSensorStatus(void);
void errLeds(void);
void loadState(void);
void updateState(void);
// Create an object of the class Bsec
Bsec iaqSensor;
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint16_t stateUpdateCounter = 0;
String output;
// Entry point for the example
void setup(void)
{
EEPROM.begin(BSEC_MAX_STATE_BLOB_SIZE + 1);
Serial.begin(115200);
delay(1000);
pinMode(LED_BUILTIN, OUTPUT);
iaqSensor.begin(BME68X_I2C_ADDR, Wire);
output = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix);
Serial.println(output);
checkIaqSensorStatus();
iaqSensor.setConfig(bsec_config_iaq);
checkIaqSensorStatus();
loadState();
bsec_virtual_sensor_t sensorList[13] = {
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_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,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
BSEC_OUTPUT_GAS_PERCENTAGE
};
iaqSensor.updateSubscription(sensorList, 13, BSEC_SAMPLE_RATE_LP);
checkIaqSensorStatus();
}
// Function that is looped forever
void loop(void)
{
unsigned long time_trigger = millis();
if (iaqSensor.run()) { // If new data is available
digitalWrite(LED_BUILTIN, HIGH);
output = "Timestamp [ms]: " + String(time_trigger) + "\n";
output += "Raw temperature [°C]: " + String(iaqSensor.rawTemperature) + "\n";
output += "Pressure [hPa]: " + String(iaqSensor.pressure/100) + "\n";
output += "Raw relative humidity [%]: " + String(iaqSensor.rawHumidity) + "\n";
output += "Gas resistance [Ohm]: " + String(iaqSensor.gasResistance/1000) +"\n";
output += "IAQ: " + String(iaqSensor.iaq) + "\n";
output += "IAQ accuracy: " + String(iaqSensor.iaqAccuracy) + "\n";
output += "Temperature [°C]: " + String(iaqSensor.temperature) + "\n";
output += "Relative humidity [%]: " + String(iaqSensor.humidity) + "\n";
output += "Static IAQ: " + String(iaqSensor.staticIaq) + "\n";
output += "CO2 equivalent: " + String(iaqSensor.co2Equivalent) + "\n";
output += "Breath VOC equivalent: " + String(iaqSensor.breathVocEquivalent) + "\n";
Serial.println(output);
digitalWrite(LED_BUILTIN, LOW);
updateState();
} else {
checkIaqSensorStatus();
}
}
// Helper function definitions
void checkIaqSensorStatus(void)
{
if (iaqSensor.bsecStatus != BSEC_OK) {
if (iaqSensor.bsecStatus < BSEC_OK) {
output = "BSEC error code : " + String(iaqSensor.bsecStatus);
Serial.println(output);
for (;;)
errLeds(); /* Halt in case of failure */
} else {
output = "BSEC warning code : " + String(iaqSensor.bsecStatus);
Serial.println(output);
}
}
if (iaqSensor.bme68xStatus != BME68X_OK) {
if (iaqSensor.bme68xStatus < BME68X_OK) {
output = "BME68X error code : " + String(iaqSensor.bme68xStatus);
Serial.println(output);
for (;;)
errLeds(); /* Halt in case of failure */
} else {
output = "BME68X warning code : " + String(iaqSensor.bme68xStatus);
Serial.println(output);
}
}
}
void errLeds(void)
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
delay(100);
digitalWrite(LED_BUILTIN, LOW);
delay(100);
}
void loadState(void)
{
if (EEPROM.read(0) == BSEC_MAX_STATE_BLOB_SIZE) {
// Existing state in EEPROM
Serial.println("Reading state from EEPROM");
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++) {
bsecState[i] = EEPROM.read(i + 1);
Serial.println(bsecState[i], HEX);
}
iaqSensor.setState(bsecState);
checkIaqSensorStatus();
} else {
// Erase the EEPROM with zeroes
Serial.println("Erasing EEPROM");
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE + 1; i++)
EEPROM.write(i, 0);
EEPROM.commit();
}
}
void updateState(void)
{
bool update = false;
if (stateUpdateCounter == 0) {
/* First state update when IAQ accuracy is >= 3 */
if (iaqSensor.iaqAccuracy >= 3) {
update = true;
stateUpdateCounter++;
}
} else {
/* Update every STATE_SAVE_PERIOD minutes */
if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) {
update = true;
stateUpdateCounter++;
}
}
if (update) {
iaqSensor.getState(bsecState);
checkIaqSensorStatus();
Serial.println("Writing state to EEPROM");
for (uint8_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE ; i++) {
EEPROM.write(i + 1, bsecState[i]);
Serial.println(bsecState[i], HEX);
}
EEPROM.write(0, BSEC_MAX_STATE_BLOB_SIZE);
EEPROM.commit();
}
}
В кода трябва да конфигурираме BME68X_I2C_ADDR. В случая той е 0x77. Също така можем да напаснем и конфигурацията на BSEC библиотеката. В кода тя е конфигурирана с generic_33v_3s_4d. Повече информация за библиотеката може да намерим тук.
Когато качим кода на борда, се виждат следните стойности в серийната конзола

Нека да разгледаме стойностите по-отблизо
Timestamp [ms]: 343045 – Това е времето, в което е почнало измерването
Raw temperature [°C]: 25.82 – Това е температурата прочетена от сензора
Pressure [hPa]: 949.18 – Това е надморското налягане прочетено от сензора (не на нивото на морето, а на актуалната височина)
Raw relative humidity [%]: 49.55 – Относителната влажност прочетена от сензора. Или това е съотношение, изразено в проценти, на количеството влага налична в атмосферна, спрямо количеството, което би било налично, ако въздухът е наситен.
Gas resistance [Ohm]: 84.00 – Съпротивлението, което се получава при измерването на VOC.
IAQ: 50.00 – Indoor air quality или Индекс на качеството на въздуха
IAQ accuracy: 1 – Този флаг е интересен и информация за него може да получим тук, но на кратко
Точността на IAQ всъщност отразява текущото състояние на процеса на калибриране на фона, като например:
- IAQ Accuracy=0 може да означава: BSEC току-що е стартирал и сензорът се стабилизира (това обикновено трае 5 мин. в режим LP или 20 мин. в режим ULP), е имало нарушение на времето (т.е. BSEC е бил извикан твърде рано или твърде късно), което трябва да бъде сигнализирано от BSEC чрез предупреждение/флаг за грешка,
- IAQ Accuracy=1 означава, че фоновата история на BSEC е несигурна. Това обикновено означава, че данните от газовия сензор са били твърде стабилни, за да може BSEC да определи ясно своите референтни стойности,
- IAQ Accuracy=2 означава, че BSEC е намерил нови данни за калибриране и в момента се калибрира,
- IAQ Accuracy=3 означава, че BSEC се е калибрирал успешно.
Temperature [°C]: 25.71 – Предполагаемата температура на въздуха
Relative humidity [%]: 49.87 – Предполагаемата относителната влажност на въздуха
Static IAQ: 50.00 – Статичен IAQ: Основната разлика между IAQ и статичния IAQ (sIAQ) се състои в мащабиращия фактор, изчислен въз основа на скорошната история на сензора. Изходът на sIAQ е оптимизиран за стационарни приложения (напр. стационарни устройства на закрито), докато изходът на IAQ е идеален за мобилни приложения (напр. преносими устройства).
CO2 equivalent: 600.00 – Еквивалентната концентрацията на CO2 (CO2eq) [ppm] в околната среда. Тя също се изчислява въз основа на изхода на sIAQ и се извлича от измервания на ЛОС и корелация от полеви проучвания.
Breath VOC equivalent: 0.50 – Еквивалента концентрация на VOC в дъха (bVOCeq) оценява общата концентрация на VOC [ppm] в околната среда. Тя се изчислява въз основа на изхода на sIAQ и се получава от лабораторни тестове.
Доста подробно обяснение на стойностите и какво може да се прави със сензора може да се намери тук.
Заключение
В тази статия видяхме, как може да се програмира сензора BMP680 и да се предават данните му на борд с ESP32 процесор. Информацията събран по този начин ни дава доста точна представа за нашият дом. По този начин може да открием фактори, застрашаващи нашето здраве, да разберем комфортната температура и влажност, да пазим история за измерените данни и да реагираме на тези промени.
Весело програмиране!