Skip to Content

Misura la qualità dell’aria con BME680

Misura la qualità dell’aria con BME680

Impara a misurare la qualità dell’aria con il BME680 sensore ambientale e un ESP32. Il BME680 è un piccolo sensore che può misurare non solo temperatura, umidità e pressione, ma anche la concentrazione di composti organici volatili (VOC), nocivi per la salute.

La BSEC-Arduino-library per il BME680 rende particolarmente semplice valutare la qualità dell’aria, poiché restituisce un Indice di Qualità dell’Aria (IAQ) con valori compresi tra 0 e 400. La tabella seguente mostra come interpretare questi valori IAQ:

Meaning of IAQ values (source)
Significato dei valori IAQ (source)

Useremo questa funzionalità per costruire un misuratore di qualità dell’aria.

Componenti necessari

Di seguito trovi i componenti necessari per questo progetto. Oltre al sensore BME680, ti servirà un microcontrollore. Ho scelto un ESP32 lite più vecchio, ma qualsiasi altro ESP32 andrà bene. Nota che il BSEC-Arduino-library che useremo supporta anche altri microcontrollori. Dai un’occhiata al suo readme file.

Sensore BME680

ESP32 lite Lolin32

ESP32 lite

USB data cable

Cavo dati USB

Dupont wire set

Set di fili Dupont

Half_breadboard56a

Breadboard

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Panoramica del sensore gas BME680

Il BME680 è un piccolo ma potente sensore ambientale 4-in-1 che può misurare temperatura, umidità, pressione barometrica e, cosa più importante per questo progetto, la resistenza ai gas, usata per stimare la qualità dell’aria interna (IAQ). L’immagine sotto mostra il sensore, che misura solo 3x3x1 mm:

BME680 Sensor
Sensore BME680

Il BME680 utilizza un sensore di gas basato sulla tecnologia metal-ossido (MOX). Non rileva direttamente gas specifici, ma reagisce a un’ampia gamma di composti organici volatili (VOC) e gas come il monossido di carbonio (CO) presenti nell’aria. Questi composti sono comunemente rilasciati da:

  • Prodotti per la pulizia e aerosol
  • Vernici e smalti
  • Mobili e tappeti (soprattutto nuovi)
  • Cottura e fumo
  • Respiro umano e odori corporei
  • Apparecchi a combustione

Sebbene il sensore non misuri le concentrazioni di gas in parti per milione (ppm), la libreria BSEC utilizza modelli di machine learning per tradurre le letture di resistenza ai gas in un significativo punteggio IAQ, rendendolo adatto al monitoraggio della qualità dell’aria interna.

Cos’è l’IAQ e perché è importante

Il valore IAQ restituito dalla libreria software del BME680 fornisce una stima numerica dell’inquinamento dell’aria interna. Combina più letture ambientali in un unico punteggio, dove:

  • 0–50 = Qualità dell’aria eccellente
  • 51–100 = Buona
  • 101–150 = Leggermente inquinata
  • 151–200 = Moderatamente inquinata
  • 201–300 = Fortemente inquinata
  • 301–500 = Gravemente inquinata

Questo valore è particolarmente utile perché fornisce un numero chiaro e facile da interpretare, invece delle letture grezze di resistenza ai gas, che possono essere difficili da comprendere da sole.

Scheda breakout per BME680

Poiché il sensore BME680 è molto piccolo, usiamo una scheda breakout per collegarlo all’ESP32. Questo ha anche il vantaggio di poter alimentare il sensore a 3.3V o 5V, poiché la scheda include un regolatore di tensione. L’immagine sotto mostra la scheda breakout con il regolatore e il sensore BME680:

Breakout board for BME680
Scheda breakout per BME680

Il BME680 è la piccola scatola metallica quadrata, sul lato destro. Sul lato sinistro ci sono i pin per l’interfaccia I2C o SPI, insieme alle connessioni di alimentazione (VCC, GND).

Useremo l’interfaccia I2C, poiché richiede solo di collegare SDA e SCL. Nota che il pin SDO determina l’indirizzo I2C del sensore. Lasciare SDO scollegato imposta l’indirizzo a 0x77, mentre collegarlo a massa cambia l’indirizzo a 0x76.

Per maggiori informazioni sul sensore BME680 consulta il nostro BME680 Environmental Sensor with Arduino tutorial, il BME680 Datasheet e forse il Bosch Website for the BME680.

Collegare il BME680 all’ESP32

Per collegare il BME680 a un ESP32 lite via I2C, dobbiamo collegare SCL al pin 23 e SDA al pin 19. I pin per l’I2C hardware dipendono dalla tua scheda e potrebbero differire. Consulta il Find I2C and SPI default pins tutorial per scoprire i pin della tua scheda.

Altrimenti, basta collegare GND a massa (G) e VCC al pin 3V (3.3V) dell’ESP32. L’immagine sotto mostra il cablaggio completo:

Connecting BME680 to ESP32
Collegare il BME680 all’ESP32

Codice per misurare dati ambientali con BME680

Come detto prima, useremo la BSEC-Arduino-library per leggere i dati dal BME680. Puoi installarla tramite il LIBRARY MANAGER. Cerca “bsec” e installa la “BSEC Software Library” come mostrato sotto:

BSEC Software Library installed in LIBRARY MANAGER
Libreria software BSEC installata nel LIBRARY MANAGER

Nota che esiste anche la più recente Bosch-BSEC2-Library, che non ho provato. Ho però usato la più semplice Adafruit BME680 library in un altro tutorial (BME680 Environmental Sensor with Arduino). Ma quella libreria non restituisce misure facilmente interpretabili della qualità dell’aria (IAQ), quindi non possiamo usarla qui.

Il codice seguente mostra come acquisire dati sulla qualità dell’aria come IAQ, percentuale di gas e anche dati ambientali semplici come temperatura, umidità e pressione con il BME680. Spiegherò il codice pezzo per pezzo e cosa fa ogni sezione:

#include "bsec.h"

Bsec sensor;

void checkSensor() {
  if (sensor.bsecStatus != BSEC_OK) {
    Serial.println("BSEC status: " + String(sensor.bsecStatus));
  }
  if (sensor.bme68xStatus != BME68X_OK) {
    Serial.println("BME68X status: " + String(sensor.bme68xStatus));
  }
}

void setup(void) {
  Serial.begin(115200);
  delay(1000);

  sensor.begin(BME68X_I2C_ADDR_HIGH, Wire);
  checkSensor();

  bsec_virtual_sensor_t sensorList[8] = {
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
    BSEC_OUTPUT_GAS_PERCENTAGE
  };

  sensor.updateSubscription(sensorList, 8, BSEC_SAMPLE_RATE_LP);
  checkSensor();
}

void loop(void) {
  if (sensor.run()) {
    Serial.printf("Acc:  %d\n", sensor.iaqAccuracy);
    Serial.printf("IAQ:  %.0f\n", sensor.iaq);
    Serial.printf("sIAQ: %.0f\n", sensor.staticIaq);
    Serial.printf("gas:  %.0f %%\n", sensor.gasPercentage);
    Serial.printf("temp: %.1f C\n", sensor.temperature);
    Serial.printf("hum:  %.0f %%\n", sensor.humidity);
    Serial.printf("pres: %.0f hPa\n", sensor.pressure / 100.0);
    Serial.println();
  }
}

librerie

All’inizio, il codice include la BSEC library, abbreviazione di Bosch Sensortec Environmental Cluster. Questa libreria combina i dati grezzi del sensore BME680 e applica algoritmi per fornire misure significative di qualità dell’aria, come IAQ e percentuale di gas.

#include "bsec.h"

oggetti

Successivamente creiamo un’istanza della classe Bsec chiamata sensor. Questo oggetto ti dà accesso a tutte le funzioni BSEC.

Bsec sensor;

checkSensor

La funzione checkSensor() è una semplice routine di controllo errori. Controlla sia lo stato dell’algoritmo BSEC sia lo stato hardware del BME688. Se c’è un problema, stampa i codici di errore corrispondenti sul Monitor Seriale per permetterti di fare il debug.

void checkSensor() {
  if (sensor.bsecStatus != BSEC_OK) {
    Serial.println("BSEC status: " + String(sensor.bsecStatus));
  }
  if (sensor.bme68xStatus != BME68X_OK) {
    Serial.println("BME68X status: " + String(sensor.bme68xStatus));
  }
}

Nota che codici di stato positivi indicano un avviso, mentre codici negativi indicano un errore!

setup

Nella funzione setup(), il codice avvia la comunicazione seriale a 115200 baud per poter stampare l’output sul Monitor Seriale. Include anche un breve ritardo per assicurarsi che la porta seriale sia pronta prima di continuare.

void setup(void) {
  Serial.begin(115200);
  delay(1000);

Poi, il sensore BME688 viene inizializzato via I2C usando la funzione sensor.begin(). Viene usato l’indirizzo BME68X_I2C_ADDR_HIGH, che corrisponde a 0x77. Dopo l’inizializzazione, il codice controlla lo stato del sensore.

sensor.begin(BME68X_I2C_ADDR_HIGH, Wire);
checkSensor();

La costante si chiama “HIGH”, poiché se il pin SDO non è collegato, una resistenza interna lo porta ad alto. Se vuoi usare l’altro indirizzo I2C (0x76) collegando SDO a massa, modifica di conseguenza.

Successivamente, lo sketch definisce a quali sensori virtuali iscriversi. Questo ti permette di selezionare quali sensori (temperatura, umidità, …) e quali valori derivati (es. temperatura grezza o compensata dal calore) usare. È importante per ridurre il consumo disabilitando sensori inutilizzati.

La funzione sensor.updateSubscription() dice alla libreria quali dati da quali sensori vogliamo e con quale frequenza:

bsec_virtual_sensor_t sensorList[8] = {
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
    BSEC_OUTPUT_GAS_PERCENTAGE
  };

  sensor.updateSubscription(sensorList, 8, BSEC_SAMPLE_RATE_LP);
  checkSensor();
}

La costante BSEC_SAMPLE_RATE_LP indica campionamento a basso consumo (LP). Questo è un altro modo per ridurre il consumo del sensore. Esiste, per esempio, un BSEC_SAMPLE_RATE_ULP per consumo ultra basso (ULP). Puoi anche definire diverse frequenze di campionamento per diversi set di sensori virtuali. Vedi l’esempio basic_config_state_ULP_LP.

loop

La funzione loop() fa il lavoro in tempo reale. La funzione sensor.run() controlla se ci sono nuovi dati disponibili dalla libreria BSEC. Se sì, il codice stampa varie letture sul Monitor Seriale:

void loop(void) {
  if (sensor.run()) {
    Serial.printf("Acc:  %d\n", sensor.iaqAccuracy);
    Serial.printf("IAQ:  %.0f\n", sensor.iaq);
    Serial.printf("sIAQ: %.0f\n", sensor.staticIaq);
    Serial.printf("gas:  %.0f %%\n", sensor.gasPercentage);
    Serial.printf("temp: %.1f C\n", sensor.temperature);
    Serial.printf("hum:  %.0f %%\n", sensor.humidity);
    Serial.printf("pres: %.0f hPa\n", sensor.pressure / 100.0);
    Serial.println();
  }
}

Nota che questo è solo un piccolo sottoinsieme delle possibili misure e valori che la libreria BSEC può restituire. La tabella seguente elenca tutti i valori, il loro significato e intervallo:

ValoreDescrizioneUnità/IntervalloTipoCategoria
iaqIndice di Qualità dell’Aria Interna (in tempo reale, dinamico)Indice (0–500)Elaboratofloat
iaqAccuracyConfidenza nel valore iaq0–3Statouint8_t
staticIaqValore IAQ smussato (meno sensibile alle fluttuazioni)Indice (0–500)Elaboratofloat
staticIaqAccuracyConfidenza nel valore staticIaq0–3Statouint8_t
co2EquivalentConcentrazione stimata di CO₂ derivata dai VOCppmElaboratofloat
co2AccuracyConfidenza nel valore co2Equivalent0–3Statouint8_t
breathVocEquivalentStima equivalente di VOC nel respiroppmElaboratofloat
breathVocAccuracyConfidenza nel valore breathVocEquivalent0–3Statouint8_t
compGasValueValore compensato del gas usato internamenteOhm (normalizzato)Internofloat
compGasAccuracyConfidenza nel valore compGasValue0–3Statouint8_t
gasPercentagePercentuale stimata di aria pulita%Elaboratofloat
gasPercentageAccuracyConfidenza nel valore gasPercentage0–3Statouint8_t
rawTemperatureTemperatura non compensata dal sensore°CGrezzafloat
temperatureTemperatura compensata (corretta per l’autoriscaldamento del sensore)°CCompensatafloat
rawHumidityUmidità relativa non compensata%Grezzafloat
humidityUmidità relativa compensata%Compensatafloat
pressurePressione barometricahPaGrezzafloat
gasResistanceResistenza grezza del sensore di gasOhmGrezzafloat
stabStatusSe il campione è stabilizzato0 o 1 (booleano)Statofloat
runInStatusSe il sensore è ancora nella fase di run-in0 o 1Statofloat

Nel codice stampiamo i valori iaqAccuracy (Acc), iaq (IAQ), staticIaq (sIAQ), gasPercentage (gas), temperature (temp), humidity (hum) e pressure (pres). Temperatura e umidità sono valori compensati (considerando il riscaldatore del sensore di gas) e la pressione è divisa per 100 per restituire la pressione in ettopascal (hPa).

I valori più interessanti sono iaq (IAQ) e staticIaq (sIAQ), dove quest’ultimo è una versione più stabile del primo. Il valore IAQ è direttamente collegato a un’interpretazione pratica della qualità dell’aria come mostrato nella tabella seguente:

Meaning of IAQ values (source)
Significato dei valori IAQ (source)

Output

Se carichi ed esegui il codice dovresti vedere le misurazioni apparire nel Monitor Seriale:

Output in Serial Monitor
Output nel Monitor Seriale

Tuttavia, il sensore di gas del BME680 richiede un tempo considerevole per il burn-in iniziale, che va da ~5 a 30 minuti a seconda delle condizioni ambientali e della frequenza di campionamento. Fino ad allora il valore IAQ appare tipicamente fisso a 25 o 50.

Il valore iaqAccuracy, che viene stampato come Acc sul Monitor Seriale, è un indicatore di confidenza per le misure IAQ (e altre) relative ai gas.

All’inizio vedrai un valore 0, che significa che le misure IAQ non sono ancora valide (vedi l’output di esempio sopra). Col tempo il valore iaqAccuracy salirà da 0 → 1 → 2 → 3 e una volta raggiunto 3 le letture IAQ sono considerate valide! Qui sotto un esempio di output dopo il burn-in completato del sensore con un iaqAccuracy (Acc) di 3:

Output in Serial Monitor after burn-in
Output nel Monitor Seriale dopo il burn-in

Ovviamente è fastidioso dover aspettare fino a 20 minuti per misure valide ogni volta che il BME680 si resetta o perde alimentazione. Fortunatamente il burn-in può essere accorciato salvando e ripristinando lo stato del sensore BME680. Come fare è descritto nella sezione successiva.

Codice per salvare e ripristinare lo stato del BME680

Il codice qui sotto estende il nostro codice precedente con due funzioni, saveState e loadState, che ci permettono di salvare lo stato del sensore BME680 e ridurre il tempo necessario prima che le misure relative ai gas siano disponibili:

#include <Preferences.h>
#include "bsec.h"

const unsigned long savePeriod = 3600 * 1000;  // 1 hour

Bsec sensor;
Preferences prefs;

void checkSensor() {
  if (sensor.bsecStatus != BSEC_OK) {
    Serial.println("BSEC status: " + String(sensor.bsecStatus));
  }
  if (sensor.bme68xStatus != BME68X_OK) {
    Serial.println("BME68X status: " + String(sensor.bme68xStatus));
  }
}

void saveState() {
  uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
  sensor.getState(state);
  prefs.begin("bsec", false);
  prefs.putBytes("state", state, BSEC_MAX_STATE_BLOB_SIZE);
  prefs.end();
  Serial.println("Sensor state saved");
}

void loadState() {
  prefs.begin("bsec", true); 
  if (prefs.getBytesLength("state") > 0) {
    uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
    prefs.getBytes("state", state, BSEC_MAX_STATE_BLOB_SIZE);
    sensor.setState(state);
    Serial.println("Sensor state loaded");
  }
  prefs.end();
}

void setup(void) {
  Serial.begin(115200);
  delay(1000);

  sensor.begin(BME68X_I2C_ADDR_HIGH, Wire);
  checkSensor();
  loadState();

  bsec_virtual_sensor_t sensorList[8] = {
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
    BSEC_OUTPUT_GAS_PERCENTAGE
  };

  sensor.updateSubscription(sensorList, 8, BSEC_SAMPLE_RATE_LP);
  checkSensor();
}

void loop(void) {
  static unsigned long lastSave = millis();

  if (sensor.run()) {
    Serial.printf("Acc:  %d\n", sensor.iaqAccuracy);
    Serial.printf("IAQ:  %.0f\n", sensor.iaq);
    Serial.printf("sIAQ: %.0f\n", sensor.staticIaq);
    Serial.printf("gas:  %.0f %%\n", sensor.gasPercentage);
    Serial.printf("temp: %.1f C\n", sensor.temperature);
    Serial.printf("hum:  %.0f %%\n", sensor.humidity);
    Serial.printf("pres: %.0f hPa\n", sensor.pressure / 100.0);
    Serial.println();

    if (millis() - lastSave > savePeriod && sensor.iaqAccuracy >=3) {
      saveState();
      lastSave = millis();
    }
  }
}

saveState

La funzione saveState usa la Preferences library dell’ESP32 per memorizzare lo stato del BME680 nella memoria non volatile (NVS) a bordo dell’ESP32. Questi dati sono conservati anche dopo riavvii o interruzioni di alimentazione.

void saveState() {
  uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
  sensor.getState(state);
  prefs.begin("bsec", false);
  prefs.putBytes("state", state, BSEC_MAX_STATE_BLOB_SIZE);
  prefs.end();
  Serial.println("Sensor state saved");
}

Lo stato include baseline apprese per resistenza ai gas, temperatura e umidità, che il sensore usa per calcolare IAQ e altri output accurati.

La funzione riserva un blocco di memoria di dimensione BSEC_MAX_STATE_BLOB_SIZE per lo stato e poi copia lo stato del sensore in questo blocco chiamando getState(state).

Poi apriamo un namespace chiamato "bsec" nella NVS dell’ESP32 chiamando prefs.begin("bsec", false), dove false indica modalità lettura e scrittura.

Successivamente memorizziamo l’array binario dello stato nella memoria flash sotto la chiave "state" e chiudiamo la sessione NVS (prefs.end) per assicurarci che i dati siano salvati

loadState

La funzione loadState recupera lo stato precedentemente salvato, se esiste:

void loadState() {
  prefs.begin("bsec", true); 
  if (prefs.getBytesLength("state") > 0) {
    uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
    prefs.getBytes("state", state, BSEC_MAX_STATE_BLOB_SIZE);
    sensor.setState(state);
    Serial.println("Sensor state loaded");
  }
  prefs.end();
}

Se prefs.getBytesLength("state") restituisce 0, significa che non abbiamo ancora salvato lo stato del sensore e saltiamo la lettura.

Altrimenti chiamiamo prefs.getBytes("state", state, BSEC_MAX_STATE_BLOB_SIZE) per recuperare lo stato e scriverlo nel sensore tramite sensor.setState(state).

setup

Nella funzione setup chiamiamo loadState, dopo aver inizializzato il sensore. Quindi, se l’ESP32 e di conseguenza il sensore BME680 perdono alimentazione o si resettano, setup viene chiamata e ripristiniamo lo stato del sensore – se è stato salvato.

void setup(void) {
  ...

  sensor.begin(BME68X_I2C_ADDR_HIGH, Wire);

  loadState();

  ...
}

loop

Nella funzione loop salviamo regolarmente lo stato del sensore, per esempio ogni ora, a condizione che iaqAccuracy sia 3 (o maggiore):

void loop(void) {
  static unsigned long lastSave = millis();

  if (sensor.run()) {
    Serial.printf("Acc:  %d\n", sensor.iaqAccuracy);
    ...

    if (millis() - lastSave > savePeriod && sensor.iaqAccuracy >=3) {
      saveState();
      lastSave = millis();
    }
  }
}

Il periodo di tempo per salvare lo stato è controllato dalla costante savePeriod. Ho scelto un’ora, ma non è necessario salvare così frequentemente. Anche ogni tre o quattro ore va bene.

const unsigned long savePeriod = 3600 * 1000;  // 1 hour

Nota che nonostante il salvataggio e il ripristino dello stato del sensore, il valore iaqAccuracy sarà ancora 0 dopo un riavvio del sensore, ma il tempo per raggiungere 3 sarà più breve.

Conclusione

In questo tutorial hai imparato come misurare la qualità dell’aria con il BME680 sensore ambientale, un ESP32 e la BSEC-Arduino-library.

La BSEC-Arduino-library contiene diversi esempi di codice che vale la pena esplorare. Il codice di questo tutorial deriva dall’esempio basic.ino. L’esempio basic_config_state.ino mostra come salvare e ripristinare lo stato del sensore. E con la basic_config_state_ulp_plus.ino puoi far funzionare il sensore in modalità ultra-basso consumo (ulp). Infine, per il deep-sleep vedi l’esempio esp32DeepSleep.ino.

Il BME680 misura anche temperatura, umidità e pressione, ma se questo è il tuo interesse principale e non ti servono i valori IAQ, usa il Adafruit BME680 library invece della più complessa BSEC-Arduino-library. Vedi il tutorial BME680 Environmental Sensor with Arduino per dettagli.

Se non ti servono misure di gas, ti suggerisco il sensore BME280, che è più piccolo e meno costoso. Vedi il tutorial How To Use BME280 Pressure Sensor With Arduino per maggiori informazioni.

Se hai domande, sentiti libero di lasciarle nella sezione commenti.

Buon divertimento con il tinkering ; )