co2-Ampel + Lora-Bee

Hätte ich noch meine Sensebox würde ich da mal versuchen, über die eingebauten LEDs eine Art logging einzubauen (sorry für den PseudoCode):
LED1 an
If Sensor.dataavailable()
LED2 an
Sensor.getCO2()
LED2 aus

LED1 aus.

Vielleicht bleibt die Kiste ja immer im gleichen Call stecken, das wär doch was…

Da meine Ampel seit mindestens Sonntag ununterbrochen läuft, hier meine Erkenntnisse. Die Ampel hat keine anderen Hardware-Zusätze (wifi-bee,…), auch der Code „v1“ ist unverändert.
Jedenfalls ist der einzige Unterschied zu vorher - die Ampel hängt jetzt senkrecht an der Wand (vorher lag sie waagrecht bzw. lehnte schräg an der Wand). Evtl. ein Hardwareproblem?

Viel Erfolg bei der Fehlersuche!

  1. Meine mit delay(100) eingegrenzte CO2-Abfrage läuft jetzt seit 26 Stunden :wink:

  2. @swenp So etwas hatte ich mal ausprobiert. Ich hatte eine Ausgabe auf dem Display „Fehler1“, „Fehler2“ usw. nach jeder Zeile Code eingebaut, weil ich einfach keinen Fehler im Code finden konnte. Es blieb dann immer an verschiedenen Stellen hängen.

  3. @Stephan Danke für die Info. Ich habe mich schon gefragt, ob es Leute gibt, die auch lange Laufzeiten haben.

Ich hatte auch schon mal lange Laufzeiten. Mit delay(60000) und deaktivierter LED, um Daten mit der SD in der Nacht aufzuzeichnen. Da lief es auch 24 Stunden problemlos. Ich hatte die LED dann testweise deaktiviert, aber mit kürzeren delay-Zeiten hing es sich auch wieder auf. Mit so langen delay Zeiten wird die Schleife in 24 Stunden nur ein paarmal durchlaufen, so dass sich entweder noch nicht so viel Datenmüll im Speicher angesammelt hat (z. B. durch falsche Typumwandlung), oder es besteht halt nur eine geringe Wahrscheinlichkeit für falsch geleitete Ströme (oder so etwas in der Art).

An einen Hardwarefehler habe ich dann auch geglaubt, weil ich die Ampel immer auf dem Fahrrad durchrüttle und sie sich mit Originalprogramm aufgehangen hat. Aber in zwischen habe ich mir einen zweiten CO2-Sensor und MCU (und noch ein paar andere Sensoren und LED-Ring) bestellt, um die Ampel nicht immer transportieren zu müssen. Hier habe ich die gleichen Probleme. Daher liegt es vermutlich doch nicht an einem von mir verursachten Hardwarefehler.

@Jan: In der Interface Description von Sensirion steht folgender Hinweis zur Abfrage via I2C:

Note that the read header should be send with a delay of > 3ms following the write sequence.
Screenshot 2020-11-24 092733

Habt ihr das drin? Die Wahrscheinlichkeit eines Absturzes scheint ja mit der Häufigkeit der Abfragen zusammen zu hängen.

Tja, schlechte Nachrichten -> die Ampel hängt jetzt nach fast 3 Tagen wieder - scheint also doch nicht die Lage zu sei ;-)))

1 Like

Neu im Handbuch: „Wichtig ist, dass die Elektronen vom Sensor zur MCU bergab fließen.“ :sweat_smile:
Aber deine Lage ist immer noch erfolgreicher als alles, was ich probiert habe. Meine delay-Version läuft jetzt seit 40 Stunden.

gestern am späten Nachmittag lief meine noch - gegen 17:30 ~ 18:00 - also die ersten 24h geschafft … heute früh habe ich leider vergessen nachzusehen … zur Lage, schräg nach hinten gelehnt auf einem Lowboard im Wohnzimmer – mit dem original-Netzteil und dem original-Kabel, welche mitgeliefert wurden.

Ich habe 58 h, wer bietet mehr? :sunglasses:
Ich habe meine zweite Ampel eben mit einem delay(10) ausgestattet, und hoffe, dass sie trotz der kürzeren Zeit auch dauerhaft läuft. Mal sehen.

Nachtrag nach 20 min:
Eigentlich wollte ich einen Langzeittest über das Wochenende machen. Ich bin leider nicht bis zum Wochenende gekommen. Mit delay(10) hing es jetzt schon fest …

… 47 h bin ich jetzt würde ich sagen … läuft noch !
// edit: — und noch 12h dabei — heute früh um 5 Uhr lief er immer noch … stay tuned

Nach 94 Stunden und 5660 Sensorabfragen ist meine Ampel mit der delay(100)-Variante heute Nacht wieder eingefroren. :sob:
Es sieht so aus, als sei sie mitten beim Display-Zugriff hängen geblieben.


Vermutlich sollte aus einem „23 °C“ gerade ein „37 % Luftfeuchtigkeit“ werden.
Schade nur, dass sie auch komplett ohne Display hängenbleibt :upside_down_face:

so nu isses so weit - gestern Abend gegen 22 Uhr isser wohl auch bei mir wieder eingeschlafen – lief vom 24.11 bis 27.11 – gestern Abend ausgesteckt, wieder eingesteckt, heute früh war er schon wieder eingefroren … :frowning:

Nach über 90 Stunden Laufzeit dachte ich, dass ich mit meinem Delay-Ansatz auf dem richtigen Weg bin. Ich habe jetzt jeden Sensor- und Display-Kontakt mit delay(25) abgepuffert.
Also:

delay(25);
  if (airSensor.dataAvailable()) {
  delay(25);
  scd30_co2 = airSensor.getCO2();
}
delay(25);

und

delay(25);
  if (iaqSensor.run()) {
    delay(25);
      bmeTemperatur = iaqSensor.temperature;
      delay(25);
      bmeHumidity = iaqSensor.humidity;
      delay(25);

und

delay(25);
  beleuchtungsstaerke = tsl.getIlluminance();
  delay(25);

und

 delay(25);
  display.display(); 
  delay(25);

Leider hat sich das Programm schon wieder mehrfach festgefressen. Jedesmal sah es so aus, als sei es während der Erneuerung des Displays passiert.

nach dem letzten Einfrieren am Samstag Vormittag habe ich mir mein Lora-Sketch wieder draufkopiert - hat super geklappt, die Box lief dann für eine halbe Stunde etwa bis zum nächsten Freeze …

Ich habe schon die Erfahrung gemacht, dass das Display zum Abstürz führen kann. Zum Beispiel wenn man versucht zu oft in der Sekunde die Bildschirmanzeige zu aktualisieren.

Versucht mal die Anzeige auf dem Display nur jede Sekunde zu aktualisieren und nicht nach jedem loop() Durchlauf. Vielleicht hilft das!
Am besten auch einen non-blocking-delay benutzen wie hier beschrieben.

1 Like

@Thiemann96 – den Test habe ich auch gespannt nach deiner verlinkten Anleitung gemacht … die Ampel lief sage und schreibe 1,5 h — und hing dann wieder …

Danke, Erik @Thiemann96, für den Hinweis auf die alternative Programmierung. Konkret diese Library kannte ich noch nicht. Ich probiere sie demnächst einmal aus. Auf das Display greife ich nur alle 2 Sekunden zu, wenn ich einen anderen Messwert anzeigen lasse. Das habe ich mit einer millis-Abfrage realisiert. Anfangs hatte ich ein Programm komplett ohne Delay geschrieben, was aber zu einem schnellen Absturz geführt hat. Mit mehr bzw. längeren Delays konnte ich die Laufzeit verlängern. Jetzt habe ich sie wieder reduziert und nur noch die Zugriffe über die I2C-Schnittstelle mit einem delay(50) davor und dahinter versehen.
Zur Zeit läuft mein Programm seit Sonntag, was für meine Verhältnisse schon recht lang ist. :man_shrugging:t4: Mal sehen …

Hi Philipp,

würdest Du wohl Deinen Code mit uns teilen, dann hätten wir alle eine „stabile“ Basis von der aus wir das Problem weiter eingrenzen können. Ich komme mit meinen Delay Einstellungen und dem Pi Netzteil selten über 28 Std. Laufzeit bis das ganze wieder einfriert.

Danke und grüße

Sascha

Hallo Sascha @taylor-77 ,
gerne könnt ihr meinen Code haben, den ich gerade im Einsatz habe. Meine Zu-Hause-Ampel ist gestern wieder nach ca. 90 Stunden eingeschlafen. Vielleicht ist es auch nur Zufall, dass sie mit meinen Delay-Einstellungen 90 Stunden schafft, und der Fehler liegt völlig wo anders.

Eine CO2-Ampel (Schule-Ampel) ist der fertige Bausatz. Im Code habe ich noch die Möglichkeit für eine Datenspeicherung auf SD-Karte vorgesehen, die ich gerade aber in der anderen Ampel verbaut habe. Über den seitlichen Knopf kann man ein Menü aufrufen und Wählen zwischen der Anzeige eines Mittelwertes, der Anzeige des aktuellen Wertes, der Datenspeicherung (fast ohne Anzeige für die Nacht) und der Kalibrierung.

Die andere CO2-Ampel hat noch den BME680- und einen Lichtsensor. Außerdem verwende ich einen LED-Ring statt des einzelnen LEDs.

Viele Grüße
Philipp

Hier die Codes (Sorry, ich bin sowohl Anfänger, als auch aus der Übung (mit den Sachen, die ich mal konnte)).
Ampel-Bausatz bzw. Schule-Ampel:

#include <SPI.h>
#include <Wire.h>
#include <SD.h> //für SD-Karte
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "SenseBoxMCU.h"  //für SenseBos
#include "SparkFun_SCD30_Arduino_Library.h" //für Sensor
#include <Adafruit_NeoPixel.h>  //für LED

//Variablen für das Menü

int programm = 1; //über diese Variable wird die Programm gesteuert. Vorauswahl: Datenampel
const long MENUEVERZOEGERUNG = 2000; //Zeit bis ein Programm gestartet wird

int radius = 5;   //Rückgriff der Aktivitätenanzeige auf diese Variable
boolean wachsen = true; //Rückgriff der Aktivitätenanzeige auf diese Variable

boolean anzeigeNeu = false; //für die Aktualisierung der Anzeige während der MittelwertAmpel

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
Button button(0);  //Botton wird angelegt
boolean buttonstatus = LOW; //Darüber wird gespeichert, ob der Button gedrückt wurde.
Adafruit_NeoPixel rgb_led_1= Adafruit_NeoPixel(6, 1,NEO_GRB + NEO_KHZ800);  //LED wird angelegt


SCD30 airSensor; //über dieses Objekt wird der Sensor angesprochen. Es sollte global sein, da der Sensor nur alle 0,5 s Werte liefert. 
//in Funktionen liegen also oft keine Werte vor.
float scd30_co2 = 0.0; //in dieser globalen Variable werden die abgefragten CO2-Werte des Sensors gespeichert. 
//Wenn keine neuen Werte vorliegen, wird auf die der letzten Abfrage zugegriffen.

File datei; //über dieses Objekt erfolgt der Zugriff auf die Datei



/*
 * Die Funktion gibt für 3 s eine Fehlermeldung auf dem Display aus. 
*/
void fehlermeldung(String fehlertext) {
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.println(fehlertext);
  display.display();
  delay (3000);
  }//END Fehlermeldung



/*
 * Fehlermeldung ohne Anzeigezeit. Macht nur Sinn, wenn das Programm einfriert. Zur Zeit nicht verwendet.
*/
void fehlermeldung2(String fehlertext) {
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.println(fehlertext);
  display.display();
  }//END Fehlermeldung



/*
 * Erzeugt einen kleinen Ball oben rechts, der die Aktivität anzeigt, um ein Einfrieren leichter erkennen zu können.
*/
void aktivitaet() {
  //display.clearDisplay();
  display.fillCircle(123,5,radius,1); //Kreis an x, y, mit Radius , ausgefüllt
  if ((wachsen)&&(radius>4)) {wachsen = false;}
  if ((!wachsen)&&(radius<2)) {wachsen = true;}
  if (wachsen){radius++;}else{radius--;}
  
  //display.display();
  }//END aktivitätenanzeige



/*
 * für eine fast anzeigenfreie Datenspeicherung 
*/

void nachtanzeige(int co2Wert){
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(1);
  display.setTextColor(WHITE,BLACK);
  display.println(String(co2Wert)+" ppm CO2");
  aktivitaet();
  display.display();
}//END nachtanzeige




/*
 * Die Funktion erstellt ein Menü aus einem übergebenen Array aus Strings. (max 10 Zeichen je String)
 * Sie liefert die Nr. des ausgewählten Menüpunkts zurück. (bzw. 0 als Fehler)
*/

int menue(int anzahlMenuepunkte, String menueueberschrift, String auswahltext[]) {  
  
  rgb_led_1.begin(); //LED wird während des Menüs ausgeschaltet.
  rgb_led_1.clear();
  rgb_led_1.show();
  
  if (anzahlMenuepunkte < 2) {return 0;} //Fehler, da zu wenig Menüpunkte (bei 0 oder 1 macht ein Menü auch keinen Sinn)
  long time_start = 0;
  long time_actual = 0;
  int menueauswahl = 1;
  time_actual = millis();  //Vergleichswert für die Schleife
  do { //Schleife bricht ab, wenn die Zeit der "Menüverzögerung" lang kein Button gedrück wurde.
    display.clearDisplay();
    display.setCursor(0,0);
    display.setTextSize(2);
    display.setTextColor(BLACK,WHITE);
    display.println(menueueberschrift);
    display.setCursor(0,17);
    display.setTextSize(2);
    display.setTextColor(WHITE,BLACK);
    display.println(auswahltext[menueauswahl-1]);
    display.setCursor(0,37);
    display.setTextSize(1);
    display.setTextColor(WHITE,BLACK);
    if (menueauswahl < (anzahlMenuepunkte)) {display.println(auswahltext[menueauswahl]); }
    else {display.println(auswahltext[0]);}
    display.display();
    buttonstatus = button.wasPressed();
    if ((buttonstatus == HIGH)&&(millis()>time_actual+250)) { //durch die Zeitabfrage wird ein versehentlicher doppelklick verhindert
        time_actual = millis();   //zweiter Vergleichswert für die Schleife wird nur genommen, wenn Button gedrückt wird.
        menueauswahl++; //Das nächste Programm wird ausgewählt
        if (menueauswahl > anzahlMenuepunkte) {menueauswahl = 1;} 

    }//END if button
    time_start = millis(); //Vergleichswert für die Schleife
  } while (time_start < time_actual + MENUEVERZOEGERUNG);
   
  return menueauswahl;
}//END Menue





/*
 * die Funktion fragt die Daten ab. Sie liefert erst einen Rückgabewert, wenn es Daten gibt.
 * scd30_co2 ist global. Daher sind dort noch die letzten Werte gespeichert, wenn noch keine neuen Daten beim Sensor vorliegen.
 * 
*/
int co2SensorWert() {
  int sensorWert = 0;
  delay(50);
  if (airSensor.dataAvailable()) {delay(50); scd30_co2 = airSensor.getCO2();}
  delay(50);
  sensorWert = int(round(scd30_co2));   
  return sensorWert;
  }//end int co2SensorWert





/* 
 *  Die Funktion gibt einen Wert und seine Einheit auf dem Display aus.
 *  Wert maximal 5 Stellen
 *  Einheit maximal 10 Stellen
*/
  void wertanzeige(int wert, String text) {
      display.clearDisplay();
      display.setCursor(0,0);
      display.setTextSize(5);
      display.setTextColor(WHITE,BLACK);
      display.println(wert);
      display.setCursor(0,47);
      display.setTextSize(2);
      display.setTextColor(WHITE,BLACK);
      display.println(text);
      aktivitaet();
      display.display(); //die Vorherigen Werte werden auf das Disply geschreiben
  } //END void wertanzeige




/* 
 *  Die Ampel zeigt grün unter 1000ppm, gelb unter 1400 ppm, rot unter 2000 ppm und blinkt rot über 2000 ppm. 
*/
  void ampelanzeige(int co2wert) {
    const long BLINKINTERVALL = 1000; //Die Hälfte dieser Zeit ist das Blinkintervall über 2000 ppm
    long time_start = 0;
    long blinkzeit = 0; 
    boolean blinker = false;
    
    const int MAXLEDHELLIGKEIT = 100;
    uint32_t farbevorher;
    uint32_t farbenachher;

    time_start = millis();
    blinkzeit = time_start % BLINKINTERVALL;
    if (blinkzeit > (BLINKINTERVALL/2)) {blinker = true; } else {blinker = false;}

    rgb_led_1.begin();
    farbevorher = rgb_led_1.getPixelColor(0);
    
    if (co2wert < 1000) {
      rgb_led_1.setBrightness(MAXLEDHELLIGKEIT);
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(0,255,0));
    } else if (co2wert >= 1000 && co2wert < 1400) {
      rgb_led_1.setBrightness(MAXLEDHELLIGKEIT);
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(255,255,0));
    } else if (co2wert >= 1400 && co2wert < 2000) {
      rgb_led_1.setBrightness(MAXLEDHELLIGKEIT);
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(255,0,0));
    } else if (co2wert >= 2000) {
      if (blinker) {
        rgb_led_1.setBrightness(MAXLEDHELLIGKEIT);
        rgb_led_1.setPixelColor(0,rgb_led_1.Color(255,0,0));
      } else {
        rgb_led_1.setBrightness(0);
      }
    } else {
      rgb_led_1.setBrightness(MAXLEDHELLIGKEIT);
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(0,0,255)); //blau bei Fehler
    }
    
    rgb_led_1.show();
    farbenachher = rgb_led_1.getPixelColor(0);
    if (farbenachher != farbevorher) {anzeigeNeu = true;}else {anzeigeNeu = false;}
  } //END void ampelanzeige





/*
 * Die LED zeigt einen Regenbogen.
 * Gleichzeitig werden die Sensordaten ein paarmal abgefragt, da sie am Anfang noch keine zuverlässigen Werte liefern.
*/
void start() {
  rgb_led_1.begin();
  rgb_led_1.setBrightness(255);

  int rotzaehler = 0;
  int gruenzaehler = 0;
  int blauzaehler = 0;
  int zaehleinheit = 5;
  const int WARTEZEIT = 10;

  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.println("CO2-Ampel wird      aktiviert");
  display.display();

  int co2Wert = co2SensorWert();
  
  for (int count = 0; count < 2; count++) {
    gruenzaehler = 255;
    while (rotzaehler < 255) {
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(rotzaehler,gruenzaehler,blauzaehler));
      rgb_led_1.show();
      delay(WARTEZEIT);      
      rotzaehler = rotzaehler + zaehleinheit;
    }
    co2Wert = co2SensorWert();
    rotzaehler = 255;
    while (gruenzaehler > 0) {
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(rotzaehler,gruenzaehler,blauzaehler));
      rgb_led_1.show();
      delay(WARTEZEIT);         
      gruenzaehler = gruenzaehler - zaehleinheit;
    }
    co2Wert = co2SensorWert();
    gruenzaehler = 0;
    while (blauzaehler < 255) {
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(rotzaehler,gruenzaehler,blauzaehler));
      rgb_led_1.show();
      delay(WARTEZEIT);       
      blauzaehler = blauzaehler + zaehleinheit;
    }
    co2Wert = co2SensorWert();
    blauzaehler = 255;
    while (rotzaehler > 0) {
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(rotzaehler,gruenzaehler,blauzaehler));
      rgb_led_1.show();
      delay(WARTEZEIT); 
      rotzaehler = rotzaehler - zaehleinheit;
    }
    co2Wert = co2SensorWert();
    rotzaehler = 0;
    while (gruenzaehler < 255) {
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(rotzaehler,gruenzaehler,blauzaehler));
      rgb_led_1.show();
      delay(WARTEZEIT); 
      gruenzaehler = gruenzaehler + zaehleinheit;
    }
    co2Wert = co2SensorWert();
    gruenzaehler = 255;
    while (blauzaehler > 0) {
      rgb_led_1.setPixelColor(0,rgb_led_1.Color(rotzaehler,gruenzaehler,blauzaehler));
      rgb_led_1.show();
      delay(WARTEZEIT); 
      blauzaehler = blauzaehler - zaehleinheit;
    }
    co2Wert = co2SensorWert();
    blauzaehler = 0;
  }
    rgb_led_1.clear();
    rgb_led_1.show();
  }//END start




/*
 * Sensor ins Freie bringen.
 * Nach 4 Minuten erfolgt die Kalibrierung auf 450 ppm
 * Er benötigt konstante Bedingungen und sollte schon etwas warmgelaufen sein. 
 * Wenn man ihn von einem warmen Raum ins kalte nach draußen bringt, sind die vier Minuten u. U. nicht ausreichend.
*/
void kalibrierung(){
  long laufzeit = millis();
  long laufzeit2 = millis();
  int countdown = 60;
  
  rgb_led_1.clear();
  rgb_led_1.setPixelColor(0,0,255,255); //türkis
  rgb_led_1.show();
  
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(1);
  display.setTextColor(WHITE,BLACK);
  display.println("Zur Kalibrierung den Sensor nach draussen bringen");
  display.display();
  delay(3000); 
  do {
    if (millis() > laufzeit2 + 1000) {
      laufzeit2 = millis();
      display.clearDisplay();
      display.setCursor(0,0);
      display.println("Kalibrierung startet in " + String(countdown) + " Sekunden");
      display.display();
      countdown--;
      } //END if
    } while (millis() < laufzeit + 60000); // 1 min Zeit um nach Draußen zu bringen
    
    countdown = 240; //4 min
    laufzeit = millis(); //neuer Vergleichswert
    do {
    if (millis() > laufzeit2 + 1000) {
      laufzeit2 = millis();
      display.clearDisplay();
      display.setCursor(0,0);
      display.println("Noch " + String(countdown) + " Sekunden");
      display.setCursor(0,9);
      display.println("Kalibrierung");
      display.display();
      countdown--;
      } //END if
    } while (millis() < laufzeit + 240000); // 4 min Kalibrierung
  
  airSensor.setForcedRecalibrationFactor(450); //Kalibrierungswert wird gesetzt

  display.clearDisplay();
  display.setCursor(0,0);
  display.println("Kalibrierung abgeschlossen");
  display.display();
  delay(3000);
  
  
  }//END void kalibrierung





/*
 * CO2-Ampel mit Sofortanzeige der Messung.
 */
void co2SofortAmpel() {
  int co2Wert = co2SensorWert();    
  
  do {
    co2Wert = co2SensorWert(); 
    wertanzeige(co2Wert, "ppm CO2");
    ampelanzeige(co2Wert);
    buttonstatus = button.wasPressed();
  } while (buttonstatus == LOW);
}//END co2SofortAmpel





/*
 * Es wird jede Sekunde eine Messunge durchgeführt und ein Mittelwert aus 20 Messungen gebildet.
 * Das Puffert leichte schwankungen am, z. B. wenn man in die Richtung der Ampel atmet.
 * Die Anzweige wird ebenfalls nur alle 20 Sekunden aktuallisiert. Die erste Anzeige erfolgt daher auch erst nach 20 Sekunden.
*/
void co2MittelwertAmpel() {  
  int co2Wert = 0;
  const int MESSUNGEN = 20; //Anzahl der Messwerte für den Mittelwert
  int co2Werte[MESSUNGEN]; //Über das Array wird ein Mittelwert der Messungen von 1 Minuten gebildet.
  int co2WerteZaehler = 0; //zeigt die Position der letzten Messung an.
  int co2Mittelwert = 0;
  int co2Zwischenwert = 0;
  int co2Altwert = 0; //für die Aktivitätenanzeige benötigt
  
  const long MESSINTERVALL = 1000; //alle 1 s soll ein Messwert genommen werden
  long time_seitstart = 0;
  long time_jetzt = 0;
  
  const long ZEIGEINTERVALL = 20000; //nur alle 20 s soll die Anzeige aktualisiert werden
  long vergleichszeit = (-1)*ZEIGEINTERVALL; //sonst erfolgt keine Anzeige beim ersten mal
    
  co2Wert = co2SensorWert(); 
  co2Mittelwert = co2Wert;
  for (int i = 0; i < MESSUNGEN; i++){ co2Werte[i] = co2Wert; }//END for//Die Schleife befüllt den Array mit der ersten CO2-Messung.
  
  do { //Die Schleife läuft, bis der Button gedrückt wird
    time_seitstart = millis(); //Zeit seit dem Systemstart
    if (time_seitstart > time_jetzt + MESSINTERVALL){ //schaut, ob schon eine Sekunde um ist und ein neuer Messwert dem Array zugefügt werden kann.
      time_jetzt = millis(); //Neue Vergleichzeit wird genommen
      co2Werte[co2WerteZaehler] = (long)co2SensorWert(); //Fügt dem Array eine neue Messung hinzu.
      if (co2WerteZaehler < MESSUNGEN-1) co2WerteZaehler++; //Gibt die nächste Position der CO2-Speicherung im Array an.
      else co2WerteZaehler = 0;
      
      co2Zwischenwert = 0; //Berechnung des Mittelwertes
        for (int i = 0; i < MESSUNGEN; i++){ 
        co2Zwischenwert = co2Zwischenwert + co2Werte[i];
        }
      co2Mittelwert = co2Zwischenwert / MESSUNGEN;
      }//End if neuer Wert?
  
    if ((time_seitstart > vergleichszeit + ZEIGEINTERVALL)||(anzeigeNeu)) {    //Akualisierung des Display je nach Zeigeintervall (z. b. 20 sec) bzw. wenn die AMpel ihre Farbe geändert hat. Dann ändert dort anzeigeNeu seinen WErt.
      vergleichszeit = millis();
      wertanzeige(co2Mittelwert, "ppm CO2");
      co2Altwert = co2Mittelwert;
    } //End if neue Anzeige?
    else {wertanzeige(co2Altwert, "ppm CO2");} //diese Zeile wird nur für die Aktivitätenanzeige benötigt
    
    ampelanzeige(co2Mittelwert);
    buttonstatus = button.wasPressed();
  } while (buttonstatus == LOW);
}//END co2MittelwertAmpel




/*
 * die Funktion speichert CO2-werte auf der SD-Karte
*/
void co2Datenaufzeichnung() {
  //Menü mit Zeitintervall 
  const int MENUEZEITAUSWAHLPUNKTE = 4;
  String zeitauswahlMenuetext[] = {"1 Sekunde","15Sekunden","1 Minute","5 Minuten"};

  long time_start = 0;
  long time_actual = millis();
  long intervall = 0;
  
  int zeitauswahl = 0; 
  int datenzaehler = 1;

  int ampelAnzeige = 0;

  int co2Wert = 0;

  zeitauswahl = menue(MENUEZEITAUSWAHLPUNKTE, "Intervall", zeitauswahlMenuetext);
  switch (zeitauswahl) {
    case 1: intervall = 1000; break;
    case 2: intervall = 15000; break;
    case 3: intervall = 60000; break;
    case 4: intervall = 300000; break;
    default: fehlermeldung("Fehler im Zeitauswahl-Menue");//z. B. Fehlermeldung oder Hauptprogramm
    break;
    }//END Switch
  time_actual -= intervall; //Sonst wird erst nach der ersten Intervall-Zeit gespeichert. So aber schon beim ersten Schleifendurchlauf

  
  //Menü, ob Ampel und Display jeweils an oder aus sein sollen
  const int MENUEAMPELPUNKTE = 3;
  String ampelauswahlMenuetext[] = {"mit Ampel", "nur Werte", "winzig"};
  ampelAnzeige = menue(MENUEAMPELPUNKTE, "Anzeige", ampelauswahlMenuetext);

  
  if (SD.begin(28)){ //Weshalb 28? Vielleicht die Stelle des SD-Bees auf der SenseBox? 
    //nur SD.begin(28) funktioniert auch, aber so kann es mit einer Fehlermeldung kombiniert werden.
    datei = SD.open("CO2Daten.txt", FILE_WRITE);  //öffnet die DAtei oder legt sie neu an.
    datei.println("CO2-Aufzeichnung"); 
    datei.println("Zeitintervall: "+String(zeitauswahlMenuetext[zeitauswahl-1])); //Überschrift für die Aufzeichung;
    datei.close(); //schließt die Datei
  } else {fehlermeldung("Keine     SD-Karte"); return;} //Fehlermeldung und Abbruch der Funktion bei fehlernder SD-Karte
 
  //Display und LED werden ausgeschlatet, falls das die Menü-Auswahl ist. 
  display.clearDisplay();
  display.println("");
  display.display();
  rgb_led_1.begin();
  rgb_led_1.clear();
  rgb_led_1.show();
  
  do { //Die Schleife läuft, bis der Button gedrückt wird
    //fehlermeldung2("Position1"); 
    co2Wert = co2SensorWert();
    //fehlermeldung2("Position1a");
    time_start = millis();
    //fehlermeldung2("Position1b");
    if (time_start > (time_actual + intervall)) {
      //fehlermeldung2("Position2");
      time_actual = millis();
      if (SD.begin(28)){ 
        //fehlermeldung2("Position3");
        datei = SD.open("CO2Daten.txt", FILE_WRITE);  //öffnet die DAtei oder legt sie neu an.
        datei.println(String(datenzaehler)+": "+String(co2Wert));  //Schreibt CO2-Werte auf SD-Karte
        datei.close();
        //fehlermeldung2("Position4");
      } else {fehlermeldung("Keine     SD-Karte");} //hier kein Abbruch. Am Anfang steht man noch neben der Ampel, aber mitten drin nicht mehr. Dann bekommt man den Datenfehler nicht mit, wenn das Programm mitten drin auf "Ampel" ohne Aufzeichung umschaltet. 
      datenzaehler++;
    }//END if
    
  //fehlermeldung2("Position5");
  switch (ampelAnzeige) { //Auswahl der Anzeige während der Datenaufzeichnung
    case 1: // Ampel- und Wertanzeige (Nur Ampel nicht möglich, da in der Ampel auch die WErte aufgerufen werden)
    ampelanzeige(co2Wert);
    wertanzeige(co2Wert, "ppm CO2");
    break;
    case 2: //Nur Werteanzeige
    wertanzeige(co2Wert, "ppm CO2");
    break;
    case 3: //fast nichts (Nachtmodus ;-) )
    nachtanzeige(co2Wert);
    break;
    default: fehlermeldung("Fehler bei der Anzeige");
    break;
    }//END switch
    //fehlermeldung2("Position6");
    buttonstatus = button.wasPressed();
  } while (buttonstatus == LOW); 
  
}//END co2Datenaufzeichnung



void setup() {
  senseBoxIO.powerI2C(true);
  delay(2000);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3D);
  display.display();
  delay(100);
  display.clearDisplay();
  button.begin();
  buttonstatus = button.wasPressed(); //Der Button muss scheinbar einmal schon im Setup abgefragt werden, sonst liefert er True zurück
  buttonstatus = LOW;

  Wire.begin(); //aktiviert den Sensor
  if (airSensor.begin() == false)  { fehlermeldung("Kein CO2-Sensor gefunden."); } //Fehlermeldung, falls Störung im CO2-Sensor
  
  start();
}//ENd Setup





void loop() {
  const int ANZAHLMENUEPUNKTE = 4;
  String MENUEPUNKTE[] = {"Ampel slow", "Ampel now", "Datenampel", "Kalibrier."}; 
  programm = menue(ANZAHLMENUEPUNKTE, "Menue", MENUEPUNKTE);
  switch (programm) {
    case 1: co2MittelwertAmpel();
    break;
    case 2: co2SofortAmpel();
    break;
    case 3: co2Datenaufzeichnung();
    break;
    case 4: kalibrierung();
    break;
    default: fehlermeldung("Funktion  nicht     vorhanden");//z. B. Fehlermeldung oder Hauptprogramm
    break;
    }//END Switch
  //}

}//END loop
1 Like

Und hier die Ampel mit mehr Sensoren und LED-Ring (Zu-Hause-Ampel):

/*
 * Das Programm misst
 * die CO2-Konzentration und gibt sie auf dem Display aus
 * weiterhin wird die Messung auf einem 24 LED-Ring angezeigt.
 * In Intervallen werden diverse Messwerte auf dem OLED ausgegeben.
 * Einige sind deaktiviert, und können ggf. noch eingebunden werden.
*/


#include <Adafruit_NeoPixel.h> //für den LED-Ring
#include "SparkFun_SCD30_Arduino_Library.h" //für CO2-Sensor
#include "SenseBoxMCU.h" //für CO2-, Licht-, Beschleunigungs-Sensor und OLED-Display
#include "bsec.h" //für Umweltsensor BME680
#include <SPI.h> //für OLDE
#include <Wire.h> //für OLED
#include <Adafruit_GFX.h> //für OLED
#include <Adafruit_SSD1306.h> //für OLED
#include <SD.h> //für SD-Karte

#define OLED_RESET 4 //für OLED

//OLED einrichten
Adafruit_SSD1306 display(OLED_RESET); //OLED-Disply initialisieren

//LED einrichten
const int LEDS = 24; //Ring mit 24 LEDs
const int LEDPIN = 5; //Der Ring wird über den Pin 5 angesteuert.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(LEDS, LEDPIN, NEO_GRB + NEO_KHZ800);  //NeoPixel als "pixels" instanziieren
int ledHelligkeit = 100; //max. 255

//SD einrichten
File datei; //über dieses Objekt erfolgt der Zugriff auf die Datei

//Sensoren einrichten
SCD30 airSensor;   //CO2-Sensor initialisieren
TSL45315 tsl; //Licht-Sensor initianlisieren
VEML6070 veml; //uv-Sensor initialisieren
Bsec iaqSensor; //Umweltsensor BME680 initialisieren

//Globale Konstanten
const int ANZEIGEINTERVALL = 2000;
char PROZENT = char(37); //%
char GRAD = char(247); //°


//Globale Variablen (für die Daten der Sensoren)
float scd30_co2 = 0.0; //CO2 in ppm. 
int co2Wert = 0; //die vom Sensor gelieferten Daten sind doppelt so hoch, wie sie sein sollten. Daher werden sie umgerechnet und hier gespeichert.
float bmeTemperatur; //in °C
float bmeHumidity; //Luftfeuchtigkeit in %
double bmePressure; //Luftdruck in Pa
float bmeIAQ; //Innenraumluftqualität IAQ
float bmeIAQAccuracy; //Kalibrierungswert
int bmeCO2; //CO2-Äquivalent 
float bmeBreathVocEquivalent; //Atemvolumen VOC Äquivalent (Übersetzungsfehler? Laut Datenblatt b-VOC = "flüchtige Organische Verbindungen")
unsigned long beleuchtungsstaerke; //in Lux
float uv;
boolean datenaufzeichnung = false; //wird true, wenn eine SD Karte eingelegt ist.
unsigned int datenzaehler = 1; //die Anzahl der Messdaten auf der SD-Karte
long time_start = 0; //für die Datenaufzeichnung
long time_actualdaten = 0; //Vergleichswert für die Datenaufzeichnung
long time_actualwertanzeige = 0; //Vergleichswert für die Anzeige
//long time_actualLedAnzeige = 0; //Vergleichswert für die ledANzeige //nicht benötigt beim Fading
long time_actualSensorenAbfrage = 0; //Vergleichswert für Sensorabfrage
boolean led24istAn = true; //für ledAktivitätenanzeige
int werteAnzeigenZaehler = 0;




/*
 * Die Funktion gibt für 3 s eine Fehlermeldung auf dem Display aus. 
*/
void fehlermeldung(String fehlertext) {
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.println(fehlertext);
  display.display();
  delay (3000);
  }//END void Fehlermeldung




/*
 * die Funktion kontrolliert die Funktionsweise des Umweltsensors BME680
*/
void checkIaqSensorStatus(void)
{
  if (iaqSensor.status != BSEC_OK) {
    if (iaqSensor.status < BSEC_OK) {
      fehlermeldung("Kein      BMW680-   Sensor");
    }
  }
  if (iaqSensor.bme680Status != BME680_OK) {
    if (iaqSensor.bme680Status < BME680_OK) {
      fehlermeldung("Kein      BMW680-   Sensor");
    }
  }
}//END void checkIaqSensorStatus





/*
 * die Funktion fragt die Daten ab. Sie liefert erst einen Rückgabewert, wenn es Daten gibt.
 * scd30_co2 ist global. Daher sind dort noch die letzten Werte gespeichert, wenn noch keine neuen Daten beim Sensor vorliegen.
 * 
*/
int co2SensorWert() {
  int sensorWert = 0;
    delay(50);
    if (airSensor.dataAvailable()) {
      delay(50);
      scd30_co2 = airSensor.getCO2();
      }
    delay(50);
    sensorWert = int(round(scd30_co2));   
  return sensorWert;
  }//end int co2SensorWert





/*
 * Fragt die Werte der Sensoren ab
*/
void sensorenAbfragen(){
  //CO2-Sensor
  co2Wert = co2SensorWert();
  //if (airSensor.dataAvailable()) {scd30_co2 = airSensor.getCO2();}  //wäre eine alternative, aber zur Zeit werden falsche Werte geliefert. Diese werden in der Funktion korrigiert.
  //Umweltsensor BME680
  delay(50);
  if (iaqSensor.run()) {
    delay(50);
      bmeTemperatur = iaqSensor.temperature;
      delay(50);
      bmeHumidity = iaqSensor.humidity;
      delay(50);
      bmePressure = iaqSensor.pressure;
      delay(50);
      bmeIAQ = iaqSensor.iaq;
      delay(50);
      bmeIAQAccuracy = iaqSensor.iaqAccuracy;
      delay(50);
      bmeCO2 = iaqSensor.co2Equivalent;
      delay(50);
      bmeBreathVocEquivalent = iaqSensor.breathVocEquivalent;
      delay(50);
    } else { checkIaqSensorStatus(); 
    }//END if - else
  delay(50);
  beleuchtungsstaerke = tsl.getIlluminance();
  delay(50);
  uv = veml.getUvIntensity();
  delay(50);
  }//END void SensorenAbfragen





/*
 * Die LED zeigt einen Regenbogen.
 * Gleichzeitig werden die Sensordaten ein paarmal abgefragt, da sie am Anfang noch keine zuverlässigen Werte liefern.
 * 
*/
void start() {

  int rotzaehler = 0;
  int gruenzaehler = 0;
  int blauzaehler = 0;
  int zaehleinheit = 5;
  const int WARTEZEIT = 10;

  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.println("CO2-Ampel wird      aktiviert");
  display.display();

  sensorenAbfragen(); //zum Aufwärmen
  
  for (int count = 0; count < 2; count++) {
    gruenzaehler = 255;
    while (rotzaehler < 255) {
      pixels.setPixelColor(0,pixels.Color(rotzaehler,gruenzaehler,blauzaehler));
      pixels.show();
      delay(WARTEZEIT);      
      rotzaehler = rotzaehler + zaehleinheit;
    }
  sensorenAbfragen(); //zum Aufwärmen
    rotzaehler = 255;
    while (gruenzaehler > 0) {
      pixels.setPixelColor(0,pixels.Color(rotzaehler,gruenzaehler,blauzaehler));
      pixels.show();
      delay(WARTEZEIT);         
      gruenzaehler = gruenzaehler - zaehleinheit;
    }
  sensorenAbfragen(); //zum Aufwärmen
    gruenzaehler = 0;
    while (blauzaehler < 255) {
      pixels.setPixelColor(0,pixels.Color(rotzaehler,gruenzaehler,blauzaehler));
      pixels.show();
      delay(WARTEZEIT);       
      blauzaehler = blauzaehler + zaehleinheit;
    }
  sensorenAbfragen(); //zum Aufwärmen
    blauzaehler = 255;
    while (rotzaehler > 0) {
      pixels.setPixelColor(0,pixels.Color(rotzaehler,gruenzaehler,blauzaehler));
      pixels.show();
      delay(WARTEZEIT); 
      rotzaehler = rotzaehler - zaehleinheit;
    }
    sensorenAbfragen();
    rotzaehler = 0;
    while (gruenzaehler < 255) {
      pixels.setPixelColor(0,pixels.Color(rotzaehler,gruenzaehler,blauzaehler));
      pixels.show();
      delay(WARTEZEIT); 
      gruenzaehler = gruenzaehler + zaehleinheit;
    }
    gruenzaehler = 255;
    while (blauzaehler > 0) {
      pixels.setPixelColor(0,pixels.Color(rotzaehler,gruenzaehler,blauzaehler));
      pixels.show();
      delay(WARTEZEIT); 
      blauzaehler = blauzaehler - zaehleinheit;
    }
  sensorenAbfragen(); //zum Aufwärmen
    blauzaehler = 0;
  }
    pixels.clear();
    pixels.show();
  }//END start





/* 
 *  Die Funktion gibt einen Wert und seine Einheit auf dem Display aus.
 *  Wert maximal 5 Stellen
 *  Einheit maximal 10 Stellen
*/
void wertanzeige(int wert, String einheit) {
  
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(5);
  display.setTextColor(WHITE,BLACK);
  display.println(wert);
  display.setCursor(0,47);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.println(einheit);
  delay(50);
  display.display(); //die Vorherigen Werte werden auf das Disply geschreiben
  delay(50);
  } //END void wertanzeige
  
void wertanzeigeKlein(int wert, String einheit) {
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(4);
  display.setTextColor(WHITE,BLACK);
  display.println(wert);
  display.setCursor(0,47);
  display.setTextSize(2);
  display.setTextColor(WHITE,BLACK);
  display.println(einheit);
  delay(50);
  display.display(); //die Vorherigen Werte werden auf das Disply geschreiben
  delay(50);
  } //END void wertanzeigeKlein




/*
 * Die Funktion wählt aus, was angezeigt werden soll und gibt es an die Funktion Wertanzeige weiter. 
 * Einige Anzeigeparameter wurden deaktiviert. Sie müssten ggf. noch in die Switch Case-Auswahl eingefügt werden. Dann muss auch der entsprechende Wert im loop geändert werden.
 * 
*/
void werteAnzeigen(int anzeigestelle) {  
  switch (anzeigestelle) {
    case 0: wertanzeige(co2Wert,"ppm CO2"); break;
    case 1: wertanzeige(int(bmeTemperatur), String(GRAD)+"C"); break;//in °C
    case 2: wertanzeige(int(bmeHumidity), String(PROZENT)+" Luftfeu."); break;//Luftfeuchtigkeit in % 
    default: fehlermeldung("Fehler in WertAnzeige");
    }//END Case

  //wertanzeige(int(bmePressure/100), "hPa LuftDr"); //Luftdruck in hPa
  //delay(ANZEIGEINTERVALL);
//  if (int(bmeIAQ) <= 50) {wertanzeige(int(bmeIAQ),"IAQ top");}
//  else if (int(bmeIAQ) <= 100) {wertanzeige(int(bmeIAQ),"IAQ gruen");}
//  else if (int(bmeIAQ) <= 150) {wertanzeige(int(bmeIAQ),"IAQ gelb");}
//  else if (int(bmeIAQ) <= 200) {wertanzeige(int(bmeIAQ),"IAQ orange");}
//  else if (int(bmeIAQ) <= 250) {wertanzeige(int(bmeIAQ),"IAQ rot");}
//  else if (int(bmeIAQ) <= 350) {wertanzeige(int(bmeIAQ),"IAQ violet");}
//  else if (int(bmeIAQ) > 351) {wertanzeige(int(bmeIAQ),"IAQ braun");}
  
   //Innenraumluftqualität IAQ
  //delay(ANZEIGEINTERVALL);
  //wertanzeige(int(bmeIAQAccuracy),"Kalib.Wert"); //Kalibrierungswert
  //delay(ANZEIGEINTERVALL);
  //wertanzeige(int(bmeCO2),"CO2-Äquiv."); //CO2-Äquivalent
  //delay(ANZEIGEINTERVALL);
  //wertanzeige(int(bmeBreathVocEquivalent),"ppm VOC"); //Atemvolumen VOC Äquivalent
  //delay(ANZEIGEINTERVALL);
  }//END void werteAnzeigen





/*
 * die Funktion gibt abhängig von der Beleuchtungsstärke einen Wert zwischen 5 und 250 zurück, mit dem die Helligkeit der LEDs angepasst werden kann
*/
int ledHelligkeitAnpassen() {
  int ledHelligkeit = int(beleuchtungsstaerke);
  if (ledHelligkeit > 250) {ledHelligkeit = 250;}
  if (ledHelligkeit < 5) {ledHelligkeit = 5;}

  return ledHelligkeit;
  }//END int ledHelligkeitAnpassen




/* 
 *  Die Ampel zeigt grün unter 1000ppm, gelb unter 1400 ppm, rot unter 2000 ppm und komplett rot über 2000 ppm. 
*/
void ampelanzeige() {
  
  int co2Ring = (co2Wert/100);
  int farbe = ledHelligkeitAnpassen();
  //pixels.setBrightness(ledHelligkeitAnpassen()); //sollte nur im Setup aufgerufen werden. Die Helligkeit wird dann über setPixelColor gesteuert.
  //pixels.setPixelColor(i, pixels.Color(red, green, blue));

  pixels.clear(); //setzt die Pixel zurück

  if (co2Wert < 1000) { 
    for (int i = 1; i <= co2Ring; i++) { pixels.setPixelColor(i,pixels.Color(0,farbe,0)); } //grün - END for unter 1000
    } else if (co2Wert >= 1000 && co2Wert < 1400) { 
      for (int i = 1; i < 10; i++){ pixels.setPixelColor(i,pixels.Color(0,farbe,0)); } //grün
      for (int i = 10; i <= co2Ring; i++) { pixels.setPixelColor(i,pixels.Color(farbe,farbe,0)); } //gelb 
    } else if (co2Wert >= 1400 && co2Wert < 2400) {
      for (int i = 1; i < 10; i++) { pixels.setPixelColor(i,pixels.Color(0,farbe,0)); } //grün bis max 10
      for (int i = 10; i < 14; i++) {pixels.setPixelColor(i,pixels.Color(farbe,farbe,0));} //gelb bis max 14
      for (int i = 14; i <= co2Ring; i++) { pixels.setPixelColor(i,pixels.Color(farbe,0,0)); } //rot bis 24
    } else if (co2Wert > 2400) {
      for (int i = 1; i < 24; i++) { pixels.setPixelColor(i,pixels.Color(farbe,0,0)); } //alle rot
   } //END if else
   

  for (int i = co2Ring+1; i < 24; i++) {  //deaktiviert alle übrigen Pixel
    pixels.setPixelColor(i,pixels.Color(0,0,0));
    }//END for für schwarze
  
  pixels.show();  //zeigt die neuen Werte
  
  } //END void ampelanzeige




/*
 * LED 0 wechselt die Farbe von blau zu türkis
*/

void ledAktivitaetenAnzeige(){
  int farbe = ledHelligkeitAnpassen(); //gibt den Zielwert passend zur Helligkeit vor

  uint32_t aktuellefarbe = pixels.getPixelColor(0); //holt die Farbe von led 0
  
   // Separiert die Farben in rot, grün, blau-Anteile
  uint8_t rot, gruen, blau;
  rot = (aktuellefarbe >> 16) & 0xFF; 
  gruen = (aktuellefarbe >> 8) & 0xFF; 
  blau = aktuellefarbe & 0xFF; 

  int wartezeit = 1000 / farbe; // passt das Delay an ein Fading von 1 s an.
 
  if (led24istAn) { //true wenn blau // false, wenn türkis
    for (int i = 0; i <= farbe; i++){
      pixels.setPixelColor(0,pixels.Color(0,i,farbe));
      pixels.show();
      delay(wartezeit);
    }
    led24istAn = false;
    }else {
      for (int i = gruen; i >= 0; i--) {
        pixels.setPixelColor(0,pixels.Color(0,i,farbe));
        pixels.show();
        delay(wartezeit);
        }
      led24istAn = true;
      }
  }//END void ledAktiviaetenAnzeige




/*
 * LED 0 und 23 wechseln die Farbe
 * 
 * 
void ledAktivitaetenAnzeige_alt(){
  int farbe = ledHelligkeitAnpassen();
  if (led24istAn) {
    pixels.setPixelColor(23,pixels.Color(0,farbe,farbe));
    pixels.setPixelColor(0,pixels.Color(0,0,farbe));
    led24istAn = false;
    }else {
      pixels.setPixelColor(0,pixels.Color(0,farbe,farbe));
      pixels.setPixelColor(23,pixels.Color(0,0,farbe));
      led24istAn = true;
      }
  pixels.show();
  }//END void ledAktiviaetenAnzeige
*/




/*
 * Die Messdaten werden gespeichert, sofern eine SD-Karte erkannt wird.
*/
void datenAufzeichnen(){

  if (SD.begin(28)){ 
  datei = SD.open("Daten.txt", FILE_WRITE);  //öffnet die DAtei oder legt sie neu an.
  datei.println(String(datenzaehler) + ";" + String(co2Wert) + ";" + String(bmeTemperatur) + ";" + String(bmeHumidity) + ";" + String(bmePressure/100) + ";" + String(bmeIAQ) + ";" + String(bmeCO2) + ";" + String(bmeBreathVocEquivalent) + ";" + String(beleuchtungsstaerke) + ";" + String(uv));
  datei.close();
  } else {fehlermeldung("Keine     SD-Karte"); return;} 
  datenzaehler++;
  
  }//END void datenAufzeichnen





void setup() {
  senseBoxIO.powerI2C(true); //I2C-Schnittstelle anschalten
  //Display aktivieren
  delay(2000);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3D);
  display.display();
  delay(100);
  display.clearDisplay();
  
  pinMode (LEDPIN, OUTPUT); //Pin für LED anschalten
  pixels.begin(); //LEDs aktivieren
  pixels.setBrightness(255); //setzt die Helligkeit auf Maximum. die einzelne Helligkeit wird später über die Farbwerte gesteuert.

  Wire.begin(); //für CO2- und Umweltsensor
  Wire.setClock(50000); //setzt die Kommunikationsgeschwindigkeit für I2C auf 50kHz. Das ist die Geschwindigkeit, mit der Sensirion die Kommunikation empfiehlt.
  if (airSensor.begin() == false) {fehlermeldung("Kein      CO2-Sensor");} //CO2-Sensor aktivieren und Fehlermeldung, falls kein CO2-Sensor erkannt
  
  iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire); //Aktivierung des Umweltsensors
  checkIaqSensorStatus(); //Kontrolle des Umweltsensors
  bsec_virtual_sensor_t sensorList[10] = { //Einrichtung der Messwerte des Umweltsensors
    BSEC_OUTPUT_RAW_TEMPERATURE,
    BSEC_OUTPUT_RAW_PRESSURE,
    BSEC_OUTPUT_RAW_HUMIDITY,
    BSEC_OUTPUT_RAW_GAS,
    BSEC_OUTPUT_IAQ,
    BSEC_OUTPUT_STATIC_IAQ,
    BSEC_OUTPUT_CO2_EQUIVALENT,
    BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
    BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
  };
  iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP);
  checkIaqSensorStatus();
  
  tsl.begin(); //Licht-Sensor aktivieren
  veml.begin(); //UV-Sensor aktivieren

  //SD-Karte aktivieren, sofern vorhanden
  if (SD.begin(28)){ //Weshalb 28? Vielleicht die Stelle des SD-Bees auf der SenseBox? 
    //nur SD.begin(28) funktioniert auch, aber so kann es mit einer Fehlermeldung kombiniert werden.
    datei = SD.open("Daten.txt", FILE_WRITE);  //öffnet die DAtei oder legt sie neu an.
    datei.println("Aufzeichnung"); 
    datei.println("Zur grafischen Auswertung in Numbers . durch , ersetzen");
    datei.println("Messung;ppm CO2;Temp in "+String(GRAD)+"C;Luftfeuchtigkeit in "+String(PROZENT)+";Luftdruck in hPa;IAQ;CO2-Aequivalent;VOC;Beleuchtung in Lux;UV in  µW/m^2");
    datei.close(); //schließt die Datei
    datenaufzeichnung = true;
  } else {fehlermeldung("Keine     SD-Karte"); datenaufzeichnung = false;} //Fehlermeldung 
 

  start();//Regenbogen und Sensoren aufwärmen
  ampelanzeige(); //Ampel wird zuerst im Setup angeschaltet, weil sie sonst erst nach einer Minute aktiviert würde
} //END void setup





void loop() {
  
  time_start = millis();
    
  if ((datenaufzeichnung)&&(time_start > (time_actualdaten+60000))) {  //daten werden ggf. aufgezeichnet, sofern 1 min vergangen und SD-Karte eingelegt.
    datenAufzeichnen(); 
    time_actualdaten = millis();
    }  //end if datenaufzeichung 

  if (time_start > (time_actualSensorenAbfrage+10000)) {//daten werden alle 10 s abgefragt
    sensorenAbfragen();
    ampelanzeige();
    time_actualSensorenAbfrage = millis();
  }//END if SensorenAbfragen

  if (time_start > (time_actualwertanzeige+2000)) { //alle 2 s wird ein neuer Wert angezeigt
    werteAnzeigen(werteAnzeigenZaehler);
    werteAnzeigenZaehler++;
    if (werteAnzeigenZaehler > 2) {werteAnzeigenZaehler = 0;}
    time_actualwertanzeige = millis();
    }//end if Wertanzeige
    
  //if (time_start > (time_actualLedAnzeige+500)) { //alle 0,5 s blinkt die LED
   // time_actualLedAnzeige = millis();
    ledAktivitaetenAnzeige(); //dauert 1 s
  //}//END if LEDAktivitätenAnzeige
  
} //END void loop
1 Like

Ich habe noch weitere Beobachtungen gemacht, die relevant sein könnten. Ich habe auch den BME680-Sensor verbaut. Er zeigte aber immer nur die gleichen Werte für IAQ und VOC an. Es stellte sich heraus, dass er auch nach fünf Tagen noch nicht mit der Selbstkalibrierung angefangen hatte, da er noch zu wenige Messwerte hatte. Ich fragte ihn immer zusammen mit dem SCD30-Sensor alle paar Sekunden ab. Also stellte ich mein Programm so um, dass die Sensoren bei jedem Schleifendurchlauf abgefragt wurden und nicht mehr nur in Sekunden-Intervallen. Jetzt wurde der BME680 zwar kalibriert, aber dafür stürzte das Programm schon nach Minuten bis wenigen Stunden ab. Also habe ich das Programm so umgestellt, dass der BME680-Sensor kontinuierlich, der SCD30-Sensor jedoch nur alle 30 Sekunden abgefragt wird. Weiterhin habe ich den Sensor auch so umprogrammiert, dass er nur noch alle 30 Sekunden einen neuen Messwert liefert. Der Befehl dazu lautet:

const int CO2ABFRAGEINTERVALL = 30;

…

airSensor.setMeasurementInterval(CO2ABFRAGEINTERVALL); //Change number of seconds between measurements: 2 to 1800 (30 minutes)

Danach lief das Programm wieder problemlos 90 Stunden, bis ich es selbst unterbrochen habe, um Anpassungen an der Darstellung in meinem LED-Ring zu programmieren.

Ich denke, dass damit der Fehler nicht behoben ist, aber man die Intervalle der Abstürze vermutlich verlängern kann.