Luftqualität / Feinstaub-Messgerät mit Panasonic SN-GCJA5 Partikel-Sensor

In diesem Artikel stelle ich einen Luftqualitätsmonitor mit einem Panasonic SN-GCJA5 Partikel-Sensor als Herzstück vor, der mir freundlicherweise von Codico zur Verfügung gestellt wurde. Außerdem soll das Gerät über ein OLED Display zur Anzeige der Werte und eine RGB-LED verfügen, die in unterschiedlichen Farben leuchten, um die momentane Luftqualität schnell erfassen zu können.

Um Gase in der Luft nachzuweisen, gibt es die unterschiedlichsten Sensoren. In meinem Artikel Alkomat: LCD auf Prototype Shield und Ausgabe von Gassensor-Messdaten war ich schon darauf eingegangen, dass der MQ-135 Ammoniak, Benzine und Alkohol gut messen kann. Und der MQ-3 speziell für Alkohole geeignet ist - warum ich auch in dem genannten Artikel ein Alkohol-Messgerät damit entwickelt habe.

Und es gibt noch weitere Gas-Sensoren, etwa für den Nachweis von Erdgas, Kohlenmonoxid, oder Kohlendioxid. Diese reagieren aber alle auf bestimmte Gase.

Heute soll es aber über sehr kleine Partikel im Mikrometerbereich gehen, auch Feinstaub genannt, auf englisch "particulate matter", die wir in der Luft messen wollen.

Der Panasonic SN-GCJA5 Feinstaub-Sensor kann Partikelgrößen von 500 Nanometer (nm), sowie 1.0, 2.5, 5.0, 7.5 und 10 Mikrometer (µm) registrieren und zählen und daraus Werte in der Maßeinheit Mikrogramm pro Kubikmeter (µg/m3) berechnen und zwar für die Partikelgröße 1.0, 2.5 und 10.0 µm. Diese Partikelgrößen sind auch im Bezug auf die Gesundheit interessant und es gibt dafür festgelegte Grenzwerte.

Was ist Feinstaub eigentlich?

Das hört sich jetzt alles ziemlich theoretisch an und Dinge in Nanometer oder Mikrometergröße kann man sich auch schlecht vorstellen. Darum hier eine anschaulichere Grafik:



Als Anwendungsbeispiel für die Partikelgröße: In der Corona-Pandemie hat bestimmt jeder einmal den Begriff "FFP2-Maske" verwendet. Doch was bedeutet diese "FFP"-Spezifikation überhaupt?

Zuerst einmal: "FFP" bedeutet "Filtering Face Piece", auf deutsch also "Filternder Gesichts-Aufsatz".

Damit eine Atemschutzmaske das CE-Kennzeichen sowie die Kennzeichnung FFP1, FFP2 oder FFP3 tragen darf, muss sie die Prüfvorgaben der Europäischen Norm (EN) 149:2001+A1:2009 (entspricht DIN EN 149:2009-08) erfüllen. Dort wird unter anderem die Filterleistung und der Dichtsitz der Maske definiert und Angabe für entsprechende Prüfungen gemacht.

Unter anderem gibt es dort einen Abschnitt zu den Prüfbedingungen mit genormten Dolomit-Staub DRB 4/15, der Partikel in den Größe 0.7 bis 12 µm beinhaltet.

Für FFP-Maskes gilt als Grenz-Partikelgröße 0.6 µm, sprich: alles bis 0.6 µm (und natürlich darüber hinaus) wird durch die Maske zu einem bestimmten Prozentsatz blockiert. Dieser Prozentsatz liegt bei FFP1 bei 80%, bei FFP2 bei 94% und bei FFP3 bei 99% (7).

Die während Corona gebräuchlichen FFP2-Masken filtern also (bei richtigem Sitz) mindestens 94% der in der Luft befindlichen Partikel bis zu einer Größe von 0,6 µm aus. FFP2 schützt somit vor Aerosolen, Nebel und Rauch und eben auch gegen das Aerosol, dass bei Niesen entsteht und Grippe- und Covid-19-Viren übertragen kann.

Aber auch bei der Arbeit sind FFP2-Masken sinnvoll, um vor Feinstaub zu schützen, etwas im Handwerk (schleifen, Lackieren) oder in der Metallindustrie oder im Bergbau.

Die Kategorie Feinstaub definiert sich also durch die Größe der Partikel in einem bestimmten Bereich. Reine Gase gehören nicht dazu und können mit dem Feinstaubsensor nicht detektiert werden (es sei denn, es handelt sich dabei um Aerosole oder verunreinigte Gase). Dafür gibt es wie oben schon erwähnt spezielle Sensoren.

Mit dem Panasonic SN-GCJA5 Feinstaub-Sensor können wir Partikel erfassen, die in Tabakrauch, Smog, Asche und dem Rauch von brennendem Öl vorkommen, als auch Allergene von Katzen und Hausstaubmilben, Bakterien, Schimmel und Pollen.

Wie entsteht Feinstaub?

Der meiste Feinstaub entsteht durch das Verbrennen und Heizen (also durch das, was oben aus dem Schornstein kommt), durch Kraftfahrzeuge durch Reifenabrieb und Abrieb von Bremsen sowie durch Ausstoßen von Verbrennungsrückständen über den Auspuff, was aber weitestgehend durch im KFz eingebaute Katalysatoren kompensiert werden sollte.

Außerdem entsteht Feinstaub durch das Umfüllen von Schüttgütern, wobei viel Staub ausgewirbelt wird. Und: es wird alten Laserdruckern nachgesagt, eine Feinstaubbelastung zu generieren, indem sie unverbrauchten Toner in die Luft pusten.

Es gibt aber auch sogenanten "sekundären" Feinstaub. Dieser entsteht durch chemische Reaktionen von z. B. Schwefel- und Stickoxide und Ammoniak. Letztere kommen zum Beispiel in der Tierhaltung vor.

Was bedeuten die Partikelgrößen für die Gesundheit?

Feinstaub wird in grobe Partikel (Durchmesser größer als 10 µm), feine Partikel (Durchmesser zwischen als 0.1 µm und 10 µm) und ultrafeine Partikel (Durchmesser kleiner 0.1 µm) klassifiziert. Geläufig ist, die Partikelgröße abzukürzen durch "PM" für "Particulate Matter" plus die Partikelgröße in Mikrometern.

PM10 können beim Menschen in die Nasenhöhle, PM2.5 bis in die Bronchien und Lungenbläschen und ultrafeine Partikel bis in das Lungengewebe und sogar in den Blutkreislauf eindringen. (1)

Dadurch können Schleimhautreizungen, lokale Entzündungen, eine erhöhte Neigung zu Thrombose und sogar eine Störung des regelmäßigen Herzschlages hervorgerufen werden.

Grenzwerte

Aus diesem Grund hat die EU Grenzwerte festgelegt, die durch die Verantwortlichen (Arbeitgeber, Gemeinden, Verkehrsbetreiber, Hausbesitzer etc.) eingehalten werden müssen. Diese Grenzwerte betragen (1):

Messen der Feinstaubwerte

Für die Feinstaubwerte draußen hat der Staat in Deutschland über 400 Luftmessstationen aufgestellt, die man mittels einer App des Umweltbundesamtes bequem abfragen kann. Dort werden außer dem auch noch Ozon und Stickoxide angezeigt.


Mit einer Farbskala gewährt die App einen schnellen Überblick. Die Farbskala beginnt bei cyan (saubere Luft), geht über grün, gelb, orange bis hin zu dunkelrot (sehr schlechte Luft).

Allerdings beinhalten nicht alle Messtationen auch einen PM10-Sensor und verfügen deswegen über keine Feinstaubdaten.

Auf einer Karte, die man übrigens auch im Web aufrufen kann, kann man seine nächste Station auswählen und sich die betreffenden Werte anzeigen lassen.

Natürlich muss man den Standort der Messstation berücksichtigen. Ob diese direkt an einer viel befahrenen Straße oder eher ländlich auf dem Dorf steht, macht natürlich einen Riesen-Unterschied. Aber so kann man schon einmal einen Eindruck über die Feinstaubbelastung im Freien machen.

Interessant ist zum Beispiel, wie sehr doch die Sylvester-Knallerei die Luft belastet. Ist die PM10-Belastung im Winter normalerweise vielieicht so um die 5 µg/m3, so waren es an Silvester/Neujahr 2020/2021 im Tagesmittelwert für PM10 in Städten oft über 250 µg/m3 gewesen (2). Das ist bestimmt nicht mehr gesund.

Das ist draussen. Doch drinnen hat jeder Raum sein eigenes Mikroklima. Da helfen uns die Messstationen draußen nichts. Hier wollen wir vielleicht überprüfen, ob der Laserdrucker Feinstaub rauspustet, oder ob der Arbeitsplatz staubfrei genug ist, um den Grenzwerten zu genügen.

Und hierzu können wir den SN-GCJA5 Sensor sehr gut gebrauchen.

Doch, wie funktioniert der eigentlich?

Funktionsweise des SN-GCJA5 Sensors

Kurz umrissen funktioniert der Sensor so: Der Sensor erfasst Partikel in der Luft durch eine optische Methode. Der Sensor nutzt eine Laserdiode (LD) als Licht emittierendes Bauteil und eine Fotodiode (PD) als Licht empfangendes Bauteil. Der Laser im Sensor emittiert Licht in die Luft im Erfassungsbereich. Die Photodiode im Sensor erfasst das Streulicht, das korreliert mit den tatsächlichen Schwebeteilchen in der Luft. Ein Mikrocomputer (MCU) im Sensor analysiert das Wellenprofil des Lasers durch einen Algorithmus und gibt dann eine konvertierte Massendichte (µg/m3) über die I2C- & UART-Schnittstelle aus.

Würde man den Sensor auseinander nehmen, so würde es intern darin so aussehen:


(Quelle: Panasonic Werbevideo auf Youtube)



Im Inneren des Sensors befindet sich ein kleiner Lüfter. Dieser saugt bei einer Messung die Umgebungsluft ins Innere und transportiert sie durch einen Laserstrahl.

Die kleinen Partikel, die mit der Luft mittransportiert werden, sind dem Laserstrahl im Weg. An den Partikeln wird das Licht abgelenkt bzw. reflektiert. Eine Photodiode, die sich abseits des Laserstrahls befindet, fängt dieses abgelenkte Licht auf und kann es detektieren. Dabei kann der Sensor Partikel mit einer Partikelgröße von 0.3 µm und mehr detektieren.

Das entsprechende Verfahren nennt sich "dynamic light scattering" oder zu deutsch "Laserbeugungs-Partikelgrößenanalyse" (5) (6). Mit speziellen Algorithmen kann man so die in der angesaugten Luft befindlichen Partikel in der Größe messen und zählen.

Der SN-GCJA5 Sensor zählt die Partikelgrößen PC0.5, PC1.0, PC2.5, PC5.0, PC7.5 und PC10, die er auch über I2C ausgeben kann. Und errechnet daraus die Partikelmenge in µg/m3 für die Größe PM1.0 PM2.5 und PM10, die man noch einmal gesondert über I2C abfragen kann.

Neben I2C verfügt der SN-GCJA5 Sensor über einen seriellen UART-Port, der die Daten über eine serielle Schnittstellen ausgibt. Hier müsste man dann einfach nur einen FTDI-Adapter anschließen, um am PC an die Daten zu kommen.

Anschlüsse des SN-GCJA5 Sensors




Zum Glück kam der Sensor zusammen mit dem dazu gehörigen Kabel, einem 5 poligen JST SM05B-GHS-TB-Kabel, dass man wohl nicht so einfach daheim rumliegen haben dürfte und erst bestellen müsste. Allerdings war das andere Ende der Leitungen einfach nur abgeknippst. Also ohne Jumperkabel oder Stecker. Deshalb musste erst einmal ein paar Dupont-Kabel anlöten.

Dabei habe ich auch gleich mal ein paar mehr Kabelfarben verwendet. Nennt mich pedantisch, aber ich habe mein GND gerne blau oder schwarz und mein Spannungsversorgung gerne rot. Beim Kabel ist Pin1 links, wenn die Kabelarretierung oben ist.

Das Pinout und meine gewählten Kabelfarben sind:
Pin1 braun 3.3V UART TX für serielle Ausgabe Pin2 grau 3.3V SDA für I2C Pin3 weiß 3.3V SCL für I2C Pin4 schwarz GND Pin5 rot 5V Power for Fan
Zu beachten ist, dass der Lüfter gerne 5 Volt an Pin 5 hätte, die anderen Pins aber nur 3.3 Volt vertragen. Zum Glück bietet das ausgewählte Board, ein ESP8266 D1 Mini beide Spannungen an.

Der Aufbau auf dem Breadboard

Die erste Schaltung baue ich wie gewöhnlich auf einem Breadboard auf. Da kann man dann noch schnell mal eine Komponente austauschen oder hinzufügen, bis man mit dem Umfang der Schaltung zufrieden ist.



Links befindet sich der ESP8266 D1 mini. Wer mit dem noch nicht vertraut ist, kann sich eine Einführung von mir durchlesen.

Oben an das Breadboard habe ich den Feinstaubsensor mit Klebeknete befestigt. Der Lufteinlass zeigt dabei nach rechts. Dort ist also unsere Test-Zone. das UART-Kabel habe ich nicht angeschlossen. Es hängt in der Luft. UART brauchen wir heute nicht.

Rechts neben dem D1 Mini befindet sich das OLED-Display. Auch hierzu gibt es von mir eine Einführung.

Das OLED ist per I2C angebunden. Ratet mal, auch zum I2C-Protokoll gibt es eine Anleitung von mir.

Da unser Feinstaubsensor auch I2C spricht und man bei I2C die Geräte parallel schalten kann, können wir unseren I2C-Bus vom OLED einfach zum Feinstaubsensor weiterführen.

Dann gibt es rechts noch eine RGB-LED, mit der man so ziemlich jede Farbe anzeigen lassen kann. Die soll für einen schnellen Blick aus der Ferne dienen: blau ist reine Luft, dann geht es über grün (sauber) über gelb und orange zu rot (auf die Dauer nicht gesund).

Wer mehr über RGB-LEDs erfahren will, liest einfach meinen Artikel über RGB-LEDs. Da steht alles Wissenswerte drin.

Die RGB-LED ist über einen Vorwiderstand von 100 Ohm angeschlossen. Den habe ich mit meinem Vorwiderstandsrechner ausgerechnet. Und ich war ein bisschen faul und habe nur einen einzigen Widerstand für alle drei Farben über common cathode (gemeinsames GND) verwendet. Das muss ich dann wieder durch entsprechende Farbwerte im Source-Code ausgleichen, damit die Farben wieder stimmen, denn nicht alle Farben leuchten gleich hell.

Und dann habe ich mir gedacht, wäre es schön, zwischen einer Anzeige der absoluten Werte und einem Kurven-Diagramm umschalten zu können, die die letzten Messungen in einer Kurve anzeigt. Dazu braucht es natürlich einen Taster. Wie Taster funktionieren, das könnt ihr hier nachlesen. Als Pullup-Widerstand habe ich hier 10 Kiloohm gewählt.

Die Verdrahtung sieht folgendermaßen aus:
D1 Mini Komponenten D0 -> Taster D1 I2C SCL -> grau -> OLED SCL -> weiß -> SN-GCJA5 SCL (Pin 3) D2 I2C SDA -> orange -> OLED SDA -> grau -> SN-GCJA5 SDA (Pin 2) D5 -> RGB-LED Rot D6 -> RGB-LED Grün D7 -> RGB-LED Blau GND -> SN-GCJA5 GND (Pin 4) 5V -> SN-GCJA5 GND (Pin 5) 3.3V -> OLED VCC 3.3V -> 10 kOhm -> Taster GND -> Taster GND -> 100 Ohm -> RGB-LED cGND
Damit ist soweit alles angeschlossen. Fehlt uns noch die Firmware zur Ansteuerung.

Den SN-GCJA5 Sensor über I2C abfragen


Ich möchte den Sensor nicht über UART, sondern über I2C abfragen, weil ich so direkt auf die Einzeldaten über die unterschiedlichen I2C-Register zugreifen kann.

Ich hatte mich schon mit unterschiedlichen I2C-Befehlen im Datenblatt vertraut gemacht, als ich die Idee gekommen bin, doch mal zu schauen, ob da nicht schon jemand vor mir was programmiert und zur Verfügung gestellt hat.

Und für wahr: Sparkfun hat schon eine fertige Library veröffentlicht, die SparkFun Particle Sensor SN-GCJA5 Arduino Library. Auch wenn die I2C-Befehle des Sensors einfach zu verstehen sind, nimmt mir das trotzdem eine Menge Fleißarbeit ab.

Die Abfrage der anderen Komponenten (OLED, RGB-LED, Taster) kaue ich hier nicht noch einmal durch. Dazu könnt ihr in meinen bereits veröffentlichten Artikeln nachlesen.

Der Beispielcode der Sparkfun Library bringt wie gewohnt auch Beispielprogramme mit, die alle I2C-Befehle beinhalten.

Die erste Firmware-Version des Luftqualitätssensors

Es gibt zwei Typen von I2C-Befehlen (bzw. Registern):


Einmal eine initiale Zustandsabfrage, ob mit dem Sensor alles in Ordnung ist. Der gibt einem dann "alles okay" oder "eine (oder mehrere) Komponenten abnormal".

Die internen Komponenten des SN-GCJA5 sind: Photo Diode, Laser Diode und Lüfter.

Laut Datenblatt braucht der Sensor 8 Sekunden lang Vorlauf. Den nutze ich zum Anzeigen eines Logos und gleichzeitig zum Testen der RGB-LED. Die geht die Farben blau (rein), grün (sauber), gelb (noch okay), orange (nicht gut) und rot (für schlechte Luft) durch, damit man die evtl. im Sourcecode kalibrieren kann. Eventuell will ja auch jemand ganz andere Farben.

Danach wird dann das Statusregister abgefragt und angezeigt. Eigentlich ist bei mir immer alles normal, nur manchmal habe ich beim Lüfter ein "abnormal mit Software-Korrektur". Die Messwerte stimmen aber trotzdem (bzw. sind plausibel).


Und dann gibt es noch die laufenden Abfragen, die man nach 8 Sekunden machen kann. Hier kann man alle Register abklappern und die Werte für PC0.5, PC1.0, PC2.5, PC5.0, PC7.5 und PC10, sowie PM1.0 PM2.5 und PM10 abfragen.

Am wichtigsten sind die PM-Werte, die aus den PC-Werten von der MCU im Sensor errechnet werden. Auf dem OLED werden die 3 Werte für PM1, PM2.5 und PM10 in µg/m3 angezeigt.

Aus diesen drei PM-Werten errechnet die Firmware einen Mittelwert, der dann die Farbe der RGB-LED bestimmt und den Text neben dem PM10-Eintrag: Luftqualität LED-Farbe PM-Mittelwert rein blau 0 ... 5 µg/m3 sauber grün 5 ... 15 µg/m3 noch okay gelb 15 ... 45 µg/m3 schlecht orange 45 ... 135 µg/m3 übel rot > 135 µg/m3 Die Wertebereiche können über Define-Statements im Source-Code angepasst werden.

In der obigen Einstellungen wird ab orange der Grenzwert überschritten und man sollte langfristig etwas dagegen tun und bei rot sollte man kurzfristig eingreifen, das heißt lüften oder den Raum verlassen. Weil nicht so viel Platz auf dem OLED ist, sind die PC-Werte ohne Partikelgröße von links nach rechts nacheinander aufgeführt. Diese sind die sechs Werte PC0.5, PC1.0, PC2.5, PC5.0, PC7.5 und PC10.

Hier kann sich der Kenner noch einmal genauer über die vorliegenden Partikelgrößen, die ja für unterschiedliche Gesundheitsrisiken stehen, informieren.

Die Leistungsaufnahme des Gerätes liegt bei ca. 70 bis 80 mA, ist also trotz Display und RGB-LED und Sensor recht genügsam. Damit sollte die Schaltung selbst an einer Powerbank lange durchhalten.

Der Taster ist dazu da, um a) schnell eine Messung zu triggern (2x drücken) und b) zwischen der Werteanzeige und der Verlaufskurve umzuschalten. In der Verlaufskurvenansicht wird eine Kurve über die Messwerte gezeichnet, die 128 Werte beinhaltet. Die Anzeige ist logarithmisch, damit auch größere Werte auf der Anzeige Platz finden und damit auch Änderungen in den häufigeren, kleineren Werten sichtbar bleiben. Sind 128 Werte gezeichnet, beginnt die Anzeige wieder links von vorne und zeichnet eine neue Kurve.

Über READ_WAIT im Quelltext kann man den Messintervall in Millisekunden einstellen. Will man schnelle Wertewechsel sehen, so nimmt man hier nur 1000 oder 2000 ms. Man kann den Messintervall aber auch auf 60'000 ms für eine Minute stellen und hat dann in der Kurvenansicht den Verlauf der letzten 2 Stunden, zum Beispiel, um zu prüfen, ob etwas vorgefallen ist.

Selbstredend muss der Lufteinlass des Sensors frei sein und darf nicht abgedeckt sein. Außerdem wird im Datenblatt darauf hinwiesen, dass der Sensor nicht im Wind stehen soll, wahrscheinlich, weil sonst die Lüfterdrehzahl beeinflusst wird und die Messergebnisse verfälschen kann.

Source-Code

Wie immer ist der Source-Code für den Luftqualitätsmonitor gut dokumentiert. Er teilt sich in zwei Teile.

Der erste Teil "Panasonic-GCJA5-Feinstaubsensor-OLED.ino" enthält den eigentliche Programmablauf.

Der zweite Teil "coollogo81x64.h" defniert lediglich für das Logo in der Einschaltmeldung und ist für die Funktion des Sensors nicht weiter wichtig. Lasst ihn bitte trotzdem drin, damit man später noch weiß, dass man unter c-o-o-l.de die Dokumentation findet.

Panasonic-GCJA5-Feinstaubsensor-OLED.ino (klicken, um diesen Abschnitt aufzuklappen)
//////////////////////////////////////////////////////// // (C) 2023 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/esp8266-esp32/ // //////////////////////////////////////////////////////// // Dokumentation: https://cool-web.de/esp8266-esp32/feinstaub-sensor-panasonic-sn-gcja5-laser-partikel-particulate-matter-oled-rgb-led-taster.htm #define PIN_SCL D1 // I2C-Bus für OLED und SN-GCJA5-Feinstaubsensor #define PIN_SDA D2 #define PIN_RGB_R D5 // Farben für die RGB-LED #define PIN_RGB_G D6 #define PIN_RGB_B D7 #define PIN_KEY D0 #define INIT_WAIT 8000 // Pause vor der ersten Messung in ms, min. 8000 lt. Datenblatt, Status wird derweil angezeigt // genaue Werte nach weiteren 20 Sekunden #define READ_WAIT 2000 // Pause in ms zwischen Messungen, mindestens 1000 ms, was eine Messung dauert // seltener messen erhöht Lebensdauer #define PM_ERROR 4294000 // Werte hierüber werden als Messfehler interpretiert und ignoriert #define PC_ERROR 65500 // Werte hierüber werden als Messfehler interpretiert und ignoriert // RGB-Werte für die RGB-LED #define blau 0,0,255 #define gruen 0,32,0 #define gelb 128,64,0 #define orange 255,32,0 #define rot 255,0,0 // Grenzwerte (Mittel aus PM 1/2.5/10) in µg/m^3 ergeben welche LED-Farbe #define gw_blau 5 // bis 5 // rein #define gw_gruen 15 // 5 bis 15 usw. // sauber #define gw_gelb 45 // noch okay #define gw_orange 135 // schlecht // Grenzwert-Überschreitung // alles drüber ist #define gw_rot // übel #include <Arduino.h> #include <Wire.h> #include <SSD1306Wire.h> // für OLED https://github.com/ThingPulse/esp8266-oled-ssd1306 // #include "fontArimo30.h" // größerer Font zur Anzeige der Uhrzeit // Available default fonts: ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24 #include "coollogo81x64.h" // Das c-o-o-l Logo für Einschaltmeldung #include "SparkFun_Particle_Sensor_SN-GCJA5_Arduino_Library.h" // http://librarymanager/All#SparkFun_Particle_SN-GCJA5 // https://github.com/sparkfun/SparkFun_Particle_Sensor_SN-GCJA5_Arduino_Library // Feinstaubsensor Objekt ableiten SFE_PARTICLE_SENSOR Feinstaubsensor; // Initialize the OLED displayusing SSD1306Wire SSD1306Wire oled(0x3c, PIN_SDA, PIN_SCL); // ADDRESS, SDA, SCL int DisplayMode = 1; // 1= Zahlen zeigen, 2 = Diagramm zeigen, umschalten mit Taster int DisplayX=0; // momentane Position für Ausgabe im Diagramm int DisplayY=64; // ganz unten ist Null void setup(void) { pinMode(LED_BUILTIN, OUTPUT); pinMode (PIN_RGB_R, OUTPUT); pinMode (PIN_RGB_G, OUTPUT); pinMode (PIN_RGB_B, OUTPUT); pinMode (PIN_KEY, INPUT_PULLUP); digitalWrite(LED_BUILTIN, HIGH); // LED erst einmal aus Serial.begin(115200); // wird nur für evtl. Debug-Ausgaben benötigt Wire.begin(); if (!oled.init()) errorStop ("OLED not found!"); oled.flipScreenVertically(); // falls anders herum eingebaut oled.setContrast(10); // kein voller Kontrast: Burn-Out (einbrennen) verhindern // Einschaltmeldung oled.clear(); oled.drawXbm(24, 0, 81, 64, CoolLogo); oled.display(); // Testen der RGB-LED int del=2; for (int i=0; i<256; i++) { ledRGB(0,0,i); // blau delay(del); } for (int i=0; i<256; i++) { ledRGB(0,i,0); // grün delay(del); } for (int i=0; i<256; i++) { ledRGB(i,0,0); // rot delay(del); } ledRGB(0,0,0); // aus delay(500); del = 1000; // Test RGB-LED-Farben (ggf. in defines oben einstellen) ledRGB(blau); delay (del); ledRGB(gruen); delay (del); ledRGB(gelb); delay (del); ledRGB(orange); delay (del); ledRGB(rot); delay (del); ledRGB(0,0,0); // aus Serial.println(F("Program started.")); oled.clear(); oled.setTextAlignment(TEXT_ALIGN_LEFT); oled.setFont(ArialMT_Plain_16); delay(100); if (!Feinstaubsensor.begin()) errorStop ("SN-GCJA5 not found!"); Serial.println(F("Sensor started.")); } void loop(void) { char tmp[100]; int x2=30; // OLED-Positionen int x3=70; // Statusanzeige für 8 Sekunden (Startup-Zeit Sensor) showSensorStatus(); delay (INIT_WAIT); while(1) { digitalWrite(LED_BUILTIN, LOW); readAndShowSensorValues(); digitalWrite(LED_BUILTIN, HIGH); if (checkKeyPressed()) { DisplayMode++; if (DisplayMode > 2) DisplayMode=1; Serial.print("DisplayMode now "); Serial.println(DisplayMode); oled.clear(); oled.display(); DisplayX=0; DisplayY=64; } } } bool checkKeyPressed() { for (int i=0; i < READ_WAIT/10; i++) { if (digitalRead(PIN_KEY) == LOW) { while (digitalRead(PIN_KEY) == LOW) delay (10); // auf loslassen warten return true; } delay (10); } return false; } void ledRGB(byte R, byte G, byte B) { analogWrite (PIN_RGB_R, R); analogWrite (PIN_RGB_G, G); analogWrite (PIN_RGB_B, B); } void showSensorStatus(){ int x2=55; Serial.println(F("Checking sensor status...")); Serial.print(F("Common sensor status: ")); byte sensorStatus = Feinstaubsensor.getStatusSensors(); if (sensorStatus == 0) Serial.println(F("All sensors good")); else if (sensorStatus == 1) Serial.println(F("One sensor or fan abnormal")); else if (sensorStatus == 2) Serial.println(F("Two sensors or fan abnormal")); else if (sensorStatus == 3) Serial.println(F("Both sensors and fan abnormal")); // Ausgabe der Werte auf dem OLED oled.clear(); byte statusPD = Feinstaubsensor.getStatusPD(); Serial.print(F("Photo diode: ")); oled.drawString(0, 0, F("Photo:")); if (statusPD == 0) { Serial.println(F("Normal")); oled.drawString(x2, 0, F("normal")); } else if (statusPD == 1) { Serial.println(F("Normal w/ software correction")); oled.drawString(x2, 0, F("normal w/ soft-corr.")); } else if (statusPD == 2) { Serial.println(F("Abnormal, loss of function")); oled.drawString(x2, 0, F("abnormal w/ loss")); } else if (statusPD == 3) { Serial.println(F("Abnormal, with software correction")); oled.drawString(x2, 0, F("abnormal w/ soft-corr.")); } byte statusLD = Feinstaubsensor.getStatusLD(); Serial.print(F("Laser diode: ")); oled.drawString(0, 20, F("Laser:")); if (statusLD == 0) { Serial.println(F("Normal")); oled.drawString(x2, 20, F("normal")); } else if (statusLD == 1) { Serial.println(F("Normal w/ software correction")); oled.drawString(x2, 20, F("norm. w/ soft-corr.")); } else if (statusLD == 2) { Serial.println(F("Abnormal, loss of function")); oled.drawString(x2, 20, F("abnorm. w/ loss")); }else if (statusLD == 3) { Serial.println(F("Abnormal, with software correction")); oled.drawString(x2, 20, F("abnorm. w/ soft-corr.")); } byte statusFan = Feinstaubsensor.getStatusFan(); Serial.print(F("Fan: ")); oled.drawString(0, 40, F("Fan:")); if (statusFan == 0) { Serial.println(F("Normal")); oled.drawString(x2, 40, F("normal")); } else if (statusFan == 1) { Serial.println(F("Normal w/ software correction")); oled.drawString(x2, 40, F("norm. w/ soft-corr.")); } else if (statusFan == 2) { Serial.println(F("In calibration")); oled.drawString(x2, 40, F("in calibration")); } else if (statusFan == 3) { Serial.println(F("Abnormal, out of control")); oled.drawString(x2, 40, F("abnorm., out of ctrl.")); } oled.display(); } void readAndShowSensorValues(){ char tmp[100]; Serial.print(F("Read sensor data: pm1.0:")); float pm1_0 = Feinstaubsensor.getPM1_0(); if (pm1_0 > PM_ERROR) { // Messfehler aussieben Serial.print (F("\nMessfehler PM 1.0 = ")); Serial.print (pm1_0); Serial.println (F(" ignoriert.")); pm1_0=0.0; } Serial.print(pm1_0, 2); //Print float with 2 decimals Serial.print(" pm2.5:"); float pm2_5 = Feinstaubsensor.getPM2_5(); Serial.print(pm2_5, 2); if (pm2_5 > PM_ERROR) { // Messfehler aussieben Serial.print (F("\nMessfehler PM 2.5 = ")); Serial.print (pm2_5); Serial.println (F(" ignoriert.")); pm2_5=0.0; } Serial.print(" pm10:"); float pm10 = Feinstaubsensor.getPM10(); if (pm10 > PM_ERROR) { // Messfehler aussieben Serial.print (F("\nMessfehler PM 10 = ")); Serial.print (pm10); Serial.println (F(" ignoriert.")); pm10=0.0; } Serial.print(pm10, 2); Serial.print(" pc0.5:"); unsigned int pc0_5 = Feinstaubsensor.getPC0_5(); Serial.print(pc0_5); Serial.print(" pc1.0:"); unsigned int pc1_0 = Feinstaubsensor.getPC1_0(); Serial.print(pc1_0); Serial.print(" pc2.5:"); unsigned int pc2_5 = Feinstaubsensor.getPC2_5(); Serial.print(pc2_5); Serial.print(" pc5.0:"); unsigned int pc5_0 = Feinstaubsensor.getPC5_0(); Serial.print(pc5_0); Serial.print(" pc7.5:"); unsigned int pc7_5 = Feinstaubsensor.getPC7_5(); Serial.print(pc7_5); Serial.print(" pc10:"); unsigned int pc10 = Feinstaubsensor.getPC10(); Serial.print(pc10); Serial.println(" µg/m^3"); // die RGB-LED-Farbe setzen float mix = (pm1_0+pm2_5+pm10) / 3.0; String bewertung = ""; if (mix <= gw_blau) { ledRGB(blau); bewertung = "rein" ; } else if (mix <= gw_gruen) { ledRGB(gruen); bewertung = "sauber"; } else if (mix <= gw_gelb) { ledRGB(gelb); bewertung = "noch ok"; } else if (mix <= gw_orange) { ledRGB(orange); bewertung = "schlecht"; } else { ledRGB(rot); bewertung = "übel"; } if (DisplayMode == 1) { // Anzeige der Werte oled.clear(); int x2=28; int x3=64; int x4=92; oled.drawString(0, 0, "M1:"); snprintf(tmp, sizeof(tmp), pm1_0 >= 100?"%0.0f":(pm1_0 >= 10?"%0.1f":"%0.2f"), pm1_0); oled.drawString(x2, 0, tmp); oled.drawString(x3, 0, "2.5:"); snprintf(tmp, sizeof(tmp), pm2_5 >= 100?"%0.0f":(pm2_5 >= 10?"%0.1f":"%0.2f"), pm2_5); oled.drawString(x4, 0, tmp); oled.drawString(0, 20, "10:"); snprintf(tmp, sizeof(tmp), pm10 >= 100?"%0.0f":(pm10 >= 10?"%0.1f":"%0.2f"), pm10); oled.drawString(x2, 20, tmp); oled.drawString(x3, 20, bewertung); snprintf(tmp, sizeof(tmp), "C: %d %d %d %d %d %d", pc0_5>PC_ERROR?-1:pc0_5, pc1_0>PC_ERROR?-1:pc1_0, pc2_5>PC_ERROR?-1:pc2_5, pc5_0>PC_ERROR?-1:pc5_0, pc7_5>PC_ERROR?-1:pc7_5, pc10>PC_ERROR?-1:pc10); oled.drawString(0, 40, tmp); oled.display(); } if (DisplayMode == 2) { // Diagramm anzeigen int x = DisplayX+1; if (x > 128) { DisplayX=0; DisplayY=64; x=0; oled.clear(); } // logarithmische Anzeige, damit auch kleinere Werte abgebildet werden if (mix > 1000) mix = 1000; int w = (int) ((log10(mix+1.))*21.+.5); int y = 63-w; Serial.print ("mix="); Serial.print (mix); Serial.print (", w="); Serial.print (w); Serial.print (", y="); Serial.println (y); oled.drawLine (DisplayX, DisplayY, x, y); oled.display(); DisplayX=x; DisplayY=y; } } void errorStop(char *msg) { // bei nicht behebbarem Fehler Meldung anzeigen und stoppen / schnell blinken Serial.println(msg); oled.clear(); oled.drawString(0, 0, msg); oled.display(); while (1) { digitalWrite(LED_BUILTIN, LOW); delay(100); digitalWrite(LED_BUILTIN, HIGH); delay(100); } }

coollogo81x64.h (klicken, um diesen Abschnitt aufzuklappen)
const uint8_t CoolLogo[] PROGMEM = { /* 81x64 */ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xf8, 0xc0, 0x00, 0x03, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xf8, 0x83, 0x81, 0x01, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xc3, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x18, 0x46, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x30, 0x76, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0x01, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x80, 0xff, 0x03, 0x00, 0xc0, 0x0f, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfe, 0x00, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0xfe, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0xfe, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0xfe, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xfe, 0x00, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xfe, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xfe, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xfe, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfe, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfe, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xfe, 0x00, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x80, 0x03, 0x00, 0x30, 0xfe, 0x00, 0x06, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0x30, 0xfe, 0x00, 0x06, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x3c, 0x00, 0xe0, 0xff, 0x00, 0x02, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0xe0, 0xff, 0x00, 0x03, 0x00, 0x18, 0x00, 0x00, 0x00, 0xc0, 0x01, 0xe0, 0xff, 0x00, 0x03, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0xff, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x06, 0xe0, 0xff, 0xf0, 0x07, 0x00, 0xe0, 0x0f, 0x00, 0xc0, 0x3f, 0x00, 0xe0, 0xff, 0xf8, 0x0f, 0x00, 0xf8, 0x1f, 0x00, 0xe0, 0x7f, 0x00, 0xe0, 0xff, 0xfc, 0x1f, 0x00, 0xfc, 0x3f, 0x00, 0xf0, 0xff, 0x00, 0xe0, 0xff, 0x3e, 0x3e, 0x00, 0x3c, 0x3c, 0x00, 0xf0, 0xf0, 0x00, 0xe0, 0xff, 0x1e, 0x3c, 0x00, 0x1e, 0x78, 0x00, 0x78, 0xe0, 0x01, 0xe0, 0xff, 0x1e, 0x00, 0xff, 0x1e, 0x78, 0xfc, 0x79, 0xe0, 0xf9, 0xe7, 0xff, 0x1e, 0x00, 0xff, 0x1e, 0x78, 0xfc, 0x79, 0xe0, 0xf9, 0xe7, 0xff, 0x1e, 0x00, 0xff, 0x1e, 0x78, 0xfc, 0x79, 0xe0, 0xf9, 0xe7, 0xff, 0x1e, 0x00, 0x7f, 0x1e, 0x78, 0xfc, 0x79, 0xe0, 0xf9, 0xe7, 0xff, 0x1e, 0x3c, 0x00, 0x1e, 0x78, 0x00, 0x78, 0xe0, 0x01, 0xe0, 0xff, 0x3e, 0x3e, 0x00, 0x3c, 0x3c, 0x00, 0xf0, 0xf0, 0x00, 0xe0, 0xff, 0xfc, 0x1f, 0x00, 0xfc, 0x3f, 0x00, 0xf0, 0xff, 0x00, 0xe0, 0xff, 0xf8, 0x0f, 0x00, 0xf8, 0x1f, 0x00, 0xe0, 0x7f, 0x00, 0xe0, 0xff, 0xf0, 0x07, 0x00, 0xe0, 0x0f, 0x00, 0xc0, 0x3f, 0x00, 0xe0, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xfe, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfe, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xfe, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x18, 0xfe, 0x00, 0x06, 0x00, 0x00, 0x18, 0x00, 0x80, 0x01, 0x00, 0x18, 0xfe, 0x00, 0x04, 0x00, 0x00, 0x70, 0x00, 0xc0, 0x00, 0x00, 0x18, 0xfe, 0x00, 0x0c, 0x00, 0x00, 0xc0, 0x03, 0x60, 0x00, 0x00, 0x08, 0xfe, 0x00, 0x18, 0x00, 0x00, 0x00, 0x0f, 0x70, 0x00, 0x00, 0x0c, 0xfe, 0x00, 0x18, 0x00, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00, 0x06, 0xfe, 0x00, 0x70, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x07, 0xfe, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0xfe, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x00, 0xfe, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfe, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfe, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0xfe, 0x00, 0x00, 0x80, 0x3f, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0xfe};

Test und Demonstration

Jetzt bleibt eigentlich nur noch, den Aufbau zu testen und ein paar Experimente mit Rauch und Feinstaub anzustellen, um zu schauen, wie der Sensor und die Anzeige reagieren.

Das dokumentiert man natürlich am besten in einem Video. In dem ich auch nochmal ein paar einführende Worte verliere. Diejenigen, die diesen Artikel ausführlich gelesen haben, können den Anfang des Videos überspringen, das steht hier alles sehr viel ausführlicher. Und sorry, dass ich manchmal "Mikrometer" statt "Mikrogramm" im Video sage. Das ich da jeweils einen Fehler mache, sollte aber offensichtlich sein. Doch nun viel Spaß beim Video:



Und hier noch ein Video, wie ich die Schaltung vom Breadboard in einer gelötete Schaltung übertrage ein Gehäuse dafür designe und mit meinem 3D-Drucker ausdrucke und dann alles zusammenfüge. Inklusive einiger kleinerer Stolpersteine:



Quellen, Literaturverweise und weiterführende Links

(1) Das Umweltbundesamt zum Thema Feinstaub
(2) Das Umweltbundesamt zum Thema Feinstaub durch Silvesterfeuerwerk
(3) Fraunhofer: Partikel in der Innenraumluft
(4) Wikipedia zum Thema Feinstaub
(5) Wikipedia: Laserbeugungs-Partikelgrößenanalyse
(6) Wikipedia: Dynamic light scattering
(7) Uvex: Atemschutzmasken