MH-Z19B: Schaltung mit ESP32

CO2-Sensor MH-Z19B

Durch die Corona-Pandemie (COVID-19) sind zur Messung der Luftqualität in Räumen, die von mehreren Personen gleichzeitig benutzt werden, vermehrt CO2-Sensoren im Einsatz (z.B. in Klassenräumen in Schulen). Der CO2-Wert gibt einen Hinweis darauf, wie „verbraucht” die Luft ist – indirekt wird daraus auf das mögliche Ansteckungsrisiko durch in der Raumluft schwebende Aerosole mit COVID19-Erregern geschlossen. Die Sensoren sind oft in Form von CO2-Ampeln im Einsatz und signalisieren, wann es erforderlich ist, den Raum zu lüften. Der MH-Z19B ist ein Sensor, mit dem man so eine CO2-Ampel selbst bauen kann.

Der Einsatz des Sensors ist nicht ganz so trivial, wie ich zuerst angenommen habe. 😉 Für die erste Version des Beitrags vom November 2020 hatte ich den Sensor zu Beginn in der Außenluft kalibriert und dann nur einige Stunden im Innenbereich Messungen per Pulsweitenmodulation (PWM) durchgeführt. Durch Hinweise aus den Kommentaren (Danke dafür!) habe ich dann den Sensor länger laufen lassen und festgestellt, dass nach wenigen Tagen Einsatz im Innenbereich nicht mehr plausible Werte geliefert wurden.
Das hat im Januar 2021 zu ausführlicherer Recherche geführt und gezeigt, dass man anscheinend nur mit der Messung per PWM dauerhaft nicht auskommt – man muss (oder kann) den Sensor (auch) über die serielle Schnittstelle ansprechen, um Einfluss auf das Kalibrierungsverhalten zu nehmen. Letztlich habe ich den Beitrag um folgende Abschnitte erweitert:
  • neuer Einleitungstext
  • Fake-Sensoren
  • Kalibrierung
  • Programmierung des Sensors über die serielle Schnittstelle
  • Quellenangaben und Links

Die Beispielprogramme stehen nun auch auf Github zum Download zur Verfügung – entsprechende Links finden sich auch unterhalb der Programme. (In bestimmten Browser-Konstellationen fügt das von mir verwendete Plugin zur Formatierung der Beispiele beim Kopieren wohl nach jeder Zeile eine Leerzeile ein – das sollte beim Kopieren von Github nicht auftreten.)

CO2-Messung nach dem NDIR-Prinzip

Der MH-Z19B misst den Kohlendioxid-Gehalt (CO2, auch Kohlenstoffdioxid genannt) der Umgebungsluft nach dem NDIR-Prinzip. In einem NDIR-Sensor (nichtdispersiver Infrarot-Sensor) wird ein Infrarotstrahl durch ein Gasgemisch geschickt. Jedes Gas des Gemisches absorbiert Infrarotlicht einer bestimmten Wellenlänge. Ein Sensor misst, welcher Anteil des IR-Lichts der für das jeweilige Gas spezifischen Wellenlänge vom Gasgemisch absorbiert wurde – bei CO2 sind es 4,3 µm. Aus der Differenz wird der Anteil des Gases im Gemisch berechnet – in der Einheit »ppm« (parts per million, Anzahl Teile auf eine Million). Eine etwas ausführlichere Beschreibung des NDIR-Prinzips liefert die deutsche Wikipedia; deutlich umfangreicher ist der englischsprachige Artikel.

CO2-Wert als Maßstab für die Luftqualität

Unsere Atemluft ist ein Gemisch verschiedener Gase, im wesentlichen sind das folgende:

  • Stickstoff – 78,08 Vol.-%
  • Sauerstoff – 20,95 Vol.-%
  • Argon – 0,93 Vol.-%
  • Kohlendioxid – 0,04 Vol.-%

Diese Zahlen beziehen sich auf trockene – also wasserdampffreie – Luft; unsere Atemluft kann zwischen 0 und 4 Vol.-% Wasserdampf enthalten, dazu (Fein-)Staub und Spurengase in noch geringerer Konzentration als CO2. [1]Quelle: Wikipedia: Luft; s. auch Wikipedia: Luftfeuchtigkeit Ein CO2-Gehalt von 0,04 Volumenprozent entspricht einem Anteil von 400 ppm bzw. 400 Millionstel Teilen (oder auch 0,4 Promille). Dieser Wert ist von verschiedenen Faktoren abhängig, hat sich aber als Orientierungsgröße durchgesetzt. [2]Zitat aus einer Bekanntmachung des Umweltbundesamtes (UBA) von 2008:»Für den Außenluftbeitrag […] werden derzeit für ländliche Gebiete übliche Werte von 350 ppm, für kleine … Continue reading

Will man eine CO2-Ampel realisieren, um einen Hinweis zur Luftqualität in einem Innenraum zu bekommen, kann man sich an den Empfehlungen des Umweltbundesamtes (UBA) orientieren: [3]Angesichts der Corona-Pandemie (auch: SARS-CoV-2, COVID19) hat das Umweltbundesamt Empfehlungen zur Einhaltung einer guten Luftqualität herausgegeben; u.a. heißt es dort: »In Räumen mit hoher … Continue reading

  • grün (< 1000 ppm CO2) – die Luftqualität ist gut
  • gelb (1000-2000 ppm CO2) – mittlere Luftqualität → Lüften ist empfehlenswert
  • rot (> 2000 ppm CO2) – die Luftqualität ist inakzeptabel → Lüften ist notwendig

Preis und Bezugsmöglichkeiten des MH-Z19B

CO2-Sensor MH-Z19B
CO2-Sensor MH-Z19B

Der MH-Z19B ist relativ teuer (jedenfalls im Vergleich mit vielen anderen Sensoren, die oft mit Mikrocontrollern verwendet werden wie Bewegungsmeldern, Temperatursensoren oder auch kleinen Displays): Im deutschen Versandhandel kostet er etwa 25-30 €, teilweise etwas günstiger gibt es ihn auf den bekannten Online-Marktplätzen wie eBay, am günstigsten ist die Direktbestellung in China mit Preisen meist deutlich unterhalb von 20 €. [4]Bezugsquellen z.B. Reichelt Elektronik, Direktbestellung in China z.B. über aliexpress.com (im November 2020 war der Sensor dort ab 14 € zu bekommen, im Januar 2021 kaum unter 16 €); … Continue reading

Man kann den MH-Z19B in zwei Versionen kaufen: mit verlöteten Pins oder mit Stecker (und Anschlusskabel). Bei einigen Anbietern gibt es den Sensor außerdem mit verschiedenen voreingestellten Messbereichen: 0-2000, 0-5000 oder 0-10.000 ppm. Laut Dokumentation kann man den Messbereich auch selbst über die serielle Schnittstelle einstellen bzw. ändern – was aber nicht ganz trivial sein soll.[5]siehe die Hinweise zur Bibliothek MH-Z19 auf github.com/WifWaf/MH-Z19
Die Dokumentation in Version 1.0 erwähnt die Messbereiche 0-2000 und 0-5000 ppm, Version 1.5 und 1.6 zusätzlich 0-10.000 ppm (Angabe »optional« im Datenblatt)[6]Dokumentation Version 1.6 Seite 5; Tabelle im Abschnitt »Main parameters«:
»Detection Range   0~2000/5000/10000ppm(optional)«
– mglw. gibt es also verschiedene Hardware-Versionen des Sensors. Evtl. liegt es auch nur an der Firmware – die gibt es in den Versionen 4.30 und 4.43.[7]Artikel bei Revspace, Abschnitt 6.1, Beschreibung des Kommandocodes 0xA0: »Firmware version string? “0430” and “0443” observed«.Ob die verschiedenen … Continue reading Für den Aufbau einer CO2-Ampel ist ein Messbereich von 10.000 ppm unnötig. Bei 2000 ppm ist eh der „rote” Bereich erreicht, und wenn man noch weiter messen will, reicht der Messbereich bis 5000 ppm aus – diesen Wert wird man in normal genutzten Innenräumen kaum erreichen können oder wollen.[8]Siehe die bereits oben verlinkte »Bekanntmachung des Umweltbundesamtes: Gesundheitliche Bewertung von Kohlendioxid in der Innenraumluft« von 2008 (PDF); sie nennt Zahlen aus … Continue reading

Außerdem findet man im Online-Handel den Vorgänger MH-Z19 und inzwischen auch den Nachfolger MH-Z19C.

Ich habe einen MH-Z19B mit Pins, einem Messbereich von 0-5000 ppm und der Firmware-Version 4.43 vorliegen, darauf bezieht sich die folgende Beschreibung.

Fake-Sensoren – mögliche Fälschungen im Handel

Auf der Seite revspace.nl gibt es einen Hinweis auf Fake-Sensoren, die bei Anbietern in Fernost zu finden sind. Sie unterscheiden sich von den Original-Sensoren hauptsächlich durch die schwarze Farbe der Platine (Original: grün), die fehlende kreisförmige Ausbuchtung des Gehäuses auf der Oberseite und die fehlenden erhabenen Ränder um die rechteckigen weiß abgedeckten „Fenster” im Gehäuse. Die Bilder auf Revspace zeigen weitere Details wie eine fehlende Beschriftung; außerdem ist ein Youtube-Video verlinkt, in dem Messwerte von Fake- und Original-Sensoren verglichen werden.
Die Messwerte dieser Sensoren sind instabil und haben die Tendenz, im Lauf der Zeit nach oben oder unten „wegzudriften” (siehe das Video). Nach den Angaben auf Revspace scheint auch die Autokalibrierung Probleme zu bereiten.

Die Fake-Sensoren wurden laut Angabe im Video auf der Plattform banggood.com gekauft. Sowohl bei banggood.com als auch bei aliexpress.com habe ich bei einer Suche[9]banggood.com: eines von vier Suchergebnissen; aliexpress.com: mehrere von 291 Suchergebnissen nach dem MH-Z19B am 11.01.2021 Anbieter solcher – vorsichtig formuliert – optisch abweichender MH-Z19B-Modelle mit schwarzer Platine und glattem Gehäuse gefunden. Da diese Varianten (Fälschungen? Kopien? Nachbauten?) auch nicht besonders günstig sind, lohnt vor dem Kauf ein genauer Blick.
Auch ein Blog-Beitrag auf steinlaus.de weist in den Kommentaren auf einen fehlerhaften bzw. gefälschten Sensor dieser Machart hin – im eingebetteten Video ist der Sensor zu sehen.

Von dieser Variante würde ich die Finger lassen.

Technische Daten und Anschlüsse (Pins)

CO2-Sensor MH-Z19B auf zwei zusammengesteckten Breadboards
CO2-Sensor MH-Z19B auf zwei zusammengesteckten Breadboards

Der Sensor ist relativ groß – das Gehäuse misst etwa 20 * 26 mm und ist 8 mm hoch, die Pin-Reihen haben einen Abstand von von ca. 30 mm. Damit ist er zu groß für die Installation auf einer einzelnen Steckplatine (Breadboard). Man kann sich aber behelfen, indem man nur eine Pin-Reihe einsteckt und die anderen Pins in der Luft hängen lässt, den Sensor komplett frei verkabelt (dazu werden Dupont-Kabel mit Female-Steckern benötigt) oder zwei Breadboards zusammensteckt [10]Hierzu muss man eine der beiden Versorgungsleisten des Breadboards durch Abknicken nach unten entfernen; vorher empfiehlt es sich, mit einem scharfen Messen oder Cutter den Klebestreifen auf der … Continue reading – dann kann man auch problemlos einen ESP32 als Mikrocontroller nutzen und auf dessen sämtliche Pins zugreifen (s. Foto und unten die aufgebaute Schaltung). Außerdem hat man so genug Platz, die Schaltung um weitere Sensoren (z.B. Temperatur, Luftfeuchtigkeit, Luftdruck), eine kleine Anzeige oder einen Speicherkartenleser zum Sichern der Messwerte auf einer SD-Karte zu erweitern.

Der MH-Z19B hat folgende technische Daten (weitere im Datenblatt):

  • Versorgungsspannung 4,5 – 5,5 V DC
  • durchschnittl. Stromaufnahme < 20 mA (bei 5 V Versorgungsspannung), maximal 150 mA
  • Interface: 3,3 V (kompatibel mit 5 V)
  • Ausgabe: serieller Port (UART, TTL Level 3,3 V), PWM oder analoger Output (DAC)
  • Aufwärmzeit: 3 min
  • Arbeitsumgebung: -10 – +50 °C; rel. Luftfeuchtigkeit 0-90 %[11]Version 1.6 der Dokumentation nennt 95 % rel. Luftfeuchtigkeit (nicht kondensierend) als Obergrenze, die älteren Versionen und die Produkthomepage 90 %.
  • Messgenauigkeit: ± (50 ppm + 5 % des Messwerts)
  • Lebensdauer: > 5 Jahre

Im Sensor arbeitet ein eigener Mikrocontroller des Typs STM32[12]STM32F051K86 lt. Revspace, dessen Firmware eine gewisse Intelligenz bei der Steuerung der Auto-Kalibrierung ermöglicht. Außerdem kann man mit Steuerkommandos über die serielle Schnittstelle das Verhalten des Sensors beeinflussen.

Intern führt der MH-Z19B alle 5 Sekunden eine Messung durch – man erkennt das am kurzen orangen Aufblinken der eingebauten Infrarot-Lampe in den Fenstern des Sensors.

Anschlusspins des MH-Z19B
Anschlusspins des MH-Z19B

Der Sensor besitzt zwei Pinreihen: eine mit fünf Pins zur Programmierung über die serielle Schnittstelle, die andere mit 4 Pins zum Auslesen der Daten per Pulsweitenmodulation (PWM) – wobei nur vier bzw. drei Pins genutzt werden.

  • HD – nur für Null-Kalibrierung per Hardware benötigt
  • Tx – UART (TXD) TTL Level data output
  • Rx – UART (RXD) TTL Level data input
  • Vo – analoger Output (Standard: 0,4 – 2 V, alternativ 0 – 2,5 V)
  • PWM – Pulsweitenmodulation
  • GND – Masse
  • Vin – Versorgungsspannung

Ich beschränke mich im ersten Teil auf die Messung per Pulsweitenmodulation – es wird also nur die Pinreihe rechts auf dem Foto genutzt (PWM, GND und Vin). Um den Sensor „richtig” einsetzen zu können, wird man aber nicht um die Nutzung der seriellen Schnittstelle herumkommen, da man nur so mit Steuerbefehlen die Konfiguration des Sensors beeinflussen kann.

Kalibrierung

Der Sensor muss zu Beginn des Einsatzes und danach mindestens alle sechs Monate[13]Dokumentation Version 1.6; Seite 9 im Abschnitt »Notes«:
»9.4 The module should be calibrated termly, the suggested period is not longer than 6 months.«
kalibriert werden. Die Kalibrierung erfolgt auf den Basiswert (zero-point) 400 ppm, also den CO2-Wert der Außenluft.
Damit der Sensor kalibriert werden kann, muss er mindestens 20 Minuten bei 400 ppm CO2 betrieben werden, egal welche Methode man zur Kalibrierung verwendet. Wenn man keine Laborumgebung nutzen kann, genügt es, ihn entsprechend lange in der Außenluft laufen zu lassen (Garten, Balkon oder aus dem Fenster hängen).
Kalibiert man den Sensor nicht bei 400 ppm, sondern bspw. bei 420 ppm, ergeben spätere Messungen zu niedrige Messwerte. Diese sind aber nicht unbedingt als offensichtlich falsch erkennbar; unrealistisch niedrig – weil unter 400 ppm – sind aber anschließende Messungen an der frischen Luft.
Wie oben bereits in einer Fußnote erwähnt: Der Wert von 400 ppm ist eine Orientierungsgröße und schwankt je nach Umgebung (Stadt, Land), Tages- oder Jahreszeit etwas. Eine exakte Kalibrierung ist in der Außenluft also kaum möglich.

Auto-Kalibrierung (Auto Baseline Correction)

Das ist lt. Hersteller der Standardmodus bei Auslieferung des Sensors:

Self-calibration:
After the module works for some time, it can judge the zero point intelligently and do the zero calibration automatically. The calibration cycle is every 24 hours since the module is power on. The zero point is 400ppm. This method is suitable for office and home environment, not suitable for agriculture greenhouse, farm, refrigerator, etc.. If the module is used in latter environment, please turn off this function.[14]Dokumentation Version 1.6 Seite 8

und

0x79- On/Off Self-calibration for Zero Point
[…]
Default status is “this function is on”.[15]Dokumentation Version 1.5 Seite 7; Beschreibung des Kommandos 0x79 zum Ein- oder Ausschalten der Auto-Kalibrierung

Das bedeutet, nach einiger Zeit in Betrieb wird der niedrigste gemessene Wert als Basiswert angenommen – man soll den Sensor auch hier anfangs wie angegeben mind. 20 Minuten in frischer Luft betreiben. Alle 24 Stunden kalibriert sich der Sensor neu, und zwar wird dann der niedrigste in dieser Zeit gemessene Wert als neuer Basiswert verwendet. Hat der Sensor nicht in frischer Luft arbeiten können, sondern z.B. im Innenraum in dieser Zeit als niedrigsten Wert 600 ppm gemessen (weil nicht oder nur kurz gelüftet werden konnte), kalibriert er sich auf diesen falschen Wert als Basiswert, d.h. die folgenden Messungen bis zur nächsten Kalibrierung ergeben zu niedrige Werte. Außerdem wertet der Mikrocontroller im Sensor aber wohl auch Messungen innerhalb von drei Wochen aus, um die Auto-Kalibrierung „intelligent” durchführen zu können – vermutlich soll das dazu führen, dass auch ein längerer Betrieb in verbrauchter Luft nicht zu falschen Messwerten führt.

Man findet Webseiten, deren Autor*innen den Sensor mit Autokalibrierung einsetzen und nicht von Fehlern berichten (jedenfalls schreiben sie nicht, dass sie den Sensor selbst kalibrieren). Auch die Dokumentation (s. Zitat oben) nennt dieses Vorgehen »geeignet (suitable)« für Wohn- und Büro-Umgebungen.

Der Autor der Bibliothek ErriezMHZ19B für den MH-Z19B schreibt:

Automatic calibration is recommended when the sensor cannot be moved outdoor with fresh air. This calibration method requires a regularly ventilated room at 400ppm, at least once in 1..3 weeks. Additionally, it requires continues power-up without interruptions, otherwise the calibration data will not be updated correctly.

Die Auto-Kalibrierung soll also in Innenräumen funktionieren, wenn der Sensor ohne Unterbrechung läuft und man sicherstellen kann, dass regelmäßig (mindestens alle 1-3 Wochen) so gut gelüftet wird, dass eine mit der Außenluft vergleichbare Luftqualität entsteht.

Ich bin damit nicht klargekommen oder war zu ungeduldig, um dem Sensor mehrere Tage bzw. bis zu drei Wochen im ununterbrochenen Betrieb Zeit zu geben, bis sich dessen eingebaute Intelligenz bei der Autokalibrierung durchgesetzt hat. Bei einem Dauerbetrieb in Innenräumen sollte man die Auto-Kalibrierung nach meiner Meinung besser ausschalten und den Sensor von Zeit zu Zeit (mindestens alle paar Monate) bewusst an der Außenluft selbst kalibrieren. So werde ich es jedenfalls bis auf weiteres handhaben.

Vom Hersteller (und oft auf englischsprachigen Seiten) wird die Auto-Kalibrierung mit »ABC« bezeichnet für »auto(matic) baseline correction«.

Kalibrierung per Steuerkommando

Der Mikrocontroller des Sensors kann über die serielle Schnittstelle Steuerkommandos entgegennehmen. Das Kommando zur Kalibrierung hat den Code »0x87«. Ebenso gibt es ein Kommando, um die Auto-Kalibrierung aus- oder einzuschalten (Code »0x79«). Verwendet man eine Bibliothek zur Steuerung des Sensors, muss man sich mit den Kommando-Codes und Parametern aber nicht weiter befassen (siehe unten: Programmierung des Sensors über die serielle Schnittstelle).[16]Version 1.6 der Dokumentation nennt Details nur für sehr wenige Kommandos (mehr findet man in Version 1.0 und eine umfangreiche Liste auch undokumentierter Kommandos bei Revspace).

Auch hier muss der Sensor mindestens 20 Minuten lang in einer Umgebung mit 400 ppm CO2 arbeiten. Man benötigt also eine Spannungsversorgung für den ESP32 bzw. Arduino (z.B. per Laptop, USB-Netzteil und Verlängerungskabel oder eine Powerbank, am Fenster kann ein USB-Verlängerungskabel genügen). Auch die Kalibrierung selbst erfolgt in dieser Umgebung.

Manuelle Kalibrierung per Hardware (Pin HD)

Ist der Sensor mindestens 20 Minuten in frischer Luft gelaufen, kann man ihn kalibrieren, indem man den Sensor-Pin HD für mindestens 7 Sekunden mit Masse (LOW-Pegel; 0 Volt) verbindet, z.B. über einen Taster.
(Wenn ich es richtig verstehe, muss der Sensor für diese Art der Kalibrierung nicht unbedingt an einen ESP32 oder Arduino angeschlossen sein; man benötigt nur die 5 Volt Spannungsversorgung für den Sensor und eine schalt- (oder steck-)bare Masseverbindung für den HD-Pin.)

Messung des CO2-Werts per Pulsweitenmodulation (PWM)

Vorüberlegungen zur Messung

Zur Messung per PWM erfährt man aus der Dokumentation, dass ein Signalzyklus 1004 Millisekunden (ms) dauert – mit einer Genauigkeit von ± 5 %:

  • die ersten 2 ms ist das Signal auf HIGH gesetzt
  • in den folgenden 1000 ms wird der eigentliche Messwert übermittelt; je länger das Signal auf HIGH bleibt, desto höher ist der CO2-Wert; 1 ms entspricht dabei 1/1000 des Messbereichs des Sensors
  • die letzten 2 ms ist das Signal auf LOW gesetzt

Vereinfacht gilt also folgende Formel (TH ist die Dauer des HIGH-Pegels in Millisekunden):

PPM-Wert = (TH – 2 ms) * Maximalwert / 1000 ms

Um auch die Messungenauigkeit bei der Pulslänge zu berücksichtigen, muss man die komplette Länge eines Zyklus bestimmen, also die Dauer des HIGH- und des LOW-Pegels – als TH und TL bezeichnet. Die Formel lautet dann:

PPM-Wert = (TH – 2 ms) * Maximalwert / (TH + TL – 4 ms)

Beispiel: Mein Sensor hat einen Messbereich von 0-5000 ppm (ein Tausendstel des Maximalwerts sind also 5 ppm). Liefert der Sensor einen HIGH-Impuls von 158 ms Dauer, entspricht das 780 ppm CO2:

PPM-Wert = (158 – 2) * 5000 / 1000 = 156 * 5 = 780

Nimmt man an, die maximale Ungenauigkeit von +5% – entsprechend 50 ms – wäre ausgereizt und der folgende LOW-Pegel dauerte 896 ms (statt 846 ms), ergibt sich als tatsächlicher Wert:

PPM-Wert = (158 – 2) * 5000 / (158 + 896 – 4) = 156 * 5000 / 1050 = 743

Zum Messen der Länge eines PWM-Pulses gibt es in der Arduino-Standardbibliothek die Funktion pulseIn() – für PWM-Messungen mit dem MH-Z19B muss also keine Programmbibliothek in der Arduino-IDE installiert werden. pulseIn() wartet darauf, dass am Pin der Wechsel zum gewünschten Pegel (hier: HIGH) eintritt. Dann wird die Dauer des Signals bis zum nächsten Wechsel des Signalpegels (auf LOW) in Mikrosekunden gemessen und als Ergebnis der Funktion zurückgegeben (0, wenn bis zum Timeout kein Signal erkannt wird). pulseIn() erwartet folgende Argumente:

  • die Pin-Nummer, an der der Impuls gemessen werden soll [17]Beim Arduino Nano (und Nachbauten) muss das einer der sechs Digital-I/O-Pins D3, D5, D6, D9, D10 oder D11 sein.
  • die Art des zu messenden Impulses (LOW oder HIGH)
  • (optional) Timeout: die Anzahl Mikrosekunden, »die gewartet werden soll, bis ein Impuls gemessen wurde« (nach meiner Interpretation also die maximale Dauer bis zum Ende der Messung); Standard: 1 Sekunde

Im ungünstigsten Fall verpasst der Beginn der Messung gerade den Moment des Signalwechsels von LOW auf HIGH (d.h. den Start des Intervalls von 1004 ms) und muss ein Intervall lang warten. Deshalb sollte man den Timeout bis zum Ende der Messung ausreichend lang wählen, nämlich nicht kürzer als die doppelte erwartete Intervalllänge. Das sind 2 * 1004 ms plus 5 % Messungenauigkeit, also großzügig aufgerundet 2200 ms bzw. 2200000 µs. Da die Funktion pulseIn() Mikrosekunden ausgibt, ist noch eine Division durch 1000 nötig, sodass sich zur Messung des PWM-Signals folgende Code-Zeile ergibt:

pulse_high = pulseIn(SENSOR_PIN, HIGH, 2200000UL) / 1000;

Da die CO2-Ampel nur eine ungefähre Orientierung bieten soll, reicht die vereinfachte Formel aus – es geht ja nicht um minuten- oder gar sekundengenaues Öffnen von Fenstern. Möchte man aber (z.B. für eine Messreihe) den genauen vom Sensor ermittelten ppm-Wert nutzen, hilft der folgende Absatz weiter. (Andernfalls einfach überspringen und beim Ampel-Programm weiterlesen).

Mit der pulseIn()-Funktion ist es nicht möglich, im gleichen Intervall die Dauer von HIGH- und LOW-Pegel zu messen, da nach Abschluss der Messung des HIGH-Pegels ja bereits der Wechsel auf LOW erfolgt ist. Misst man nun die Länge des LOW-Pegels, betrifft das erst das nächste Intervall. Man kann sich aber behelfen, indem man nach Messung des HIGH-Pegels die Systemzeit ermittelt – zu diesem Zeitpunkt beginnt der LOW-Pegel – und auch den direkt folgenden HIGH-Pegel mit pulseIn() misst. Nimmt man nach der zweiten Messung wieder die Systemzeit, ergibt die Differenz zur ersten Zeit abzüglich der Dauer des zweiten HIGH-Pegels die Dauer des LOW-Pegels dazwischen.

Nachteil: Während der Messung des PWM-Signals wartet das Programm auf das Ergebnis der Messung, läuft also nicht weiter, und der Mikrocontroller kann keine anderen Aufgaben ausführen. Durch die zweite Messung blockiert man das Programm für eine weitere Sekunde. In den meisten Fällen ist das nicht weiter schlimm, wenn man sowieso nur alle paar Sekunden eine Messung durchführt und dazwischen mit delay() eine Pause im Programm einfügt. Soll der Controller noch etwas anderes machen, sollte man sich der (zusätzlichen) Verzögerung des Programms zumindest bewusst sein. Man kann sich die mit dem folgenden Testprogramm gemessene gesamte Intervalldauer auch merken und als Korrektur-Konstante nutzen – das zeigt das zweite Programm weiter unten.

Ein erster Test

Folgendes Programm ermittelt die Dauer des HIGH- und LOW-Pegels eines Übertragungszyklus des MH-Z19B und berechnet den CO2-Wert sowohl nach der vereinfachten als auch nach der korrekten (vollständigen) Formel. Die Werte werden im seriellen Monitor angezeigt.

  1. /* CO2-Messung mit dem Sensor MH-Z19B
  2.  * per Pulsweitenmodulation (PWM)
  3.  *
  4.  * 2020-11-08 Heiko (unsinnsbasis.de)
  5.  */
  6.  
  7. // Definition der GPIO-Pins
  8. #define SENSOR_PIN 14  // beim Arduino z.B. D3 (oder 5, 6, 9, 10, 11)
  9.  
  10. // Bitrate für die Datenübertragung zum seriellen Monitor
  11. // (ESP: z.B. 115200, Arduino: zwingend 9600)
  12. #define BITRATE 115200  // Arduino: 9600
  13.  
  14. // Obergrenze des Messbereichs (0-2000. 0-5000, 0-10000 ppm CO2)
  15. #define RANGE 5000
  16.  
  17. void setup() {
  18.   // Sensor-Pin als Eingang verwenden
  19.   pinMode(SENSOR_PIN, INPUT);
  20.  
  21.   // Übertragungsrate zum seriellen Monitor setzen
  22.   Serial.begin(BITRATE);
  23. }
  24.  
  25. void loop() {
  26.  
  27.   int pulse_high, pulse_high_2, pulse_low;
  28.   unsigned long time_start;
  29.  
  30.   // pulseIn() arbeitet mit Mikro- (nicht Milli-)Sekunden
  31.   // erst einmal Länge des HIGH-Pegels ermitteln ...
  32.   pulse_high = pulseIn(SENSOR_PIN, HIGH, 2200000UL) / 1000;
  33.  
  34.   time_start = millis();  // jetzt beginnt der LOW-Pegel
  35.  
  36.   // ... dann die Länge des folgenden HIGH-Pegels messen
  37.   pulse_high_2 = pulseIn(SENSOR_PIN, HIGH, 1100000UL) / 1000;
  38.  
  39.   // die Dauer des LOW-Pegels ist die Zeit seit Beginn der 
  40.   // Messung minus die Dauer des zweiten HIGH-Pegels;
  41.   pulse_low = millis() - time_start - pulse_high_2;
  42.  
  43.   // Ausgabe im seriellen Monitor: 
  44.   // - CO2-Wert nach einfacher Formel
  45.   // - Wert nach vollständiger Formel
  46.   // - Roh-Werte der Messung
  47.   Serial.print("PPM CO2: ");
  48.   Serial.print((pulse_high - 2) * RANGE / 1000);
  49.   Serial.print(" (einfach); ");
  50.   Serial.print((pulse_high - 2) * RANGE / (pulse_high + pulse_low -4));
  51.   Serial.print(" (korrekt); ");
  52.   Serial.print("Rohwerte (HIGH, LOW): ");
  53.   Serial.print(pulse_high);
  54.   Serial.print(", ");
  55.   Serial.println(pulse_low);
  56.  
  57.   delay(2500);  // vor der nächsten Messung etwas warten
  58. }
Ausgabe des Testprogramms
Ausgabe des Testprogramms

Sieht man sich die Ausgabe im seriellen Monitor an (im Beispiel beim Lüften – die CO2-Werte werden kleiner), erkennt man, dass bei meinem Sensor die Gesamtdauer von HIGH- und LOW-Pegel etwa 997 ms beträgt (minimal 996, maximal 998 ms) statt der in der Dokumentation genannten 1004 ms. Die vereinfachte Formel liefert um ca. 0,7 % niedrigere Werte als die vollständige; die (Un-)Genauigkeit liegt innerhalb des vom Hersteller genannten Bereichs von ± 5 %.

Programm und Schaltung: Die CO2-Ampel

In diesem Programm definiere ich eine Konstante INTERVALL, die die mit obigem Programm ermittelte tatsächliche Dauer eines Messzyklus des MH-Z19B nutzt. Man kann der Einfachheit halber aber auch den Wert aus der Dokumentation des Herstellers von 1004 Millisekunden nehmen. (Wie oben geschrieben: Die CO2-Ampel soll ja nur einen halbwegs genauen Hinweis zum Lüften liefern – da kommt es auf ein paar ppm mehr oder weniger nicht an.)

  1. /* CO2-Messung mit dem Sensor MH-Z19B
  2.  * per Pulsweitenmodulation (PWM)
  3.  *
  4.  * Anzeige der Messwerte als CO2-Ampel
  5.  *
  6.  * 2020-11-08 Heiko (unsinnsbasis.de)
  7.  */
  8.  
  9. // Definition der GPIO-Pins
  10. // (für die LEDs beim Arduino jeweils einen freien Digitalpin Dx verwenden)
  11. #define LED_GREEN  32
  12. #define LED_YELLOW 33
  13. #define LED_RED    25
  14. #define SENSOR_PIN 14  // beim Arduino z.B. D3 (oder 5, 6, 9, 10, 11)
  15.  
  16. // Bitrate für die Datenübertragung zum seriellen Monitor
  17. // (ESP: z.B. 115200, Arduino: zwingend 9600)
  18. #define BITRATE 115200  // Arduino: 9600
  19.  
  20. // tatsächliche Dauer eines Messzyklus des MH-Z19B
  21. // (wenn nicht bekannt, den Standardwert von 1004 ms verwenden)
  22. #define INTERVALL 997  // Standard: 1004
  23.  
  24. // Obergrenze des Messbereichs des Sensors
  25. // (0-2000. 0-5000, 0-10000 ppm CO2)
  26. #define RANGE 5000
  27.  
  28. // Grenzen des "grünen" und "gelben" Bereichs
  29. #define LIMIT_GREEN 1000
  30. #define LIMIT_YELLOW 2000
  31.  
  32. void setup() {
  33.   // Sensor-Pin als Eingang verwenden
  34.   pinMode(SENSOR_PIN, INPUT);
  35.   // GPIO-Pins der LEDs sind Ausgänge
  36.   pinMode(LED_GREEN, OUTPUT);
  37.   pinMode(LED_YELLOW, OUTPUT);
  38.   pinMode(LED_RED, OUTPUT);
  39.  
  40.   // Übertragungsrate zum seriellen Monitor setzen
  41.   Serial.begin(BITRATE);
  42.  
  43.   // alle LEDs aus
  44.   digitalWrite(LED_GREEN, LOW);
  45.   digitalWrite(LED_YELLOW, LOW);
  46.   digitalWrite(LED_RED, LOW);
  47. }
  48.  
  49. void loop() {
  50.   int ppm;
  51.  
  52.   // Verwendung der einfachen Formel; allerdings wird - sofern
  53.   // bekannt - mit dem Wert von INTERVALL die tatsächliche
  54.   // Intervalllänge des Sensors berücksichtigt
  55.   ppm = (pulseIn(SENSOR_PIN, HIGH, 2200000UL) / 1000 - 2) * RANGE / (INTERVALL - 4);
  56.  
  57.   // Ausgabe des CO2-Werts in ppm im seriellen Monitor
  58.   Serial.print("PPM CO2: ");
  59.   Serial.println(ppm);
  60.  
  61.   // je nach Wert die passende LED an- und die anderen
  62.   // beiden ausschalten
  63.   if (ppm < LIMIT_GREEN) {
  64.     digitalWrite(LED_GREEN, HIGH);
  65.     digitalWrite(LED_YELLOW, LOW);
  66.     digitalWrite(LED_RED, LOW);
  67.   } else if (ppm < LIMIT_YELLOW) {
  68.     digitalWrite(LED_GREEN, LOW);
  69.     digitalWrite(LED_YELLOW, HIGH);
  70.     digitalWrite(LED_RED, LOW);
  71.   } else {
  72.     digitalWrite(LED_GREEN, LOW);
  73.     digitalWrite(LED_YELLOW, LOW);
  74.     digitalWrite(LED_RED, HIGH);
  75.   }
  76.   delay(2500);  // vor der nächsten Messung etwas warten
  77. }

Will man die tatsächliche Dauer des LOW-Pegels messen, ersetzt man die Code-Zeile

  1.   ppm = pulseIn(SENSOR_PIN, HIGH, 2200000UL) / 1000 * RANGE / (INTERVALL - 4);

durch folgenden Block:

  unsigned long pulse_high, pulse_high_2, time_start;
 
  // Länge des HIGH-Pegels ermitteln
  pulse_high = pulseIn(SENSOR_PIN, HIGH, 2200000UL) / 1000;
  time_start = millis();  // jetzt beginnt der LOW-Pegel
  // ... dann die Länge des folgenden HIGH-Pegels messen
  pulse_high_2 = pulseIn(SENSOR_PIN, HIGH, 1100000UL) / 1000;
 
  // die Dauer des LOW-Pegels ist die Zeit seit Beginn der 
  // Messung minus die Dauer des zweiten HIGH-Pegels;
  // statt die Dauer des LOW-Pegels
  //      millis() - time_start - pulse_high_2
  // in einer Variablen zwischenzuspeichern, wird der Wert
  // direkt in die Formel eingesetzt
  ppm = (pulse_high - 2) * RANGE / (pulse_high + (millis() - time_start - pulse_high_2) - 4);

Die Schaltung ist nicht weiter kompliziert. Die Kathoden der LEDs (die kürzeren Beinchen) werden jeweils über einen Vorwiderstand von 220 Ω mit Masse verbunden, die Anoden mit einem Pin des Controllers, der zur digitalen Ausgabe geeignet ist. Da der MH-Z19B eine Versorgungsspannung von 5 Volt erwartet, muss man am ESP32 den passenden Pin (Beschriftung meist »5V«; auch »V5« oder »VIN«) nutzen.

(Getestet ist das Programm nur mit einem ESP32 – es sollte sich aber durch die Verwendung von Konstanten im Programmkopf z.B. für die Nummern der GPIO-Pins einfach an andere Controller wie Arduino oder ESP8266 und Varianten anpassen lassen.)

MH-Z19B: Schaltung mit ESP32
MH-Z19B: Schaltung mit ESP32

Kommunikation mit dem Sensor über die serielle Schnittstelle

Ein großer Vorteil bei der Nutzung der seriellen Schnittstellen zur Ermittlung der Messwerte ist, dass das Programm während der Messung nicht blockiert wird. Wie oben beschrieben, blockiert die pulseIn()-Funktion das Programm für die Dauer der Messung: Mindestens eine, im ungünstigsten Fall zwei Sekunden kann der Mikrocontroller nichts anderes tun, bspw. Messwerte oder eine Uhrzeit anzeigen, andere Sensoren abfragen oder per WLAN kommunizieren. Über die serielle Schnittstelle werden die Messwerte sofort zurückgeliefert und der Controller kann bei Bedarf andere Aufgaben erledigen. Außerdem entfallen Ungenauigkeiten durch die Konvertierung des Messwerts in das PWM-Signal und dessen anschließende Umsetzung zurück in einen Zahlenwert. Und die PWM-Methode hat eine geringere Auflösung: Jede Millisekunde Signallänge entspricht einem Tausendstel des Sensormessbereichs – bei meinem Sensor also 5 ppm. Kleinere Änderungen von 1 oder 2 ppm können mit der Pulsweitenmodulation nicht dargestellt werden.

MH-Z19 Bibliothek

Wenn man sich nicht mit dem Aufbau der Steuerkommandos befassen will, kann man eine passende Bibliothek nutzen. Ich habe die MH-Z19 Library von Jonathan Dempsey verwendet, die man über die Bibliotheksverwaltung der Arduino-IDE installieren kann (Suchbegriff z.B. »mhz19«; weitere Bibliotheken sind unten verlinkt).

Sie stellt u.a. folgende Funktionen zur Verfügung:

  • getCO2() – ppm CO2 als ganze Zahl (integer)
  • getTemperature() – Temperatur in Grad Celsius (integer)
  • getABC() – Abfrage des Status der Auto-Kalibrierung (an = TRUE, aus = FALSE) als boolscher Wert
  • getVersion(char rVersion[]) – ermittelt die Firmware-Version
  • autoCalibration(bool isON = true, byte ABCPeriod = 24) – An- oder Abschalten der Autokalibrierung; Setzen des Abstands zwischen zwei Kalibrierungen in Stunden
  • calibrate() – „Null”-Kalibrierung auf den Basiswert von 400 ppm

Außerdem werden Beispielprogramme mitgeliefert, die die Anwendung der Funktionen zeigen, u.a.:

  • BasicUsage.ino – liest CO2– und Temperatur-Messwerte aus
  • Calibration.ino – schaltet die Auto-Kalibrierung aus und kalibriert den Sensor
  • RetrieveDeviceInfo.ino – zeigt einige Sensor-Parameter an, u.a. die Firmware-Version

Um die Beispielprogramme mit einem ESP32 unverändert nutzen zu können, habe ich in der Arduino-IDE noch eine SoftwareSerial-Bibliothek zur Kommunikation mit der seriellen Schnittstelle installiert, und zwar die EspSoftwareSerial. (Grundsätzlich ist die Installation der SoftwareSerial-Bibliothek für den ESP32 nicht nötig, da er drei serielle Schnittstellen hardwaremäßig unterstützt (siehe folgender Abschnitt). Ich habe es hier aus Bequemlichkeit gemacht, weil so nur die folgende kleine Änderung am Code der Beispielprogramme aus der MH-Z19-Library nötig war.)

Danach waren in den Beispielprogrammen nur die Pins für Tx und Rx anzupassen – ich habe am ESP32 die GPIO-Pins 19 für Rx und 18 für Tx genutzt.

#define RX_PIN 19                                         
#define TX_PIN 18

Am restlichen Code musste ich nichts mehr ändern. Evtl. muss man noch im seriellen Monitor der Arduino-IDE die Übertragungsrate anpassen – die Beispiele arbeiten mit 9600 Baud.
Zu beachten ist bei der Verkabelung der seriellen Verbindung, dass Tx und Rx über Kreuz verbunden werden müssen, also Sensor-Tx mit Controller-Rx und Sensor-Rx mit Controller-Tx.

Damit war die Kalibrierung des Sensors kein Problem, deshalb spare ich mir hier die Wiedergabe des Kalibrierungsprogramms. Dieses Programm beginnt mit einer 20-minütigen Wartezeit und führt dann die Kalibrierung durch. Man kann es also am Arbeitsplatz auf den ESP32 (oder Arduino) laden, diesen vom PC abstöpseln und draußen mit einer Powerbank oder einem USB-Netzteil bzw. Ladegerät mit Strom versorgen. Dort lässt man ihn gut 20 Minuten laufen, bis die Kalibrierung erledigt ist. Anschließend sollte man ein anderes Programm auf den Mikrocontroller laden, damit man nicht versehentlich drinnen noch eine Kalibrierung durchführt, wenn man den Controller wieder an den Rechner anschließt und etwas liegen lässt.

Programm und Schaltung: Anzeige der Messwerte

Das folgende Programm soll zwei Dinge erledigen:

  1. Vergleich der Temperatur-Messung des MH-Z19B mit einem anderen Temperatur-Sensor. Nach meiner Einschätzung zeigt der MH-Z19B deutlich zu hohe Messwerte für die Raumtemperatur an (ich heize sicher nicht bis auf 28°), deshalb kommt parallel ein anderer Sensor, nämlich der DS18B20, zum Einsatz.
  2. Vergleich der CO2-Messwerte, die man mit der PWM-Übertragung misst, mit denen, die seriell ausgelesen werden – mich interessiert, ob hierbei Abweichungen auftreten, die größer als 5 ppm sind (wie oben beschrieben, sind 5 ppm der Wert, der 1 ms Intervall-Länge des PWM-Signals entspricht)

Die Werte sollen auf einem OLED-Display (Typ SSD1306) angezeigt werden, und zwar alle 5 Sekunden im Wechsel die Temperaturen und die CO2-Werte.

Der ESP32 unterstützt hardwaremäßig drei serielle Schnittstellen. Eine davon soll verwendet werden, statt wie oben eine per SoftwareSerial bereitgestellte zusätzliche Schnittstelle zu nutzen.[18]»Seriell« heißt, die Daten werden bitweise nacheinander übertragen. Die Umsetzung eines Bytes (8 bit parallel) in die bitweise Reihenfolge übernimmt ein UART (Universal Asynchronous … Continue reading

  • UART0: wird für die USB-Datenübertragung zwischen der IDE auf dem PC und dem Mikrocontroller verwendet, d.h. das Hochladen des übersetzten Programms und die Übertragung von Ausgaben des Programm zum seriellen Monitor (z.B. mit dem Befehl Serial.print()). Nutzt man also den seriellen Monitor für Ausgaben des ESP32 am PC, kann man diese Schnittstelle nicht anderweitig verwenden. Standard-Pins sind GPIO 3 für Rx0 und GPIO 1 für Tx0.
  • UART1: ist nur nutzbar, wenn man die Standard-Pins (Rx1: GPIO 9, Tx1: GPIO 10) „umbiegt” (also auf andere Pins verlegt), denn GPIO 9 und 10 sind vom ESP32 zur Ansteuerung des Flash-Speichers reserviert und können nicht in Programmen verwendet werden
  • UART2: ist ohne weiteres nutzbar und deshalb für mich die Schnittstelle der Wahl; die Standard-Pins sind GPIO 16 für Rx2 und GPIO 17 für Tx2

Die wesentlichen den MH-Z19B betreffenden Programmteile sind folgende:

// Nutzung der Schnittstelle UART2 an den Default-Pins RX 16, TX 17
#define RX2 16
#define TX2 17
#define MHZ19_BAUDRATE 9600
#define MHZ19_PROTOCOL SERIAL_8N1

Man definiert die verwendeten Pins (sind das wie hier die Standard-Pins, können die beiden Zeilen entfallen) sowie Übertragungsrate und Protokoll – diese beiden Angaben findet man im Datenblatt zum MH-Z19B. »8N1« in der Angabe des Protokolls steht für 8 Datenbits, kein Parity-Bit, 1 Stop-Bit.[19]Dokumentation Version 1.6 Seite 8:»Set serial port baud rate be 9600, data bytes have 8 bytes, stop byte has 1 byte, parity byte is null.« (Wobei hier Bits und Bytes etwas durcheinander … Continue reading

  Serial2.begin(MHZ19_BAUDRATE, MHZ19_PROTOCOL, RX2, TX2);

Hier wird die gewünschte Schnittstelle definiert (Serial2 entspricht UART2, Serial (ohne Ziffer) entspricht UART0, Serial1 steht für UART1). Parameter sind die Übertragungsrate, das Protokoll und die beiden Pins für die Kommunikation.
Alternativ:

  Serial2.begin(MHZ19_BAUDRATE);

Verwendet man das Standardprotokoll (SERIAL_8N1, das Protokoll des MH-Z19B, ist auch das Standardprotokoll) und die Standard-GPIO-Pins der jeweiligen Schnittstelle, können die entsprechenden Paraneter entfallen.

  mhz19b.begin(Serial2);  // MH-Z19B-Sensor eine Schnittstelle zuweisen

Diese Anweisung initialisiert das Objekt für den Sensor und weist ihm die vorher eingerichtete Schnittstelle Serial2 zu.

  co2_ser = mhz19b.getCO2();

liest den Messwert vom Sensor. Mit diesen Angaben sollte es nicht allzu schwer sein, das Programm zur CO2-Ampel auf das Auslesen per serieller Schnittstelle umzustellen. Die Anwendung sieht man im folgenden Programm.

Schaltung: MH-Z19B, DS18B20 und OLED-Display am ESP32
Schaltung: MH-Z19B, DS18B20 und OLED-Display am ESP32

Hinweise zu den Pin-Verbindungen:

  • V5 des ESP32 an Vin des MH-Z19B (5 Volt; rot); das OLED-Display und der DS18B20 nutzen 3,3 V (Pin 3V3 des ESP32)
  • TX2 (GPIO 17) an Rx des MH-Z19B (hellblau)
  • RX2 (GPIO 16) an Tx des MH-Z19B (orange)
  • P5 (GPIO 5) an PWM des MH-Z19B (grün)
  • P18 (GPIO 18) an mittleren Pin (DAT) des DS18B20 (gelb); DAT wird außerdem über einen 4,7 kΩ-Widerstand mit 3,3 V verbunden
  • I2C_SCL (GPIO 22) an SCL des OLED (blau)
  • I2C_SDA (GPIO 21) an SDA des OLED (weiß)
  1. /* Abfrage von Sensoren und Anzeige auf OLED
  2.  *  - MH-Z19B: CO2 und Temperatur
  3.  *  - DS18B20: Temperatur
  4.  *
  5.  * Die OLED-Anzeige ändert sich alle 5 Sekunden:
  6.  * - CO2-Wert: Abfrage des MH-Z19B seriell und per PWM
  7.  * - Temperatur: Messwerte beider Sensoren
  8.  *
  9.  * 2021-01-14 Heiko (unsinnsbasis.de)
  10.  */
  11.  
  12. /* ------------------------------------------------------------
  13.  *  Einbinden der benötigten Bibliotheken, 
  14.  *  Defintion der GPIO-Pins und anderer Konstanten,
  15.  *  Anlegen der Datenobjekte
  16.  * ------------------------------------------------------------ */
  17. // Übertragungsrate für Ausgabe zum seriellen Monitor
  18. #define SERIAL_BAUDRATE 115200 // Arduino: 9600, ESP32: z.B. 115200
  19.  
  20. /* ------------------------------------------------------------
  21.  * DS18B20
  22.  * ------------------------------------------------------------ */
  23. #include <OneWire.h>
  24. #include <DallasTemperature.h>
  25. #define DS18B20_PIN 18  // GPIO-Pin für Daten des DS18B20
  26. // OneWire-Objekt erstellen ...
  27. OneWire ow(DS18B20_PIN);
  28. // ... und einen Zeiger darauf an ein Sensor-Objekt übergeben
  29. DallasTemperature ds18b20(&ow);
  30. DeviceAddress ds18b20_id;  // ID des DS18B20 (Adresse)
  31.  
  32. /* ------------------------------------------------------------
  33.  * MH-Z19B
  34.  * ------------------------------------------------------------ */
  35. #include <Arduino.h>
  36. #include <MHZ19.h>
  37. // Nutzung der Schnittstelle UART2 an den Default-Pins RX 16, TX 17
  38. #define RX2 16
  39. #define TX2 17
  40. #define MHZ19_BAUDRATE 9600
  41. #define MHZ19_PROTOCOL SERIAL_8N1
  42. #define MHZ19_RANGE 5000  // Obergrenze des Messbereichs des Sensors
  43. #define MHZ19_PWM_PIN 5
  44. MHZ19 mhz19b;  // Sensor-Objekt
  45.  
  46. /* ------------------------------------------------------------
  47.  * SSD1306-OLED-Display (I2C)
  48.  * ------------------------------------------------------------ */
  49. // Bibliothek bindet auch <Adafruit_GFX.h>, <SPI.h>, <Wire.h> ein
  50. #include <Adafruit_SSD1306.h>
  51. // I2C-Adresse des Displays (0x3C oder 0x3D)
  52. #define DISPLAY_I2C_ADDRESS 0x3C
  53. // Auflösung des SSD1306-OLED-Displays
  54. #define DISPLAY_WIDTH 128  // Breite in Pixeln
  55. #define DISPLAY_HEIGHT 64  // Höhe in Pixeln
  56. // Datenobjekt für das Display
  57. // - Verbindung per I2C (Standard-Pins SCL, SDA)
  58. // - Display hat keinen Reset-Pin
  59. Adafruit_SSD1306 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, &Wire, -1);
  60.  
  61. /* Timer für die Ausgabe:
  62.  * - alle 10 Sekunden die Temperatur des CO2-Sensors und 
  63.  *   des DS18B20 anzeigen
  64.  *   -> immer wenn die Sekundenzahl auf 0 endet
  65.  * - 5 Sekunden später die CO2-Werte (seriell und PWM) anzeigen
  66.  *   -> immer wenn die Sekundenzahl auf 5 endet
  67.  */
  68. unsigned long timer_output_0 = 0, timer_output_5 = 5000;
  69. // CO2-Werte bei serieller und PWM-Messung
  70. int co2_ser, co2_pwm;
  71. float temp_ds;  //Temperatur des DS18B20
  72. int temp_mh;    //Temperatur des MH-Z19B
  73.  
  74. /* ------------------------------------------------------------ */
  75.  
  76. void setup(){
  77.   int i;
  78.   bool error = false;
  79.   char mhz19_version[4];          
  80.  
  81.   Serial.begin(SERIAL_BAUDRATE);
  82.   delay(500);  // kurz warten, bis die ser. Schnittstelle bereit ist
  83.   Serial.println("\n\n");  // Abstand zur vorigen Ausgabe
  84.  
  85.   // Display initialisieren
  86.   // im Fehlerfall Meldung ausgeben
  87.   if(!display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_I2C_ADDRESS)) {
  88.     Serial.println("SSD1306 nicht gefunden");
  89.     error = true;
  90.   } else {
  91.     display.clearDisplay();  // Display löschen
  92.     display.display();
  93.   }
  94.  
  95.   // Sensoren initialisieren
  96.   // ROM-Adresse (ID) des DS18B20 ermitteln und anzeigen
  97.   if (!ow.search(ds18b20_id)) {
  98.     Serial.println("Kein DS18B20-Sensor gefunden");
  99.     error = true;
  100.   } else {
  101.     Serial.print("DS18B20 ID (Sensor-Adresse):");
  102.     // Adress-Bytes als Hexadezimalwerte ausgeben
  103.     for (i = 0; i < sizeof(DeviceAddress); i++) {
  104.       Serial.print(' ');
  105.       Serial.print(ds18b20_id[i], HEX);
  106.     }
  107.     Serial.print(' ');
  108.   }
  109.   Serial.println();
  110.  
  111.   Serial2.begin(MHZ19_BAUDRATE, MHZ19_PROTOCOL, RX2, TX2);
  112.   mhz19b.begin(Serial2);  // MH-Z19B-Sensor eine Schnittstelle zuweisen
  113.   // ein paar Daten der Sensor-Konfiguration ausgeben
  114.   mhz19b.getVersion(mhz19_version);
  115.   Serial.print("--------------------\nMH-Z19B Firmware Version: ");
  116.   for(i = 0; i < 4; i++) {
  117.     Serial.print(mhz19_version[i]);
  118.     if(i == 1)
  119.       Serial.print(".");
  120.   }
  121.   Serial.print("\nMH-Z19B Messbereich: ");
  122.   Serial.println(mhz19b.getRange());   
  123.   Serial.print("MH-Z19B Autokalibrierung (ABC): ");
  124.   mhz19b.getABC() ? Serial.println("AN") :  Serial.println("AUS");
  125.   Serial.println("--------------------");
  126.  
  127.   // im Fehlerfall Programm nicht fortsetzen (leere Dauerschleife))
  128.   if(error) {
  129.     for(;;);
  130.   }
  131. }
  132.  
  133. /* ------------------------------------------------------------ */
  134.  
  135. void loop(){
  136.   // Timer und Intervalllängen für die PWM-Messung des CO2-Werts
  137.   unsigned long pulse_high, pulse_high_2, time_start, timer;
  138.  
  139.   timer = millis();
  140.   if ((timer / 1000) % 10 == 0 && timer > timer_output_0) {
  141.     timer_output_0 += 10000;  // nächste Anzeige in 10 Sekunden
  142.  
  143.     ds18b20.requestTemperatures();  // DS18B20-Abfrage starten
  144.     temp_ds = ds18b20.getTempC(ds18b20_id);
  145.  
  146.     temp_mh = mhz19b.getTemperature();
  147.  
  148.     display.clearDisplay();
  149.     display.setTextColor(WHITE);  // helle Schrift, dunkler Grund
  150.     display.setTextSize(2);       // doppelt hohe Schrift
  151.     display.setCursor(0, 0);
  152.     display.print("Temperatur");
  153.     display.setCursor(12, 20);
  154.     display.print("MH: ");
  155.     display.print(temp_mh);
  156.     display.setCursor(12, 48);
  157.     display.print("DS: ");
  158.     display.print(temp_ds, 1);
  159.     display.display();
  160.  
  161.     // im Anschluss die PWM-Messung durchführen (dauert bis zu
  162.     // zwei Sekunden), um die anschließende Ausgabe nicht
  163.     // zu verzögern;
  164.     // mit der vollständigen Formel und der
  165.     // tatsächlich gemessenen Intervalllänge arbeiten.
  166.     // Länge des HIGH-Pegels ermitteln
  167.     pulse_high = pulseIn(MHZ19_PWM_PIN, HIGH, 2200000UL) / 1000;
  168.     time_start = millis();  // jetzt beginnt der LOW-Pegel
  169.     // ... dann die Länge des folgenden HIGH-Pegels messen
  170.     pulse_high_2 = pulseIn(MHZ19_PWM_PIN, HIGH, 1100000UL) / 1000;
  171.  
  172.     // die Dauer des LOW-Pegels ist die Zeit seit Beginn der 
  173.     // Messung minus die Dauer des zweiten HIGH-Pegels;
  174.     // statt die Dauer des LOW-Pegels
  175.     //      millis() - time_start - pulse_high_2
  176.     // in einer Variablen zwischenzuspeichern, wird der Wert
  177.     // direkt in die Formel eingesetzt
  178.     co2_pwm = (pulse_high - 2) * MHZ19_RANGE / (pulse_high + (millis() - time_start - pulse_high_2) - 4);
  179.     co2_ser = mhz19b.getCO2();
  180.   } else if ((timer / 1000) % 10 == 5 && timer > timer_output_5) {
  181.     timer_output_5 += 10000;
  182.     display.clearDisplay();
  183.     display.setTextColor(WHITE);
  184.     display.setTextSize(2);
  185.     display.setCursor(0, 0);
  186.     display.print("CO2 (ppm)");
  187.     display.setCursor(0, 20);
  188.     display.print("ser.: ");
  189.     display.print(co2_ser);
  190.     display.setCursor(0, 48);
  191.     display.print("PWM:  ");
  192.     display.print(co2_pwm);
  193.     display.display();
  194.   }
  195.   delay(100);  // kurz warten (1/10 Sekunde; kann entfallen)
  196. }

Ergebnis

Man erkennt auf dem Display, dass die CO2-Werte bei serieller Ausgabe oder Messung des PWM-Signals nur um wenige ppm voneinander abweichen. Beim Lüften oder danach konnte ich maximal 10 ppm Abweichung beobachten, also dann, wenn sich die Werte schnell ändern. Da ich im Programm die Signale aus zwei PWM-Zyklen auswerte, ist das nicht unplausibel – es kann passieren, dass der Sensor während dieser Zeit eine neue Messung durchführt.

Die vom MH-Z19B gelieferten Temperatur-Werte sind eindeutig zu hoch, und zwar mehrere Grad über denen anderer Sensoren. Ich habe neben dem DS18B20 auch noch andere Sensoren angeschlossen (die Temperatur- und Luftfeuchtigkeitssensoren DHT22, SHT20 und AM2320 sowie den Luftdruck- und Temperatursensor BMP180) und aus den Messwerten von etwa eineinhalb Tagen folgendes Diagramm erstellt (die Messwerte des MH-Z19B bilden die obere, treppenförmige Kurve):

Vergleichsmessungen verschiedener Temperatursensoren
Vergleichsmessungen verschiedener Temperatursensoren

Es wird deutlich, dass auch die anderen Sensoren tlw. um mehr als 1 Grad voneinander abweichende Werte liefern. (Ähnlich sieht es aus, wenn man die Werte der Sensoren für die relative Luftfeuchtigkeit vergleicht: Unterschiede um bis zu 3 Prozentpunkte waren sichtbar.) Ich werde also demnächst einmal alle Temperatur- und Luftfeuchtigkeitssensoren, die ich habe, nebeneinander aufbauen und messen lassen – Thema für einen neuen Beitrag. Da ich kein geeichtes Thermometer habe und erst recht keine Laborbedingungen zum Messen, werde ich zwar nicht herausbekommen, welcher Sensor „richtig” misst, aber vielleicht lassen sich weitere Ausreißer finden.

Material-Liste für die CO2-Ampel

  • CO2-Sensor MH-Z19B
  • ESP32 oder Arduino(-Klon)
  • 1 oder 2 Mini-Breadboards (400 Kontakte)
  • 3 farbige LEDs (grün, gelb, rot)
  • 3 Widerstände mit 220 (oder 330) Ω
  • ein paar Steckbrücken und Jumperkabel; bei fliegender Verkabelung des Sensors ein paar Dupont-Kabel mit Buchse (female-male)

Rechnet man zum Preis des Sensors (25-30 €) noch den für die Mini-Breadboards (je ca. 4 €) und einen ESP32 (ca. 8 €; Arduino-Klon ab 5, Original-Arduino mind. 20 €) hinzu, dazu ein paar Kleinteile wie Kabel, LEDs und Widerstände, landet man für diese einfache CO2-Ampel bei Kosten von 40-50 €. Wie üblich kann man die Kosten ungefähr halbieren, wenn man in China bestellt – unter Verlust von Garantie bzw. Gewährleistung und mit Lieferzeiten im Bereich mehrerer Wochen (zwischen 2,5 Wochen und deutlich über zwei Monaten habe ich schon erlebt).

Natürlich kann man die Ausgabe statt mit einfachen LEDs auch mit einem LED-Panel, einem OLED-Display oder mit RGB-LEDs schöner darstellen. Kostengünstiger ist es, die Netzwerkfähigkeiten eines ESP32 oder ESP8266 zu nutzen, um die Messwerte im WLAN auf einen PC zu übertragen und dort grafisch darzustellen – Thema für einen zukünftigen Beitrag.

Datenblatt/Dokumentation:

  • Version 1.6 vom 23.04.2020 (PDF) – soweit nicht anders angegeben, beziehen sich die Angaben im Beitrag auf diese Version der Dokumentation
  • ältere: Version 1.5 vom 23.09.2019; Version 1.0 vom 21.01.2016 (enthält Beschreibungen zu ein paar mehr Steuerkommandos als die neueren Versionen)
  • Produkthomepage beim Hersteller Winsen Electronics

Auf Revspace wird der Sensor im Detail analysiert (und zerlegt). Dort findet sich auch eine Liste von nicht dokumentierten Steuerbefehlen: https://revspace.nl/MH-Z19B

Bibliotheken
Selbst wenn man nicht alle Bibliotheken ausprobieren will, findet man in den Beschreibungen, tlw. auch in den Quellcode-Kommentaren, Hinweise zur Funktionsweise des Sensors. Deshalb führe ich hier die verschiedenen Bibliotheken auf, die mir begegnet sind. Zum Programmieren habe ich nur die erste, die MH-Z19 Library, verwendet. Vermutlich gibt es weitere.

Fake-Sensoren:

Der Beitrag Arduino-Projekt: CO2-Messung mit dem Sensor MHZ19B zeigt die Funktionsweise eines NDIR-Sensors und der PWM-Messung anschaulich mit Grafiken. Mit einem Arduino werden Messwerte per PWM und seriell ausgelesen.

Vergleich der Messwerte von MH-Z19 und MH-Z19B mit denen eines Senseair S8-Sensors: www.letscontrolit.com/forum/viewtopic.php?p=21691#p21691

Alternativen zum CO2-Sensor MH-Z19B

Oft wird der günstige Sensor MQ-135 zur CO2-Messung eingesetzt. Er kostet je nach Stückzahl und Quelle ca. 2-5 €, misst aber nur relativ unspezifisch mehrere Gase (neben CO2 Alkohol, Benzene, NOx und NH3 – wobei diese üblicherweise in normaler Umgebung nur wenig relevant sind). Während einer sog. Einbrenn- oder Kalibrierungsphase (24-48 Stunden) ermittelt man einen Referenzwert für „gute Luft” (am besten Frischluft im Außenbereich) und kann diesen dann nutzen, um bei Über- oder Unterschreiten des Wertes eine Aktion auszuführen. Eine Messung des absoluten CO2-Wertes in ppm ist nicht möglich. Ein Beispiel zum Einsatz des MQ-135 findet sich auf funduino.de, mehr liefert wie üblich die Suchmaschine der Wahl.

Der SCD30 von Sensirion arbeitet ebenfalls nach dem NDIR-Prinzip und misst neben dem CO2-Wert auch Temperatur und relative Luftfeuchtigkeit. Die Zeitschrift Make des Heise-Verlags hatte in Ausgabe 5/2020 einen Artikel veröffentlicht, in dem ein bei Seeed Studio erhältliches Sensormodul für rund 60 $ zum Bau einer CO2-Ampel eingesetzt wird. Die Linksammlung zum Artikel nennt weitere Bezugsquellen (ab ca. 45 €) – der Sensor scheint derzeit (November 2020) in Deutschland aber schwer lieferbar zu sein.
Auch ein Projekt der Hochschule Trier verwendet den SCD30 (und auch sonst ähnliche Hardware). Beide Bauvorschäge nennen den Umweltsensor Bosch BME680 als Alternative oder Ergänzung – dieser misst flüchtige organische Bestandteile der Luft und ermittelt daraus eine Einschätzung der Luftgüte nach dem IAQ-Index (Indoor Air Quality). Sensormodule für den Einsatz mit dem Arduino oder anderen Mikrocontrollern findet man ab ca. 20 € im Onlinehandel.

Außerdem gibt es ein Nachfolgemodell des MH-Z19B, den MH-Z19C. Auch der Vorgänger MH-Z19 wird bei Direktbestellung in China noch angeboten – den würde ich aber nicht mehr kaufen.

Fußnoten

Fußnoten
1 Quelle: Wikipedia: Luft; s. auch Wikipedia: Luftfeuchtigkeit
2 Zitat aus einer Bekanntmachung des Umweltbundesamtes (UBA) von 2008:
»Für den Außenluftbeitrag […] werden derzeit für ländliche Gebiete übliche Werte von 350 ppm, für kleine Städte von 375 ppm und für Stadtzentren von 400 ppm genannt. Diese Werte werden bestätigt durch 58 Messungen in der Außenluft vor bayerischen Schulen, bei denen sich die mittleren Konzentrationen (Mediane) zwischen 383 ppm in der Sommermessperiode und 405 ppm bei den Wintermessungen bewegten.«
(Quelle: Bekanntmachung des Umweltbundesamtes: Gesundheitliche Bewertung von Kohlendioxid in der Innenraumluft, 2008, www.umweltbundesamt.de/sites/default/files/medien/pdfs/kohlendioxid_2008.pdf; Seite 1360 bzw. S. 3 des PDFs)
3 Angesichts der Corona-Pandemie (auch: SARS-CoV-2, COVID19) hat das Umweltbundesamt Empfehlungen zur Einhaltung einer guten Luftqualität herausgegeben; u.a. heißt es dort:
»In Räumen mit hoher Personenbelegung, wie z. B. Schulen, können sogenannte CO2-Ampeln als grober Anhaltspunkt für gute oder schlechte Lüftung dienen. Kohlendioxid (CO2) gilt seit langem als guter Indikator für den Luftwechsel, eine CO2-Konzentration von höchstens 1000 ppm (0,1 Vol-%) zeigt unter normalen Bedingungen einen hygienisch ausreichenden Luftwechsel an.«
(Quelle: Das Risiko einer Übertragung von SARS-CoV-2 in Innenräumen lässt sich durch geeignete Lüftungsmaßnahmen reduzieren – Stellungnahme der Kommission Innenraumlufthygiene am Umweltbundesamt, 12.08.2020, www.umweltbundesamt.de/sites/default/files/medien/2546/dokumente/irk_stellungnahme_lueften_sars-cov-2_0.pdf)
In der bereits genannten Bekanntmachung des UBA »Gesundheitliche Bewertung von Kohlendioxid in der Innenraumluft« heißt es außerdem:
»Danach gelten Konzentrationen unter 1000 ppm Kohlendioxid in der Raumluft als unbedenklich, Konzentrationen zwischen 1000 und 2000 ppm als auffällig und Konzentrationen über 2000 ppm als inakzeptabel.« (Zusammenfassung auf Seite 1363 bzw. S. 6 des PDFs)
Der Arbeitsplatzgrenzwert beträgt 5000 ppm; erkennbare gesundheitliche Auswirkungen treten lt. verschiedenen dort genannten Studien erst bei wesentlich höheren Konzentration und über längere Zeiträume auf.

Die Norm DIN EN 13779 nennt (für mechanisch belüftete Gebäude) etwas niedrigere Werte und vier Stufen: (Quelle: Wikipedia: Kohlenstoffdioxid, Abschnitt »Wirkung auf Tiere und Menschen«)

  • < 800 ppm – gute Luftqualität
  • 800-1000 ppm – mittlere Luftqualität
  • 1000-1400 ppm – mäßige Qualität
  • > 1400 ppm – niedrige Qualität

Mich interessieren aber Messungen in natürlich belüfteten Räumen – also »Fenster auf« statt Klimaanlage – deshalb verwende ich die vom UBA genannten Zahlen. Wer mag, kann die Schwellwerte im Programm nach eigenen Wünschen anpassen.

Einen Überblick zum aktuellen Stand der Forschung bzgl. Belüftung, empfohlener Austausch-Häufigkeit und mehr gibt der Artikel »Warum Innenräume noch immer Covid-Hotspots sind« vom 08.04.2021 auf Spektrum.de. Der Text ist eine Übersetzung aus dem britischen Wissenschaftsjournal »Nature«: Dyani Lewis, Why indoor spaces are still prime COVID hotspots, 30.03.2021, www.nature.com/articles/d41586-021-00810-9.

4 Bezugsquellen z.B. Reichelt Elektronik, Direktbestellung in China z.B. über aliexpress.com (im November 2020 war der Sensor dort ab 14 € zu bekommen, im Januar 2021 kaum unter 16 €); jeweils zzgl. Versandkosten.
5 siehe die Hinweise zur Bibliothek MH-Z19 auf github.com/WifWaf/MH-Z19
6 Dokumentation Version 1.6 Seite 5; Tabelle im Abschnitt »Main parameters«:
»Detection Range   0~2000/5000/10000ppm(optional)«
7 Artikel bei Revspace, Abschnitt 6.1, Beschreibung des Kommandocodes 0xA0:
»Firmware version string? “0430” and “0443” observed«.
Ob die verschiedenen Firmware-Versionen eine Auswirkung auf Funktionen des Sensors haben, ist mir nicht bekannt. Welche Firmware ein angebotener Sensor hat, erfährt man auch erst nach dem Kauf, wenn man den Sensor selber ausliest. Auf Webseiten von Anbietern habe ich diese Information nicht gefunden.
8 Siehe die bereits oben verlinkte »Bekanntmachung des Umweltbundesamtes: Gesundheitliche Bewertung von Kohlendioxid in der Innenraumluft« von 2008 (PDF); sie nennt Zahlen aus Untersuchungen verschiedener Raumarten (Wohn- und Schlafräume, Schulen und Kitas, Büros, Verkehrsmittel in verschiedenen Ländern).
9 banggood.com: eines von vier Suchergebnissen; aliexpress.com: mehrere von 291 Suchergebnissen
10 Hierzu muss man eine der beiden Versorgungsleisten des Breadboards durch Abknicken nach unten entfernen; vorher empfiehlt es sich, mit einem scharfen Messen oder Cutter den Klebestreifen auf der Unterseite des Breadboards zu durchtrennen.
11 Version 1.6 der Dokumentation nennt 95 % rel. Luftfeuchtigkeit (nicht kondensierend) als Obergrenze, die älteren Versionen und die Produkthomepage 90 %.
12 STM32F051K86 lt. Revspace
13 Dokumentation Version 1.6; Seite 9 im Abschnitt »Notes«:
»9.4 The module should be calibrated termly, the suggested period is not longer than 6 months.«
14 Dokumentation Version 1.6 Seite 8
15 Dokumentation Version 1.5 Seite 7; Beschreibung des Kommandos 0x79 zum Ein- oder Ausschalten der Auto-Kalibrierung
16 Version 1.6 der Dokumentation nennt Details nur für sehr wenige Kommandos (mehr findet man in Version 1.0 und eine umfangreiche Liste auch undokumentierter Kommandos bei Revspace).
17 Beim Arduino Nano (und Nachbauten) muss das einer der sechs Digital-I/O-Pins D3, D5, D6, D9, D10 oder D11 sein.
18 »Seriell« heißt, die Daten werden bitweise nacheinander übertragen. Die Umsetzung eines Bytes (8 bit parallel) in die bitweise Reihenfolge übernimmt ein UART (Universal Asynchronous Receiver/Transmitter); dieser kommuniziert über zwei Datenleitungen mit dem UART des empfangenden Systems, der die seriellen Bits wieder in parallele Daten wandelt. Dabei ist der Pin Tx des Senders (Transmitter; Sende-Leitung) mit dem Pin Rx des Empfängers (Receiver) verbunden und der Rx-Pin des Senders (Empfangsleitung) mit dem Tx-Pin des Empfängers (die Pins werden also gekreuzt verbunden).
Siehe auch Wikipedia: UART und Serielle Schnittstelle
Die seriellen Schnittstellen des ESP32 sind programmierbar bzgl. Übertragungsrate, Anzahl Daten- und Stop-Bits und Übertragungsprotokoll. Details finden sich in Kapitel 14 des Technical Reference Manual zum ESP32.
19 Dokumentation Version 1.6 Seite 8:
»Set serial port baud rate be 9600, data bytes have 8 bytes, stop byte has 1 byte, parity byte is null.« (Wobei hier Bits und Bytes etwas durcheinander geraten sind.)

8 Kommentare

  1. Sehr gut dokumentiert finde ich, aber wenn ich es nachstellen will, erscheint im Monitor folgendes:
    “21:44:44.324 -> PPM CO2: -⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮Y⸮⸮⸮⸮⸮Y⸮”
    Nach ~5min sieht der Monitor so aus:
    “21:44:44.324 -> PPM CO2: -⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮Y⸮⸮⸮⸮⸮Y⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮Y⸮⸮⸮⸮⸮⸮⸮⸮⸮Y⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮Y⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮”
    Es werden bei der Ausgabe nur alle paar Sekunden mehr Fragezeichen.
    Die Grüne LED leuchtet permanent.
    Kann man mir mitteilen wo das Problem liegen kann?

    1. Normalerweise deuten die Fragezeichen darauf hin, dass die Übertragungsraten im Programm (festgelegt mit “Serial.begin(…);”) und die Baudrate im seriellen Monitor nicht übereinstimmen. Im seriellen Monitor ist das am unteren Rand die Dropddown-Liste (2. von rechts; siehe https://unsinnsbasis.de/wp-content/uploads/2019/03/esp32_Prozessortemperatur.jpg). Da muss der gleiche Wert ausgewählt werden, den man bei SerialBegin() im Program gesetzt hat. Bei den Arduinos sollten beide Angaben 9600 sein, mit den ESPs kann man auch höhere Werte nehmen (meist 115200).
      Was mich etwas irritiert, ist allerdings, dass zu Beginn noch Klartext ausgegeben wird – normalerweise kommen nur die Fragezeichen. Das kann aber passieren, wenn man zwischendurch die Übertragungsrate im seriellen Monitor auf einen nicht passenden Wert ändert.

      1. Guten Tag,
        Bis jetzt lief der Sensor zwei Tage am stück ohne ein Problem mit nachvollziehbaren Werten.
        Nun gibt es sobald ich ihn anstecke immer “PPM CO2: 40” aus.
        Sobald ich den Anschluss entferne gibt es “PPM CO2: -30419” aus.
        Es entstehen NUR diese beiden Werte.
        Gibt es dazu eine logische Erklärung & Änderungsmöglichkeiten?
        Am Code & an der Verkabelung wurde eigentlich garnichts geändert, bis jetzt liefs immer.

        1. Hmmmm… 😉
          Erstmal der einfache Teil: Dass beim Entfernen des Sensors “irgendetwas” angezeigt wird, ist normal. Die Spannung an einem offenen Pin ist wegen des Einflusses von Störsignalen aus der Umgebung nicht vorhersagbar, man sollte sich nicht darauf verlassen, dass dort 0 Volt (oder überhaupt ein definierter Wert) “gemessen” wird. Siehe auch die Arduino-Referenz (https://www.arduino.cc/reference/de/language/functions/analog-io/analogread/ – ganz unten):
          “Wenn der analoge Pin nicht verbunden ist, wird der zurückgegebene Wert schwanken basierend auf mehreren Faktoren (z.B. Die Werte der anderen analogen Pins, Wie nahe die Hand am Board ist, etc.).” Da würde ich also keinen sinnvollen Wert erwarten.
          Warum bei angeschlossenem Sensor nur 40 angezeigt wird, kann ich leider nicht sagen. Generell würde ich bei laufendem ESP oder Arduino nichts an der Verkabelung ändern (z.B. Sensor anschließen oder entfernen), sondern vorher immer die Stromversorgung entfernen (USB-Kabel abziehen). Ich würde Folgendes versuchen, um den Fehler loszuwerden (aber das ist eher Standard nach der Methode “Versuch und Irrtum”, also ausprobieren, ob es hilft):
          – den Controller neu starten (also Stromversorgung/USB-Kabel trennen und neu verbinden)
          – alle Jumper-Kabel prüfen (bzw. abziehen und neu stecken; bei ausgeschaltetem Controller) – manchmal sind die Verbindungen auf dem Breadboard ja etwas wackelig
          – das Programm nochmal neu in der IDE übersetzen und hochladen
          – vielleicht auch einen anderen Pin (z.B. 25 oder 33…) am Controller zum Lesen des Sensor-Wertes ausprobieren (was ja ebenfalls bedeutet, das Programm mit der geänderten Pin-Angabe neu zu übersetzen und hochzuladen)
          Das sind alles eher allgemeine Ideen als etwas, was ganz konkret zu dem beschriebenen Fehler passt. Falls mir besseres einfällt, melde ich mich natürlich noch einmal.

        2. Hallo nochmal,
          das Thema hat mich noch weiter beschäftigt und ich bin auf folgenden Beitrag gestoßen, der sich auch dem MH-Z19B befasst: http://steinlaus.de/stinkt-das-hier-teil-2-mit-dem-winsen-mh-z19b/
          Dort wird darauf hingewiesen, dass sich der Sensor einmal am Tag kalibriert, und zwar auf den niedrigsten in der Zeit gemessenen CO2-Wert. Diesen Wert sieht der Sensor dann bis zur nächsten Kalibrierung als Basis für saubere Luft (also 400 ppm CO2) an. War der tatsächliche Wert aber höher (weil der Sensor “nicht an die frische Luft gekommen ist”), ist diese Kalibrierung natürlich falsch. Misst der Sensor danach in besserer Luft, werden so Werte unter 400 ermittelt. Ausführlich ist das unter obigem Link beschrieben.
          Ich werde in nächster Zeit mit meinem Sensor versuchen, das zu prüfen. Komme ich zu einem ähnlichen Ergebnis, passe ich den Beitrag hier entsprechend an.
          Außerdem gab es dort den Hinweis, dass wohl auch nachgebaute (gefälschte?) Sensoren auf dem Markt sind, die sich mglw. nicht wie das Original verhalten. Sie haben auf der Unterseite eine schwarze Platine (bei meinem Sensor ist sie grün), und auf der Oberseite nicht die kreisrunde Erhebung, wie sie auf diesem Bild zu sehen ist: https://unsinnsbasis.de/co2-sensor-mhz19b/mhz19b_pins_4625_4672/ – siehe auch https://revspace.nl/MH-Z19B#Fake_MH-Z19B_.28black_PCB.29
          Auch das werde ich versuchen, zu verifizieren.

  2. Moin,
    ein sehr guter, ausführlicher Beitrag. Leider bekomme ich beim Messen über PWM bei dem genauen Wert keinen plausiblen Werte raus. So habe ich für 30 min. den Sensor draußen auf den Balkon messen lassen. Folgende Werte kamen dabei heraus:
    14:06:06.673 -> PPM CO2: 390 (einfach); -3 (korrekt); Rohwerte (HIGH, LOW): 80, 765
    Dieser Wert war auch sehr konstant die ganze Messung über, was ich auch für sehr unrealistisch halte.
    Irgendeine Idee, wie das zustande kommt. Ich bin mir sehr sicher, das alles richtig verschaltet sein müsste.
    Gruß,
    MM

    1. Die Rohwerte sind mMn. tlw. unplausibel. In der Summe sollte ungefähr 1004 herauskommen: 1000 ms Signallänge für den Datenwert (ein Teil HIGH, ein Teil LOW) und je 2 ms für den HIGH-Pegel zu Beginn und Ende (natürlich mit gewissen Fehlertoleranzen; lt. Datenblatt +/- 5%), also irgendwo zwischen 954 und 1054 wäre plausibel. Die 80 Millisekunden für den HIGH-Pegel klingen sinnvoll – das wären bei einem Sensor mit einem Messbereich von 5000 ppm die 400 ppm, die man an der frischen Luft erwarten könnte; passt auch zu den 390 ppm als “einfacher” Messwert. Nur der gemessene LOW-Pegel ist viel zu kurz, der müsste etwa bei 920 ms liegen.
      Mir fehlt im Moment aber leider eine Idee, was die Ursache sein könnte.

      Im Datenblatt steht der Hinweis: “The module should be away from heat, and avoid direct sunlight or other heat radiation.” Könnte das die Ursache sein? Der Sensor sollte also möglichst im Schatten laufen und vllt. auch nicht auf einer Fläche stehen, die sich vorher in der Sonne stark erhitzt hat.

      Egal wie, dürfte bei den genannten Messwerten aber nicht -3 als “korrekter” Wert herauskommen, sondern 464.
      (pulse_high – 2) * RANGE / (pulse_high + pulse_low – 4) = (80 – 2) * 5000 / (80 + 765 – 4) = 78 * 5000 / 841 = 463,733…

      Falls möglich, würde ich nach meinen Erfahrungen mit dem MH-Z19B empfehlen, die Messung per serieller Schnittstelle durchzuführen. Man vermeidet Ungenauigkeiten – der Sensor muss den digital gemessenen Wert nicht erst in das Pulssignal umwandeln, das der Mikrocontroller dann misst und wieder in ein digitales Signal zurückwandelt – und man hat auch die Kalibrierung unter Kontrolle. (Aber das hilft natürlich nicht bei der Beantwortung deiner Frage.)

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht.