MQ135 Arduino : capteur de qualité de l'air, calibrage et ppm
MQ135 avec Arduino : gaz détectés, préchauffage de 24 à 48 h, calibrage de R0 et formule des ppm, mesure-t-il le CO2 ?, ventilation auto, journal ESP32.
Les projets MQ135 avec Arduino sont le premier réflexe de tous ceux qui veulent surveiller simplement et à petit prix la qualité de l'air d'une pièce. Le MQ-135 est un capteur de gaz à oxyde métallique chauffé qui réagit à des gaz comme l'ammoniac, les oxydes d'azote, le benzène, l'alcool et la fumée. Dans les kits, il se présente généralement sous forme d'un petit module avec un comparateur et un potentiomètre de réglage ; il se branche sur l'Arduino par sa sortie analogique et se lit avec un simple analogRead(). Mais transformer le nombre qu'il renvoie en une mesure qui a du sens demande un peu plus de connaissances que ce qu'expliquent la plupart des tutoriels.
Dans ce guide, tu vas découvrir ce qui se passe dans le capteur, à quels gaz il est le plus sensible et pourquoi il doit chauffer 24 à 48 heures avant sa première vraie utilisation. Ensuite, nous construirons pas à pas le calcul des ppm à partir de la résistance de charge (RL), de la résistance du capteur (Rs) et de la résistance de référence (R0), nous calibrerons R0 en air pur et répondrons honnêtement à la question la plus fréquente : le MQ-135 mesure-t-il vraiment le CO2 ? Enfin, nous piloterons automatiquement un ventilateur, enregistrerons les mesures avec un ESP32 dans un fichier horodaté, écrirons du code MicroPython pour le Raspberry Pi Pico et comparerons le MQ-135 au CCS811 et aux capteurs de CO2 NDIR.

Qu'est-ce que le MQ-135 et comment fonctionne-t-il ?
Derrière la grille en inox, la surface d'un petit tube en céramique est recouverte de dioxyde d'étain (SnO2), et un fil chauffant qui le traverse porte cette couche à plusieurs centaines de degrés. En air pur, les molécules d'oxygène s'accrochent à la surface de SnO2, captent les électrons libres du matériau et la résistance de la couche reste élevée. Quand un gaz réducteur comme l'ammoniac, l'alcool ou le benzène arrive dans l'air, il réagit avec l'oxygène de surface, les électrons sont libérés et la résistance diminue. Plus le gaz est concentré, plus la résistance baisse. Le module place cette résistance dans un pont diviseur avec une résistance de charge : la tension de la broche AO monte donc quand le gaz augmente.
| Caractéristique | Valeur |
|---|---|
| Matériau sensible | Dioxyde d'étain (SnO2), tube céramique chauffé |
| Tension de chauffe | 5 V ± 0,1 V |
| Résistance et courant de chauffe | 33 Ω ± 5 %, environ 150 mA |
| Puissance de chauffe | 800 mW au maximum |
| Plage de détection | NH3 10–300 ppm, benzène 10–1000 ppm, alcool 10–300 ppm |
| Préchauffage initial | Plus de 24 heures |
| Conditions d'utilisation | De −10 à 45 °C, humidité relative inférieure à 95 % |
| Sorties du module | AO (analogique) et DO (seuil numérique par LM393) |
Quels gaz le MQ-135 détecte-t-il ?
Le MQ-135 n'est pas un capteur sélectif qui mesure un seul gaz ; il réagit en même temps à de nombreux gaz qui réagissent avec la surface chaude de SnO2, sans pouvoir les distinguer. Le fabricant le décrit comme particulièrement sensible à l'ammoniac, aux composés soufrés et aux vapeurs de type benzène ; il réagit aussi nettement à l'alcool, à la fumée et aux oxydes d'azote. Le mieux est donc de voir le MQ-135 comme un « indicateur de pollution de l'air » : quand la valeur monte, quelque chose a changé dans l'air, mais il ne dit pas quoi. Pour les fuites de gaz naturel ou de butane, le capteur de gaz MQ-2, conçu pour cela, convient bien mieux.
| Gaz | D'où vient-il à la maison ? | Réaction du MQ-135 |
|---|---|---|
| Ammoniac (NH3) | Produits ménagers, litière, engrais | Forte |
| Benzène, toluène et solvants | Peinture, diluant, colle, fumée de cigarette | Forte |
| Vapeur d'alcool | Gel hydroalcoolique, eau de Cologne, parfum | Forte |
| Fumée | Cuisine, cigarettes, bougies | Moyenne |
| Oxydes d'azote (NOx) | Gazinière, gaz d'échappement | Moyenne |
| Dioxyde de carbone (CO2) | Respiration, pièces fermées et pleines de monde | Faible, indirecte |
Le MQ-135 mesure-t-il le CO2 ?
Réponse courte : non, pas vraiment ; il l'estime. La fiche technique du MQ-135 contient une courbe pour le CO2, et la plupart des bibliothèques en ligne en tirent une valeur de « CO2 en ppm ». Mais le CO2 est un gaz chimiquement stable qui ne réagit que très faiblement avec la surface de SnO2. Dans une pièce fermée, quand les gens respirent, l'humidité et les composés organiques volatils émis par le corps augmentent en même temps que le CO2, et c'est surtout à eux que réagit le MQ-135. Le « CO2 » affiché est donc en fait une estimation tirée de la pollution générale de l'air de la pièce. La preuve la plus simple : ouvre un flacon d'eau de Cologne à côté et regarde le « CO2 » grimper à 5000 ppm.
Cela ne veut pas dire que le MQ-135 est inutile. Il fonctionne très bien pour des décisions relatives comme « la pièce devient confinée, ouvre la fenêtre » ou « il y a de la fumée dans la cuisine, allume la hotte ». Mais si tu as besoin de la vraie valeur de CO2 dans une salle de classe, un bureau ou une chambre, utilise plutôt un des capteurs de CO2 NDIR présentés dans la comparaison à la fin de ce guide. Le tableau résume ce que signifient les mesures d'un vrai capteur de CO2 ; tu peux interpréter les estimations du MQ-135 avec les mêmes plages.
| CO2 (ppm) | Qu'est-ce que cela signifie ? |
|---|---|
| 400–450 | Air extérieur pur |
| 450–800 | Intérieur bien aéré |
| 800–1000 | Acceptable, pense à aérer |
| 1000–1500 | Air confiné, la concentration baisse ; aère |
| 1500–2000 | Maux de tête et somnolence possibles |
| 5000 | Valeur limite d'exposition professionnelle sur 8 heures |
Branchement du MQ-135 sur l'Arduino
| Broche du module | Arduino Uno | ESP32 | Raspberry Pi Pico |
|---|---|---|---|
| VCC (chauffage compris) | 5V | VIN (5 V de l'USB) | VBUS (5 V de l'USB) |
| GND | GND | GND | GND |
| AO (sortie analogique) | A0 | GPIO 34, via un pont diviseur 10 kΩ / 20 kΩ | GP26 (ADC0), via un pont diviseur 10 kΩ / 20 kΩ |
| DO (seuil numérique, facultatif) | D2 | N'importe quel GPIO via un pont diviseur | N'importe quelle broche GP via un pont diviseur |
Préchauffage : le rodage de 24 à 48 heures et la chauffe quotidienne
Les capteurs de la série MQ ont deux chauffes distinctes, souvent confondues. La première est le préchauffage initial (rodage ou burn-in) : à la surface d'un capteur neuf, ou resté longtemps inutilisé en stock, s'accumulent humidité et impuretés. Le fabricant demande de l'alimenter plus de 24 heures avant les mesures normalisées ; en pratique, le laisser tourner en continu 24 à 48 heures permet de voir les valeurs dériver lentement puis se stabiliser. Si tu calibres R0 avant la fin de cette période, toutes tes mesures dériveront en quelques jours. La seconde est la chauffe quotidienne : même rodé, le capteur a besoin de quelques minutes à chaque mise sous tension pour que le chauffage atteigne l'équilibre ; les valeurs de la première minute sont nettement trop hautes ou trop basses. C'est pourquoi le code attend au moins 2 à 3 minutes ; pour des mesures précises, mieux vaut attendre 10 à 20 minutes.
Rs, RL et R0 : comment calculer les ppm avec le MQ-135 ?
Dans le module, la résistance du capteur Rs et la résistance de charge RL de la carte sont en série et se partagent les 5 V. La tension de la broche AO vaut Vout = 5 × RL / (Rs + RL). En inversant la formule, tu obtiens la résistance actuelle du capteur à partir de la tension mesurée : Rs = RL × (5 − Vout) / Vout. Sur l'Arduino Uno, l'ADC utilise les mêmes 5 V comme référence, il n'est même pas nécessaire de convertir en volts : Rs = RL × (1023 − valeur brute) / valeur brute. Seule, Rs ne veut pas dire grand-chose, car elle varie énormément d'un capteur à l'autre. Ce qui compte, c'est son rapport à sa valeur dans un air connu, appelée R0.
Les courbes de la fiche technique relient le rapport Rs/R0 à la concentration du gaz et sont presque des droites en échelle log-log. On peut donc les écrire sous forme de loi de puissance : ppm = A × (Rs/R0)^−B. Les bibliothèques MQ-135 les plus répandues utilisent pour la courbe du CO2 les coefficients A = 116,602 et B = 2,769. Pour trouver R0, tu places le capteur en air extérieur pur ; comme l'air extérieur contient aujourd'hui environ 420 ppm de CO2, tu inverses la formule et calcules R0 = Rs × (420 / A)^(1/B) ≈ Rs × 1,59. N'oublie pas que ces coefficients sont différents pour d'autres gaz et que le résultat n'est qu'une estimation.
Calibrage de R0 du MQ-135 pas à pas
- Relie le VCC du module au 5V, le GND au GND et AO à A0, puis termine le préchauffage initial en laissant tourner le capteur sans interruption pendant 24 à 48 heures.
- Lis le code de la résistance de charge au dos de la carte et règle RL_KOHM en conséquence dans le code ci-dessous.
- Emporte le montage en air extérieur pur, sur un balcon ou devant une fenêtre ouverte, loin de la circulation, des cigarettes et de la cuisine.
- Téléverse le code de calibrage, ouvre le moniteur série à 9600 bauds et attends au moins 10 minutes.
- Quand la moyenne de R0 affichée ne bouge presque plus pendant plusieurs minutes, note-la : c'est le R0 de ton capteur.
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}Chaque seconde, le code fait la moyenne de 50 mesures, en déduit Rs et le R0 du moment, puis ajoute cette valeur à une moyenne glissante lente. Cette moyenne empêche une bouffée de gaz d'échappement apportée par le vent de fausser le résultat. Le R0 obtenu n'appartient qu'à ce capteur et à cette valeur de RL ; si tu changes de capteur, recommence le calibrage. Au fil des mois, le capteur vieillit et R0 dérive lentement : pour les projets de longue durée, prends l'habitude de recalibrer tous les quelques mois.
Code de mesure : ppm estimés et niveau de qualité de l'air
Une fois R0 trouvée, tu l'inscris dans le code de mesure. Le code attend d'abord 3 minutes de chauffe, puis toutes les 2 secondes il affiche dans le moniteur série le rapport Rs/R0, les ppm estimés et un niveau de qualité de l'air d'après le tableau ci-dessus. En air pur, le rapport tourne autour de 0,6 ; plus il baisse, plus il y a de gaz dans l'air. Pour tester, mets quelques gouttes d'eau de Cologne sur un coton près du capteur : en quelques secondes le rapport chute et les ppm montent ; retire le coton et les valeurs reviennent à la normale en quelques minutes.
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}Comment la température et l'humidité influencent-elles le MQ-135 ?
La résistance des capteurs à oxyde métallique ne dépend pas seulement du gaz, mais aussi de la température et de l'humidité ambiantes. D'après la courbe de la fiche technique, dans le même air, le rapport Rs/R0 baisse nettement quand la température passe de 20 °C à 40 °C, et il baisse aussi quand l'humidité augmente ; le capteur interprète cela comme « plus de gaz ». Résultat : des ppm qui montent tout seuls dans une pièce ensoleillée l'après-midi ou dans la salle de bain après une douche. Deux moyens de limiter cet effet : calibrer à une température et une humidité proches de celles du lieu d'utilisation, ou mesurer température et humidité avec un DHT22 ou DHT11 et appliquer la formule de correction des bibliothèques. Les fonctions comme getCorrectedPPM(température, humidité) des bibliothèques MQ135 font exactement cela. Pour mesurer la température et l'humidité plus précisément qu'avec un DHT11, tu peux utiliser un capteur BME280.
Automatiser un ventilateur d'aération
La tâche la plus utile du MQ-135 est d'allumer tout seul un ventilateur ou une hotte quand l'air devient lourd. Deux détails comptent. Le premier est l'hystérésis : le ventilateur démarre au-dessus de 1000 ppm mais ne s'arrête que lorsque la valeur repasse sous 800 ppm ; avec un seul seuil, le relais claquerait chaque seconde pendant que la valeur oscille autour. Le second est une durée minimale de fonctionnement : une fois lancé, le ventilateur tourne au moins 2 minutes pour que l'air de la pièce soit vraiment renouvelé. Le code lisse aussi les mesures pour qu'un pic passager ne déclenche pas le ventilateur.
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}La broche IN du module relais va sur D7, son VCC et son GND sur l'Arduino ; le ventilateur se branche sur sa propre alimentation à travers les bornes COM et NO du relais. Avec un ventilateur de PC 12 V, un MOSFET est plus silencieux et dure plus longtemps qu'un relais ; le câblage est le même que celui de la mini pompe à eau. Si tu commutes un appareil 230 V comme une VMC de salle de bain, respecte à la lettre les règles de sécurité du guide du module relais : enferme la partie secteur dans un boîtier et ne touche jamais le montage sous tension. Mettre d'abord la broche au niveau « arrêt » avant de la passer en sortie dans setup() évite que les relais actifs à l'état bas collent un instant au démarrage.
Enregistrer la qualité de l'air avec un ESP32
Voir comment la qualité de l'air évolue au fil de la journée en apprend bien plus qu'une mesure isolée : seul un historique révèle l'air confiné qui s'accumule dans la chambre au matin, le pic dans la cuisine pendant la cuisson ou l'effet d'une aération. Le code ESP32 ci-dessous mesure une fois par minute, récupère l'heure sur internet (NTP) pour dater chaque ligne et enregistre le résultat au format CSV dans le système de fichiers LittleFS de la mémoire flash de l'ESP32. En ouvrant l'adresse IP de l'ESP32 dans un navigateur, tu vois la dernière mesure et télécharges d'un clic tout l'historique sous forme de fichier CSV lisible dans Excel. Aucune bibliothèque supplémentaire n'est nécessaire.
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}Comme l'ADC de l'ESP32 ne mesure pas comme celui de l'Uno, le mieux est de refaire le calibrage de R0 avec ce montage ; il suffit d'adapter la fonction readRs() du code de calibrage avec la logique de readPpm(). Branche le capteur sur une broche ADC1, entre GPIO 32 et 39 : les broches ADC2 sont inutilisables quand le Wi-Fi est actif. Chaque ligne fait environ 22 octets ; à une mesure par minute, cela fait environ 32 Ko par jour, et la zone de 1,5 Mo du schéma de partitions par défaut tient environ un mois et demi. Pour des historiques plus longs, utilise un module carte SD ; pour envoyer les données directement dans un tableur, vois la méthode ESP32 avec Google Sheets.
MQ-135 avec le Raspberry Pi Pico (MicroPython)
Sur la Pico, tu alimentes le module en 5 V par la broche VBUS et tu relies la sortie AO à GP26, là encore via un pont diviseur 10 kΩ / 20 kΩ. L'ADC de la Pico renvoie une valeur sur 16 bits (0–65535) rapportée à 3,3 V ; le code la convertit d'abord en tension, puis, grâce au rapport du pont, en tension de sortie du capteur. La formule est la même que sur l'Arduino. Comme VBUS peut varier entre 4,8 et 5,1 V selon la source USB, mesure-la au multimètre et corrige le 5.0 du code si tu veux un résultat précis.
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)Comparatif : MQ-135, CCS811 et capteur de CO2 NDIR
Il existe trois familles de capteurs de qualité de l'air, et chacune répond à une question différente. Le MQ-135 est bon marché, analogique et simple ; il montre les variations générales de l'air. Le CCS811 est lui aussi un capteur à oxyde métallique, mais il intègre son propre microcontrôleur, gère lui-même le chauffage et fournit directement en I2C le TVOC (composés organiques volatils totaux) et l'eCO2 (CO2 équivalent) ; nous le détaillons dans le guide du capteur de qualité de l'air CCS811. Les seuls à mesurer le vrai CO2 sont les capteurs NDIR (infrarouge non dispersif) : des modules comme le MH-Z19B ou le SenseAir S8 mesurent l'absorption de la lumière infrarouge à 4,26 µm par les molécules de CO2 et ne sont presque pas influencés par les autres gaz.
| Caractéristique | MQ-135 | CCS811 | NDIR (MH-Z19B, SenseAir S8) |
|---|---|---|---|
| Principe de mesure | Résistance du SnO2 chauffé | Oxyde métallique + microcontrôleur intégré | Absorption de lumière infrarouge |
| Mesure le vrai CO2 ? | Non, il l'estime | Non, calcule l'eCO2 à partir des COV | Oui |
| Sortie | Analogique (et seuil numérique) | I2C | UART ou PWM |
| Alimentation | 5 V, environ 150 mA | 3,3 V, environ 30 mA en moyenne | 5 V, moins de 60 mA en moyenne |
| Chauffe | 24–48 h la première fois, puis quelques minutes | 48 h la première fois, puis 20 minutes | Environ 3 minutes |
| Précision | Faible, pour des variations relatives | Moyenne, pour suivre les COV en relatif | Environ ±50 ppm + 3–5 % de la mesure |
| Prix | Le moins cher | Moyen | Le plus cher |
En résumé : pour apprendre, déclencher une alarme ou un ventilateur et suivre les variations de l'air, le MQ-135 suffit. Pour suivre les vapeurs de peinture, de colle ou de produits ménagers, le CCS811 fournit des données plus lisses et plus stables. S'il te faut un chiffre fiable pour savoir s'il est temps d'ouvrir la fenêtre dans une salle de classe, un bureau ou une chambre, il faudra investir dans un capteur de CO2 NDIR. Beaucoup utilisent les deux : le capteur NDIR suit le CO2 et le MQ-135 repère les événements soudains comme la fumée ou une odeur de solvant dans la cuisine.
Idées de projets avec le MQ-135
- Indicateur d'air de la pièce : Une LED RVB qui s'allume en vert, jaune ou rouge selon la qualité de l'air, ou un graphique en direct sur un écran OLED.
- Hotte de cuisine intelligente : Un système qui allume la hotte quand la fumée et la vapeur montent pendant la cuisson et l'éteint quand l'air est propre.
- Alerte pour garage et atelier de peinture : Un montage qui prévient par buzzer et lance la ventilation quand les vapeurs de solvant augmentent.
- Surveillance de la litière : Un projet ESP32 qui prévient le téléphone quand l'ammoniac monte et qu'il est temps de changer la litière.
- Tableau de l'air en classe : Un graphique qui enregistre les mesures avec un ESP32 et les compare à l'emploi du temps pour montrer les habitudes d'aération.
- Domotique : Envoie les valeurs à Home Assistant via MQTT et allume un purificateur d'air avec une prise connectée quand l'air se dégrade.
Erreurs fréquentes et solutions
| Symptôme | Cause probable | Solution |
|---|---|---|
| Les valeurs dérivent pendant des heures | Préchauffage initial non terminé | Laisse tourner le capteur 24 à 48 heures sans interruption, puis calibre |
| Valeurs beaucoup trop hautes ou basses à l'allumage | Chauffe quotidienne non respectée | Attends au moins 3 minutes, idéalement 10 à 20, avant de mesurer |
| Des milliers de ppm même en air pur | R0 erronée ou calibrage fait à l'intérieur | Refais le calibrage en air extérieur pur |
| Sur l'ESP32, les valeurs sont toujours au maximum | AO monte à 5 V ou le capteur est alimenté en 3,3 V | Alimente le capteur en 5 V et ajoute un pont 10k/20k sur AO |
| L'Arduino chauffe ou l'USB se déconnecte | Le chauffage de 150 mA et les autres charges surchargent l'USB | Utilise une alimentation 5 V externe et relie les masses |
| Les ppm montent au soleil ou après une douche | Effet de la température et de l'humidité | Calibre dans des conditions proches ou applique la correction avec un DHT22 |
Tant que tu connais ses limites, le MQ-135 est un excellent capteur pour découvrir la qualité de l'air et automatiser l'aération : alimente-le en 5 V, préchauffe-le 24 à 48 heures, trouve R0 en air pur, lis les résultats de manière relative et passe à un capteur NDIR quand il te faut le vrai CO2. Dans l'application BlueGrays, tu trouveras les projets Capteur de gaz, Alarme flamme et gaz et Détecteur de gaz MQ-2 avec ESP32, ainsi que des projets température–humidité avec DHT11 et DHT22 et un enregistreur de température ESP32, avec schémas et code pas à pas.
Questions fréquentes
Le MQ135 mesure-t-il le CO2 ?
Le MQ-135 ne mesure pas vraiment le CO2, il l'estime. Il ne réagit que faiblement au CO2 ; sa vraie réponse concerne des gaz comme l'alcool, l'ammoniac et le benzène, ainsi que les composés volatils émis par les personnes. La valeur de CO2 en ppm affichée par les bibliothèques est calculée à partir de cette pollution générale ; pour le vrai CO2, il faut un capteur NDIR comme le MH-Z19B.
Combien de temps le MQ135 met-il à chauffer ?
Un MQ-135 neuf demande un préchauffage initial de 24 à 48 heures, à terminer impérativement avant le calibrage. Ensuite, le capteur est utilisable en 2 à 3 minutes environ à chaque mise sous tension ; pour les valeurs les plus stables, mieux vaut attendre 10 à 20 minutes.
Comment trouver la valeur R0 du MQ135 ?
Emporte le capteur préchauffé en air extérieur pur, calcule sa résistance avec Rs = RL × (1023 − brut) / brut puis, en prenant environ 420 ppm de CO2 dehors, calcule R0 = Rs × (420 / 116,6)^(1 / 2,769). Fais la moyenne sur plusieurs minutes. Le R0 obtenu ne vaut que pour ce capteur et cette résistance de charge.
Quelle est la différence entre le MQ135 et le MQ2 ?
Ce sont deux capteurs à oxyde métallique chauffés, mais réglés pour des gaz différents. Le MQ-2 convient au GPL, au propane, au méthane, à l'hydrogène et à la fumée : c'est celui des alarmes de fuite de gaz. Le MQ-135 est sensible aux polluants de l'air comme l'ammoniac, le benzène, l'alcool, les NOx et la fumée, et sert à surveiller la qualité de l'air.
Peut-on utiliser le MQ135 avec un ESP32 ?
Oui, mais attention à deux points. Le chauffage demande 5 V : alimente le module par la broche VIN de l'ESP32. La sortie AO peut elle aussi atteindre 5 V : ramène-la à 3,3 V avec un pont diviseur de 10 kΩ et 20 kΩ et relie-la à une broche ADC1 entre GPIO 32 et 39.
Quels gaz le MQ135 détecte-t-il ?
Le MQ-135 est surtout sensible à l'ammoniac, aux composés soufrés et aux vapeurs aromatiques comme le benzène. Il réagit aussi nettement à l'alcool, à la fumée et aux oxydes d'azote, et faiblement au CO2 et au monoxyde de carbone. Il ne sait pas distinguer ces gaz ; il montre seulement la variation globale de l'air.
Projets à réaliser après cet article
Voir tous les projets
Température et humidité avec DHT11
Quelle température fait-il dans ta chambre, et l'air est-il humide ou sec ? Le capteur DHT11 mesure la température et l'humidité, et l'Arduino affiche les valeurs sur un écran LCD à deux lignes, mises

Utilisation des capteurs de gaz
Un petit gardien qui repère une fuite de gaz dans la cuisine avant toi. Le capteur de gaz renifle l'air sans arrêt ; tant que tout va bien, la LED verte reste allumée, et quand le gaz dans l'air dépas

Alarme du capteur de flamme et de gaz
Un petit gardien qui te prévient tout de suite en cas de fuite de gaz ou de flamme quelque part. Le capteur de gaz surveille l'air et le capteur de flamme guette la lumière d'un feu ; tant que tout va

Alarme de détection de gaz/fumée ESP32 MQ-2
Dans ce projet, la détection de gaz combustibles (GPL, méthane, propane) et de fumée est assurée par un module capteur de gaz MQ-2 ; dès la détection, une alarme sonore semblable à une sirène retentit via un buzzer passif. La sortie numérique de seuil (DO) du capteur est connectée au GPIO 4 de l'ESP32 ; quand le gaz ou la fumée dépasse le seuil, DO passe à LOW et l'alarme se déclenche. La sensibilité se règle via le potentiomètre du module. Le buzzer est piloté par tone().

ESP32 DHT22 Température et humidité (avec écran LCD)
Il s'agit d'un projet permettant de mesurer la température ambiante et le taux d'humidité à l'aide d'un capteur DHT22 et d'afficher ces données en temps réel sur un écran LCD I2C 16x2. Les données du capteur sont lues à l'aide du protocole one-wire grâce à la bibliothèque Adafruit DHT. Ce projet constitue la base des stations météo IoT, de la domotique ou des systèmes de surveillance de l'environnement dans les serres.

Enregistreur de Température ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Construis un enregistreur de température horodaté avec ESP32 ! Lis la température/humidité du DHT22 chaque minute, obtiens l'heure réelle du DS1307 RTC et enregistre sur carte microSD au format CSV. L'OLED affiche en direct la température (grande police), le pourcentage d'humidité, l'horodatage et le compteur. Le fichier /temp_log.csv sur la carte SD s'ouvre dans Excel, Google Sheets ou Python pandas pour analyse — parfait pour surveillance de serre, suivi de température à domicile, c

Écran LCD I2C ESP32 (16x2)
Dans ce projet, nous affichons du texte en connectant un écran LCD 16x2 à l'ESP32 via le protocole I2C. Grâce à l'I2C, nous pouvons contrôler l'écran avec seulement 2 broches de données (SDA et SCL) ; cela évite le gaspillage de 6 broches ou plus inhérent à la connexion parallèle classique. Le code affiche un message fixe sur la première ligne et un compteur qui augmente chaque seconde sur la deuxième ligne. De plus, au démarrage, nous lançons un scanner I2C pour afficher l'adresse de l'écran su

Ampoule 220V avec bouton poussoir (Push)
Tant que tu appuies sur le bouton, une vraie ampoule de 220 volts reste allumée, et elle s'éteint dès que tu relâches. Le petit signal de l'Arduino ne peut pas allumer l'ampoule directement : un relai
Autres articles
Application BlueGrays
Réalise ce montage pas à pas sur ton téléphone
Schéma, code et liste des composants sur un seul écran. Plus de 200 projets Arduino, ESP32 et Pico à réaliser.
Scanne avec ton téléphone
Pointe l’appareil photo vers le code : la boutique d’applications de ton téléphone s’ouvre.
ou ouvre la boutique

