Componentes · 15 min de leitura

MQ135 com Arduino: sensor de qualidade do ar, calibração e cálculo de ppm

Guia do MQ135 com Arduino: gases que ele detecta, pré-aquecimento de 24–48 h, calibração de R0 e fórmula de ppm, se mede CO2, controle de ventoinha, registro com ESP32 e CCS811.

Projetos com MQ135 e Arduino são a primeira parada para quem quer um jeito barato e simples de acompanhar o quanto o ar de um ambiente está limpo. O MQ-135 é um sensor de gás de óxido metálico com aquecedor, que reage a gases como amônia, óxidos de nitrogênio, benzeno, álcool e fumaça. Nos kits hobby, ele costuma vir como um pequeno módulo com um CI comparador e um potenciômetro de ajuste; liga ao Arduino pela saída analógica e é lido com um único analogRead(). Mas transformar o número que esse sensor entrega em uma medição com significado exige um pouco mais de conhecimento do que a maioria dos tutoriais explica.

Neste guia, você vai aprender o que acontece dentro do sensor, o quanto ele é sensível a cada gás e por que precisa de 24–48 horas de aquecimento antes do primeiro uso de verdade. Depois, vamos montar o cálculo de ppm passo a passo com o resistor de carga (RL), a resistência do sensor (Rs) e a resistência de referência (R0), fazer a calibração de R0 no ar limpo e dar uma resposta honesta à pergunta mais comum: o MQ-135 mede mesmo CO2? Por fim, vamos ligar uma ventoinha de exaustão automaticamente, registrar as medições com um ESP32 em um arquivo com data e hora, escrever código MicroPython para o Raspberry Pi Pico e comparar o MQ-135 com o CCS811 e com sensores de CO2 NDIR.

Módulo sensor de qualidade do ar MQ-135
Módulo MQ-135: sensor de óxido metálico com aquecedor, comparador LM393, potenciômetro de sensibilidade e pinos VCC, GND, DO, AO.

O que é o MQ-135 e como ele funciona?

Atrás da tela de aço inox, a superfície de um pequeno tubo cerâmico é revestida de dióxido de estanho (SnO2), e um fio aquecedor que passa por dentro dele aquece essa camada a algumas centenas de graus Celsius. No ar limpo, moléculas de oxigênio se prendem à superfície de SnO2, capturam elétrons livres do material e a resistência da camada fica alta. Quando um gás redutor, como amônia, álcool ou benzeno, entra no ar, ele reage com o oxigênio da superfície, os elétrons são liberados e a resistência cai. Quanto mais denso o gás, menor a resistência. O módulo coloca essa resistência em um divisor de tensão com um resistor de carga, então a tensão no pino AO sobe conforme o gás aumenta.

CaracterísticaValor
Material sensorDióxido de estanho (SnO2), tubo cerâmico com aquecedor
Tensão do aquecedor5 V ± 0,1 V
Resistência e corrente do aquecedor33 Ω ± 5%, cerca de 150 mA
Potência do aquecedorAté 800 mW
Faixa de detecçãoNH3 10–300 ppm, benzeno 10–1.000 ppm, álcool 10–300 ppm
Pré-aquecimento inicialMais de 24 horas
Condições de operação−10 a 45 °C, umidade relativa abaixo de 95%
Saídas do móduloAO (analógica) e DO (limiar digital via LM393)

Quais gases o MQ-135 detecta?

O MQ-135 não é um sensor seletivo que mede um único gás; ele reage ao mesmo tempo a vários gases que reagem com a superfície aquecida de SnO2 e não consegue diferenciá-los. O fabricante o descreve como especialmente sensível a amônia, compostos de enxofre e vapores do tipo benzeno; ele também responde claramente a álcool, fumaça e óxidos de nitrogênio. Por isso, o melhor é pensar no MQ-135 como um “indicador de poluição do ar”: quando o valor sobe, ele diz que algo mudou no ar, mas não diz o quê. Para vazamentos de gás natural e GLP, o sensor de gás MQ-2, projetado para essa função, é muito mais adequado.

GásDe onde vem em casa?Resposta do MQ-135
Amônia (NH3)Produtos de limpeza, areia de gato, aduboAlta
Benzeno, tolueno e solventesTinta, thinner, cola, fumaça de cigarroAlta
Vapor de álcoolÁlcool em gel, colônia, perfumeAlta
FumaçaCozinhar, cigarro, velasMédia
Óxidos de nitrogênio (NOx)Fogão a gás, escapamento de veículosMédia
Dióxido de carbono (CO2)Respiração humana, ambientes fechados e cheiosFraca, indireta

O MQ-135 mede CO2?

A resposta curta: não de verdade; ele estima. O datasheet do MQ-135 traz uma curva de CO2, e a maioria das bibliotecas na internet calcula um valor de “CO2 ppm” a partir dela. Mas o CO2 é um gás quimicamente estável e reage só muito fracamente com a superfície de SnO2. Em um ambiente fechado, conforme as pessoas respiram, a umidade e os compostos orgânicos voláteis liberados pelo corpo aumentam junto com o CO2, e é a eles que o MQ-135 realmente reage. Ou seja, o “CO2” que o sensor mostra é, na verdade, uma estimativa baseada na poluição geral do ar do ambiente. A prova mais fácil: abra um frasco de colônia ao lado dele e veja o valor de “CO2” disparar para 5.000 ppm.

Isso não quer dizer que o MQ-135 seja inútil. Ele funciona muito bem para decisões relativas, como “o ambiente está abafado, abra a janela” ou “tem fumaça na cozinha, ligue o exaustor”. Se você precisa do valor real de CO2 em uma sala de aula, escritório ou quarto, porém, deve usar um dos sensores de CO2 NDIR da comparação no final deste guia. A tabela abaixo resume o que significam as leituras de um medidor de CO2 de verdade; você pode interpretar as estimativas do MQ-135 com as mesmas faixas.

CO2 (ppm)O que significa?
400–450Ar externo limpo
450–800Ambiente interno bem ventilado
800–1000Aceitável, pense em ventilar
1000–1500Ar abafado, a concentração pode cair; ventile
1500–2000Podem surgir dor de cabeça e sonolência
5000Limite de exposição de 8 horas no local de trabalho

Ligação do MQ-135 ao Arduino

Pino do móduloArduino UnoESP32Raspberry Pi Pico
VCC (inclui o aquecedor)5VVIN (5 V do USB)VBUS (5 V do USB)
GNDGNDGNDGND
AO (saída analógica)A0GPIO 34, por um divisor de 10 kΩ / 20 kΩGP26 (ADC0), por um divisor de 10 kΩ / 20 kΩ
DO (limiar digital, opcional)D2Qualquer GPIO, por um divisorQualquer pino GP, por um divisor
Só o aquecedor do MQ-135 consome cerca de 150 mA e precisa de 5 V; alimentado pelo pino 3V3 de um ESP32 ou Pico, o sensor não esquenta o suficiente e as leituras não terão sentido. Quando o módulo funciona com 5 V, a saída AO pode subir até 5 V, o que danifica a entrada ADC de 3,3 V do ESP32 e do Pico. Monte um divisor de tensão de 10 kΩ + 20 kΩ entre AO e GND e ligue o ponto do meio ao pino ADC: 5 V × 20 / 30 = 3,33 V. Você pode testar outros pares de resistores com a calculadora de divisor de tensão. Um sensor em funcionamento fica quente demais para segurar; não toque na tela dele.

Pré-aquecimento: o burn-in de 24–48 horas e o aquecimento diário

Os sensores da série MQ têm dois aquecimentos diferentes, e é comum confundi-los. O primeiro é o pré-aquecimento inicial (burn-in): umidade e contaminação se acumulam na superfície de um sensor novinho ou de um que ficou muito tempo guardado sem uso. O fabricante pede que o sensor seja alimentado por mais de 24 horas antes das medições padrão; na prática, deixá-lo ligado sem parar por 24–48 horas permite ver os valores mudarem devagar até finalmente se estabilizarem. Se você fizer a calibração de R0 antes de terminar esse período, todas as suas leituras vão se deslocar em poucos dias. O segundo é o aquecimento diário: um sensor que já completou o burn-in ainda precisa de alguns minutos toda vez que é ligado para o aquecedor entrar em equilíbrio; os valores do primeiro minuto vêm claramente altos ou baixos demais. Por isso o código inclui uma espera de pelo menos 2–3 minutos; para medições precisas, é melhor esperar 10–20 minutos.

Rs, RL e R0: como calcular ppm com o MQ-135?

Dentro do módulo, a resistência do sensor Rs e o resistor de carga da placa RL estão em série e dividem os 5 V. A tensão no pino AO é Vout = 5 × RL / (Rs + RL). Invertendo essa fórmula, você obtém a resistência atual do sensor a partir da tensão medida: Rs = RL × (5 − Vout) / Vout. No Arduino Uno, o ADC usa os mesmos 5 V como referência, então nem é preciso converter para volts: Rs = RL × (1023 − bruto) / bruto. O Rs sozinho não diz muito, porque a resistência varia bastante de um sensor para outro. O que importa é a razão entre Rs e o valor dele em um ar conhecido, chamado R0.

As curvas do datasheet relacionam a razão Rs/R0 com a concentração do gás e são quase retas em eixos log-log. Isso significa que podem ser expressas por uma função potência: ppm = A × (Rs/R0)^−B. Os coeficientes que as bibliotecas populares do MQ-135 usam para a curva de CO2 são A = 116,602 e B = 2,769. Para encontrar R0, você coloca o sensor no ar externo limpo; como o CO2 externo hoje é de cerca de 420 ppm, você inverte a fórmula e calcula R0 = Rs × (420 / A)^(1/B) ≈ Rs × 1,59. Lembre-se de que esses coeficientes são diferentes para outros gases e de que o resultado é só uma estimativa.

Um dos pequenos resistores SMD na parte de trás do módulo é o RL, e o código impresso nele indica o valor: 102 = 1 kΩ, 103 = 10 kΩ, 203 = 20 kΩ. Explicamos esses códigos em códigos de resistores SMD. Informar o RL errado não muda o resultado em ppm, porque o R0 é calculado com o mesmo RL e a razão continua igual; só os números de Rs e R0 que aparecem na tela ficam em outra escala. Mas, se o RL for muito pequeno (1 kΩ), a tensão em AO fica muito baixa no ar limpo e a resolução do ADC piora; se você notar os valores tremendo, tirar uma média resolve bem.

Calibração de R0 do MQ-135 passo a passo

  1. Ligue o VCC do módulo ao 5V, o GND ao GND e o AO ao A0, e complete o pré-aquecimento inicial deixando o sensor ligado sem parar por 24–48 horas.
  2. Leia o código do resistor de carga na parte de trás da placa e ajuste RL_KOHM no código abaixo de acordo.
  3. Leve o circuito para um ar externo limpo, como uma varanda ou uma janela aberta, longe de trânsito, cigarro e cozinha.
  4. Carregue o código de calibração, abra o Monitor Serial a 9600 baud e espere pelo menos 10 minutos.
  5. Quando o R0 médio na tela quase não mudar por vários minutos, anote o valor; esse é o R0 do seu sensor.
C++ (Arduino IDE)
1const byte MQ_PIN = A0;                 // MQ-135 AO
2const float RL_KOHM = 1.0;              // karttaki yük direnci: 102 = 1, 103 = 10 / load resistor on the board
3const float PARA = 116.6020682;         // CO2 eğrisi katsayıları / CO2 curve coefficients
4const float PARB = 2.769034857;
5const float CLEAN_AIR_PPM = 420.0;      // dış havadaki CO2 / CO2 in outdoor air
6
7float r0Average = 0;
8
9float readRs() {
10  long sum = 0;
11  for (int i = 0; i < 50; i++) {        // 50 ölçüm, yaklaşık 1 saniye / 50 readings, about 1 second
12    sum += analogRead(MQ_PIN);
13    delay(20);
14  }
15  float raw = sum / 50.0;
16  if (raw < 1) raw = 1;                 // sıfıra bölmeyi önle / avoid dividing by zero
17  return RL_KOHM * (1023.0 - raw) / raw;   // Rs = RL × (Vc − Vout) / Vout
18}
19
20void setup() {
21  Serial.begin(9600);
22  Serial.println(F("Temiz havada en az 10 dk bekle / wait at least 10 min in clean air"));
23}
24
25void loop() {
26  float rs = readRs();
27  float r0 = rs * pow(CLEAN_AIR_PPM / PARA, 1.0 / PARB);    // R0 = Rs × (420 / A)^(1/B)
28  if (r0Average == 0) r0Average = r0;
29  r0Average = r0Average * 0.95 + r0 * 0.05;                // yavaş ortalama / slow running average
30
31  Serial.print(F("Rs = "));
32  Serial.print(rs, 2);
33  Serial.print(F(" kOhm   R0 = "));
34  Serial.print(r0, 2);
35  Serial.print(F("   ortalama / average R0 = "));
36  Serial.println(r0Average, 2);
37}

A cada segundo, o código tira a média de 50 leituras, calcula com ela o Rs e o R0 atual e depois soma esse valor a uma média móvel lenta. A média móvel impede que uma rápida lufada de fumaça de escapamento trazida pelo vento estrague o resultado. O R0 encontrado vale só para aquele sensor e aquele valor de RL; se trocar o sensor, calibre de novo. Com o passar dos meses, conforme o sensor envelhece, o R0 se desloca devagar, então, em projetos que funcionam por muito tempo, é um bom hábito repetir a calibração a cada poucos meses.

Código de medição: ppm estimado e nível de qualidade do ar

Depois de encontrar o R0, você o coloca no código de medição. O código primeiro espera 3 minutos de aquecimento e, depois, a cada 2 segundos, mostra no Monitor Serial a razão Rs/R0, o ppm estimado e um rótulo de qualidade do ar baseado na tabela acima. No ar limpo, a razão fica em torno de 0,6; quanto menor ela fica, mais gás há no ar. Para testar, pingue algumas gotas de colônia em um algodão perto do sensor: em segundos a razão cai e o ppm sobe, e quando você afasta o algodão os valores voltam ao normal em poucos minutos.

C++ (Arduino IDE)
1const byte MQ_PIN = A0;
2const float RL_KOHM = 1.0;              // kalibrasyondaki RL ile aynı / same RL as in calibration
3const float R0_KOHM = 3.6;              // kendi kalibrasyon sonucunla değiştir / replace with your calibration result
4const float PARA = 116.6020682;
5const float PARB = 2.769034857;
6const unsigned long WARMUP_MS = 180000; // 3 dk ısınma / 3 min warm-up
7
8float readRs() {
9  long sum = 0;
10  for (int i = 0; i < 20; i++) {
11    sum += analogRead(MQ_PIN);
12    delay(10);
13  }
14  float raw = sum / 20.0;
15  if (raw < 1) raw = 1;
16  return RL_KOHM * (1023.0 - raw) / raw;
17}
18
19const __FlashStringHelper* airLabel(float ppm) {
20  if (ppm < 800)  return F("Iyi / good");
21  if (ppm < 1000) return F("Orta / moderate");
22  if (ppm < 1500) return F("Havalandir / ventilate");
23  return F("Kotu / poor");
24}
25
26void setup() {
27  Serial.begin(9600);
28  Serial.println(F("Isiniyor / warming up..."));
29  while (millis() < WARMUP_MS) delay(100);
30}
31
32void loop() {
33  float ratio = readRs() / R0_KOHM;           // gaz arttıkça küçülür / falls as gas rises
34  float ppm = PARA * pow(ratio, -PARB);       // tahmini CO2 eşdeğeri / estimated CO2 equivalent
35
36  Serial.print(F("Rs/R0 = "));
37  Serial.print(ratio, 2);
38  Serial.print(F("   ~"));
39  Serial.print(ppm, 0);
40  Serial.print(F(" ppm   "));
41  Serial.println(airLabel(ppm));
42  delay(2000);
43}

Como a temperatura e a umidade afetam o MQ-135?

A resistência dos sensores de óxido metálico depende não só do gás, mas também da temperatura e da umidade do ambiente. Segundo a curva do datasheet, no mesmo ar a razão Rs/R0 cai bastante quando a temperatura sobe de 20 °C para 40 °C, e também cai conforme a umidade aumenta; o sensor interpreta isso como “mais gás”. O resultado são valores de ppm que sobem sozinhos em um cômodo que pega sol à tarde ou no banheiro depois do banho. Há duas maneiras de reduzir isso: calibrar em temperatura e umidade próximas das do local onde o sensor será usado, ou medir temperatura e umidade com um DHT22 ou DHT11 e aplicar a fórmula de correção das bibliotecas. Funções como getCorrectedPPM(temperatura, umidade) das bibliotecas MQ135 prontas fazem exatamente isso. Se você quiser medir temperatura e umidade com mais precisão que um DHT11, pode usar um sensor BME280.

Automatizando uma ventoinha de exaustão

A função mais útil do MQ-135 é ligar sozinho uma ventoinha ou um exaustor quando o ar fica pesado. Dois detalhes importam aqui. O primeiro é a histerese: a ventoinha liga acima de 1.000 ppm, mas só desliga quando o valor cai abaixo de 800 ppm; com um único limiar, o relé estalaria a cada segundo enquanto o valor oscila em torno dele. O segundo é um tempo mínimo de funcionamento: uma vez ligada, a ventoinha funciona por pelo menos 2 minutos, para que o ar do ambiente seja realmente renovado. O código também suaviza as leituras para que picos momentâneos não acionem a ventoinha.

C++ (Arduino IDE)
1const byte MQ_PIN = A0;
2const byte FAN_PIN = 7;                    // röle modülü IN / relay module IN
3const bool RELAY_ACTIVE_LOW = true;        // çoğu modül LOW ile çeker / most modules switch on with LOW
4const float RL_KOHM = 1.0;
5const float R0_KOHM = 3.6;                 // kendi kalibrasyon sonucun / your calibration result
6const float PARA = 116.6020682;
7const float PARB = 2.769034857;
8const float FAN_ON_PPM = 1000;             // bunun üstünde fanı aç / fan on above this
9const float FAN_OFF_PPM = 800;             // bunun altında fanı kapat / fan off below this
10const unsigned long MIN_RUN_MS = 120000;   // fan en az 2 dk çalışır / fan runs at least 2 min
11const unsigned long WARMUP_MS = 180000;    // 3 dk ısınma / 3 min warm-up
12
13bool fanOn = false;
14unsigned long fanStart = 0;
15float smoothPpm = 0;
16
17void setFan(bool on) {
18  fanOn = on;
19  if (on) fanStart = millis();
20  bool level = RELAY_ACTIVE_LOW ? !on : on;
21  digitalWrite(FAN_PIN, level ? HIGH : LOW);
22}
23
24float readPpm() {
25  long sum = 0;
26  for (int i = 0; i < 20; i++) {
27    sum += analogRead(MQ_PIN);
28    delay(10);
29  }
30  float raw = sum / 20.0;
31  if (raw < 1) raw = 1;
32  float rs = RL_KOHM * (1023.0 - raw) / raw;
33  return PARA * pow(rs / R0_KOHM, -PARB);
34}
35
36void setup() {
37  digitalWrite(FAN_PIN, RELAY_ACTIVE_LOW ? HIGH : LOW);   // önce kapalı seviye, sonra çıkış / off level first, then output
38  pinMode(FAN_PIN, OUTPUT);
39  Serial.begin(9600);
40  while (millis() < WARMUP_MS) delay(100);
41  smoothPpm = readPpm();
42}
43
44void loop() {
45  smoothPpm = smoothPpm * 0.8 + readPpm() * 0.2;          // ani sıçramaları yumuşat / smooth out spikes
46
47  if (!fanOn && smoothPpm > FAN_ON_PPM) {
48    setFan(true);
49  } else if (fanOn && smoothPpm < FAN_OFF_PPM && millis() - fanStart > MIN_RUN_MS) {
50    setFan(false);
51  }
52
53  Serial.print(F("~"));
54  Serial.print(smoothPpm, 0);
55  Serial.print(F(" ppm   fan: "));
56  Serial.println(fanOn ? F("ACIK / ON") : F("KAPALI / OFF"));
57  delay(1000);
58}

O pino IN do módulo relé vai ao D7, e o VCC e o GND dele vão ao Arduino; a ventoinha é ligada à sua própria alimentação pelos terminais COM e NO (comum e NA) do relé. Se você usar uma ventoinha de computador de 12 V, um MOSFET é mais silencioso e dura mais que um relé; a ligação é a mesma do circuito da minibomba d'água. Se for chavear um aparelho de 230 V, como um exaustor de banheiro, siga à risca as regras de segurança do guia do módulo relé: coloque o lado da rede elétrica em uma caixa fechada e nunca toque no circuito energizado. No setup(), colocar primeiro o pino no nível de desligado e só depois configurá-lo como saída evita que relés ativos em LOW acionem por um instante ao ligar.

Registrando a qualidade do ar com um ESP32

Ver como a qualidade do ar muda ao longo do dia ensina muito mais do que uma leitura isolada: só um registro revela o ar abafado que se acumula no quarto até de manhã, o pico na cozinha durante o preparo da comida ou o efeito de abrir uma janela. O código de ESP32 abaixo faz uma medição por minuto, busca a hora na internet (NTP) para colocar data e hora em cada linha e salva o resultado em CSV no sistema de arquivos LittleFS, na própria memória flash do ESP32. Ao abrir o endereço IP do ESP32 no navegador, você vê a última medição e pode baixar o registro completo com um clique, como um arquivo CSV que abre no Excel. Nenhuma biblioteca extra é necessária.

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WebServer.h>
3#include <LittleFS.h>
4#include <time.h>
5
6const char* WIFI_SSID = "AgAdi";              // Wi-Fi adın / your Wi-Fi name
7const char* WIFI_PASS = "Sifre123";           // Wi-Fi şifren / your Wi-Fi password
8const int MQ_PIN = 34;                        // AO, 10k/20k bölücü üzerinden (ADC1) / through a 10k/20k divider (ADC1)
9const float DIVIDER = 1.5;                    // (10k + 20k) / 20k
10const float RL_KOHM = 1.0;
11const float R0_KOHM = 3.6;                    // ESP32 ile yeniden kalibre et / recalibrate with the ESP32
12const float PARA = 116.6020682;
13const float PARB = 2.769034857;
14const unsigned long LOG_INTERVAL = 60000;     // dakikada bir kayıt / one entry per minute
15
16WebServer server(80);
17unsigned long lastLog = 0;
18float lastPpm = 0;
19
20float readPpm() {
21  uint32_t mv = 0;
22  for (int i = 0; i < 20; i++) {
23    mv += analogReadMilliVolts(MQ_PIN);       // kalibre edilmiş mV / calibrated mV
24    delay(5);
25  }
26  float vout = (mv / 20.0) / 1000.0 * DIVIDER;   // sensör çıkışı (V) / sensor output (V)
27  if (vout < 0.05) vout = 0.05;
28  float rs = RL_KOHM * (5.0 - vout) / vout;
29  return PARA * pow(rs / R0_KOHM, -PARB);
30}
31
32void handleRoot() {
33  String html = "<!DOCTYPE html><html><head><meta charset='utf-8'>"
34                "<meta name='viewport' content='width=device-width,initial-scale=1'></head>"
35                "<body style='font-family:sans-serif'><h2>MQ-135</h2><p>Son ölçüm / last reading: ~";
36  html += String(lastPpm, 0);
37  html += " ppm</p><p><a href='/log.csv'>CSV indir / download</a> | "
38          "<a href='/clear'>Kaydı sil / clear log</a></p></body></html>";
39  server.send(200, "text/html", html);
40}
41
42void handleCsv() {
43  File f = LittleFS.open("/log.csv", "r");
44  if (!f) {
45    server.send(404, "text/plain", "Kayit yok / no log yet");
46    return;
47  }
48  server.streamFile(f, "text/csv");
49  f.close();
50}
51
52void handleClear() {
53  LittleFS.remove("/log.csv");
54  server.sendHeader("Location", "/");
55  server.send(303);
56}
57
58void setup() {
59  Serial.begin(115200);
60  analogSetPinAttenuation(MQ_PIN, ADC_11db);  // yaklaşık 0-3,1 V ölçüm aralığı / about 0-3.1 V range
61  if (!LittleFS.begin(true)) {                // true: ilk açılışta biçimlendir / format on first use
62    Serial.println("LittleFS hatasi / error");
63  }
64  WiFi.mode(WIFI_STA);
65  WiFi.begin(WIFI_SSID, WIFI_PASS);
66  while (WiFi.status() != WL_CONNECTED) {
67    delay(500);
68    Serial.print(".");
69  }
70  Serial.println();
71  Serial.println(WiFi.localIP());
72  configTime(3 * 3600, 0, "pool.ntp.org");    // UTC+3; kendi saat dilimine göre değiştir / change to your time zone
73
74  server.on("/", handleRoot);
75  server.on("/log.csv", handleCsv);
76  server.on("/clear", handleClear);
77  server.begin();
78  lastLog = millis();                         // ilk kayıt 1 dk sonra, sensör ısınırken / first entry after 1 min
79}
80
81void loop() {
82  server.handleClient();
83  if (millis() - lastLog >= LOG_INTERVAL) {
84    lastLog = millis();
85    lastPpm = readPpm();
86    char stamp[20] = "saat-yok";
87    struct tm now;
88    if (getLocalTime(&now, 100)) {
89      strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M", &now);
90    }
91    File f = LittleFS.open("/log.csv", "a");  // dosyanın sonuna ekle / append to the file
92    if (f) {
93      f.printf("%s,%.0f\n", stamp, lastPpm);
94      f.close();
95    }
96    Serial.printf("%s  ~%.0f ppm\n", stamp, lastPpm);
97  }
98}

Como o ADC do ESP32 mede de forma diferente do Uno, o ideal é refazer a calibração de R0 com este circuito; basta adaptar a função readRs() do código de calibração usando a lógica da readPpm() daqui. Ligue o sensor a um dos pinos ADC1 entre GPIO 32 e 39: os pinos ADC2 não podem ser usados com o Wi-Fi ligado. Cada linha tem cerca de 22 bytes; com um registro por minuto, acumulam-se cerca de 32 KB de dados por dia, e a área de 1,5 MB do esquema de partição padrão dura aproximadamente um mês e meio. Para registros mais longos, você pode usar um módulo de cartão SD e, se quiser mandar os dados direto para uma planilha, veja o método ESP32 com Google Sheets.

MQ-135 com o Raspberry Pi Pico (MicroPython)

No Pico, você alimenta o módulo com 5 V pelo pino VBUS e liga a saída AO ao GP26, de novo por um divisor de 10 kΩ / 20 kΩ. O ADC do Pico retorna um valor de 16 bits (0–65535) com referência de 3,3 V; o código converte esse valor primeiro em tensão e depois, usando a razão do divisor, na saída do sensor. A fórmula é a mesma do Arduino. Como o VBUS pode variar entre 4,8 e 5,1 V conforme a fonte USB, para resultados precisos meça o VBUS com um multímetro e corrija o valor 5.0 no código.

MicroPython
1from machine import ADC
2import time
3
4adc = ADC(26)                          # GP26 = ADC0, AO bölücü üzerinden / AO through a divider
5DIVIDER = 1.5                          # (10k + 20k) / 20k
6RL = 1.0                               # kΩ, karttaki yük direnci / load resistor on the board
7R0 = 3.6                               # kΩ, kendi kalibrasyon sonucun / your calibration result
8PARA, PARB = 116.6020682, 2.769034857
9VCC = 5.0                              # VBUS gerilimi / VBUS voltage
10
11def read_rs():
12    total = 0
13    for _ in range(20):
14        total += adc.read_u16()
15        time.sleep_ms(5)
16    v = total / 20 / 65535 * 3.3 * DIVIDER    # sensör çıkışı (V) / sensor output (V)
17    v = max(v, 0.05)                          # sıfıra bölmeyi önle / avoid dividing by zero
18    return RL * (VCC - v) / v
19
20print("Isiniyor / warming up...")
21time.sleep(180)                        # 3 dk ısınma / 3 min warm-up
22while True:
23    ratio = read_rs() / R0
24    ppm = PARA * ratio ** -PARB        # tahmini CO2 eşdeğeri / estimated CO2 equivalent
25    print("Rs/R0 = %.2f   ~%d ppm" % (ratio, ppm))
26    time.sleep(2)

MQ-135 vs CCS811 vs sensores de CO2 NDIR

Existem três classes de sensores de qualidade do ar, e cada uma responde a uma pergunta diferente. O MQ-135 é barato, analógico e simples; mostra mudanças gerais no ar. O CCS811 também é um sensor de óxido metálico, mas tem um microcontrolador próprio dentro, controla o aquecedor sozinho e entrega TVOC (compostos orgânicos voláteis totais) e eCO2 (CO2 equivalente) direto via I2C; falamos dele no guia do sensor de qualidade do ar CCS811. O único grupo que mede CO2 real são os sensores NDIR (infravermelho não dispersivo): módulos como o MH-Z19B ou o SenseAir S8 medem quanto da luz infravermelha de 4,26 µm é absorvida pelas moléculas de CO2 e quase não são afetados por outros gases.

CaracterísticaMQ-135CCS811NDIR (MH-Z19B, SenseAir S8)
Princípio de mediçãoResistência do SnO2 aquecidoÓxido metálico + microcontrolador embutidoAbsorção de luz infravermelha
Mede CO2 real?Não, estimaNão, calcula eCO2 a partir dos VOCsSim
SaídaAnalógica (mais limiar digital)I2CUART ou PWM
Alimentação5 V, cerca de 150 mA3,3 V, cerca de 30 mA em média5 V, menos de 60 mA em média
Aquecimento24–48 h quando novo, depois alguns minutos48 h quando novo, depois 20 minutosCerca de 3 minutos
PrecisãoBaixa, para mudanças relativasMédia, para acompanhamento relativo de VOCsCerca de ±50 ppm + 3–5% da leitura
PreçoO mais baratoMédioO mais caro

Resumindo: para aprender, acionar um alarme ou uma ventoinha e acompanhar mudanças no ar, o MQ-135 basta. Se você quer monitorar vapores de tinta, cola ou produtos de limpeza, o CCS811 entrega dados mais suaves e estáveis. Se precisa de um número confiável para responder “já é hora de abrir a janela?” em uma sala de aula, escritório ou quarto, vai ter que investir em um sensor de CO2 NDIR. Muita gente usa os dois: o sensor NDIR acompanha o CO2, enquanto o MQ-135 pega eventos repentinos, como fumaça ou cheiro de solvente na cozinha.

Ideias de projetos com o MQ-135

  • Indicador do ar do ambiente: um LED RGB que acende verde, amarelo ou vermelho conforme a qualidade do ar, ou um gráfico ao vivo em um display OLED de 0,96 polegada.
  • Exaustor de cozinha inteligente: um sistema que liga o exaustor quando fumaça e vapor aumentam durante o preparo da comida e desliga quando o ar fica limpo.
  • Alerta para garagem e oficina de pintura: um circuito que toca um buzzer e liga a ventilação quando o vapor de solvente aumenta.
  • Monitor da areia do gato: um projeto com ESP32 que avisa no celular quando a amônia sobe e é hora de trocar a areia.
  • Painel do ar da sala de aula: um gráfico que registra as medições com um ESP32 e as compara com os horários das aulas para mostrar os hábitos de ventilação.
  • Automação residencial: envie os valores ao Home Assistant via MQTT e ligue um purificador de ar com uma tomada inteligente quando o ar piorar.

Erros comuns e como corrigi-los

SintomaCausa provávelSolução
Os valores ficam mudando por horasPré-aquecimento inicial não concluídoDeixe o sensor ligado sem parar por 24–48 horas e depois calibre
Valores altos ou baixos demais ao ligarSem espera do aquecimento diárioEspere pelo menos 3 minutos, de preferência 10–20, antes de medir
Milhares de ppm mesmo no ar limpoR0 errado ou calibração feita em ambiente fechadoRepita a calibração no ar externo limpo
No ESP32, os valores ficam sempre no máximoAO chega a 5 V, ou o sensor está sendo alimentado com 3,3 VAlimente o sensor com 5 V e coloque um divisor de 10k/20k no AO
O Arduino esquenta ou o USB desconectaO aquecedor de 150 mA e as outras cargas sobrecarregam o USBUse uma fonte externa de 5 V e una os GNDs
O ppm sobe com sol ou depois do banhoEfeito da temperatura e da umidadeCalibre em condições parecidas ou aplique a correção com DHT22
Interprete os valores do MQ-135 não como uma medição absoluta, mas como uma variação em relação a uma referência. Depois que o sensor aquecer, tome como referência o valor medido em um momento de ar limpo e decida com limiares relativos, como “30% acima da referência”; essa abordagem é muito mais robusta contra erros de calibração, envelhecimento do sensor e mudanças de estação. Falamos dos filtros de média móvel e mediana que você pode usar para suavizar as leituras em filtragem de dados de sensores.

Desde que você conheça os limites dele, o MQ-135 é um ótimo sensor inicial para aprender sobre qualidade do ar e automatizar a ventilação: alimente com 5 V, dê 24–48 horas de pré-aquecimento, encontre o R0 no ar limpo, leia os resultados de forma relativa e passe para um sensor NDIR quando precisar de CO2 real. No app BlueGrays, você encontra os projetos Uso do sensor de gás, Alarme com sensor de chama e de gás e Detector de gás MQ-2 com ESP32, junto com projetos de temperatura e umidade com DHT11 e DHT22 e de registrador de temperatura com ESP32, com esquemas do circuito e códigos passo a passo.

Perguntas frequentes

O MQ135 mede CO2?

O MQ-135 não mede CO2 de verdade; ele estima. O sensor responde só fracamente ao CO2; a resposta real dele é a gases como álcool, amônia e benzeno e aos compostos voláteis liberados pelas pessoas. O valor de CO2 em ppm que as bibliotecas mostram é calculado a partir dessa poluição geral; para CO2 real, você precisa de um sensor NDIR como o MH-Z19B.

Quanto tempo o MQ135 leva para aquecer?

Um MQ-135 novo precisa de um pré-aquecimento inicial de 24–48 horas, que deve ser concluído antes da calibração. Depois disso, o sensor fica utilizável em cerca de 2–3 minutos toda vez que é ligado; para valores mais estáveis, é melhor esperar 10–20 minutos.

Como encontrar o valor de R0 do MQ135?

Leve um sensor que já completou o pré-aquecimento para o ar externo limpo, calcule a resistência dele com Rs = RL × (1023 − bruto) / bruto e, considerando que o CO2 externo é de cerca de 420 ppm, calcule R0 = Rs × (420 / 116,6)^(1 / 2,769). Tire a média de vários minutos de leituras. O R0 encontrado vale só para aquele sensor e aquele resistor de carga.

Qual é a diferença entre o MQ135 e o MQ2?

Os dois são sensores de óxido metálico com aquecedor, mas são ajustados para gases diferentes. O MQ-2 é indicado para GLP, propano, metano, hidrogênio e fumaça, ou seja, é o certo para alarmes de vazamento de gás. O MQ-135 é sensível a poluentes do ar como amônia, benzeno, álcool, NOx e fumaça, e é usado para monitorar a qualidade do ar.

Dá para usar o MQ135 com um ESP32?

Sim, mas preste atenção em duas coisas. O aquecedor do sensor precisa de 5 V, então alimente o módulo pelo pino VIN do ESP32. A saída AO também pode chegar a 5 V, então reduza para 3,3 V com um divisor de tensão de 10 kΩ e 20 kΩ e ligue a um pino ADC1 entre GPIO 32 e 39.

Quais gases o MQ135 detecta?

O MQ-135 é mais sensível a amônia, compostos de enxofre e vapores aromáticos como o benzeno. Também responde claramente a álcool, fumaça e óxidos de nitrogênio, e fracamente a CO2 e monóxido de carbono. Ele não consegue diferenciar esses gases; só mostra a variação geral no ar.

Ver todos os projetos
Temperatura e umidade com DHT11
#55Intermediário

Temperatura e umidade com DHT11

Qual é a temperatura do seu quarto? O ar está úmido ou seco? O sensor DHT11 mede a temperatura e a umidade, e o Arduino mostra os valores em um display LCD de duas linhas, atualizando a cada poucos se

4.7(47)
73
Uso de um sensor de gás
#51Iniciante
Premium

Uso de um sensor de gás

Um pequeno guardião que percebe um vazamento de gás na cozinha antes de você. O sensor de gás monitora o ar continuamente; enquanto está tudo bem, o LED verde fica aceso e, quando o gás no ar ultrapas

4.6(37)
50
Alarme com sensores de chama e gás
#52Avançado
Premium

Alarme com sensores de chama e gás

Um pequeno guardião que avisa você imediatamente quando há um vazamento de gás ou surge uma chama. O sensor de gás monitora o gás no ar e o sensor de chama detecta a luz do fogo; enquanto está tudo be

4.8(52)
67
Alarme detector de gás e fumaça com ESP32 e MQ-2
#122Intermediário

Alarme detector de gás e fumaça com ESP32 e MQ-2

Neste projeto, um módulo sensor de gás MQ-2 detecta gases combustíveis (LPG, metano e propano) e fumaça, acionando um alarme tipo sirene em um buzzer passivo. A saída digital de limiar (DO) do sensor é conectada ao GPIO 4 do ESP32. Quando a concentração de gás ou fumaça ultrapassa o limiar, DO passa para LOW e o alarme é acionado. A sensibilidade é ajustada pelo potenciômetro do módulo. O buzzer passivo é acionado com tone() para criar um efeito de sirene que alterna entre duas frequências.

4.7(28)
43
Temperatura e umidade com ESP32 e DHT22 (com display LCD)
#117Intermediário

Temperatura e umidade com ESP32 e DHT22 (com display LCD)

Este projeto mede a temperatura e a umidade do ambiente com um sensor DHT22 e exibe os dados em tempo real em um LCD I2C 16x2. Os dados são lidos do sensor pelo protocolo de um fio (one-wire) com a biblioteca Adafruit DHT. Este projeto serve de base para estações meteorológicas IoT, automação residencial ou sistemas de monitoramento ambiental em estufas. A comunicação com o DHT22 é feita pelo pino GPIO 4 do ESP32.

4.8(38)
56
Registrador de temperatura com ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avançado
Premium

Registrador de temperatura com ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

Monte um registrador de temperatura com data e hora usando ESP32! Leia a temperatura e a umidade do DHT22 a cada minuto, obtenha a hora real do DS1307 RTC e grave os dados em CSV no cartão microSD. O OLED exibe a temperatura atual em letras grandes, a porcentagem de umidade, a data e a hora e o contador de registros. O arquivo /temp_log.csv no cartão SD pode ser aberto no Excel, Google Sheets ou pandas do Python para análise — ideal para monitorar estufas, acompanhar a temperatura de casa ou…

4.7(7)
12
Display LCD I2C com ESP32 (16x2)
#119Intermediário
Premium

Display LCD I2C com ESP32 (16x2)

Neste projeto, exibimos texto conectando um display LCD de 16x2 caracteres ao ESP32 pelo protocolo I2C. Com o I2C, controlamos o display usando apenas dois pinos de dados (SDA e SCL), dispensando o uso de seis ou mais pinos da conexão paralela tradicional. O código mostra uma mensagem fixa na primeira linha e um contador que aumenta a cada segundo na segunda linha. Na inicialização, também executamos um scanner I2C para mostrar o endereço do display no Monitor Serial (geralmente 0x27 ou 0x3F).

4.9(23)
40
Acender uma lâmpada de 220V com botão de pressão
#19Avançado
Premium

Acender uma lâmpada de 220V com botão de pressão

Enquanto você mantém o botão pressionado, uma lâmpada de 220 volts fica acesa e apaga assim que você solta. O sinal fraco do Arduino não consegue acender a lâmpada diretamente, então um relé, que é um

4.4(30)
38