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.

¿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ística | Valor |
|---|---|
| Material sensible | Dióxido de estaño (SnO2), tubo cerámico con calefactor |
| Tensión del calefactor | 5 V ± 0,1 V |
| Resistencia y corriente del calefactor | 33 Ω ± 5 %, unos 150 mA |
| Potencia del calefactor | Hasta 800 mW |
| Rango de detección | NH3 10–300 ppm, benceno 10–1000 ppm, alcohol 10–300 ppm |
| Precalentamiento inicial | Más de 24 horas |
| Condiciones de trabajo | De −10 a 45 °C, humedad relativa inferior al 95 % |
| Salidas del módulo | AO (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, abono | Alta |
| Benceno, tolueno y disolventes | Pintura, disolvente, pegamento, humo de tabaco | Alta |
| Vapor de alcohol | Gel hidroalcohólico, colonia, perfume | Alta |
| Humo | Cocinar, cigarrillos, velas | Media |
| Óxidos de nitrógeno (NOx) | Cocina de gas, gases de escape | Media |
| Dióxido de carbono (CO2) | Respiración humana, habitaciones cerradas y llenas | Dé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–450 | Aire limpio exterior |
| 450–800 | Interior bien ventilado |
| 800–1000 | Aceptable, plantéate ventilar |
| 1000–1500 | Aire cargado, cuesta concentrarse; ventila |
| 1500–2000 | Pueden aparecer dolor de cabeza y somnolencia |
| 5000 | Límite de exposición laboral de 8 horas |
Conexión del MQ-135 a Arduino
| Pin del módulo | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC (incluye el calefactor) | 5V | VIN (5 V del USB) | VBUS (5 V del USB) |
| GND | GND | GND | GND |
| AO (salida analógica) | A0 | GPIO 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) | D2 | Cualquier GPIO con divisor | Cualquier pin GP con divisor |
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.
Calibración de R0 del MQ-135 paso a paso
- 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.
- 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.
- 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.
- Carga el código de calibración, abre el Monitor Serie a 9600 baudios y espera al menos 10 minutos.
- Cuando el R0 medio de la pantalla apenas cambie durante varios minutos, apúntalo: ese es el R0 de tu 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}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.
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.
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.
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.
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ística | MQ-135 | CCS811 | NDIR (MH-Z19B, SenseAir S8) |
|---|---|---|---|
| Principio de medida | Resistencia del SnO2 caliente | Óxido metálico + microcontrolador integrado | Absorción de luz infrarroja |
| ¿Mide CO2 real? | No, lo estima | No, calcula eCO2 a partir de los VOC | Sí |
| Salida | Analógica (y umbral digital) | I2C | UART o PWM |
| Alimentación | 5 V, unos 150 mA | 3,3 V, unos 30 mA de media | 5 V, menos de 60 mA de media |
| Calentamiento | 24–48 h la primera vez, luego unos minutos | 48 h la primera vez, luego 20 minutos | Unos 3 minutos |
| Precisión | Baja, para cambios relativos | Media, para seguir los VOC de forma relativa | Unos ±50 ppm + 3–5 % de la lectura |
| Precio | El más barato | Medio | El 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íntoma | Causa probable | Solución |
|---|---|---|
| Los valores se desplazan durante horas | No se completó el precalentamiento inicial | Deja el sensor funcionando 24–48 horas seguidas y luego calibra |
| Valores muy altos o bajos al encender | No se esperó el calentamiento diario | Espera al menos 3 minutos, mejor 10–20, antes de medir |
| Miles de ppm incluso en aire limpio | R0 incorrecto o calibración hecha en interior | Repite la calibración en aire exterior limpio |
| En el ESP32 los valores siempre están al máximo | AO llega a 5 V o el sensor se alimenta a 3,3 V | Alimenta el sensor a 5 V y pon un divisor 10k/20k en AO |
| El Arduino se calienta o se corta el USB | El calefactor de 150 mA y otras cargas fuerzan el USB | Usa una fuente externa de 5 V y une las masas |
| Los ppm suben con el sol o tras una ducha | Efecto de la temperatura y la humedad | Calibra en condiciones parecidas o aplica la corrección con un DHT22 |
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.
Proyectos para hacer después de leer esto
Ver todos los proyectos
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

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

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

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

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.

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

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

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í
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

