Inhaltsverzeichnis

Klimamessbox - Außenbereich mit Feinstaubmessung

Sensor Pin (GPIO) Anschluss Besonderheit
MH-Z19B GPIO12/14 D6 / D5 Spannungsteiler für D6; SoftwareSerial
SDS011 GPIO13/15 D7 / D8 Spannungsteiler für D7 darf NICHT eingebaut werden, der SDS sendet bereits mit 3,3V Logik; SoftwareSerial
BMP180 GPIO4/5 D2 / D1 I²C (fest: SDA=SDA, SCL=SCL)
LDR ADC0 A0 analog
DHT22 GPIO3 RX Nur nach Upload einstecken!

Di-mini-Platine

ACHTUNG: Für den SDS011 KEINEN Spannungsteiler verwenden, er sendet bereits mit 3,3 V Logik

Sensorplatine

TESTCODE KLIMABOX BME, MHZ und SDS

BME280

#include <Wire.h>
#include <Adafruit_BME280.h>

#define BME280_ADDRESS 0x76    // ggf. 0x77 testen
Adafruit_BME280 bme;

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nBME280 Test");

  Wire.begin(4, 5);            // SDA = GPIO4, SCL = GPIO5

  if (!bme.begin(BME280_ADDRESS)) {
    Serial.println("BME280 nicht gefunden, Adresse oder Verkabelung prüfen!");
    while (1) delay(100);
  }
  Serial.println("BME280 gefunden.");
}

void loop() {
  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0; // hPa

  Serial.print("Temp: "); Serial.print(t); Serial.println(" °C");
  Serial.print("Feuchte: "); Serial.print(h); Serial.println(" %");
  Serial.print("Druck: "); Serial.print(p); Serial.println(" hPa");
  Serial.println("----");
  delay(2000);
}
18:11:23.194 -> ----
18:11:25.191 -> Temp: 22.28 °C
18:11:25.191 -> Feuchte: 35.97 %
18:11:25.191 -> Druck: 1008.70 hPa
18:11:25.191 -> ----

MHZ-19B

#include <SoftwareSerial.h>

SoftwareSerial mhzSerial(12, 14);  // RX = GPIO12, TX = GPIO14
byte cmdReadCO2[] = {0xFF, 0x01, 0x86, 0, 0, 0, 0, 0, 0x79};

bool readMHZ19B(int &ppm, int &tempC) {
  while (mhzSerial.available()) mhzSerial.read();

  mhzSerial.write(cmdReadCO2, 9);

  uint8_t resp[9];
  int i = 0;
  uint32_t start = millis();
  while (millis() - start < 200) {
    if (mhzSerial.available()) {
      resp[i++] = mhzSerial.read();
      if (i == 9) break;
    }
  }
  if (i != 9) return false;
  if (resp[0] != 0xFF || resp[1] != 0x86) return false;

  uint8_t sum = 0;
  for (int k = 1; k < 8; k++) sum += resp[k];
  sum = 0xFF - sum + 1;
  if (sum != resp[8]) return false;

  ppm   = resp[2] * 256 + resp[3];
  tempC = resp[4] - 40;
  return true;
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nMH-Z19B Test");

  mhzSerial.begin(9600);
  Serial.println("Sensor heizt auf (ca. 3 Minuten)...");
}

void loop() {
  int ppm, t;
  if (readMHZ19B(ppm, t)) {
    Serial.print("CO2: ");
    Serial.print(ppm);
    Serial.print(" ppm, T-Sensor: ");
    Serial.print(t);
    Serial.println(" °C");
  } else {
    Serial.println("Fehler beim Lesen (noch am Aufwärmen?)");
  }
  delay(2000);
}
18:10:07.806 -> CO2: 674 ppm, T-Sensor: 24 °C
18:10:09.844 -> CO2: 675 ppm, T-Sensor: 24 °C

SDS011

#include <SoftwareSerial.h>

SoftwareSerial sdsSerial(13, 15);  // RX = GPIO13, TX = GPIO15

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nSDS011 Test");

  sdsSerial.begin(9600);
}

bool readSDS011(float &pm25, float &pm10) {
  uint8_t buf[10];
  int idx = 0;
  uint32_t start = millis();

  while (millis() - start < 2000) {
    if (sdsSerial.available()) {
      uint8_t b = sdsSerial.read();
      if (idx == 0 && b != 0xAA) continue;
      buf[idx++] = b;
      if (idx == 10) {
        if (buf[0] == 0xAA && buf[1] == 0xC0 && buf[9] == 0xAB) {
          pm25 = (buf[2] + 256 * buf[3]) / 10.0;
          pm10 = (buf[4] + 256 * buf[5]) / 10.0;
          return true;
        } else {
          idx = 0;
        }
      }
    }
  }
  return false;
}

void loop() {
  float pm25, pm10;
  if (readSDS011(pm25, pm10)) {
    Serial.print("PM2.5: ");
    Serial.print(pm25);
    Serial.print(" µg/m³, PM10: ");
    Serial.print(pm10);
    Serial.println(" µg/m³");
  } else {
    Serial.println("Kein gültiges SDS011 Paket empfangen.");
  }
  delay(2000);
}
18:29:09.513 -> PM2.5: 4.60 µg/m³, PM10: 8.10 µg/m³
18:29:11.513 -> PM2.5: 4.60 µg/m³, PM10: 8.00 µg/m³

Alle drei Sensoren

#include <Wire.h>
#include <Adafruit_BME280.h>
#include <SoftwareSerial.h>

// BME280 an I2C: SDA = GPIO4 (D2), SCL = GPIO5 (D1)
#define BME280_ADDRESS 0x76
Adafruit_BME280 bme;

// MH-Z19B an GPIO12/14
SoftwareSerial mhzSerial(12, 14);  // RX = 12, TX = 14
byte cmdReadCO2[] = {0xFF, 0x01, 0x86, 0, 0, 0, 0, 0, 0x79};

// SDS011 an GPIO13/15
SoftwareSerial sdsSerial(13, 15);  // RX = 13, TX = 15

byte sds011_sleep[]  = {0xAA, 0xB4, 0x06, 0x01, 0x00,
                        0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00,
                        0xFF, 0xFF, 0x05, 0xAB};
byte sds011_wakeup[] = {0xAA, 0xB4, 0x06, 0x01, 0x01,
                        0x00, 0x00, 0x00, 0x00, 0x00,
                        0x00, 0x00, 0x00, 0x00,
                        0xFF, 0xFF, 0x06, 0xAB};

// ---- Hilfsfunktionen ----

bool readMHZ19B(int &ppm, int &tempC) {
  while (mhzSerial.available()) mhzSerial.read();
  mhzSerial.write(cmdReadCO2, 9);

  uint8_t resp[9];
  int i = 0;
  uint32_t start = millis();
  while (millis() - start < 200) {
    if (mhzSerial.available()) {
      resp[i++] = mhzSerial.read();
      if (i == 9) break;
    }
  }
  if (i != 9) return false;
  if (resp[0] != 0xFF || resp[1] != 0x86) return false;

  uint8_t sum = 0;
  for (int k = 1; k < 8; k++) sum += resp[k];
  sum = 0xFF - sum + 1;
  if (sum != resp[8]) return false;

  ppm   = resp[2] * 256 + resp[3];
  tempC = resp[4] - 40;
  return true;
}

bool readSDS011(float &pm25, float &pm10) {
  uint8_t buf[10];
  int idx = 0;
  uint32_t start = millis();

  while (millis() - start < 2000) {
    if (sdsSerial.available()) {
      uint8_t b = sdsSerial.read();
      if (idx == 0 && b != 0xAA) continue;
      buf[idx++] = b;
      if (idx == 10) {
        if (buf[0] == 0xAA && buf[1] == 0xC0 && buf[9] == 0xAB) {
          pm25 = (buf[2] + 256 * buf[3]) / 10.0;
          pm10 = (buf[4] + 256 * buf[5]) / 10.0;
          return true;
        } else {
          idx = 0;
        }
      }
    }
  }
  return false;
}

void setSDS011Sleep(bool sleep) {
  if (sleep) {
    sdsSerial.write(sds011_sleep, sizeof(sds011_sleep));
  } else {
    sdsSerial.write(sds011_wakeup, sizeof(sds011_wakeup));
  }
  delay(100);
}

// ---- Setup & Loop ----

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("\nGesamttest: BME280 + MH-Z19B + SDS011");

  // BME280
  Wire.begin(4, 5);  // SDA, SCL
  if (!bme.begin(BME280_ADDRESS)) {
    Serial.println("BME280 nicht gefunden!");
    while (1) delay(100);
  }
  Serial.println("BME280 OK.");

  // MH-Z19B
  mhzSerial.begin(9600);
  Serial.println("MH-Z19B UART gestartet (9600 Baud).");

  // SDS011
  sdsSerial.begin(9600);
  setSDS011Sleep(false);
  Serial.println("SDS011 aufgeweckt, Lüfter sollte laufen.");
}

void loop() {
  Serial.println("\n--- neuer Messzyklus ---");

  // BME280
  bme.takeForcedMeasurement();
  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0;

  Serial.print("BME280: ");
  Serial.print(t); Serial.print(" °C, ");
  Serial.print(h); Serial.print(" %, ");
  Serial.print(p); Serial.println(" hPa");

  // MH-Z19B
  int ppm, tco2;
  if (readMHZ19B(ppm, tco2)) {
    Serial.print("MH-Z19B: ");
    Serial.print(ppm); Serial.print(" ppm, ");
    Serial.print(tco2); Serial.println(" °C (Sensorsensor)");
  } else {
    Serial.println("MH-Z19B: kein gültiges Paket.");
  }

  // SDS011
  float pm25, pm10;
  if (readSDS011(pm25, pm10)) {
    Serial.print("SDS011: PM2.5=");
    Serial.print(pm25);
    Serial.print(" µg/m³, PM10=");
    Serial.print(pm10);
    Serial.println(" µg/m³");
  } else {
    Serial.println("SDS011: kein gültiges Paket.");
  }

  delay(10000);
}
18:33:44.196 -> --- neuer Messzyklus ---
18:33:44.196 -> BME280: 21.54 °C, 37.46 %, 1008.72 hPa
18:33:44.259 -> MH-Z19B: 673 ppm, 22 °C (Sensorsensor)
18:33:44.259 -> SDS011: PM2.5=4.20 µg/m³, PM10=7.20 µg/m³

Copyright (C) 2025 Biebl/Sankt-Ansgar-Schule Hamburg

"Klimamessstation für Schulen" von Martin Biebl/Sankt-Ansgar-Schule Hamburg,
lizenziert unter CC BY-SA 4.0
(https://creativecommons.org/licenses/by-sa/4.0/)
Diese Programme sind freie Software: Sie können es unter den
Bedingungen der GNU General Public License, wie von der Free
Software Foundation veröffentlicht, weitergeben und/oder
modifizieren, entweder gemäß Version 3 der Lizenz oder (nach
Ihrer Wahl) jeder späteren Version.

Die Veröffentlichung dieser Programme erfolgt in der Hoffnung,
dass es Ihnen von Nutzen sein wird, aber OHNE JEDE GEWÄHRLEISTUNG,
sogar ohne die implizite Gewährleistung der MARKTFÄHIGKEIT oder
EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. Details finden Sie in der
GNU General Public License.

Vollständiger Lizenztext: https://www.gnu.org/licenses/gpl-3.0.de.html