====== Flaschenkühler - Programmversion 0.4 ====== Diese Programmversion dokumentiert den Wechsel von der experimentellen Verdrahtung auf dem Breadboard auf gelötete Platinen. Außerdem ist der experimentelle Aufbau der Hardware fertiggestellt. ACHTUNG: Weil der AREF am Arduino mit dessen 3,3-Volt-Ausgang verbunden ist, dürfen auf keinen Fall ältere Programmversionen ohne die folgende Änderung im Setup geladenwerden: analogReference(EXTERNAL); Ohne diesen Befehl wird die interne Referenzspannung mit dem 3,3-Volt-Ausgang kurzgeschlossen, was den Arduino zerstören kann. Es wurden die folgenden Anpassungen vorgenommen: * Das Dispaly zeigt nun korrekt die Soll- und Isttemperatur des Kühlbechers an. * Über die beiden Taster wird die Solltemperatur des Kühlbechers eingestellt. * Die PID-Regler für den Lüfter und das Peltier-Element wurden angepasst. (Weitere Optimierung ist aber noch notwendig.) * Die Solltemperatur für den Kühlkörper wird an die Umgebungstemperatur (+ 10 °C) angepasst. * Die Auswertung des Tachosignals wurde verfeinert. Das Programm ignoriert jetzt nicht nur zu niedrige und zu hohe Frequenzen des Tachosignals, sondern filtert Ausreißer aus, indem aus den letzten fünf Werten der Median verwendet wird. Die Ausreißer entstehen, wenn andere Programmteile verhindern, dass die Signalflanken nach einem tatsächlichen Wechsel zeitnah registriert werden. Nach der Umrechnung in die Drehzahl in Umdrehungen pro Minute wird aus den letzten zehn Werten die größte Drehzahl bestimmt, da dies der tatsächlichen Drehzahl am nächsten kommen sollte. * Mit der Freeware [[http://www.serialcominstruments.com/instrument4.php|SerialComInstruments 4]] können Ist- und Solltemteraturen sowie die Outputs der PID-Regler bzw. die Lüfterdrehzahl am Computer dargestellt werden. To Dos: * Die Anzeige der Kühlleistung und der Lüfterdrehzahl sollte in Anzeigomodus 1 (erweitert) mittig ausgerichtet werden. Mit der Version 0.4 errecht das Programm seinen finalen Entwicklungsstatus und ist damit der "Release Candidate". Das Auslesen des Thermistors Nr. 3 (A5, Kühlbecherboden) ist auskommentiert, da der Arbeitsspeicher nicht ausreicht. Auch ohne das Auslesen des Thermistors Nr. 3 (und die anschließende Berechnung des Widerstands und der Temperatur) ist der dynamische Speicher des Arduino Nano am Limit. Wenn das Auslesen von Thermistor Nr. 3 auskommentiert ist, läuft das Programm aber stabil. Der Verzicht auf Thermistor Nr. 3 ist verschmerzbar, da Thermistor Nr. 4 (A7, Kühlbecherrand) völlig ausreicht, um den PID-Regler für das Peltier-Element mit Daten zu füttern. Evtl. könnte die Tabelle mit den Farbwerten für die farbliche Darstellung der Temperaturen auf dem Display in ein speicherschonendes Format gebracht oder gekürzt werden. So ließe sich Speicherplatz hinzugewinnen. // Flaschenkühler - Programmversion 0.4 // Diese Version des Programms ... // ... regelt ein Peltier-Element und einen Lüfter mit jeweils einem PID-Regler ... // ... misst dafür vier Thermistoren und berechnet die Temperaturen ... // ... stellt die Solltemperatur des Kühlbechers über zwei Taster ein ... // ... speichert die Solltemperatur im EEPROM ... // ... misst das Tachosignal des Lüfters aus und berechnet die Drehzahl ... // ... überwacht die Funktion des Lüfters ... // ... zeigt verschiedene Werte auf einem OLED-Display an ... // ... unterstützt verschiedene Anzeigemodi ... // ... zählt die Betriebsstunden (viertelstundengenau) und speichert sie im EEPROM ... // ... empfiehlt alle 100 Stunden eine Reinigung ... // ... gibt Daten an die serielle Schnittstelle in einem Format uas, das mit SerialComInstruments interpretiert werden kann ... // ... und ist damit eigentlich fertig. //------------------------- Eingebundene Bibliotheken ---------------------// #include // Grafik-Bibliothek für OLED-Display #include // Bibliothek für OLED-Display (Adafruit OLED Breakout Board 1.27") #include // Bibliothek Serial Peripheral Interface (SPI) #include // Bibliothek für Änderung der Frequenz der Timer #include // Bibliothek für Berechnung von Medianwerten #include // Bibliothek zur Glättung analoger Inpots #include // Bibliothek für PID-Regler #include // Bibliothek zum Entprellen der Taster #include // Bibliothek für Lesen und Schreiben des EEPROMS //------------------------- Definition der Inputs und Outputs ---------------------// #define thermistor1Pin A1 // Input-Pin für den Thermistor 1 (Umgebungstemperatur) #define thermistor2Pin A3 // Input-Pin für den Thermistor 2 (Kühlkörper) #define thermistor3Pin A5 // Input-Pin für den Thermistor 3 (Kühlbecherboden) #define thermistor4Pin A7 // Input-Pin für den Thermistor 4 (Kühlbecherrand) #define tachoPin 2 // Pin für Tachosignal des Lüfters #define peltierPin 3 // PWM-Pin für Peltier-Element (hier zunächst nur als Funktionstest) #define powerPin 4 // Schaltet den MOSFET für den Lüfter #define dc 5 // #define cs 6 // Chip Select #define rst 7 // Reset #define button2Pin 8 // Taster 2 #define fanPin 9 // PWM-Pin für Lüfter #define button1Pin 12 // Taster 1 //------------------------- Definition der Farben ---------------------// #define BLACK 0x0000 #define BLUE 0x0418 #define BLUEBERRY 0xF810 #define RED 0xF800 //#define GREEN 0x07E0 //#define CYAN 0x07FF //#define MAGENTA 0xF81F //#define YELLOW 0xFFE0 #define WHITE 0xFFFF //------------------------- Definition der Variablen ---------------------// float thermistor1Value = 0; // Variable in der der Wert von Thermistor 1 gespeichert wird float thermistor2Value = 0; // Variable in der der Wert von Thermistor 2 gespeichert wird //float thermistor3Value = 0; // Variable in der der Wert von Thermistor 3 gespeichert wird float thermistor4Value = 0; // Variable in der der Wert von Thermistor 4 gespeichert wird // Temperaturen float tempAmbient = 0; // Umgebungstemperatur float tempDissipator = 0; // Kühlkörpertemperatur //float tempCupbase = 0; // Kühlbecherboden float tempCupedge = 0; // Kühlbecherrand float previousTempCupedge = 0; // Wird verwendet um festzustellen, ob die Anzeige der Isttemperatur auf dem OLED-Display aktualisiert werden muss. float TargetTemp = 0; // Solltemperatur // PWM Frequenzen unsigned short frequencyFan = 25000; // PWM-Frequenz für den Lüfter (in Hz) unsigned short frequencyPeltier = 16000; // PWM-Frequenz für den Lüfter (in Hz) // Lüfter bool tachoSignal; // Tachosignal unsigned long pulseOn = 0; // Ansteigende Signalflanke (Variablenformat nicht verändern!) unsigned long pulseOff = 0; // Abfallende Signalflanke (Variablenformat nicht verändern!) unsigned long duration; // Zeit in Mikrosekunden zwischen ansteigender und abfallender Flanke (Variablenformat nicht verändern!) bool high = false; // Statevariable int rpm = 0; // Drehzahl des Lüfters in U/Min int rpmArray[10]; // Array für die bestimmung der größten Drehzahl aus den letzten 10 Werten unsigned char a = 0; int rpmMax = 0; // Maximale Drehzahl des Lüfter (aus den letzten 10 Wertens) int previousRPMMax = 0; // Zur Bestimmung von Änderungen der Lüfterdrehzahl bool fanAlert = false; // Wird wahr, wenn der Lüfter blockiert ist bool fanAlertState = true; unsigned long fanAlertDelay = 2000; // Gibt die Verzögerung des Alarms "Lüfterfehlfunktion" in Millisekunden an unsigned long previousMillis = 0; // Betriebsstundenzähler und Serviceintervall float operatingTime; // Betriebsstunden als Dezimalwert long lastTime = 0; float serviceInterval; // Serviceintervall als Dezimalwert bool serviceIntervalReset = false; // Wird wahr, wenn das Service Intervall zurückgesetzt wird // Adressen im EEPROM unsigned char addrOperatingTime = 0; // Startadresse für eine Variable im Datentyp float (4 Byte!) unsigned char addrTargetTemp = 4; // Startadresse für eine Variable im Datentyp double (8 Byte!) unsigned char addrDisplayMode = 13; // Adresse für den Anzeigemodus (2 Byte reserviert) unsigned char addrServiceInterval = 15; // Startadresse für eine Variable im Datentyp float (4 Byte!) // Definiert das OLED Adafruit_SSD1351 tft = Adafruit_SSD1351(cs, dc, rst); // Definiert ein Array, aus dem Farbwerte ausgelesen werden unsigned long colors[] = {0x07FF, 0x07DF, 0x077F, 0x06FF, 0x069F, 0x063F, 0x05DF, 0x057F, 0x053F, 0x049F, 0x043F, 0x03DF, 0x037F, 0x031F, 0x029F, 0x023F, 0x01DF, 0x017F, 0x011F, 0x009F, 0x003F, 0x001F, 0x101F, 0x201F, 0x281F, 0x381F, 0x401F,0x501F, 0x601F, 0x681F, 0x781F, 0x801F, 0x901F, 0x981F, 0xA81F, 0xB81F, 0xC01F, 0xD01F, 0xD81F, 0xE81F, 0xF81F, 0xF81F, 0xF81D, 0xF81B, 0xF81A, 0xF818, 0xF817, 0xF815, 0xF813, 0xF812, 0xF810, 0xF80F, 0xF80D, 0xF80C, 0xF80A, 0xF808, 0xF807, 0xF805, 0xF804, 0xF802, 0xF800}; // Informationsanzeige char displayMode; // Anzeigemodus ("0" = Einfach; "1" = Erweitert; "2" = grafisch) bool refreshPeltier = true; // Wird wahr, wenn der Anzeigebereich für das Peltier-Element aktualisiert werden muss bool refreshTargettemp = true; // Wird wahr, wenn der Anzeigebereich für die Solltemperatur aktualisiert werden muss bool refreshFan = true; // Wird wahr, wenn der Anzeigebereich für den Lüfter aktualisiert werden muss unsigned char x = 0; // Instantiiert ButtonEvents-Objekte ButtonEvents button1; ButtonEvents button2; // Instantiiert ein RunningMedian-Objekt zur Bechnung von Medianwerten RunningMedian medianDuration(5); // Median aus 5 Werten // Instantiirt ResponsiveAnalogRead-Objekte zur Glättung der analogen Inputs ResponsiveAnalogRead thermistor1(thermistor1Pin, true); ResponsiveAnalogRead thermistor2(thermistor2Pin, true); //ResponsiveAnalogRead thermistor3(thermistor3Pin, true); ResponsiveAnalogRead thermistor4(thermistor4Pin, true); // Definiert die PID-Regler double setpointFan, inputFan, outputFan; double fanP = 40, fanI = 1, fanD = 2; PID fanPID(&inputFan, &outputFan, &setpointFan, fanP, fanI, fanD, REVERSE); double setpointPeltier, inputPeltier, outputPeltier; double peltierP = 30, peltierI = 0.5, peltierD = 0; PID peltierPID(&inputPeltier, &outputPeltier, &setpointPeltier, peltierP, peltierI, peltierD, REVERSE); double previousOutputPeltier = 0; // Definiert die Tracking-Variablen für die IF-Abfragen unsigned long previousMillisControllers = 0; // Auslesen der analogen Inputs und Berechnung der Temperaturen unsigned long previousMillisCalculateFanSpeed = 0; // Berechnung der Drehzahl des Lüfters unsigned long previousMillisDisplayCoolingPower = 0; // Berechnung der PID-Regler (Lüfter und Peltier-Element) unsigned long previousMillisDisplayFanSpeed = 0; // Ausgabe der gemittelten Lüfterdrehzahl an das OLED-Display unsigned long previousMillisDisplayGraph = 0; // Ausgabe der gemittelten Lüfterdrehzahl an das OLED-Display unsigned long previousMillisSerialPrint = 0; // Ausgabe an die serielle Schnittstelle // Definiert die Intervalle für die IF-Abfragen in Millisekunden const unsigned long intervalControllers = 100; // Auslesen der analogen Inputs und Berechnung der Temperaturen const unsigned long intervalCalculateFanSpeed = 100; // Berechnung der Drehzahl des Lüfters const unsigned long intervalDisplayCoolingPower = 1000;// Berechnung der PID-Regler (Lüfter und Peltier-Element) const unsigned long intervalDisplayFanSpeed = 1000; // Ausgabe der gemittelten Lüfterdrehzahl an das OLED-Display const unsigned long intervalDisplayGraph = 1000; // Ausgabe der gemittelten Lüfterdrehzahl an das OLED-Display const unsigned long intervalSerialPrint = 500; // Ausgabe an die serielle Schnittstelle int loopCounter = 0; //------------------------- Setup ---------------------// void setup() { Serial.begin(115200); // Initialisiert das OLED-Dispaly tft.begin(); // Definiert AREF als externe Referenzspannung analogReference(EXTERNAL); // Definiert die Pins pinMode(tachoPin, INPUT_PULLUP); // Tachosignal des Lüfters pinMode(powerPin, OUTPUT); // Schaltet den MOSFET für den Lüfter pinMode(fanPin, OUTPUT); // PWM-Signal für Lüfter pinMode(peltierPin, OUTPUT); // PWM-Signal für Peltier-Element pinMode(button1Pin, INPUT_PULLUP); // Intur Taster 1 pinMode(button2Pin, INPUT_PULLUP); // Input Taster 2 // Initialisiert die ButtonEvents-Objekte button1.attach(button1Pin); button1.debounceTime(10); button1.doubleTapTime(60); button1.holdTime(500); button2.attach(button2Pin); button2.debounceTime(10); button2.doubleTapTime(60); button2.holdTime(500); // Initialisiert timer1 und timer2 (timer0 bleibt unberührt) InitTimersSafe(); // Definiert die Frequenzen für die angegebenen Pins SetPinFrequencySafe(fanPin, frequencyFan); SetPinFrequencySafe(peltierPin, frequencyPeltier); /* bool successFan = SetPinFrequencySafe(fanPin, frequencyFan); bool successPeltier = SetPinFrequencySafe(peltierPin, frequencyPeltier); //if the pin frequency was set successfully, turn pin 13 on if(successFan) { pinMode(13, OUTPUT); digitalWrite(13, HIGH); Serial.print("PWM frequency PIN D9 and D10 is set to: "); Serial.println(frequencyFan); } if(successPeltier) { Serial.print("PWM frequency PIN D3 and D11 is set to: "); Serial.println(frequencyPeltier); } */ // Initialisiert die PID-Regler fanPID.SetMode(AUTOMATIC); peltierPID.SetMode(AUTOMATIC); // Liest die im EEPROM gespeicherten Variablen aus displayMode = EEPROM.read(addrDisplayMode); operatingTime = EEPROM.readFloat(addrOperatingTime); serviceInterval = EEPROM.readFloat(addrServiceInterval); TargetTemp = EEPROM.readDouble(addrTargetTemp); //operatingTime = 0; // Soll auf "0" gesetzt werden, bis die Testphase abgeschlossen ist //serviceInterval = 0; // Meldung "Klar zum Start!" Serial.println(""); Serial.print(""); Serial.print(""); // Startbildschirm tft.fillScreen(BLACK); tft.setCursor(25, 32); tft.setTextColor(WHITE, BLACK); tft.setTextSize(0); tft.print("Flaschenk"); tft.print((char)154); tft.print("hler"); tft.setCursor(20, 55); tft.print("Programmversion"); tft.setCursor(45, 75); tft.setTextSize(2); tft.print("0.4"); if (serviceInterval >= 100.00) { tft.setCursor(0, 96); tft.setTextColor(RED, BLACK); tft.setTextSize(0); tft.print("Reinigung empfohlen!"); } tft.setCursor(10, 108); tft.setTextColor(WHITE, BLACK); tft.setTextSize(0); tft.print("Betriebsstunden:"); tft.setCursor(10, 121); tft.print(operatingTime); tft.print(" Stunden"); delay(2000); tft.fillScreen(BLACK); } //------------------------- Loop ---------------------// void loop() { // Aktuelle Zeit abfragen unsigned long currentMillis = millis(); loopCounter++; //------------------------- Abfrage der Taster ---------------------// // Lesen und entprellen des Tasters button1.update(); button2.update(); if (button1.tapped() == true) { TargetTemp = TargetTemp + 0.5; /*outputPeltier = outputPeltier + 10; if (outputPeltier >= 255) { outputPeltier = 255; } */ EEPROM.updateDouble(addrTargetTemp, TargetTemp); Serial.print("Updated TargetTemp: "); Serial.println(TargetTemp); refreshTargettemp = true; if (TargetTemp >= 30.00) { TargetTemp = 30.00; } } if (button1.held() == true) { displayMode++; if (displayMode > 2) { displayMode = 0; } tft.fillScreen(BLACK); // Der Bildschirm muss einmalig gelöscht werden EEPROM.update(addrDisplayMode, displayMode); //Serial.print("Displaymode: "); Serial.println(displayMode); refreshPeltier = true; refreshTargettemp = true; previousTempCupedge = 150; refreshFan= true; x = 0; } if (button1.doubleTapped() == true) { serviceInterval = 0; EEPROM.updateFloat(addrServiceInterval, serviceInterval); Serial.println("Service Interval reseted!"); serviceIntervalReset = true; } if (button2.tapped() == true) { TargetTemp = TargetTemp - 0.5; /*outputPeltier = outputPeltier -10; if (outputPeltier <= 0.00) { outputPeltier = 0.00; } */ EEPROM.updateDouble(addrTargetTemp, TargetTemp); Serial.print("Updated TargetTemp: "); Serial.println(TargetTemp); refreshTargettemp = true; if (TargetTemp <= 0.00) { TargetTemp = 0.00; } } if (button2.held() == true) { displayMode--; if (displayMode < 0) { displayMode = 2; } tft.fillScreen(BLACK); // Der Bildschirm muss einmalig gelöscht werden EEPROM.update(addrDisplayMode, displayMode); //Serial.print("Displaymode: "); Serial.println(displayMode); refreshPeltier = true; refreshTargettemp = true; previousTempCupedge = 150; refreshFan= true; x = 0; } //------------------------- Auslesen der analogen Inputs (Thermistoren), Berechnung der Temperaturen und Regelung ---------------------// if ((unsigned long)(currentMillis - previousMillisControllers) >= intervalControllers) { // Widerstandswerte // R1 (A1) = 10000 // R2 (A3) = 10500 // R3 (A5) = 10400 // R4 (A7) = 10300 // Thermistor 1: Umgebungstemperatur thermistor1.update(); thermistor1Value = thermistor1.getValue(); // Das Ergebnis wird ausgelesen // Berechnung der Temperatur thermistor1Value = 1023 / thermistor1Value - 1; thermistor1Value = 10000 / thermistor1Value; // Der Wert wird in einen Widerstand umgerechnet tempAmbient = thermistor1Value / 10000; // (R/Ro) tempAmbient = log(tempAmbient); // ln(R/Ro) tempAmbient /= 3950; // 1/B * ln(R/Ro) tempAmbient += 1.0 / (25 + 273.15); // + (1/To) tempAmbient = 1.0 / tempAmbient; // Invert tempAmbient -= 273.15; // convert to C // Thermistor 2: Kühlkörper thermistor2.update(); // Das ResponsiveAnalogRead-Objekt wird aktualisiert thermistor2Value = thermistor2.getValue(); // Das Ergebnis wird ausgelesen // Berechnung der Temperatur thermistor2Value = 1023 / thermistor2Value - 1; thermistor2Value = 10500 / thermistor2Value; // Der Wert wird in einen Widerstand umgerechnet tempDissipator = thermistor2Value / 10000; // (R/Ro) tempDissipator = log(tempDissipator); // ln(R/Ro) tempDissipator /= 3950; // 1/B * ln(R/Ro) tempDissipator += 1.0 / (25 + 273.15); // + (1/To) tempDissipator = 1.0 / tempDissipator; // Invert tempDissipator -= 273.15; // convert to C /* // Thermistor 3: Kühlbecherboden thermistor3.update(); // Das ResponsiveAnalogRead-Objekt wird aktualisiert thermistor3Value = thermistor3.getValue(); // Das Ergebnis wird ausgelesen // Berechnung der Temperatur thermistor3Value = 1023 / thermistor3Value - 1; thermistor3Value = 10400 / thermistor3Value; // Der Wert wird in einen Widerstand umgerechnet tempCupbase = thermistor3Value / 10000; // (R/Ro) tempCupbase = log(tempCupbase); // ln(R/Ro) tempCupbase /= 3950; // 1/B * ln(R/Ro) tempCupbase += 1.0 / (25 + 273.15); // + (1/To) tempCupbase = 1.0 / tempCupbase; // Invert tempCupbase -= 273.15; // convert to C */ // Thermistor 4: Kühlbecherrand thermistor4.update(); // Das ResponsiveAnalogRead-Objekt wird aktualisiert thermistor4Value = thermistor4.getValue(); // Das Ergebnis wird ausgelesen // Berechnung der Temperatur thermistor4Value = 1023 / thermistor4Value - 1; thermistor4Value = 10300 / thermistor4Value; // Der Wert wird in einen Widerstand umgerechnet tempCupedge = thermistor4Value / 10000; // (R/Ro) tempCupedge = log(tempCupedge); // ln(R/Ro) tempCupedge /= 3950; // 1/B * ln(R/Ro) tempCupedge += 1.0 / (25 + 273.15); // + (1/To) tempCupedge = 1.0 / tempCupedge; // Invert tempCupedge -= 273.15; // convert to C // PID-Regler Lüfter inputFan = tempDissipator; // Input ist die Kühlkörpertemperatur in *C setpointFan = tempAmbient + 10.00; // Umgebungstemperatur + 5 *C fanPID.Compute(); // PID-Regler wir aufgerufen pwmWrite(fanPin, outputFan); // Gibt den Output des PID-Reglers an den Lüfter // Wenn der Output des PID-Reglers Null ist, wird der Lüfter ausgeschaltet. if (outputFan > 0) { digitalWrite(powerPin, HIGH); //Serial.print("HIGH"); Serial.print("; "); } else { digitalWrite(powerPin, LOW); //Serial.print("LOW"); Serial.print("; "); } // PID-Regler Peltier-Element inputPeltier = tempCupedge; // Input ist die temperatureHotSide des Thermistors in *C setpointPeltier = TargetTemp; peltierPID.Compute(); // PID-Regler wir aufgerufen pwmWrite(peltierPin, outputPeltier); // Gibt den Output des PID-Reglers an das Peltier-Element // Überpüft, ob outputPeltier sich geändert hat. if (byte(previousOutputPeltier) != byte(outputPeltier)) { // Änderungen hinter dem Komma sollen ignoriert werden refreshPeltier = true; //Serial.println("refreshPeltier: "); previousOutputPeltier = outputPeltier; } previousMillisControllers = currentMillis; } //------------------------- Tachosignal und Drehzahl des Lüfters ---------------------// // Messung der Pulsweite des Tachosignals tachoSignal = digitalRead(tachoPin); if (tachoSignal == HIGH && high != true) { // Zeit in micros bei ansteigender Flanke pulseOn = micros(); high = true; } else if (tachoSignal == LOW && high == true) { pulseOff = micros(); // Zeit in micros bei fallender Flanke high = false; duration = pulseOff - pulseOn; // Aus der Differenz wir die Dauer berechnet, die das Tachosignal HIGH ist if (duration > 7500 && duration < 30000) { // Liegt die Variable über bzw. unter den angegebenen Werten, liegt ein Messfehler vor medianDuration.add(duration); // Speichert die Daten in ein Arr } } // Berechnung der Drehzahl des Lüfters if ((unsigned long)(currentMillis - previousMillisCalculateFanSpeed) >= intervalCalculateFanSpeed) { //Berechnung der Drehzal if (outputFan == 0) { // Wenn der Lüfter ausgeschaltet wird, soll die Drehzahl "0" angezeigt werden rpm = 0; } else { rpm = float(100000 * 2 * 60 / medianDuration.getMedian()); // Berechnung der RPM } // Bestimmung der höchsten Drehzahl aus den letzten 10 Werten a++; // Es werden 10 Werte in einem Array gespeichert rpmArray[a] = rpm; if (a > 9) { a = 0; } unsigned char kmax=0; // Es wird der größte Wert in dem Arrach bestimmt int max=0; for (unsigned char k=0; k<10; k++) { if (rpmArray[k] > max) { max = rpmArray[k]; kmax = k; } } rpmMax = rpmArray[kmax]; // Überpüft, ob rpmMax sich geändert hat. if (previousRPMMax != rpmMax) { // Änderungen hinter dem Komma sollen ignoriert werden refreshFan = true; //Serial.println("refreshFan"); previousRPMMax = rpmMax; } /*/Debugging Serial.print("; pulseOn: ");Serial.print(pulseOn); Serial.print("; pulseOff: ");Serial.print(pulseOff); Serial.print("; Duration: "); Serial.print(duration); Serial.print("; RPM: "); Serial.print(rpm); Serial.print("; average RPM: "); Serial.print(rpmMax); //Serial.print("; fanAlert: "); Serial.print(fanAlert); //Serial.print("; temperatureHotSide: "); Serial.println(temperatureHotSide); //Serial.print("; operatingTime: "); Serial.print(operatingTime); //Serial.print("; serviceInterval: "); Serial.print(serviceInterval); //Serial.print("; Displaymode: "); Serial.print(displayMode); Serial.print("; Loops: "); Serial.println(loopCounter); */ // Meldet eine Fehlfunktion des Lüfters, wenn das Tachosignal "0" ist, obwohl der Lüfter sich drehen sollte if (millis() >= 10000) { // Diese Funktion wird erst nach 10 Sekunden aktiv. if (outputFan > 0) { // Wenn der Lüfter sich drehen sollte ... if (rpmMax == 0) { // ... aber die gemittelte Drehzahl gleich "0" ist ... if (millis() - previousMillis > fanAlertDelay) { // ... und eine definierte Zeit verstrichen ist. previousMillis = millis(); fanAlert = true; fanAlertState = true; Serial.println("Fan blocked!"); refreshFan = true; } } else if (rpmMax > 0) { // ... und die gemittelte Drehzahl größer "0" ist ... fanAlert = false; } } else if (outputFan == 0) { // Wenn der Lüfter sich nicht drehen soll kann nicht festgestellt werden, ob er blockiert ist. fanAlert = false; previousMillis = millis(); // Muss aktualisiert werden, damit if (fanAlert != fanAlertState) { refreshFan = true; fanAlertState = false; } } } previousMillisCalculateFanSpeed = currentMillis; } //------------------------- Betriebsstunden ---------------------// // Die Betriebsstunden werden alle 15 Minuten im EEPROM gespeichert. Die Einheit der Variable operatinTime ist also 0,25 Stunden. if (millis() - lastTime >= 900000) { // Betriebsstundenzähler operatingTime = operatingTime + 0.25; lastTime = millis(); EEPROM.updateFloat(addrOperatingTime, operatingTime); Serial.print("Updated operatingTime: "); Serial.println(operatingTime); // Serviceintervallzähler serviceInterval = serviceInterval + 0.25; EEPROM.updateFloat(addrServiceInterval, serviceInterval); } //------------------------- Ausgabe an das Display ---------------------// // Anzeige "Serviceintervall zurückgesetzt if (serviceIntervalReset == true) { tft.fillScreen(BLACK); tft.setCursor(15, 65); tft.setTextColor(WHITE, BLACK); tft.setTextSize(0); tft.print("Service Intervall"); tft.setCursor(26, 85); tft.print("zur"); tft.print((char)154); tft.print("ckgesetzt"); delay(2000); tft.fillScreen(BLACK); refreshPeltier = true; refreshTargettemp = true; previousTempCupedge = 150; refreshFan= true; serviceIntervalReset = false; } // Anzeigemodus "0" (einfach) if (displayMode == 0) { // Anzeige Infobereich Zieltemperatur if (refreshTargettemp == true) { // Wenn die Solltemperatur verändert wurde, muss die Anzeige aktualisiert werden. tft.setCursor(15, 49); unsigned char i = TargetTemp * 2; // Berechnet den Index für den Array "colors" if (i < 0) i = 0; // i soll nicht kleiner als "0" werden if (i > 60) i = 60; // i soll nicht größer als "60" werden tft.setTextColor(colors[i], BLACK); tft.setTextSize(0); tft.print("Solltemperatur"); tft.setCursor(15, 62); tft.setTextSize(2); if (TargetTemp < 10.00) { tft.print("0"); } tft.print(TargetTemp); tft.print(" "); tft.print((char)247); tft.print("C"); tft.setTextColor(BLACK, BLACK); tft.print((char)218); refreshTargettemp = false; } // Anzeige Infobereich Isttemperatur if (tempCupedge != previousTempCupedge) { // Wenn sich die Isttemperatur verändert hat, muss das Display aktualisiert werden. tft.setCursor(15, 85); unsigned char i = tempCupedge * 2; // Berechnet den Index für den Array "colors" if (i < 0) i = 0; // i soll nicht kleiner als "0" werden if (i > 60) i = 60; // i soll nicht größer als "60" werden tft.setTextColor(colors[i], BLACK); tft.setTextSize(0); tft.print("Isttemperatur"); tft.setCursor(15, 98); tft.setTextSize(2); if (tempCupedge < 10.00) { tft.print("0"); } tft.print(tempCupedge); tft.print(" "); tft.print((char)247); tft.print("C"); tft.setTextColor(BLACK, BLACK); tft.print((char)218); previousTempCupedge = tempCupedge; } } // Anzeigemodus "1" (erweitert) if (displayMode == 1) { // Anzeige Infobereich Peltier-Element if ((unsigned long)(currentMillis - previousMillisDisplayCoolingPower) >= intervalDisplayCoolingPower) { if (outputPeltier > 0) { if (refreshPeltier == true) { tft.setCursor(7, 32); tft.setTextSize(0); tft.setTextColor(WHITE, BLACK); tft.print("K"); tft.print((char)154); tft.print("hlleistung "); tft.print(int(outputPeltier * 0.39 + 0.55)); tft.print(" %"); tft.setTextColor(BLACK, BLACK); for (unsigned char i=0; i<3; i++) { // Füllt die Zeile mit schwarzen Kästchen; i muss ggf. angepasst werden tft.print((char)218); } refreshPeltier = false; } } else if (outputPeltier == 0) { if (refreshPeltier == true) { tft.setCursor(7, 32); tft.setTextColor(WHITE, BLACK); tft.setTextSize(0); tft.print("K"); tft.print((char)154); tft.print("hlelement aus"); tft.setTextColor(BLACK, BLACK); for (unsigned char i=0; i<3; i++) { // Füllt die Zeile mit schwarzen Kästchen; i muss ggf. angepasst werden tft.print((char)218); } refreshPeltier = false; } } previousMillisDisplayCoolingPower = currentMillis; } // Anzeige Infobereich Solltemperatur if (refreshTargettemp == true) { tft.setCursor(15, 49); unsigned char i = TargetTemp * 2; // Berechnet den Index für den Array "colors" if (i < 0) i = 0; // i soll nicht kleiner als "0" werden if (i > 60) i = 60; // i soll nicht größer als "60" werden tft.setTextColor(colors[i], BLACK); tft.setTextSize(0); tft.print("Solltemperatur"); tft.setCursor(15, 62); tft.setTextSize(2); if (TargetTemp < 10.00) { tft.print("0"); } tft.print(TargetTemp); tft.print(" "); tft.print((char)247); tft.print("C"); tft.setTextColor(BLACK, BLACK); tft.print((char)218); refreshTargettemp = false; } // Anzeige Infobereich Isttemperatur if (tempCupedge != previousTempCupedge) { tft.setCursor(15, 85); unsigned char i = tempCupedge * 2; // Berechnet den Index für den Array "colors" if (i < 0) i = 0; // i soll nicht kleiner als "0" werden if (i > 60) i = 60; // i soll nicht größer als "60" werden tft.setTextColor(colors[i], BLACK); tft.setTextSize(0); tft.print("Isttemperatur"); tft.setCursor(15, 98); tft.setTextSize(2); if (tempCupedge < 10.00) { tft.print("0"); } tft.print(tempCupedge); tft.print(" "); tft.print((char)247); tft.print("C"); tft.setTextColor(BLACK, BLACK); tft.print((char)218); previousTempCupedge = tempCupedge; } // Anzeige Infobereich Lüfter if (fanAlert == true) { if (refreshFan == true) { tft.setCursor(0, 121); tft.setTextSize(0); tft.setTextColor(RED, BLACK); tft.print("L"); tft.print((char)154); tft.print("fterfehlfunktion!"); refreshFan = false; } } else if (fanAlert == false) { if ((unsigned long)(currentMillis - previousMillisDisplayFanSpeed) >= intervalDisplayFanSpeed) { if (rpmMax > 0) { // Wenn die Drehzahl größer "0" ist, aktualisiere den Anzeigebereich all 500 Millisekunden if (refreshFan == true) { tft.setCursor(12, 121); tft.setTextSize(0); tft.setTextColor(WHITE, BLACK); tft.print("L"); tft.print((char)154); tft.print("fter "); tft.print(rpmMax); tft.print(" U/min"); tft.setTextColor(BLACK, BLACK); for (unsigned char i=0; i<5; i++) { // Füllt die Zeile mit schwarzen Kästchen; i muss ggf. angepasst werden tft.print((char)218); } refreshFan = false; } } else if (rpmMax == 0) { if (refreshFan == true) { tft.setCursor(12, 121); tft.setTextSize(0); tft.setTextColor(WHITE, BLACK); tft.print("L"); tft.print((char)154); tft.print("fter aus"); tft.setTextColor(BLACK, BLACK); for (unsigned char i=0; i<12; i++) { // Füllt die Zeile mit schwarzen Kästchen; i muss ggf. angepasst werden tft.print((char)218); } refreshFan = false; } } previousMillisDisplayFanSpeed = currentMillis; } } } // Anzeigemodus "2" (grafisch) if (displayMode == 2) { // Anzeige Solltemperatur if (refreshTargettemp == true) { tft.setCursor(0, 32); tft.setTextColor(WHITE, BLACK); tft.setTextSize(0); tft.print("Soll "); if (TargetTemp < 10.00) { tft.print("0"); } tft.print(TargetTemp); refreshTargettemp = false; } // Anzeige Isttemperatur if (tempCupedge != previousTempCupedge) { tft.setCursor(70, 32); tft.setTextSize(0); unsigned char i = tempCupedge * 2; // Berechnet den Index für den Array "colors" if (i < 0) i = 0; // i soll nicht kleiner als "0" werden if (i > 60) i = 60; // i soll nicht größer als "60" werden tft.setTextColor(colors[i], BLACK); tft.print("Ist "); if (tempCupedge < 10.00) { tft.print("0"); } tft.print(tempCupedge); tft.setTextColor(BLACK, BLACK); tft.print((char)218); previousTempCupedge = tempCupedge; } // Anzeige Graphen if ((unsigned long)(currentMillis - previousMillisDisplayGraph) >= intervalDisplayGraph) { x++; if (x >= 128) { x = 0; //tft.fillScreen(BLACK); tft.fillRect(0, 40, 128, 80, BLACK); //tft.drawLine(0, 44, 127, 44, WHITE); //tft.drawLine(0, 114, 127, 114, WHITE); //previousTempHotSide = 150; //refreshTargettemp = true; //refreshFan = true; //refreshPeltier = true; } // Graph Solltemperatur unsigned char tt = 114 - TargetTemp * 2; tft.drawPixel(x, tt, WHITE); // Graph Isttemperatur unsigned char tc = 114 - tempCupedge *2; unsigned char i = tempCupedge * 2; // Berechnet den Index für den Array "colors" if (i < 0) i = 0; // i soll nicht kleiner als "0" werden if (i > 60) i = 60; // i soll nicht größer als "60" werden tft.drawPixel(x, tc, colors[i]); // Graph Kühlleistung unsigned char p = 114 - outputPeltier * 0.25; tft.drawPixel(x, p, BLUEBERRY); // Graph Lüfterdrehzahl unsigned char d = 114 - rpmMax / 21; tft.drawPixel(x, d, BLUE); previousMillisDisplayGraph = currentMillis; } // Anzeige Peltier-Element if ((unsigned long)(currentMillis - previousMillisDisplayCoolingPower) >= intervalDisplayCoolingPower) { if (refreshPeltier == true) { tft.setCursor(0, 121); tft.setTextColor(BLUEBERRY, BLACK); tft.setTextSize(0); tft.print(int(outputPeltier * 0.39 + 0.55)); tft.print(" %"); tft.setTextColor(BLACK, BLACK); for (unsigned char i=0; i<2; i++) { // Füllt die Zeile mit schwarzen Kästchen; i muss ggf. angepasst werden tft.print((char)218); } refreshPeltier = false; } previousMillisDisplayCoolingPower = currentMillis; } // Anzeige Lüfter if ((unsigned long)(currentMillis - previousMillisDisplayFanSpeed) >= intervalDisplayFanSpeed) { tft.setCursor(64, 121); tft.setTextSize(0); tft.setTextColor(BLUE, BLACK); tft.print(rpmMax); tft.print(" U/min "); tft.setTextColor(BLACK, BLACK); for (unsigned char i=0; i<3; i++) { // Füllt die Zeile mit schwarzen Kästchen; i muss ggf. angepasst werden tft.print((char)218); } refreshFan = true; previousMillisDisplayFanSpeed = currentMillis; } } //------------------------- Ausgabe an die serielle Schnittstelle ---------------------// if ((unsigned long)(currentMillis - previousMillisSerialPrint) >= intervalSerialPrint) { // Ausgabe für den seriellen Monitor /* //Serial.print("tempAmbient: ");Serial.print(tempAmbient); //Serial.print("; tempDissipator: ");Serial.print(tempDissipator); //Serial.print("; tempCupbase: ");Serial.print(tempCupbase); Serial.print("; setpointFan: ");Serial.print(setpointFan); //Serial.print("; inputFan: ");Serial.print(inputFan); Serial.print("; outputFan: ");Serial.print(outputFan); //Serial.print("; setpointPeltier: ");Serial.print(setpointPeltier); //Serial.print("; inputPeltier: ");Serial.print(inputPeltierFan); //Serial.print("; outputPeltier: ");Serial.print(outputPeltier); //Serial.print("; pulseOn: ");Serial.print(pulseOn); //Serial.print("; pulseOff: ");Serial.print(pulseOff); //Serial.print("; Duration: "); Serial.print(duration); Serial.print("; RPM: "); Serial.print(rpm); Serial.print("; average RPM: "); Serial.print(rpmMax); //Serial.print("; fanAlert: "); Serial.print(fanAlert); //Serial.print("; temperatureHotSide: "); Serial.println(temperatureHotSide); //Serial.print("; operatingTime: "); Serial.print(operatingTime); //Serial.print("; serviceInterval: "); Serial.print(serviceInterval); //Serial.print("; Displaymode: "); Serial.print(displayMode); Serial.print("; Loops: "); Serial.println(loopCounter); */ // Ausgabe für SerialComInstruments 4.1 Serial.print("#1M"); Serial.print(setpointFan); Serial.print("<"); Serial.print("#2M"); Serial.print(tempDissipator); Serial.print("<"); Serial.print("#3M"); Serial.print(rpmMax); Serial.print("<"); Serial.print("#4M"); Serial.print(TargetTemp); Serial.print("<"); //Serial.print("#5M"); Serial.print(tempCupbase); Serial.print("<"); Serial.print("#6M"); Serial.print(outputPeltier); Serial.print("<"); Serial.print("#7M"); Serial.print(tempAmbient); Serial.print("<"); Serial.print("#8M"); Serial.print(loopCounter); Serial.print("<"); Serial.print("#9M"); Serial.print(tempCupedge); Serial.println("<"); loopCounter = 0; //Speichere die aktuelle Zeit in die zughörige Variable previousMillisSerialPrint = currentMillis; } } Der Sketch verwendet 24428 Bytes (79%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes. Globale Variablen verwenden 1700 Bytes (83%) des dynamischen Speichers, 348 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes. Tags: #Arduino #OLED #Peltier