Skip to Content

Medir a Qualidade do Ar com BME680

Medir a Qualidade do Ar com BME680

Aprenda a medir a qualidade do ar com o BME680 Sensor Ambiental e um ESP32. O BME680 é um sensor pequeno que pode medir não só a temperatura, humidade e pressão, mas também a concentração de Compostos Orgânicos Voláteis (VOC), que são prejudiciais à saúde.

O BSEC-Arduino-library para o BME680 torna especialmente fácil avaliar a qualidade do ar, pois devolve um Índice de Qualidade do Ar (IAQ) com valores entre 0 e 400. A tabela seguinte mostra como interpretar esses valores de IAQ:

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

Vamos usar esta funcionalidade para construir um Medidor de Qualidade do Ar.

Peças Necessárias

Abaixo encontra as peças necessárias para este projeto. Para além do sensor BME680, vai precisar de um microcontrolador. Eu escolhi um ESP32 lite mais antigo, mas qualquer outro ESP32 também funciona bem. Note que o BSEC-Arduino-library que vamos usar suporta outros microcontroladores também. Veja o seu readme file.

Sensor BME680

ESP32 lite Lolin32

ESP32 lite

USB data cable

Cabo USB de Dados

Dupont wire set

Conjunto de Fios 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.

Visão Geral do Sensor de Gás BME680

O BME680 é um sensor ambiental 4-em-1 pequeno mas poderoso que pode medir temperatura, humidade, pressão barométrica e, mais importante para este projeto, resistência a gases, usada para estimar a Qualidade do Ar Interior (IAQ). A imagem abaixo mostra o sensor, que tem apenas 3x3x1 mm de dimensão:

BME680 Sensor
Sensor BME680

O BME680 usa um sensor de gás baseado na tecnologia de óxido metálico (MOX). Não deteta gases individuais diretamente, mas reage a uma ampla gama de compostos orgânicos voláteis (VOCs) e gases como monóxido de carbono (CO) no ar. Estes compostos são normalmente libertados por:

  • Produtos de limpeza e aerossóis
  • Tintas e vernizes
  • Mobiliário e carpetes (especialmente novos)
  • Cozinhar e fumar
  • Respiração humana e odor corporal
  • Aparelhos de combustão

Embora o sensor não meça concentrações de gases em partes por milhão (ppm), a biblioteca BSEC usa modelos de machine learning para traduzir as leituras de resistência do sensor em um valor significativo de IAQ, tornando-o adequado para monitorização da qualidade do ar interior.

O que é IAQ e por que é importante

O valor IAQ devolvido pela biblioteca de software do BME680 dá uma estimativa numérica da poluição do ar interior. Combina várias leituras ambientais num único valor, onde:

  • 0–50 = Qualidade do ar excelente
  • 51–100 = Boa
  • 101–150 = Levemente poluído
  • 151–200 = Moderadamente poluído
  • 201–300 = Fortemente poluído
  • 301–500 = Severamente poluído

Este valor é particularmente útil porque fornece um número claro e fácil de entender, em vez de leituras brutas de resistência de gás, que podem ser difíceis de interpretar isoladamente.

Placa breakout para BME680

Como o sensor BME680 é muito pequeno, usamos uma placa breakout para o ligar ao ESP32. Isto também tem a vantagem de podermos alimentar o sensor a 3.3V ou 5V, pois a placa inclui um regulador de tensão. A imagem abaixo mostra a placa breakout com o regulador e o sensor BME680:

Breakout board for BME680
Placa breakout para BME680

O BME680 é a pequena caixa metálica quadrada, do lado direito. Do lado esquerdo estão os pinos para a interface I2C ou SPI, juntamente com as ligações de alimentação (VCC, GND).

Vamos usar a interface I2C, pois só requer ligar SDA e SCL. Note que o pino SDO determina o endereço I2C do sensor. Deixar o SDO desconectado define o endereço para 0x77, enquanto ligá-lo ao terra muda o endereço para 0x76.

Para mais informações sobre o sensor BME680 veja o nosso BME680 Environmental Sensor with Arduinotutorial, o BME680 Datasheet e talvez o Bosch Website for the BME680.

Ligação do BME680 ao ESP32

Para ligar o BME680 a um ESP32 lite via I2C, precisamos ligar o SCL ao pino 23 e o SDA ao pino 19. Os pinos para I2C hardware dependem da sua placa e podem ser diferentes. Veja o Find I2C and SPI default pins tutorial para descobrir os pinos da sua placa.

Caso contrário, só precisamos ligar o GND ao terra (G) e o VCC ao pino 3V (3.3V) do ESP32. A imagem abaixo mostra a ligação completa:

Connecting BME680 to ESP32
Ligação do BME680 ao ESP32

Código para medir dados ambientais com BME680

Como mencionado antes, vamos usar a BSEC-Arduino-library para ler dados do BME680. Pode instalá-la via LIBRARY MANAGER. Procure por “bsec” e instale a “BSEC Software Library” como mostrado abaixo:

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

Note que existe também a mais recente Bosch-BSEC2-Library, que eu não experimentei. No entanto, usei a mais simples Adafruit BME680 library noutro tutorial (BME680 Environmental Sensor with Arduino). Mas essa biblioteca não devolve medidas facilmente interpretáveis da qualidade do ar (IAQ), por isso não a podemos usar aqui.

O código seguinte mostra como capturar dados de qualidade do ar como IAQ, percentagem de gás e também dados ambientais simples como temperatura, humidade e pressão com o BME680. Vou explicar o código parte a parte e o que cada secção faz:

#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

No topo, o código inclui a BSEC library, que é a abreviação de Bosch Sensortec Environmental Cluster. Esta biblioteca combina dados brutos do sensor BME680 e aplica algoritmos para fornecer medidas significativas da qualidade do ar, como IAQ e percentagem de gás.

#include "bsec.h"

objetos

De seguida criamos uma instância da classe Bsec chamada sensor. Este objeto dá acesso a todas as funções BSEC.

Bsec sensor;

checkSensor

A função checkSensor() é uma rotina simples de verificação de erros. Verifica o estado do algoritmo BSEC e o estado do hardware BME688. Se houver um problema, imprime os códigos de erro correspondentes no Monitor Serial para facilitar a depuração.

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

Note que códigos de estado positivos indicam um aviso, enquanto códigos negativos indicam um erro!

setup

Na função setup(), o código inicia a comunicação serial a 115200 baud para poder imprimir no Monitor Serial. Inclui também um pequeno atraso para garantir que a porta serial está pronta antes de continuar.

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

Depois, o sensor BME688 é inicializado via I2C usando a função sensor.begin(). O endereço BME68X_I2C_ADDR_HIGH é usado, que corresponde a 0x77. Após a inicialização, o código verifica o estado do sensor.

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

A constante é chamada “HIGH”, pois se o pino SDO não estiver ligado, um resistor interno puxa-o para alto. Se quiser usar o outro endereço I2C (0x76) ligando o SDO ao terra, altere isto em conformidade.

De seguida, o sketch define a que sensores virtuais se vai subscrever. Isto permite selecionar quais sensores (temperatura, humidade, …) e quais valores derivados (ex. temperatura bruta versus compensada pelo calor) usar. Isto é importante para reduzir o consumo de energia desativando sensores não usados.

A função sensor.updateSubscription() indica à biblioteca que dados de que sensores queremos e com que frequência:

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

A constante BSEC_SAMPLE_RATE_LP significa amostragem de baixo consumo (LP). Esta é outra forma de reduzir o consumo do sensor. Existe, por exemplo, um BSEC_SAMPLE_RATE_ULP para consumo ultra baixo (ULP). Pode até definir diferentes taxas de amostragem para diferentes conjuntos de sensores virtuais. Veja o exemplo basic_config_state_ULP_LP.

loop

A função loop() faz o trabalho em tempo real. A função sensor.run() verifica se há novos dados disponíveis da biblioteca BSEC. Se sim, o código imprime várias leituras no 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();
  }
}

Note que este é apenas um pequeno subconjunto das possíveis medições e valores que a biblioteca BSEC pode devolver. A tabela seguinte lista todos os valores, seu significado e intervalo:

ValorDescriçãoUnidade/IntervaloTipoTipo
iaqÍndice de Qualidade do Ar Interior (em tempo real, dinâmico)Índice (0–500)Processadofloat
iaqAccuracyConfiança no valor iaq0–3Estadouint8_t
staticIaqValor IAQ suavizado (menos sensível a flutuações)Índice (0–500)Processadofloat
staticIaqAccuracyConfiança no valor staticIaq0–3Estadouint8_t
co2EquivalentConcentração estimada de CO₂ a partir de VOCsppmProcessadofloat
co2AccuracyConfiança no valor co2Equivalent0–3Estadouint8_t
breathVocEquivalentEstimativa equivalente de VOCs na respiraçãoppmProcessadofloat
breathVocAccuracyConfiança no valor breathVocEquivalent0–3Estadouint8_t
compGasValueValor compensado de gás usado internamenteOhms (normalizado)Internofloat
compGasAccuracyConfiança no valor compGasValue0–3Estadouint8_t
gasPercentagePercentagem estimada de ar limpo%Processadofloat
gasPercentageAccuracyConfiança no valor gasPercentage0–3Estadouint8_t
rawTemperatureTemperatura não compensada do sensor°CBrutofloat
temperatureTemperatura compensada (ajustada pelo autoaquecimento do sensor)°CCompensadofloat
rawHumidityHumidade relativa não compensada%Brutofloat
humidityHumidade relativa compensada%Compensadofloat
pressurePressão barométricahPaBrutofloat
gasResistanceResistência bruta do sensor de gásOhmsBrutofloat
stabStatusSe a amostra está estabilizada0 ou 1 (tipo booleano)Estadofloat
runInStatusSe o sensor ainda está na fase de adaptação0 ou 1Estadofloat

No código estamos a imprimir os valores iaqAccuracy (Acc), iaq (IAQ), staticIaq (sIAQ), gasPercentage (gas), temperature (temp), humidity (hum) e pressure (pres). Temperatura e humidade são valores compensados (considerando o aquecedor do sensor de gás) e a pressão é dividida por 100 para devolver a pressão em hectopascal (hPa).

Os valores mais interessantes são iaq (IAQ) e staticIaq (sIAQ), sendo este último uma versão mais estável do primeiro. O valor IAQ está diretamente ligado a uma interpretação prática da Qualidade do Ar, como mostrado na tabela seguinte:

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

Saída

Se carregar e executar o código, deverá ver as medições a aparecer no Monitor Serial:

Output in Serial Monitor
Saída no Monitor Serial

No entanto, o sensor de gás do BME680 demora um tempo considerável para o burn-in inicial, variando entre ~5 a 30 minutos dependendo das condições do ar ambiente e da taxa de amostragem. Até lá, o valor IAQ normalmente aparece fixo em 25 ou 50.

O valor iaqAccuracy, que é impresso como Acc no Monitor Serial, é um indicador de confiança para as medições de IAQ (e outros gases).

No início verá um valor 0, que significa que as medições IAQ ainda não são válidas (veja o exemplo de saída acima). Com o tempo, o valor iaqAccuracy sobe de 0 → 1 → 2 → 3 e, uma vez que atinja 3, as leituras IAQ são consideradas válidas! Abaixo um exemplo de saída após o burn-in completo do sensor com um iaqAccuracy (Acc) de 3:

Output in Serial Monitor after burn-in
Saída no Monitor Serial após burn-in

Obviamente, é muito incómodo ter de esperar até 20 minutos por medições válidas sempre que o BME680 reinicia ou perde energia. Felizmente, o burn-in pode ser encurtado guardando e restaurando o estado do sensor BME680. Como fazer isso está descrito na próxima secção.

Código para guardar e restaurar o estado do BME680

O código abaixo estende o código anterior com duas funções, saveState e loadState, que nos permitem guardar o estado do sensor BME680 e encurtar o tempo até as medições relacionadas com gases estarem disponíveis:

#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

A função saveState usa a biblioteca Preferences do ESP32 para armazenar o estado do BME680 na memória não volátil (NVS) do ESP32. Estes dados são mantidos entre reinícios e falhas de energia do sistema.

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

O estado inclui as linhas base aprendidas para resistência a gases, temperatura e humidade, que o sensor usa para calcular IAQ e outras saídas com precisão.

A função reserva um bloco de memória do tamanho BSEC_MAX_STATE_BLOB_SIZE para o estado e depois copia o estado do sensor para esse bloco de memória chamando getState(state).

De seguida iniciamos um namespace chamado "bsec" na NVS do ESP32 chamando prefs.begin("bsec", false), onde false indica modo de leitura e escrita.

Depois armazenamos o array binário do estado na memória flash sob a chave "state" e fechamos a sessão NVS (prefs.end) para garantir que os dados são gravados

loadState

A função loadState recupera o estado guardado anteriormente, se existir:

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") devolver 0, ainda não guardámos o estado do sensor e saltamos a leitura do estado.

Caso contrário, chamamos prefs.getBytes("state", state, BSEC_MAX_STATE_BLOB_SIZE) para recuperar o estado e escrevê-lo no sensor via sensor.setState(state).

setup

Na função setup chamamos loadState, após inicializar o sensor. Assim, se o ESP32 e consequentemente o sensor BME680 perderem energia ou forem reiniciados, setup é chamado e restauramos o estado do sensor – desde que tenha sido guardado.

void setup(void) {
  ...

  sensor.begin(BME68X_I2C_ADDR_HIGH, Wire);

  loadState();

  ...
}

loop

Na função loop guardamos regularmente o estado do sensor, por exemplo a cada hora, desde que iaqAccuracy seja 3 (ou maior):

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

O período para guardar o estado é controlado pela constante savePeriod. Eu escolhi uma hora, mas não precisa guardar com tanta frequência. Até a cada três ou quatro horas está bem.

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

Note que apesar de guardar e restaurar o estado do sensor, o valor iaqAccuracy ainda estará a 0 após um reinício do sensor, mas o tempo até atingir 3 será mais curto.

Conclusão

Neste tutorial aprendeu a medir a Qualidade do Ar com o BME680 Sensor Ambiental, um ESP32 e a BSEC-Arduino-library.

A BSEC-Arduino-library tem vários exemplos de código que vale a pena ver. O código deste tutorial é derivado do exemplo basic.ino. O exemplo basic_config_state.ino mostra como guardar e restaurar o estado do sensor. E com a basic_config_state_ulp_plus.ino pode executar o sensor em modo ultra-baixo consumo (ulp). Finalmente, para deep-sleep veja o exemplo esp32DeepSleep.ino.

O BME680 também mede temperatura, humidade e pressão, mas se esse for o seu principal interesse e não precisar de valores IAQ, use o Adafruit BME680 library em vez do mais complexo BSEC-Arduino-library. Veja o tutorial BME680 Environmental Sensor with Arduino para detalhes.

Se não precisar de medições de gás, sugiro o sensor BME280, que é mais pequeno e barato. Veja o tutorial How To Use BME280 Pressure Sensor With Arduino para mais informações.

Se tiver alguma dúvida, sinta-se à vontade para deixar nos comentários.

Boas experiências ; )