Bauteile · 12 Min. Lesezeit

MQ135 Arduino: Luftqualitätssensor kalibrieren und ppm berechnen

MQ135 am Arduino: erkannte Gase, 24–48 h Einbrennen, R0-Kalibrierung und ppm-Formel, misst er CO2?, Lüftersteuerung, ESP32-Logging und Vergleich mit CCS811.

Projekte mit MQ135 und Arduino sind der erste Anlaufpunkt für alle, die die Luftqualität in einem Raum günstig und einfach überwachen wollen. Der MQ-135 ist ein beheizter Metalloxid-Gassensor, der auf Gase wie Ammoniak, Stickoxide, Benzol, Alkohol und Rauch reagiert. In Bastelsets steckt er meist auf einem kleinen Modul mit Komparator-Chip und Einstellpoti; er wird über seinen Analogausgang an den Arduino angeschlossen und mit einem einzigen analogRead() gelesen. Aus der Zahl, die er liefert, einen sinnvollen Messwert zu machen, braucht aber etwas mehr Wissen, als die meisten Anleitungen vermitteln.

In dieser Anleitung erfährst du, was im Sensor passiert, wie empfindlich er auf welche Gase reagiert und warum er vor dem ersten echten Einsatz 24–48 Stunden geheizt werden muss. Danach bauen wir mit Lastwiderstand (RL), Sensorwiderstand (Rs) und Referenzwiderstand (R0) Schritt für Schritt die ppm-Berechnung auf, kalibrieren R0 an frischer Luft und beantworten ehrlich die häufigste Frage: Misst der MQ-135 wirklich CO2? Zum Schluss schalten wir einen Lüfter automatisch, zeichnen Messwerte mit dem ESP32 in einer Datei mit Zeitstempel auf, schreiben MicroPython-Code für den Raspberry Pi Pico und vergleichen den MQ-135 mit dem CCS811 und NDIR-CO2-Sensoren.

MQ-135-Luftqualitätssensor-Modul
MQ-135-Modul: beheizter Metalloxidsensor, Komparator LM393, Empfindlichkeitspoti und die Pins VCC, GND, DO, AO.

Was ist der MQ-135 und wie funktioniert er?

Hinter dem Edelstahlnetz ist die Oberfläche eines kleinen Keramikröhrchens mit Zinndioxid (SnO2) beschichtet, und ein durchlaufender Heizdraht heizt diese Schicht auf einige hundert Grad Celsius. In sauberer Luft lagern sich Sauerstoffmoleküle an der SnO2-Oberfläche an, binden freie Elektronen und der Widerstand der Schicht bleibt hoch. Kommt ein reduzierendes Gas wie Ammoniak, Alkohol oder Benzol in die Luft, reagiert es mit dem Sauerstoff an der Oberfläche, die Elektronen werden frei und der Widerstand sinkt. Je dichter das Gas, desto kleiner der Widerstand. Das Modul schaltet diesen Widerstand mit einem Lastwiderstand zu einem Spannungsteiler; steigt die Gaskonzentration, steigt also die Spannung am AO-Pin.

EigenschaftWert
SensormaterialZinndioxid (SnO2), beheiztes Keramikröhrchen
Heizspannung5 V ± 0,1 V
Heizwiderstand und -strom33 Ω ± 5 %, etwa 150 mA
HeizleistungHöchstens 800 mW
MessbereichNH3 10–300 ppm, Benzol 10–1000 ppm, Alkohol 10–300 ppm
Erstes VorheizenMehr als 24 Stunden
Einsatzbedingungen−10 bis 45 °C, relative Feuchte unter 95 %
ModulausgängeAO (analog) und DO (digitale Schwelle per LM393)

Welche Gase erkennt der MQ-135?

Der MQ-135 ist kein selektiver Sensor für ein einzelnes Gas; er reagiert gleichzeitig auf viele Gase, die mit der heißen SnO2-Oberfläche reagieren, und kann sie nicht auseinanderhalten. Der Hersteller beschreibt ihn als besonders empfindlich für Ammoniak, Schwefelverbindungen und benzolartige Dämpfe; auch auf Alkohol, Rauch und Stickoxide spricht er deutlich an. Deshalb betrachtest du den MQ-135 am besten als „Luftbelastungsanzeige“: Steigt der Wert, hat sich etwas in der Luft verändert, aber nicht, was. Für Erdgas- und Flüssiggaslecks ist der dafür gebaute MQ-2-Gassensor viel besser geeignet.

GasWoher kommt es im Haushalt?Reaktion des MQ-135
Ammoniak (NH3)Putzmittel, Katzenstreu, DüngerHoch
Benzol, Toluol und LösungsmittelFarbe, Verdünner, Kleber, ZigarettenrauchHoch
AlkoholdampfDesinfektionsmittel, Kölnischwasser, ParfümHoch
RauchKochen, Zigaretten, KerzenMittel
Stickoxide (NOx)Gasherd, AbgaseMittel
Kohlendioxid (CO2)Atemluft, geschlossene und volle RäumeSchwach, indirekt

Misst der MQ-135 CO2?

Kurz gesagt: nein, nicht wirklich, er schätzt nur. Im Datenblatt des MQ-135 gibt es eine CO2-Kurve, und die meisten Bibliotheken im Netz berechnen daraus einen „CO2-ppm“-Wert. CO2 ist aber chemisch sehr stabil und reagiert kaum mit der SnO2-Oberfläche. Atmen Menschen in einem geschlossenen Raum, steigen mit dem CO2 auch Feuchte und flüchtige organische Verbindungen aus dem Körper an; auf diese reagiert der MQ-135 eigentlich. Der angezeigte „CO2“-Wert ist also eine Schätzung aus der allgemeinen Luftbelastung im Raum. Der einfachste Beweis: Öffne daneben eine Flasche Kölnischwasser, und der „CO2“-Wert springt auf 5000 ppm.

Das heißt nicht, dass der MQ-135 nutzlos ist. Für relative Entscheidungen wie „der Raum wird stickig, Fenster auf“ oder „Rauch in der Küche, Dunstabzug an“ funktioniert er sehr gut. Brauchst du im Klassenzimmer, Büro oder Schlafzimmer aber den echten CO2-Wert, nimm einen der NDIR-CO2-Sensoren aus dem Vergleich am Ende dieses Beitrags. Die Tabelle fasst zusammen, was die Werte eines echten CO2-Messgeräts bedeuten; die Schätzwerte des MQ-135 kannst du mit denselben Bereichen deuten.

CO2 (ppm)Was bedeutet das?
400–450Saubere Außenluft
450–800Gut gelüfteter Innenraum
800–1000Akzeptabel, ans Lüften denken
1000–1500Stickige Luft, Konzentration lässt nach; lüften
1500–2000Kopfschmerzen und Müdigkeit möglich
5000Arbeitsplatzgrenzwert für 8 Stunden

MQ-135 am Arduino anschließen

ModulpinArduino UnoESP32Raspberry Pi Pico
VCC (inklusive Heizung)5VVIN (5 V vom USB)VBUS (5 V vom USB)
GNDGNDGNDGND
AO (Analogausgang)A0GPIO 34, über Teiler 10 kΩ / 20 kΩGP26 (ADC0), über Teiler 10 kΩ / 20 kΩ
DO (digitale Schwelle, optional)D2Beliebiger GPIO über einen TeilerBeliebiger GP-Pin über einen Teiler
Die Heizung des MQ-135 allein zieht etwa 150 mA und braucht unbedingt 5 V; am 3V3-Pin eines ESP32 oder Pico wird der Sensor nicht heiß genug und die Messwerte sind wertlos. Läuft das Modul mit 5 V, kann der AO-Ausgang bis 5 V erreichen, und das beschädigt den 3,3-V-ADC-Eingang von ESP32 und Pico. Baue zwischen AO und GND einen Spannungsteiler aus 10 kΩ + 20 kΩ und verbinde den Mittelpunkt mit dem ADC-Pin: 5 V × 20 / 30 = 3,33 V. Andere Widerstandspaare probierst du mit dem Spannungsteiler-Rechner aus. Im Betrieb wird der Sensor so heiß, dass man ihn nicht anfassen kann; berühre das Netz nicht.

Vorheizen: 24–48 Stunden Einbrennen und tägliches Aufwärmen

Sensoren der MQ-Reihe haben zwei verschiedene Aufwärmphasen, die oft verwechselt werden. Die erste ist das erste Vorheizen (Einbrennen): Auf der Oberfläche eines neuen oder lange gelagerten Sensors sammeln sich Feuchtigkeit und Verunreinigungen. Der Hersteller verlangt vor Standardmessungen mehr als 24 Stunden Versorgung; in der Praxis siehst du bei 24–48 Stunden Dauerbetrieb, wie die Werte langsam wandern und sich schließlich einpendeln. Kalibrierst du R0 vorher, driften alle Messungen innerhalb weniger Tage weg. Die zweite ist das tägliche Aufwärmen: Auch ein eingebrannter Sensor braucht nach jedem Einschalten einige Minuten, bis die Heizung im Gleichgewicht ist; die Werte der ersten Minute sind deutlich zu hoch oder zu niedrig. Deshalb wartet der Code mindestens 2–3 Minuten; für genaue Messungen sind 10–20 Minuten besser.

Rs, RL und R0: Wie berechnet man mit dem MQ-135 ppm?

Im Modul liegen der Sensorwiderstand Rs und der Lastwiderstand RL auf der Platine in Reihe und teilen sich die 5 V. Am AO-Pin liegt also Vout = 5 × RL / (Rs + RL). Stellst du die Formel um, erhältst du aus der gemessenen Spannung den aktuellen Sensorwiderstand: Rs = RL × (5 − Vout) / Vout. Beim Arduino Uno nutzt der ADC dieselben 5 V als Referenz, du musst also nicht einmal in Volt umrechnen: Rs = RL × (1023 − Rohwert) / Rohwert. Rs allein sagt wenig, denn der Widerstand streut von Sensor zu Sensor stark. Entscheidend ist das Verhältnis von Rs zu seinem Wert in einer bekannten Luft, genannt R0.

Die Kurven im Datenblatt verknüpfen das Verhältnis Rs/R0 mit der Gaskonzentration und sind in doppelt logarithmischer Darstellung fast gerade Linien. Deshalb lassen sie sich als Potenzfunktion schreiben: ppm = A × (Rs/R0)^−B. Verbreitete MQ-135-Bibliotheken verwenden für die CO2-Kurve die Koeffizienten A = 116,602 und B = 2,769. Für R0 stellst du den Sensor in saubere Außenluft; da Außenluft heute etwa 420 ppm CO2 enthält, stellst du die Formel um und rechnest R0 = Rs × (420 / A)^(1/B) ≈ Rs × 1,59. Denk daran, dass die Koeffizienten für andere Gase anders sind und das Ergebnis nur eine Schätzung ist.

Einer der kleinen SMD-Widerstände auf der Rückseite ist RL, und sein Aufdruck verrät den Wert: 102 = 1 kΩ, 103 = 10 kΩ, 203 = 20 kΩ. Die Codes erklären wir unter SMD-Widerstandscodes. Ein falsch eingetragenes RL ändert das ppm-Ergebnis nicht, denn R0 wird mit demselben RL berechnet und das Verhältnis bleibt gleich; nur die angezeigten Zahlen für Rs und R0 werden skaliert. Ist RL aber sehr klein (1 kΩ), bleibt die AO-Spannung in sauberer Luft sehr niedrig und die ADC-Auflösung leidet; zappeln die Werte, hilft Mittelwertbildung.

MQ-135-R0-Kalibrierung Schritt für Schritt

  1. Verbinde VCC des Moduls mit 5V, GND mit GND und AO mit A0 und schließe das erste Vorheizen ab, indem du den Sensor 24–48 Stunden ununterbrochen laufen lässt.
  2. Lies den Code des Lastwiderstands auf der Rückseite und stelle RL_KOHM im Code unten passend ein.
  3. Bring die Schaltung an saubere Außenluft, etwa auf den Balkon oder an ein offenes Fenster, weg von Verkehr, Zigaretten und Küche.
  4. Lade den Kalibriercode hoch, öffne den seriellen Monitor mit 9600 Baud und warte mindestens 10 Minuten.
  5. Ändert sich der angezeigte R0-Mittelwert über mehrere Minuten kaum noch, notiere ihn; das ist der R0 deines Sensors.
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}

Der Code bildet jede Sekunde den Mittelwert aus 50 Messungen, berechnet daraus Rs und den aktuellen R0 und gibt diesen Wert in einen langsamen gleitenden Mittelwert. So verfälscht ein kurzer Hauch Abgas, den der Wind herüberträgt, das Ergebnis nicht. Der gefundene R0 gilt nur für genau diesen Sensor und diesen RL-Wert; tauschst du den Sensor, kalibrierst du neu. Über Monate altert der Sensor und R0 wandert langsam, deshalb ist es bei Langzeitprojekten eine gute Gewohnheit, alle paar Monate neu zu kalibrieren.

Messcode: geschätzte ppm und Luftqualitätsstufe

Kennst du R0, trägst du ihn in den Messcode ein. Der Code wartet zuerst 3 Minuten Aufwärmzeit und schreibt dann alle 2 Sekunden das Verhältnis Rs/R0, die geschätzten ppm und eine Luftqualitätsstufe nach der Tabelle oben in den seriellen Monitor. In sauberer Luft liegt das Verhältnis um 0,6; je kleiner es wird, desto mehr Gas ist in der Luft. Zum Testen gibst du etwas Kölnischwasser auf einen Wattebausch neben dem Sensor: Innerhalb von Sekunden fällt das Verhältnis und die ppm steigen; nimmst du die Watte weg, normalisieren sich die Werte in wenigen Minuten.

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}

Wie beeinflussen Temperatur und Feuchte den MQ-135?

Der Widerstand von Metalloxidsensoren hängt nicht nur vom Gas ab, sondern auch von Raumtemperatur und Luftfeuchte. Laut Datenblattkurve fällt das Verhältnis Rs/R0 in derselben Luft deutlich, wenn die Temperatur von 20 °C auf 40 °C steigt, und es fällt auch bei steigender Feuchte; der Sensor deutet das als „mehr Gas“. Die Folge sind ppm-Werte, die in einem Zimmer mit Nachmittagssonne oder im Bad nach dem Duschen von selbst steigen. Zwei Wege mildern das: in einer Umgebung mit ähnlicher Temperatur und Feuchte wie im Einsatz kalibrieren oder Temperatur und Feuchte mit einem DHT22 oder DHT11 messen und die Korrekturformel der Bibliotheken anwenden. Funktionen wie getCorrectedPPM(Temperatur, Feuchte) in fertigen MQ135-Bibliotheken tun genau das. Willst du Temperatur und Feuchte genauer messen als mit einem DHT11, nimm einen BME280.

Lüfter automatisch steuern

Die nützlichste Aufgabe des MQ-135: einen Lüfter oder Abzug selbstständig einschalten, wenn die Luft schlecht wird. Zwei Details sind wichtig. Erstens die Hysterese: Der Lüfter geht über 1000 ppm an, aber erst unter 800 ppm wieder aus; mit nur einer Schwelle würde das Relais jede Sekunde klicken, während der Wert um sie herum pendelt. Zweitens eine Mindestlaufzeit: Ist der Lüfter einmal an, läuft er mindestens 2 Minuten, damit die Raumluft wirklich ausgetauscht wird. Außerdem glättet der Code die Messwerte, damit kurze Spitzen den Lüfter nicht auslösen.

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}

Der IN-Pin des Relaismoduls kommt an D7, VCC und GND an den Arduino; der Lüfter hängt über COM und NO des Relais an seiner eigenen Versorgung. Für einen 12-V-PC-Lüfter ist ein MOSFET leiser und langlebiger als ein Relais; die Verdrahtung entspricht der Schaltung der Mini-Wasserpumpe. Schaltest du ein 230-V-Gerät wie einen Badlüfter, halte dich strikt an die Sicherheitsregeln im Beitrag zum Relaismodul: Netzseite in ein geschlossenes Gehäuse und nie an der Schaltung arbeiten, solange Spannung anliegt. Dass setup() den Pin zuerst auf den Aus-Pegel setzt und erst dann als Ausgang schaltet, verhindert, dass Active-LOW-Relais beim Einschalten kurz anziehen.

Luftqualität mit dem ESP32 aufzeichnen

Wie sich die Luftqualität über den Tag verändert, lehrt dich viel mehr als ein Einzelwert: Erst eine Aufzeichnung zeigt die verbrauchte Luft im Schlafzimmer am Morgen, die Spitze in der Küche beim Kochen oder die Wirkung des Lüftens. Der ESP32-Code unten misst einmal pro Minute, holt die Uhrzeit aus dem Internet (NTP), versieht jede Zeile mit Datum und Uhrzeit und speichert das Ergebnis als CSV im LittleFS-Dateisystem im Flash des ESP32. Rufst du die IP-Adresse des ESP32 im Browser auf, siehst du den letzten Messwert und lädst mit einem Klick das ganze Protokoll als CSV-Datei herunter, die sich in Excel öffnen lässt. Eine zusätzliche Bibliothek brauchst du nicht.

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}

Weil der ADC des ESP32 anders misst als der des Uno, kalibrierst du R0 am besten mit dieser Schaltung neu; passe dazu einfach readRs() aus dem Kalibriercode nach der Logik von readPpm() an. Schließe den Sensor an einen ADC1-Pin zwischen GPIO 32 und 39 an: ADC2-Pins sind bei aktivem WLAN nicht nutzbar. Jede Zeile hat etwa 22 Byte; bei einem Eintrag pro Minute kommen rund 32 KB am Tag zusammen, und der 1,5-MB-Bereich der Standard-Partitionierung reicht etwa anderthalb Monate. Für längere Aufzeichnungen nimmst du ein SD-Kartenmodul, und willst du die Daten direkt in eine Tabelle schicken, sieh dir ESP32 mit Google Sheets an.

MQ-135 am Raspberry Pi Pico (MicroPython)

Am Pico versorgst du das Modul mit 5 V vom VBUS-Pin und verbindest AO wieder über einen Teiler aus 10 kΩ / 20 kΩ mit GP26. Der ADC des Pico liefert einen 16-Bit-Wert (0–65535) bezogen auf 3,3 V; der Code rechnet ihn erst in eine Spannung und dann mit dem Teilerverhältnis in die Sensorspannung um. Die Formel ist dieselbe wie am Arduino. Weil VBUS je nach USB-Quelle zwischen 4,8 und 5,1 V liegen kann, misst du für genaue Ergebnisse VBUS mit dem Multimeter und korrigierst den Wert 5,0 im Code.

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

MQ-135, CCS811 und NDIR-CO2-Sensor im Vergleich

Es gibt drei Klassen von Luftqualitätssensoren, und jede beantwortet eine andere Frage. Der MQ-135 ist billig, analog und einfach; er zeigt allgemeine Veränderungen der Luft. Der CCS811 ist ebenfalls ein Metalloxidsensor, hat aber einen eigenen Mikrocontroller, steuert die Heizung selbst und liefert über I2C direkt TVOC (flüchtige organische Verbindungen gesamt) und eCO2 (CO2-Äquivalent); mehr dazu im Beitrag CCS811-Luftqualitätssensor. Echtes CO2 messen nur NDIR-Sensoren (nichtdispersiv infrarot): Module wie MH-Z19B oder SenseAir S8 messen, wie stark CO2-Moleküle Infrarotlicht bei 4,26 µm absorbieren, und reagieren kaum auf andere Gase.

EigenschaftMQ-135CCS811NDIR (MH-Z19B, SenseAir S8)
MessprinzipWiderstand von beheiztem SnO2Metalloxid + eingebauter MikrocontrollerInfrarot-Absorption
Misst echtes CO2?Nein, schätztNein, berechnet eCO2 aus VOCJa
AusgangAnalog (plus digitale Schwelle)I2CUART oder PWM
Versorgung5 V, etwa 150 mA3,3 V, im Mittel etwa 30 mA5 V, im Mittel unter 60 mA
AufwärmenNeu 24–48 h, danach einige MinutenNeu 48 h, danach 20 MinutenEtwa 3 Minuten
GenauigkeitGering, für relative ÄnderungenMittel, für relative VOC-VerläufeEtwa ±50 ppm + 3–5 % des Messwerts
PreisAm günstigstenMittelAm teuersten

Kurz gesagt: Zum Lernen, für einen Alarm oder Lüfter und um Veränderungen der Luft zu verfolgen, reicht der MQ-135. Willst du Dämpfe von Farbe, Kleber oder Putzmitteln verfolgen, liefert der CCS811 glattere, stabilere Daten. Brauchst du im Klassenzimmer, Büro oder Schlafzimmer eine verlässliche Zahl für die Frage „Muss ich lüften?“, führt kein Weg an einem NDIR-CO2-Sensor vorbei. Viele nutzen beide: Der NDIR-Sensor misst das CO2, der MQ-135 erkennt plötzliche Ereignisse wie Rauch oder Lösungsmittelgeruch in der Küche.

Projektideen mit dem MQ-135

  • Raumluft-Anzeige: Eine RGB-LED, die je nach Luftqualität grün, gelb oder rot leuchtet, oder ein Live-Diagramm auf einem OLED-Display.
  • Intelligente Dunstabzugshaube: Ein System, das beim Kochen bei Rauch und Dampf den Abzug startet und bei sauberer Luft wieder ausschaltet.
  • Warnung für Garage und Lackierwerkstatt: Eine Schaltung, die bei Lösungsmitteldämpfen per Buzzer warnt und die Lüftung startet.
  • Katzenklo-Wächter: Ein ESP32-Projekt, das bei steigendem Ammoniak meldet, dass die Streu gewechselt werden muss.
  • Klassenzimmer-Luftanzeige: Ein Diagramm, das Messwerte mit dem ESP32 aufzeichnet und mit den Unterrichtsstunden vergleicht, um das Lüftverhalten zu zeigen.
  • Hausautomation: Schicke die Werte per MQTT an Home Assistant und schalte bei schlechter Luft einen Luftreiniger über eine smarte Steckdose ein.

Häufige Fehler und ihre Lösungen

SymptomMögliche UrsacheLösung
Werte wandern stundenlangErstes Vorheizen nicht abgeschlossenSensor 24–48 Stunden ununterbrochen betreiben, dann kalibrieren
Beim Einschalten viel zu hohe oder niedrige WerteTägliche Aufwärmzeit nicht abgewartetVor dem Messen mindestens 3, besser 10–20 Minuten warten
Tausende ppm selbst in sauberer LuftFalsches R0 oder Kalibrierung im InnenraumKalibrierung an sauberer Außenluft wiederholen
Am ESP32 sind die Werte immer am AnschlagAO erreicht 5 V oder der Sensor läuft an 3,3 VSensor mit 5 V versorgen, AO über Teiler 10k/20k führen
Arduino wird warm oder USB bricht ab150-mA-Heizung plus weitere Lasten überfordern USBExterne 5-V-Versorgung nutzen, Massen verbinden
ppm steigen bei Sonne oder nach dem DuschenEinfluss von Temperatur und FeuchteBei ähnlichen Bedingungen kalibrieren oder mit DHT22 korrigieren
Deute die Werte des MQ-135 nicht als absolute Messung, sondern als Veränderung gegenüber einer Basislinie. Nimm nach dem Aufwärmen einen Wert aus einem Moment mit sauberer Luft als Basis und entscheide mit relativen Schwellen wie „30 % über der Basis“; das ist viel robuster gegen Kalibrierfehler, Alterung und Jahreszeiten. Gleitenden Mittelwert und Medianfilter zum Glätten erklären wir unter Sensordaten filtern.

Solange du seine Grenzen kennst, ist der MQ-135 ein hervorragender Einstiegssensor, um Luftqualität zu verstehen und Lüftung zu automatisieren: mit 5 V versorgen, 24–48 Stunden vorheizen, R0 an sauberer Luft bestimmen, Ergebnisse relativ deuten und für echtes CO2 auf einen NDIR-Sensor wechseln. In der BlueGrays-App findest du die Projekte Gassensor, Flammen- und Gassensor-Alarm und ESP32-MQ-2-Gasmelder sowie Projekte zu Temperatur und Feuchte mit DHT11 und DHT22 und einen ESP32-Temperaturlogger, mit Schritt-für-Schritt-Schaltplänen und Code.

Häufige Fragen

Kann der MQ135 CO2 messen?

Der MQ-135 misst CO2 nicht wirklich, er schätzt es. Auf CO2 reagiert er nur schwach; eigentlich reagiert er auf Gase wie Alkohol, Ammoniak und Benzol und auf flüchtige Stoffe, die Menschen abgeben. Der CO2-ppm-Wert der Bibliotheken wird aus dieser allgemeinen Belastung berechnet; für echtes CO2 brauchst du einen NDIR-Sensor wie den MH-Z19B.

Wie lange muss der MQ135 vorheizen?

Ein neuer MQ-135 braucht ein erstes Vorheizen von 24–48 Stunden, und das muss vor der Kalibrierung abgeschlossen sein. Danach ist der Sensor nach jedem Einschalten in etwa 2–3 Minuten einsatzbereit; für die stabilsten Werte wartest du besser 10–20 Minuten.

Wie bestimmt man den R0-Wert des MQ135?

Bring den vorgeheizten Sensor an saubere Außenluft, berechne seinen Widerstand mit Rs = RL × (1023 − Rohwert) / Rohwert und rechne mit etwa 420 ppm CO2 in der Außenluft R0 = Rs × (420 / 116,6)^(1 / 2,769). Bilde den Mittelwert über einige Minuten. Der gefundene R0 gilt nur für diesen Sensor und diesen Lastwiderstand.

Was ist der Unterschied zwischen MQ135 und MQ2?

Beide sind beheizte Metalloxidsensoren, aber auf unterschiedliche Gase abgestimmt. Der MQ-2 eignet sich für Flüssiggas, Propan, Methan, Wasserstoff und Rauch, also für Gaswarnmelder. Der MQ-135 reagiert auf Luftschadstoffe wie Ammoniak, Benzol, Alkohol, NOx und Rauch und dient zur Überwachung der Luftqualität.

Kann man den MQ135 mit dem ESP32 nutzen?

Ja, aber achte auf zwei Punkte. Die Heizung braucht 5 V, versorge das Modul also über den VIN-Pin des ESP32. Auch der AO-Ausgang kann 5 V erreichen; teile ihn mit 10 kΩ und 20 kΩ auf 3,3 V herunter und schließe ihn an einen ADC1-Pin zwischen GPIO 32 und 39 an.

Welche Gase erkennt der MQ135?

Am empfindlichsten ist der MQ-135 für Ammoniak, Schwefelverbindungen und aromatische Dämpfe wie Benzol. Deutlich reagiert er auch auf Alkohol, Rauch und Stickoxide, schwach auf CO2 und Kohlenmonoxid. Er kann diese Gase nicht unterscheiden, sondern zeigt nur die Gesamtveränderung der Luft.

Alle Projekte ansehen
Temperatur und Luftfeuchtigkeit mit DHT11
#55Mittel

Temperatur und Luftfeuchtigkeit mit DHT11

Wie warm ist dein Zimmer, und ist die Luft feucht oder trocken? Der DHT11-Sensor misst Temperatur und Luftfeuchtigkeit, und der Arduino schreibt die Werte auf ein zweizeiliges LCD und aktualisiert sie

4.7(47)
73
Verwendung von Gassensoren
#51Anfänger
Premium

Verwendung von Gassensoren

Ein kleiner Wächter, der ein Gasleck in der Küche bemerkt, bevor du es tust. Der Gassensor schnuppert ständig an der Luft; solange alles in Ordnung ist, leuchtet die grüne LED, übersteigt das Gas in d

4.6(37)
50
Flammen- und Gassensor-Alarm
#52Fortgeschritten
Premium

Flammen- und Gassensor-Alarm

Ein kleiner Wächter, der dich sofort warnt, wenn Gas austritt oder irgendwo eine Flamme auftaucht. Der Gassensor überwacht die Luft, der Flammensensor achtet auf das Licht eines Feuers; solange alles

4.8(52)
67
ESP32 MQ-2 Gas-/Rauchmelder-Alarm
#122Mittel

ESP32 MQ-2 Gas-/Rauchmelder-Alarm

In diesem Projekt wird die Erkennung brennbarer Gase (LPG, Methan, Propan) und Rauch mit einem MQ-2-Gassensormodul durchgeführt; bei Erkennung ertönt über einen passiven Summer ein sirenenähnlicher Alarmton. Der digitale Schwellenausgang (DO) des Sensors wird an GPIO 4 des ESP32 angeschlossen; überschreiten Gas oder Rauch den Schwellenwert, geht DO auf LOW und der Alarm wird ausgelöst. Die Empfindlichkeit wird über das Potentiometer eingestellt. Der passive Summer wird mit tone() angesteuert.

4.7(28)
43
ESP32 DHT22 Temperatur und Luftfeuchtigkeit (mit LCD-Display)
#117Mittel

ESP32 DHT22 Temperatur und Luftfeuchtigkeit (mit LCD-Display)

Bei diesem Projekt werden die Umgebungstemperatur und die Luftfeuchtigkeit mit einem DHT22-Sensor gemessen und in Echtzeit auf einem 16x2 I2C-LCD angezeigt. Mithilfe der Adafruit-DHT-Bibliothek werden die Daten über das One-Wire-Protokoll vom Sensor ausgelesen. Dieses Projekt bildet die Grundlage für IoT-Wetterstationen, Smart-Home-Automatisierung oder Umgebungsüberwachungssysteme in Gewächshäusern. Die Kommunikation mit dem DHT22 erfolgt über den GPIO-4-Pin des ESP32.

4.8(38)
56
ESP32 Temperatur-Datenlogger (DHT22 + DS1307 RTC + microSD + OLED)
#144Fortgeschritten
Premium

ESP32 Temperatur-Datenlogger (DHT22 + DS1307 RTC + microSD + OLED)

Baue einen zeitgestempelten Temperaturlogger mit ESP32! Lese Temperatur/Feuchtigkeit vom DHT22 jede Minute, hole die aktuelle Zeit vom DS1307 RTC und protokolliere auf microSD-Karte als CSV. Das OLED zeigt live Temperatur (große Schrift), Luftfeuchtigkeit, Zeitstempel und Logzähler. Die /temp_log.csv-Datei auf der SD-Karte öffnet sich in Excel, Google Sheets oder Python pandas zur Analyse — perfekt für Gewächshausüberwachung, Raumtemperaturverfolgung, Laboraufzeichnung, Lebensmittellageru

4.7(7)
12
ESP32 I2C-LCD-Display (16×2)
#119Mittel
Premium

ESP32 I2C-LCD-Display (16×2)

In diesem Projekt zeigen wir Text an, indem wir ein 16x2-Zeichen-LCD-Display über das I2C-Protokoll an den ESP32 anschließen. Dank I2C können wir das Display mit nur zwei Datenpins (SDA und SCL) steuern; die Verschwendung von 6+ Pins bei einer klassischen parallelen Verbindung entfällt. Der Code schreibt in die erste Zeile eine feste Meldung und in die zweite Zeile einen Zähler, der sich jede Sekunde erhöht. Außerdem lassen wir zu Beginn den I2C-Scanner laufen und geben die Adresse des Displays

4.9(23)
40
220V Glühbirne mit Druckknopf (Push)
#19Fortgeschritten
Premium

220V Glühbirne mit Druckknopf (Push)

Solange du den Knopf gedrückt hältst, leuchtet eine echte 220-Volt-Glühbirne, und sobald du loslässt, geht sie aus. Das schwache Signal des Arduino kann die Birne nicht direkt einschalten, deshalb sit

4.4(30)
38