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