Componentes · 16 min de lectura

MQ135 Arduino: sensor de calidad del aire, calibración y ppm

MQ135 con Arduino: gases que detecta, precalentamiento de 24–48 h, calibración de R0 y fórmula de ppm, ¿mide CO2?, ventilador automático, registro con ESP32.

Los proyectos con MQ135 y Arduino son la primera parada para quien quiere vigilar de forma barata y sencilla lo limpio que está el aire de una habitación. El MQ-135 es un sensor de gas de óxido metálico con calefactor que reacciona a gases como el amoníaco, los óxidos de nitrógeno, el benceno, el alcohol y el humo. En los kits suele venir como un pequeño módulo con un comparador y un potenciómetro de ajuste; se conecta al Arduino por su salida analógica y se lee con un solo analogRead(). Pero convertir el número que da este sensor en una medida con sentido requiere algo más de conocimiento del que explican la mayoría de los tutoriales.

En esta guía aprenderás qué ocurre dentro del sensor, a qué gases es más sensible y por qué necesita calentarse 24–48 horas antes de usarlo en serio por primera vez. Después montaremos paso a paso el cálculo de ppm con la resistencia de carga (RL), la resistencia del sensor (Rs) y la resistencia de referencia (R0), haremos la calibración de R0 en aire limpio y daremos una respuesta honesta a la pregunta más repetida: ¿el MQ-135 mide de verdad CO2? Por último encenderemos un ventilador de forma automática, guardaremos las medidas con un ESP32 en un archivo con fecha y hora, escribiremos código MicroPython para la Raspberry Pi Pico y compararemos el MQ-135 con el CCS811 y los sensores de CO2 NDIR.

Módulo sensor de calidad del aire MQ-135
Módulo MQ-135: sensor de óxido metálico con calefactor, comparador LM393, potenciómetro de sensibilidad y pines VCC, GND, DO y AO.

¿Qué es el MQ-135 y cómo funciona?

Detrás de la malla de acero inoxidable, la superficie de un pequeño tubo cerámico está recubierta de dióxido de estaño (SnO2), y un hilo calefactor que lo atraviesa calienta esta capa hasta varios cientos de grados. En aire limpio, las moléculas de oxígeno se adhieren a la superficie de SnO2, atrapan los electrones libres del material y la resistencia de la capa se mantiene alta. Cuando entra en el aire un gas reductor como el amoníaco, el alcohol o el benceno, reacciona con el oxígeno de la superficie, los electrones quedan libres y la resistencia baja. Cuanto más denso es el gas, menor es la resistencia. El módulo coloca esta resistencia en un divisor de tensión con una resistencia de carga, de modo que la tensión del pin AO sube al aumentar el gas.

CaracterísticaValor
Material sensibleDióxido de estaño (SnO2), tubo cerámico con calefactor
Tensión del calefactor5 V ± 0,1 V
Resistencia y corriente del calefactor33 Ω ± 5 %, unos 150 mA
Potencia del calefactorHasta 800 mW
Rango de detecciónNH3 10–300 ppm, benceno 10–1000 ppm, alcohol 10–300 ppm
Precalentamiento inicialMás de 24 horas
Condiciones de trabajoDe −10 a 45 °C, humedad relativa inferior al 95 %
Salidas del móduloAO (analógica) y DO (umbral digital con LM393)

¿Qué gases detecta el MQ-135?

El MQ-135 no es un sensor selectivo que mida un solo gas; reacciona a la vez a muchos gases que reaccionan con la superficie caliente de SnO2 y no puede distinguirlos. El fabricante lo describe como especialmente sensible al amoníaco, los compuestos de azufre y los vapores de tipo benceno; también responde claramente al alcohol, al humo y a los óxidos de nitrógeno. Por eso lo más acertado es ver el MQ-135 como un «indicador de contaminación del aire»: cuando el valor sube te dice que algo ha cambiado en el aire, pero no qué. Para fugas de gas natural o butano, el sensor de gas MQ-2, diseñado para ello, es mucho más adecuado.

Gas¿De dónde sale en casa?Respuesta del MQ-135
Amoníaco (NH3)Productos de limpieza, arena de gato, abonoAlta
Benceno, tolueno y disolventesPintura, disolvente, pegamento, humo de tabacoAlta
Vapor de alcoholGel hidroalcohólico, colonia, perfumeAlta
HumoCocinar, cigarrillos, velasMedia
Óxidos de nitrógeno (NOx)Cocina de gas, gases de escapeMedia
Dióxido de carbono (CO2)Respiración humana, habitaciones cerradas y llenasDébil, indirecta

¿El MQ-135 mide CO2?

Respuesta corta: no de verdad; lo estima. La hoja de datos del MQ-135 incluye una curva de CO2 y la mayoría de las librerías de internet calculan con ella un valor de «CO2 en ppm». Pero el CO2 es un gas químicamente estable y reacciona muy débilmente con la superficie de SnO2. En una habitación cerrada, a medida que la gente respira, aumentan junto con el CO2 la humedad y los compuestos orgánicos volátiles que desprende el cuerpo, y es a eso a lo que reacciona de verdad el MQ-135. Así que el «CO2» que muestra el sensor es en realidad una estimación hecha a partir de la contaminación general del aire de la habitación. La prueba más fácil: abre un frasco de colonia a su lado y verás el «CO2» dispararse hasta 5000 ppm.

Eso no significa que el MQ-135 no sirva. Funciona muy bien para decisiones relativas como «la habitación está cargada, abre la ventana» o «hay humo en la cocina, enciende el extractor». Pero si necesitas el valor real de CO2 en un aula, una oficina o un dormitorio, deberías usar uno de los sensores de CO2 NDIR de la comparación del final de esta guía. La tabla resume qué significan las lecturas de un medidor de CO2 real; puedes interpretar las estimaciones del MQ-135 con los mismos rangos.

CO2 (ppm)¿Qué significa?
400–450Aire limpio exterior
450–800Interior bien ventilado
800–1000Aceptable, plantéate ventilar
1000–1500Aire cargado, cuesta concentrarse; ventila
1500–2000Pueden aparecer dolor de cabeza y somnolencia
5000Límite de exposición laboral de 8 horas

Conexión del MQ-135 a Arduino

Pin del móduloArduino UnoESP32Raspberry Pi Pico
VCC (incluye el calefactor)5VVIN (5 V del USB)VBUS (5 V del USB)
GNDGNDGNDGND
AO (salida analógica)A0GPIO 34, a través de un divisor de 10 kΩ / 20 kΩGP26 (ADC0), a través de un divisor de 10 kΩ / 20 kΩ
DO (umbral digital, opcional)D2Cualquier GPIO con divisorCualquier pin GP con divisor
El calefactor del MQ-135 consume por sí solo unos 150 mA y necesita 5 V sí o sí; si lo alimentas desde el pin 3V3 de un ESP32 o una Pico, no se calentará lo suficiente y las lecturas no servirán. Con el módulo a 5 V, la salida AO puede llegar a 5 V, y eso daña la entrada ADC de 3,3 V del ESP32 y de la Pico. Monta entre AO y GND un divisor de tensión de 10 kΩ + 20 kΩ y conecta el punto medio al pin ADC: 5 V × 20 / 30 = 3,33 V. Puedes probar otras parejas de resistencias con la calculadora de divisor de tensión. En marcha, el sensor se calienta tanto que no se puede tocar; no toques su malla.

Precalentamiento: el rodaje de 24–48 horas y el calentamiento diario

Los sensores de la serie MQ tienen dos calentamientos distintos que a menudo se confunden. El primero es el precalentamiento inicial (rodaje o burn-in): en la superficie de un sensor nuevo, o que ha pasado mucho tiempo guardado sin uso, se acumulan humedad y suciedad. El fabricante pide alimentarlo más de 24 horas antes de las medidas estándar; en la práctica, tenerlo funcionando sin parar 24–48 horas te permite ver cómo los valores se desplazan despacio y al final se estabilizan. Si calibras R0 antes de que pase ese tiempo, todas tus lecturas se desviarán en pocos días. El segundo es el calentamiento diario: un sensor ya rodado necesita igualmente unos minutos cada vez que se enciende para que el calefactor llegue al equilibrio; los valores del primer minuto salen claramente altos o bajos. Por eso el código espera al menos 2–3 minutos; para medidas precisas es mejor esperar 10–20 minutos.

Rs, RL y R0: ¿cómo se calculan los ppm con el MQ-135?

Dentro del módulo, la resistencia del sensor Rs y la resistencia de carga de la placa RL están en serie y se reparten los 5 V. La tensión del pin AO es Vout = 5 × RL / (Rs + RL). Si despejas, obtienes la resistencia actual del sensor a partir de la tensión medida: Rs = RL × (5 − Vout) / Vout. En el Arduino Uno el ADC usa esos mismos 5 V como referencia, así que ni siquiera hace falta pasar a voltios: Rs = RL × (1023 − valor bruto) / valor bruto. Rs por sí sola no dice mucho, porque la resistencia cambia muchísimo de un sensor a otro. Lo que importa es su relación con el valor que tiene en un aire conocido, llamado R0.

Las curvas de la hoja de datos relacionan la razón Rs/R0 con la concentración de gas y en ejes logarítmicos son casi rectas. Por eso se pueden expresar con una función potencial: ppm = A × (Rs/R0)^−B. Los coeficientes que usan las librerías más conocidas del MQ-135 para la curva de CO2 son A = 116,602 y B = 2,769. Para hallar R0 pones el sensor en aire exterior limpio; como hoy el aire exterior tiene unos 420 ppm de CO2, despejas y calculas R0 = Rs × (420 / A)^(1/B) ≈ Rs × 1,59. Recuerda que estos coeficientes son distintos para otros gases y que el resultado es solo una estimación.

Una de las pequeñas resistencias SMD de la parte trasera del módulo es RL, y el código impreso indica su valor: 102 = 1 kΩ, 103 = 10 kΩ, 203 = 20 kΩ. Explicamos estos códigos en códigos de resistencias SMD. Poner un RL equivocado no cambia el resultado en ppm, porque R0 se calcula con la misma RL y la razón no varía; solo se escalan los números de Rs y R0 que ves en pantalla. Pero si RL es muy pequeña (1 kΩ), la tensión de AO queda muy baja en aire limpio y la resolución del ADC empeora; si ves que los valores bailan, promediar es una buena solución.

Calibración de R0 del MQ-135 paso a paso

  1. Conecta VCC del módulo a 5V, GND a GND y AO a A0, y completa el precalentamiento inicial dejando el sensor funcionando sin parar 24–48 horas.
  2. Lee el código de la resistencia de carga en la parte trasera de la placa y ajusta RL_KOHM en el código de abajo.
  3. Lleva el circuito a aire exterior limpio, como un balcón o una ventana abierta, lejos del tráfico, del tabaco y de la cocina.
  4. Carga el código de calibración, abre el Monitor Serie a 9600 baudios y espera al menos 10 minutos.
  5. Cuando el R0 medio de la pantalla apenas cambie durante varios minutos, apúntalo: ese es el R0 de tu 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}

Cada segundo el código promedia 50 lecturas, calcula con ellas Rs y el R0 del momento y añade ese valor a una media móvil lenta. La media móvil impide que una bocanada de humo de coche traída por el viento estropee el resultado. El R0 que obtengas pertenece solo a ese sensor y a ese valor de RL; si cambias el sensor, vuelve a calibrar. Con los meses, a medida que el sensor envejece, R0 se desplaza poco a poco, así que en proyectos de larga duración conviene repetir la calibración cada pocos meses.

Código de medida: ppm estimados y nivel de calidad del aire

Cuando tengas R0, lo pones en el código de medida. El código espera primero 3 minutos de calentamiento y luego, cada 2 segundos, escribe en el Monitor Serie la razón Rs/R0, los ppm estimados y una etiqueta de calidad del aire según la tabla de arriba. En aire limpio la razón ronda 0,6; cuanto más baja, más gas hay en el aire. Para probarlo, echa unas gotas de colonia en un algodón cerca del sensor: en segundos la razón baja y los ppm suben, y al retirar el algodón los valores vuelven a la normalidad en unos 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}

¿Cómo afectan la temperatura y la humedad al MQ-135?

La resistencia de los sensores de óxido metálico no depende solo del gas, sino también de la temperatura y la humedad ambiente. Según la curva de la hoja de datos, en el mismo aire la razón Rs/R0 baja claramente cuando la temperatura sube de 20 °C a 40 °C, y también baja al aumentar la humedad; el sensor lo interpreta como «más gas». El resultado son ppm que suben solos en una habitación con sol de tarde o en el baño después de una ducha. Hay dos formas de reducirlo: calibrar a una temperatura y humedad parecidas a las del lugar de uso, o medir temperatura y humedad con un DHT22 o DHT11 y aplicar la fórmula de corrección de las librerías. Funciones como getCorrectedPPM(temperatura, humedad) de las librerías MQ135 hacen justo eso. Si quieres medir la temperatura y la humedad con más precisión que con un DHT11, puedes usar un sensor BME280.

Automatizar un ventilador

El trabajo más útil del MQ-135 es encender solo un ventilador o un extractor cuando el aire se carga. Aquí importan dos detalles. El primero es la histéresis: el ventilador se enciende por encima de 1000 ppm, pero solo se apaga cuando el valor baja de 800 ppm; con un único umbral, el relé haría clic cada segundo mientras el valor oscila a su alrededor. El segundo es un tiempo mínimo de funcionamiento: una vez encendido, el ventilador funciona al menos 2 minutos para que el aire de la habitación se renueve de verdad. Además, el código suaviza las lecturas para que los picos momentáneos no activen el ventilador.

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}

El pin IN del módulo de relé va a D7 y su VCC y GND al Arduino; el ventilador se conecta a su propia alimentación a través de los bornes COM y NO del relé. Si usas un ventilador de ordenador de 12 V, un MOSFET es más silencioso y dura más que un relé; el cableado es el mismo que en el circuito de la mini bomba de agua. Si vas a conmutar un aparato de 230 V como el extractor del baño, sigue al pie de la letra las normas de seguridad de la guía del módulo de relé: mete la parte de red en una caja cerrada y nunca toques el circuito con tensión. Poner primero el pin al nivel de apagado y solo después configurarlo como salida en setup() evita que los relés activos a nivel bajo se activen un instante al arrancar.

Registrar la calidad del aire con un ESP32

Ver cómo cambia la calidad del aire a lo largo del día enseña mucho más que una lectura suelta: solo un registro revela el aire cargado que se acumula en el dormitorio por la mañana, el pico de la cocina mientras cocinas o el efecto de ventilar. El código ESP32 de abajo toma una medida por minuto, obtiene la hora de internet (NTP) para poner fecha y hora en cada línea y guarda el resultado como CSV en el sistema de archivos LittleFS de la propia memoria flash del ESP32. Al abrir la IP del ESP32 en el navegador ves la última medida y descargas con un clic todo el registro como un archivo CSV que se abre en Excel. No hace falta ninguna librería extra.

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 el ADC del ESP32 mide de forma distinta al del Uno, lo mejor es repetir la calibración de R0 con este circuito; basta con adaptar la función readRs() del código de calibración con la lógica de readPpm(). Conecta el sensor a uno de los pines ADC1, entre GPIO 32 y 39: los pines ADC2 no se pueden usar con el Wi-Fi activo. Cada línea ocupa unos 22 bytes; con una entrada por minuto se acumulan unos 32 KB al día, y el área de 1,5 MB del esquema de particiones por defecto da para mes y medio aproximadamente. Para registros más largos puedes usar un módulo de tarjeta SD, y si quieres mandar los datos directamente a una hoja de cálculo, mira el método ESP32 con Google Sheets.

MQ-135 con Raspberry Pi Pico (MicroPython)

En la Pico alimentas el módulo con 5 V desde el pin VBUS y conectas la salida AO a GP26, de nuevo a través de un divisor de 10 kΩ / 20 kΩ. El ADC de la Pico devuelve un valor de 16 bits (0–65535) referido a 3,3 V; el código lo pasa primero a tensión y después, con la relación del divisor, a la salida del sensor. La fórmula es la misma que en Arduino. Como VBUS puede variar entre 4,8 y 5,1 V según la fuente USB, si quieres precisión mide VBUS con un multímetro y corrige el 5.0 del 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)

Comparativa: MQ-135, CCS811 y sensor de CO2 NDIR

Hay tres clases de sensores de calidad del aire y cada una responde a una pregunta distinta. El MQ-135 es barato, analógico y sencillo; muestra los cambios generales del aire. El CCS811 también es un sensor de óxido metálico, pero lleva su propio microcontrolador, gestiona él mismo el calefactor y entrega por I2C directamente TVOC (compuestos orgánicos volátiles totales) y eCO2 (CO2 equivalente); lo explicamos en la guía del sensor de calidad del aire CCS811. El único grupo que mide CO2 real son los sensores NDIR (infrarrojo no dispersivo): módulos como el MH-Z19B o el SenseAir S8 miden cuánta luz infrarroja de 4,26 µm absorben las moléculas de CO2 y apenas les afectan otros gases.

CaracterísticaMQ-135CCS811NDIR (MH-Z19B, SenseAir S8)
Principio de medidaResistencia del SnO2 calienteÓxido metálico + microcontrolador integradoAbsorción de luz infrarroja
¿Mide CO2 real?No, lo estimaNo, calcula eCO2 a partir de los VOCSí
SalidaAnalógica (y umbral digital)I2CUART o PWM
Alimentación5 V, unos 150 mA3,3 V, unos 30 mA de media5 V, menos de 60 mA de media
Calentamiento24–48 h la primera vez, luego unos minutos48 h la primera vez, luego 20 minutosUnos 3 minutos
PrecisiónBaja, para cambios relativosMedia, para seguir los VOC de forma relativaUnos ±50 ppm + 3–5 % de la lectura
PrecioEl más baratoMedioEl más caro

En resumen: para aprender, activar una alarma o un ventilador y seguir los cambios del aire, el MQ-135 es suficiente. Si quieres seguir los vapores de pintura, pegamento o productos de limpieza, el CCS811 da datos más suaves y estables. Si en un aula, una oficina o un dormitorio necesitas un número fiable para saber si toca abrir la ventana, tendrás que invertir en un sensor de CO2 NDIR. Mucha gente usa los dos: el sensor NDIR mide el CO2 y el MQ-135 capta sucesos repentinos como humo u olor a disolvente en la cocina.

Ideas de proyectos con el MQ-135

  • Indicador del aire de la habitación: Un LED RGB que se pone verde, amarillo o rojo según la calidad del aire, o una gráfica en vivo en una pantalla OLED.
  • Campana extractora inteligente: Un sistema que enciende el extractor cuando aumentan el humo y el vapor al cocinar y lo apaga cuando el aire está limpio.
  • Aviso para garaje y taller de pintura: Un circuito que avisa con un zumbador y pone en marcha la ventilación cuando suben los vapores de disolvente.
  • Control del arenero del gato: Un proyecto con ESP32 que avisa al móvil cuando sube el amoníaco y toca cambiar la arena.
  • Panel del aire del aula: Una gráfica que registra las medidas con un ESP32 y las compara con el horario de clases para ver los hábitos de ventilación.
  • Domótica: Envía los valores a Home Assistant por MQTT y enciende un purificador con un enchufe inteligente cuando el aire empeore.

Errores frecuentes y cómo solucionarlos

SíntomaCausa probableSolución
Los valores se desplazan durante horasNo se completó el precalentamiento inicialDeja el sensor funcionando 24–48 horas seguidas y luego calibra
Valores muy altos o bajos al encenderNo se esperó el calentamiento diarioEspera al menos 3 minutos, mejor 10–20, antes de medir
Miles de ppm incluso en aire limpioR0 incorrecto o calibración hecha en interiorRepite la calibración en aire exterior limpio
En el ESP32 los valores siempre están al máximoAO llega a 5 V o el sensor se alimenta a 3,3 VAlimenta el sensor a 5 V y pon un divisor 10k/20k en AO
El Arduino se calienta o se corta el USBEl calefactor de 150 mA y otras cargas fuerzan el USBUsa una fuente externa de 5 V y une las masas
Los ppm suben con el sol o tras una duchaEfecto de la temperatura y la humedadCalibra en condiciones parecidas o aplica la corrección con un DHT22
Interpreta los valores del MQ-135 no como una medida absoluta, sino como un cambio respecto a una línea base. Cuando el sensor ya esté caliente, toma como base el valor medido en un momento de aire limpio y decide con umbrales relativos como «un 30 % por encima de la base»; este enfoque resiste mucho mejor los errores de calibración, el envejecimiento del sensor y los cambios de estación. Explicamos la media móvil y el filtro de mediana para suavizar las lecturas en filtrar datos de sensores.

Mientras conozcas sus límites, el MQ-135 es un sensor estupendo para empezar a entender la calidad del aire y automatizar la ventilación: aliméntalo a 5 V, dale 24–48 horas de precalentamiento, halla R0 en aire limpio, interpreta los resultados de forma relativa y pasa a un sensor NDIR cuando necesites CO2 real. En la app BlueGrays encontrarás los proyectos Uso del sensor de gas, Alarma con sensor de llama y gas y Detector de gas MQ-2 con ESP32, junto con proyectos de temperatura y humedad con DHT11 y DHT22 y un registrador de temperatura con ESP32, con esquemas y código paso a paso.

Preguntas frecuentes

¿El MQ135 mide CO2?

El MQ-135 no mide CO2 de verdad, lo estima. El sensor responde muy poco al CO2; su respuesta real es a gases como el alcohol, el amoníaco y el benceno y a los compuestos volátiles que desprenden las personas. El valor de CO2 en ppm que muestran las librerías se calcula a partir de esa contaminación general; para CO2 real necesitas un sensor NDIR como el MH-Z19B.

¿Cuánto tarda en calentarse el MQ135?

Un MQ-135 nuevo necesita un precalentamiento inicial de 24–48 horas, que debe completarse antes de calibrarlo. Después, el sensor está listo en unos 2–3 minutos cada vez que se enciende; para los valores más estables es mejor esperar 10–20 minutos.

¿Cómo se obtiene el valor R0 del MQ135?

Lleva el sensor ya precalentado a aire exterior limpio, calcula su resistencia con Rs = RL × (1023 − bruto) / bruto y, suponiendo unos 420 ppm de CO2 en el exterior, calcula R0 = Rs × (420 / 116,6)^(1 / 2,769). Promedia varios minutos de lecturas. El R0 que obtengas solo vale para ese sensor y esa resistencia de carga.

¿Qué diferencia hay entre el MQ135 y el MQ2?

Los dos son sensores de óxido metálico con calefactor, pero están ajustados a gases distintos. El MQ-2 sirve para GLP, propano, metano, hidrógeno y humo, es decir, para alarmas de fuga de gas. El MQ-135 es sensible a contaminantes del aire como el amoníaco, el benceno, el alcohol, los NOx y el humo, y se usa para vigilar la calidad del aire.

¿Se puede usar el MQ135 con un ESP32?

Sí, pero cuida dos cosas. El calefactor necesita 5 V, así que alimenta el módulo desde el pin VIN del ESP32. La salida AO también puede llegar a 5 V, por lo que debes bajarla a 3,3 V con un divisor de 10 kΩ y 20 kΩ y conectarla a un pin ADC1 entre GPIO 32 y 39.

¿Qué gases detecta el MQ135?

El MQ-135 es más sensible al amoníaco, los compuestos de azufre y los vapores aromáticos como el benceno. También responde claramente al alcohol, el humo y los óxidos de nitrógeno, y débilmente al CO2 y al monóxido de carbono. No puede distinguir estos gases entre sí; solo muestra el cambio global del aire.

Ver todos los proyectos
Temperatura y humedad con DHT11
#55Intermedio

Temperatura y humedad con DHT11

¿Qué temperatura hace en tu habitación, y el aire está húmedo o seco? El sensor DHT11 mide la temperatura y la humedad, y el Arduino escribe los valores en una pantalla LCD de dos líneas, actualizándo

4.7(47)
73
Uso del sensor de gas
#51Principiante
Premium

Uso del sensor de gas

Un pequeño vigilante que detecta un escape de gas en la cocina antes que tú. El sensor de gas olfatea el aire sin parar; mientras todo va bien se enciende el LED verde, y cuando el gas del aire supera

4.6(37)
50
Alarma con sensor de llama y gas
#52Avanzado
Premium

Alarma con sensor de llama y gas

Un pequeño vigilante que te avisa enseguida si hay una fuga de gas o aparece una llama en algún sitio. El sensor de gas vigila el aire y el sensor de llama busca la luz del fuego; mientras todo va bie

4.8(52)
67
Alarma de detector de gas/humo ESP32 MQ-2
#122Intermedio

Alarma de detector de gas/humo ESP32 MQ-2

En este proyecto, la detección de gases combustibles (GLP, metano, propano) y humo se realiza mediante un módulo sensor de gas MQ-2; al detectarlos, se emite un sonido de alarma similar a una sirena a través de un zumbador pasivo. La salida digital de umbral (DO) del sensor se conecta al GPIO 4 del ESP32; cuando el gas o el humo superan el umbral, DO pasa a LOW y se activa la alarma. La sensibilidad se ajusta con el potenciómetro del módulo. El zumbador se controla con tone().

4.7(28)
43
ESP32 DHT22 Temperatura y humedad (con pantalla LCD)
#117Intermedio

ESP32 DHT22 Temperatura y humedad (con pantalla LCD)

Se trata de un proyecto que mide la temperatura y la humedad ambientales mediante el sensor DHT22 y las muestra en tiempo real en una pantalla LCD I2C 16x2. Los datos del sensor se leen mediante el protocolo de un solo cable (one-wire) utilizando la biblioteca DHT de Adafruit. Este proyecto constituye la base para estaciones meteorológicas IoT, sistemas de automatización doméstica inteligente o sistemas de monitorización ambiental en invernaderos.

4.8(38)
56
Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
#144Avanzado
Premium

Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)

¡Construye un registrador de temperatura con marca de tiempo con ESP32! Lee temperatura/humedad del DHT22 cada minuto, obtén la hora real del DS1307 RTC y registra en tarjeta microSD como CSV. El OLED muestra en vivo la temperatura (fuente grande), porcentaje de humedad, marca de tiempo y contador de registros. El archivo /temp_log.csv en la tarjeta SD se abre en Excel, Google Sheets o Python pandas para análisis — perfecto para monitoreo de invernadero, seguimiento de temperatura del hog

4.7(7)
12
Pantalla LCD I2C para ESP32 (16x2)
#119Intermedio
Premium

Pantalla LCD I2C para ESP32 (16x2)

En este proyecto, mostramos texto conectando una pantalla LCD de 16x2 caracteres al ESP32 a través del protocolo I2C. Gracias al I2C, podemos controlar la pantalla con solo 2 pines de datos (SDA y SCL); así se elimina el desperdicio de pines que supone la conexión paralela clásica, que requiere más de 6 pines. El código imprime un mensaje fijo en la primera línea y un contador que aumenta cada segundo en la segunda línea. Además, al inicio ejecutamos el escáner I2C para mostrar la dirección de l

4.9(23)
40
Bombilla 220V con Pulsador (Push)
#19Avanzado
Premium

Bombilla 220V con Pulsador (Push)

Mientras mantienes pulsado el botón, una bombilla real de 220 voltios se queda encendida, y se apaga en cuanto lo sueltas. La pequeña señal del Arduino no puede encender la bombilla directamente, así

4.4(30)
38