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.

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ística | Valor |
|---|---|
| Material sensor | Dióxido de estanho (SnO2), tubo cerâmico com aquecedor |
| Tensão do aquecedor | 5 V ± 0,1 V |
| Resistência e corrente do aquecedor | 33 Ω ± 5%, cerca de 150 mA |
| Potência do aquecedor | Até 800 mW |
| Faixa de detecção | NH3 10–300 ppm, benzeno 10–1.000 ppm, álcool 10–300 ppm |
| Pré-aquecimento inicial | Mais de 24 horas |
| Condições de operação | −10 a 45 °C, umidade relativa abaixo de 95% |
| Saídas do módulo | AO (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ás | De onde vem em casa? | Resposta do MQ-135 |
|---|---|---|
| Amônia (NH3) | Produtos de limpeza, areia de gato, adubo | Alta |
| Benzeno, tolueno e solventes | Tinta, thinner, cola, fumaça de cigarro | Alta |
| Vapor de álcool | Álcool em gel, colônia, perfume | Alta |
| Fumaça | Cozinhar, cigarro, velas | Média |
| Óxidos de nitrogênio (NOx) | Fogão a gás, escapamento de veículos | Média |
| Dióxido de carbono (CO2) | Respiração humana, ambientes fechados e cheios | Fraca, 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–450 | Ar externo limpo |
| 450–800 | Ambiente interno bem ventilado |
| 800–1000 | Aceitável, pense em ventilar |
| 1000–1500 | Ar abafado, a concentração pode cair; ventile |
| 1500–2000 | Podem surgir dor de cabeça e sonolência |
| 5000 | Limite de exposição de 8 horas no local de trabalho |
Ligação do MQ-135 ao Arduino
| Pino do módulo | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC (inclui o aquecedor) | 5V | VIN (5 V do USB) | VBUS (5 V do USB) |
| GND | GND | GND | GND |
| AO (saída analógica) | A0 | GPIO 34, por um divisor de 10 kΩ / 20 kΩ | GP26 (ADC0), por um divisor de 10 kΩ / 20 kΩ |
| DO (limiar digital, opcional) | D2 | Qualquer GPIO, por um divisor | Qualquer pino GP, por um divisor |
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.
Calibração de R0 do MQ-135 passo a passo
- 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.
- 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.
- Leve o circuito para um ar externo limpo, como uma varanda ou uma janela aberta, longe de trânsito, cigarro e cozinha.
- Carregue o código de calibração, abra o Monitor Serial a 9600 baud e espere pelo menos 10 minutos.
- Quando o R0 médio na tela quase não mudar por vários minutos, anote o valor; esse é o R0 do seu sensor.
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.
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.
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.
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.
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ística | MQ-135 | CCS811 | NDIR (MH-Z19B, SenseAir S8) |
|---|---|---|---|
| Princípio de medição | Resistência do SnO2 aquecido | Óxido metálico + microcontrolador embutido | Absorção de luz infravermelha |
| Mede CO2 real? | Não, estima | Não, calcula eCO2 a partir dos VOCs | Sim |
| Saída | Analógica (mais limiar digital) | I2C | UART ou PWM |
| Alimentação | 5 V, cerca de 150 mA | 3,3 V, cerca de 30 mA em média | 5 V, menos de 60 mA em média |
| Aquecimento | 24–48 h quando novo, depois alguns minutos | 48 h quando novo, depois 20 minutos | Cerca de 3 minutos |
| Precisão | Baixa, para mudanças relativas | Média, para acompanhamento relativo de VOCs | Cerca de ±50 ppm + 3–5% da leitura |
| Preço | O mais barato | Médio | O 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
| Sintoma | Causa provável | Solução |
|---|---|---|
| Os valores ficam mudando por horas | Pré-aquecimento inicial não concluído | Deixe o sensor ligado sem parar por 24–48 horas e depois calibre |
| Valores altos ou baixos demais ao ligar | Sem espera do aquecimento diário | Espere pelo menos 3 minutos, de preferência 10–20, antes de medir |
| Milhares de ppm mesmo no ar limpo | R0 errado ou calibração feita em ambiente fechado | Repita a calibração no ar externo limpo |
| No ESP32, os valores ficam sempre no máximo | AO chega a 5 V, ou o sensor está sendo alimentado com 3,3 V | Alimente o sensor com 5 V e coloque um divisor de 10k/20k no AO |
| O Arduino esquenta ou o USB desconecta | O aquecedor de 150 mA e as outras cargas sobrecarregam o USB | Use uma fonte externa de 5 V e una os GNDs |
| O ppm sobe com sol ou depois do banho | Efeito da temperatura e da umidade | Calibre em condições parecidas ou aplique a correção com DHT22 |
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.
Projetos para montar depois desta leitura
Ver todos os projetos
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

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

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

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.

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.

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…

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).

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
Mais artigos
App BlueGrays
Monte este circuito passo a passo pelo celular
Esquema do circuito, código e lista de componentes em uma só tela. Mais de 200 projetos com Arduino, ESP32 e Pico prontos para montar.
Escaneie com seu celular
Aponte a câmera para o código para abrir a loja de aplicativos do seu celular.
ou abrir a loja

