Skip to Content

Mide la calidad del aire con BME680

Mide la calidad del aire con BME680

Aprende a medir la calidad del aire con el BME680 sensor ambiental y un ESP32. El BME680 es un sensor pequeño que no solo puede medir temperatura, humedad y presión, sino también la concentración de compuestos orgánicos volátiles (VOC), que son perjudiciales para la salud.

El BSEC-Arduino-library para el BME680 facilita especialmente la evaluación de la calidad del aire, ya que devuelve un Índice de Calidad del Aire (IAQ) con valores en el rango de 0 a 400. La siguiente tabla muestra cómo interpretar esos valores de IAQ:

Meaning of IAQ values (source)
Significado de los valores de IAQ (source)

Usaremos esta funcionalidad para construir un medidor de calidad del aire.

Partes necesarias

A continuación encontrarás las partes necesarias para este proyecto. Además del sensor BME680, necesitarás un microcontrolador. Yo elegí un ESP32 lite antiguo, pero cualquier otro ESP32 funcionará perfectamente. Ten en cuenta que el BSEC-Arduino-library que vamos a usar también soporta otros microcontroladores. Echa un vistazo a su readme file.

Sensor BME680

ESP32 lite Lolin32

ESP32 lite

USB data cable

Cable de datos USB

Dupont wire set

Juego de cables Dupont

Half_breadboard56a

Protoboard

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.

Descripción general del sensor de gas BME680

El BME680 es un sensor ambiental pequeño pero potente 4 en 1 que puede medir temperatura, humedad, presión barométrica y, lo más importante para este proyecto, resistencia de gas, que se usa para estimar la calidad del aire interior (IAQ). La imagen a continuación muestra el sensor, que mide solo 3x3x1 mm:

BME680 Sensor
Sensor BME680

El BME680 utiliza un sensor de gas basado en tecnología de óxido metálico (MOX). No detecta gases individuales directamente, sino que reacciona a una amplia gama de compuestos orgánicos volátiles (VOC) y gases como el monóxido de carbono (CO) en el aire. Estos compuestos se liberan comúnmente de:

  • Productos de limpieza y aerosoles
  • Pinturas y barnices
  • Muebles y alfombras (especialmente los nuevos)
  • Cocinar y fumar
  • Aliento humano y olor corporal
  • Aparatos de combustión

Aunque el sensor no mide concentraciones de gas en partes por millón (ppm), la biblioteca BSEC usa modelos de aprendizaje automático para traducir las lecturas de resistencia de gas del sensor en un puntaje significativo de IAQ, haciéndolo adecuado para el monitoreo de la calidad del aire interior.

Qué es IAQ y por qué importa

El valor IAQ que devuelve la biblioteca de software del BME680 ofrece una estimación numérica de la contaminación del aire interior. Combina múltiples lecturas ambientales en un solo puntaje, donde:

  • 0–50 = Calidad de aire excelente
  • 51–100 = Buena
  • 101–150 = Ligeramente contaminada
  • 151–200 = Moderadamente contaminada
  • 201–300 = Muy contaminada
  • 301–500 = Severamente contaminada

Este valor es especialmente útil porque te da un número claro y fácil de entender en lugar de lecturas crudas de resistencia de gas, que pueden ser difíciles de interpretar por sí solas.

Placa breakout para BME680

Dado que el sensor BME680 es tan pequeño, usamos una placa breakout para conectarlo al ESP32. Esto también tiene la ventaja de que podemos alimentar el sensor con 3.3V o 5V, ya que la placa incluye un regulador de voltaje. La imagen a continuación muestra la placa breakout con el regulador y el sensor BME680:

Breakout board for BME680
Placa breakout para BME680

El BME680 es la pequeña caja metálica cuadrada, en el lado derecho. En el lado izquierdo están los pines para la interfaz I2C o SPI, junto con las conexiones de alimentación (VCC, GND).

Vamos a usar la interfaz I2C, ya que solo requiere conectar SDA y SCL. Ten en cuenta que el pin SDO determina la dirección I2C del sensor. Dejar SDO sin conectar establece la dirección en 0x77, mientras que conectarlo a tierra cambia la dirección a 0x76.

Para más información sobre el sensor BME680, consulta nuestro BME680 Environmental Sensor with Arduinotutorial, el BME680 Datasheety tal vez el Bosch Website for the BME680.

Conexión del BME680 al ESP32

Para conectar el BME680 a un ESP32 lite vía I2C, necesitamos conectar SCL al pin 23 y SDA al pin 19. Los pines para I2C hardware dependen de tu placa y pueden variar. Consulta el Find I2C and SPI default pinstutorial para identificar los pines de tu placa.

Además, solo necesitamos conectar GND a tierra (G) y VCC al pin 3V (3.3V) del ESP32. La imagen a continuación muestra el cableado completo:

Connecting BME680 to ESP32
Conexión del BME680 al ESP32

Código para medir datos ambientales con BME680

Como se mencionó antes, vamos a usar la BSEC-Arduino-library para leer datos del BME680. Puedes instalarla vía LIBRARY MANAGER. Busca «bsec» e instala la «BSEC Software Library» como se muestra a continuación:

BSEC Software Library installed in LIBRARY MANAGER
Biblioteca BSEC instalada en LIBRARY MANAGER

Ten en cuenta que también existe la más reciente Bosch-BSEC2-Library, que no he probado. Sin embargo, he usado la más simple Adafruit BME680 library en otro tutorial (BME680 Environmental Sensor with Arduino). Pero esa biblioteca no devuelve medidas fácilmente interpretables de calidad del aire (IAQ), por lo que no la usaremos aquí.

El siguiente código muestra cómo capturar datos de calidad del aire como IAQ, porcentaje de gas y también datos ambientales simples como temperatura, humedad y presión con el BME680. Desglosaré el código por partes y explicaré qué hace cada sección:

#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();
  }
}

bibliotecas

Al inicio, el código incluye la BSEC library, que es la abreviatura de Bosch Sensortec Environmental Cluster. Esta biblioteca combina datos crudos del sensor BME680 y aplica algoritmos para entregar medidas significativas de calidad del aire, como IAQ y porcentaje de gas.

#include "bsec.h"

objetos

Luego creamos una instancia de la clase Bsecllamada sensor. Este objeto te da acceso a todas las funciones de BSEC.

Bsec sensor;

checkSensor

La función checkSensor()es una rutina simple para verificar errores. Revisa tanto el estado del algoritmo BSEC como el estado del hardware BME688. Si hay un problema, imprime los códigos de error correspondientes en el Monitor Serial para que puedas depurarlos.

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

Ten en cuenta que los códigos de estado positivos indican una advertencia, mientras que los negativos indican un error.

setup

En la función setup(), el código inicia la comunicación serial a 115200 baudios para poder imprimir salida en el Monitor Serial. También incluye una breve pausa para asegurar que el puerto serial esté listo antes de continuar.

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

Luego, el sensor BME688 se inicializa vía I2C usando la función sensor.begin(). Se usa la dirección BME68X_I2C_ADDR_HIGH, que corresponde a 0x77. Después de la inicialización, el código verifica el estado del sensor.

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

La constante se llama «HIGH», ya que si el pin SDO no está conectado, una resistencia interna lo mantiene en alto. Si quieres usar la otra dirección I2C (0x76) conectando SDO a tierra, cambia esto en consecuencia.

Luego, el sketch define a qué sensores virtuales suscribirse. Esto te permite seleccionar qué sensores (temperatura, humedad, …) y qué valores derivados (por ejemplo, temperatura cruda versus compensada por calor) usar. Esto es importante si quieres reducir el consumo de energía desactivando sensores no usados.

La función sensor.updateSubscription() indica a la biblioteca qué datos de qué sensor queremos y con qué frecuencia:

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 constante BSEC_SAMPLE_RATE_LPsignifica muestreo de bajo consumo (LP). Esta es otra forma de reducir el consumo del sensor. Por ejemplo, existe un modo BSEC_SAMPLE_RATE_ULP para consumo ultra bajo (ULP). Incluso puedes definir diferentes tasas de muestreo para distintos conjuntos de sensores virtuales. Consulta el ejemplo basic_config_state_ULP_LP.

loop

La función loop()realiza el trabajo en tiempo real. La función sensor.run()verifica si hay datos nuevos disponibles de la biblioteca BSEC. Si es así, el código imprime varias lecturas en el Monitor Serial:

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();
  }
}

Ten en cuenta que esto es solo un pequeño subconjunto de las posibles mediciones y valores que la biblioteca BSEC puede devolver. La siguiente tabla lista todos los valores, su significado y rango:

ValorDescripciónUnidad/RangoTipoClase
iaqÍndice de Calidad del Aire Interior (en tiempo real, dinámico)Índice (0–500)Procesadofloat
iaqAccuracyConfianza en el valor iaq0–3Estadouint8_t
staticIaqValor IAQ suavizado (menos sensible a fluctuaciones)Índice (0–500)Procesadofloat
staticIaqAccuracyConfianza en staticIaq0–3Estadouint8_t
co2EquivalentConcentración estimada de CO₂ a partir de VOCsppmProcesadofloat
co2AccuracyConfianza en co2Equivalent0–3Estadouint8_t
breathVocEquivalentEstimación equivalente de VOCs en el alientoppmProcesadofloat
breathVocAccuracyConfianza en breathVocEquivalent0–3Estadouint8_t
compGasValueValor de gas compensado usado internamenteOhmios (normalizado)Internofloat
compGasAccuracyConfianza en compGasValue0–3Estadouint8_t
gasPercentagePorcentaje estimado de aire limpio%Procesadofloat
gasPercentageAccuracyConfianza en gasPercentage0–3Estadouint8_t
rawTemperatureTemperatura sin compensar del sensor°CCrudofloat
temperatureTemperatura compensada (ajustada por auto-calentamiento del sensor)°CCompensadofloat
rawHumidityHumedad relativa sin compensar%Crudofloat
humidityHumedad relativa compensada%Compensadofloat
pressurePresión barométricahPaCrudofloat
gasResistanceResistencia cruda del sensor de gasOhmiosCrudofloat
stabStatusSi la muestra está estabilizada0 o 1 (tipo booleano)Estadofloat
runInStatusSi el sensor aún está en fase de puesta a punto0 o 1Estadofloat

En el código imprimimos los valores iaqAccuracy(Acc), iaq(IAQ), staticIaq(sIAQ), gasPercentage(gas), temperature(temp), humidity(hum) y pressure(pres). La temperatura y humedad son valores compensados (teniendo en cuenta el calentador del sensor de gas) y la presión se divide por 100 para devolver la presión en hectopascales (hPa).

Los valores más interesantes son iaq(IAQ) y staticIaq(sIAQ), siendo este último una versión más estable del primero. El valor IAQ está directamente vinculado a una interpretación práctica de la calidad del aire como se muestra en la siguiente tabla:

Meaning of IAQ values (source)
Significado de los valores de IAQ (source)

Salida

Si subes y ejecutas el código deberías ver las mediciones aparecer en el Monitor Serial:

Output in Serial Monitor
Salida en Monitor Serial

Sin embargo, el sensor de gas del BME680 requiere un tiempo considerable para el burn-in inicial, que varía entre ~5 y 30 minutos dependiendo de las condiciones ambientales y la tasa de muestreo. Hasta entonces, el valor IAQ suele estar fijo en 25 o 50.

El valor iaqAccuracy, que se imprime como Acc en el Monitor Serial, es un indicador de confianza para las mediciones de IAQ (y otros gases).

Al principio verás un valor de 0, lo que significa que las mediciones IAQ aún no son válidas (ver salida de ejemplo arriba). Con el tiempo, el valor iaqAccuracysubirá de 0 → 1 → 2 → 3 y una vez que alcance 3, las lecturas IAQ se consideran válidas. A continuación un ejemplo de salida tras completar el burn-in del sensor con un iaqAccuracy (Acc) de 3:

Output in Serial Monitor after burn-in
Salida en Monitor Serial tras burn-in

Obviamente, es muy molesto tener que esperar hasta 20 minutos para obtener mediciones válidas cada vez que el BME680 se reinicia o pierde energía. Afortunadamente, el burn-in puede acortarse guardando y restaurando el estado del sensor BME680. Cómo hacerlo se describe en la siguiente sección.

Código para guardar y restaurar el estado del BME680

El código a continuación extiende nuestro código previo con dos funciones, saveState y loadState, que nos permiten guardar el estado del sensor BME680 y acortar el tiempo hasta que las mediciones relacionadas con gases estén disponibles:

#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 función saveState usa la biblioteca Preferences del ESP32 para almacenar el estado del BME680 en la memoria no volátil (NVS) a bordo del ESP32. Estos datos se conservan tras reinicios y cortes de energía.

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");
}

El estado incluye líneas base aprendidas para resistencia de gas, temperatura y humedad, que el sensor usa para calcular con precisión IAQ y otras salidas.

La función reserva un bloque de memoria de tamaño BSEC_MAX_STATE_BLOB_SIZE para el estado y luego copia el estado del sensor en este bloque llamando a getState(state).

Luego iniciamos un espacio de nombres llamado "bsec" en la NVS del ESP32 llamando a prefs.begin("bsec", false), donde false indica modo lectura y escritura.

A continuación almacenamos el arreglo binario del estado en la memoria flash bajo la clave "state" y cerramos la sesión NVS (prefs.end) para asegurar que los datos se guarden.

loadState

La función loadState recupera el estado guardado previamente, si existe:

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();
}

Si prefs.getBytesLength("state") devuelve 0, significa que aún no hemos guardado el estado del sensor y se omite la lectura del estado.

De lo contrario, llamamos a prefs.getBytes("state", state, BSEC_MAX_STATE_BLOB_SIZE) para recuperar el estado y escribirlo en el sensor mediante sensor.setState(state).

setup

En la función setup llamamos a loadState, después de inicializar el sensor. Por lo tanto, si el ESP32 y consecuentemente el sensor BME680 pierden energía o se reinician, se llama a setup y restauramos el estado del sensor, siempre que haya sido guardado.

void setup(void) {
  ...

  sensor.begin(BME68X_I2C_ADDR_HIGH, Wire);

  loadState();

  ...
}

loop

En la función loop guardamos regularmente el estado del sensor, por ejemplo cada hora, siempre que iaqAccuracy sea 3 (o mayor):

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();
    }
  }
}

El período para guardar el estado está controlado por la constante savePeriod. Elegí una hora, pero no es necesario guardar tan frecuentemente. Incluso cada tres o cuatro horas está bien.

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

Ten en cuenta que a pesar de guardar y restaurar el estado del sensor, el valor iaqAccuracy seguirá en 0 tras un reinicio del sensor, pero el tiempo hasta que alcance 3 será más corto.

Conclusión

En este tutorial aprendiste cómo medir la calidad del aire con el BME680 sensor ambiental, un ESP32 y la BSEC-Arduino-library.

La BSEC-Arduino-library tiene varios ejemplos de código que vale la pena revisar. El código de este tutorial deriva del ejemplo basic.ino. El ejemplo basic_config_state.ino muestra cómo guardar y restaurar el estado del sensor. Y con la basic_config_state_ulp_plus.ino puedes ejecutar el sensor en modo ultra bajo consumo (ulp). Finalmente, para deep-sleep consulta el ejemplo esp32DeepSleep.ino.

El BME680 también mide temperatura, humedad y presión, pero si eso es lo que más te interesa y no necesitas valores IAQ, usa el Adafruit BME680 library en lugar del más complejo BSEC-Arduino-library. Consulta el tutorial BME680 Environmental Sensor with Arduino para más detalles.

Si no necesitas mediciones de gases, te sugiero el sensor BME280, que es más pequeño y barato. Consulta el tutorial How To Use BME280 Pressure Sensor With Arduino para más información.

Si tienes alguna pregunta, no dudes en dejarla en la sección de comentarios.

¡Feliz bricolaje ; )