arduino:flaschenkuehler:programmversion_0.5
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
Beide Seiten der vorigen RevisionVorhergehende ÜberarbeitungNächste Überarbeitung | Vorhergehende Überarbeitung | ||
arduino:flaschenkuehler:programmversion_0.5 [09.09.2017 20:47] – Frickelpiet | arduino:flaschenkuehler:programmversion_0.5 [18.05.2023 12:34] (aktuell) – Externe Bearbeitung 127.0.0.1 | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
- | ====== Flaschenkühler - Programmversion 0.5 (RC2)====== | + | ====== Flaschenkühler - Programmversion 0.5 ====== |
Diese Programmversion wurde einem ausführlichen Praxistest unterzogen (ca. 150 Stunden). Die dabei gewonnenen Erkenntnisse flossen in letzte Programmverbesserungen ein. | Diese Programmversion wurde einem ausführlichen Praxistest unterzogen (ca. 150 Stunden). Die dabei gewonnenen Erkenntnisse flossen in letzte Programmverbesserungen ein. | ||
- | Es zeigte sich, dass der grafische Anzeigemodus in der Art und Weise, wie er mit dem Arduino Nano umgesetzt werden kann, wenig informativ ist. Er wurde ersetzt | + | Es zeigte sich, dass der grafische Anzeigemodus in der Art und Weise, wie er mit dem Arduino Nano umgesetzt werden kann, wenig informativ ist. Ich habe ihn daher ersetzt durch einen " |
* Solltemperatur | * Solltemperatur | ||
* Isttemperatur Becherrand | * Isttemperatur Becherrand | ||
Zeile 14: | Zeile 14: | ||
Der dadurch frei gewordene dynamische Speicher wird genutzt, um auch den Thermistor am Becherboden auszulesen. Er fleißt aber nicht in die Regelung des Peltier-Elements ein. | Der dadurch frei gewordene dynamische Speicher wird genutzt, um auch den Thermistor am Becherboden auszulesen. Er fleißt aber nicht in die Regelung des Peltier-Elements ein. | ||
- | Außerdem wurde der Wert für den Activity threshold für die Thermistoren Nr. 2 und Nr. 4 von 4 (default) auf 7 erhöht, damit die Isttemperatur am Kühlbecherrand und des Kühlkörpers weniger " | + | Außerdem wurde der Wert für den Activity threshold für die Thermistoren Nr. 2, Nr. 3 und Nr. 4 von 4 (default) auf 6 erhöht, damit die Isttemperatur am Kühlbecherrand und des Kühlkörpers weniger " |
- | Schließlich wurde der Wert reduziert, der auf die gemessene Umgebungstemperatur addiert wird und der Setpoint für den PID-Regler des Lüfters ist. Experimentell zeigt sich, dass die Reduktion der Kühlkörpertemperatur um 1 °C dazu führt, dass das Peltierelement mit 5% weniger Leistung aufbringen muss, um eine Temperatur von 8°C halten zu können. Es zeigt sich, dass ein Offset von 8 °C optimal ist. Wird der Offset-Wert kleiner angesetzt, läuft der Lüfter sehr häufig mit hohen Drehzahlen. | + | <del>Schließlich wurde der Wert reduziert, der auf die gemessene Umgebungstemperatur addiert wird und der Setpoint für den PID-Regler des Lüfters ist. Experimentell zeigt sich, dass die Reduktion der Kühlkörpertemperatur um 1 °C dazu führt, dass das Peltierelement mit 5% weniger Leistung aufbringen muss, um eine Temperatur von 8°C halten zu können. Es zeigt sich, dass ein Offset von 8 °C optimal ist. Wird der Offset-Wert kleiner angesetzt, läuft der Lüfter sehr häufig mit hohen Drehzahlen.</ |
- | ToDo: | + | Das Array mit den Farbwerten für die Temperaturanzeigen auf dem OLED-Display wurde mit PROGMEM in das EEPROM verlegt, da sonst der Arbeitsspeicher |
- | * Arbeitsspeicher | + | |
- | * Berechnung der Lüfterdrehzahl: | + | |
- | Tags: #Arduino # | + | To Dos: |
+ | * Nach etwa 250 Betriebsstunden zeigt der Thermistor am Becherrand viel zu hohe Temperaturen an. Ich fürchte, er ist kaputt. Kurz darauf hat der Thermistor am Becherboden das gleiche Verhalten gezeigt. | ||
+ | * Da Thermistoren offenbar kaputt gehen können, sollte für die Kühlung der heißen Seite eine Schutzfunktion für Überhitzung programmiert werden. Diese könnte so aussehen, dass der Lüfter in einen vom PID-Regler unabhängigen Notfallmodus schaltet, wenn der Duty Cycle des Peltierelements über eine gewisse Zeit über beispielsweise 30 % liegt. Der Prozentwert sollte von der passiven Kühlfähigkeit des Kühlkörpers abhängig sein. | ||
+ | |||
+ | < | ||
+ | // Flaschenkühler - Programmversion 0.5 | ||
+ | // 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. | ||
+ | |||
+ | // | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | #include < | ||
+ | |||
+ | // | ||
+ | #define thermistor1Pin | ||
+ | #define thermistor2Pin | ||
+ | #define thermistor3Pin | ||
+ | #define thermistor4Pin | ||
+ | #define tachoPin | ||
+ | #define peltierPin | ||
+ | #define powerPin | ||
+ | #define dc 5 // | ||
+ | #define cs 6 // Chip Select | ||
+ | #define rst | ||
+ | #define button2Pin | ||
+ | #define fanPin | ||
+ | #define button1Pin | ||
+ | |||
+ | |||
+ | // | ||
+ | #define BLACK | ||
+ | //#define BLUE 0x0418 | ||
+ | //#define BLUEBERRY | ||
+ | #define RED | ||
+ | //#define GREEN | ||
+ | //#define CYAN 0x07FF | ||
+ | //#define MAGENTA | ||
+ | //#define YELLOW | ||
+ | #define WHITE | ||
+ | |||
+ | |||
+ | // | ||
+ | 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, | ||
+ | float TargetTemp = 0; // Solltemperatur | ||
+ | |||
+ | // PWM Frequenzen | ||
+ | unsigned short frequencyFan = 25000; | ||
+ | unsigned short frequencyPeltier = 16000; | ||
+ | |||
+ | // Lüfter | ||
+ | bool tachoSignal; | ||
+ | unsigned long pulseOn = 0; // Ansteigende Signalflanke (Variablenformat nicht verändern!) | ||
+ | unsigned long pulseOff = 0; // Abfallende Signalflanke (Variablenformat nicht verändern!) | ||
+ | unsigned long duration; | ||
+ | bool high = false; | ||
+ | int rpm = 0; // Drehzahl des Lüfters in U/Min | ||
+ | int rpmArray[10]; | ||
+ | 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; | ||
+ | bool fanAlertState = true; | ||
+ | unsigned long fanAlertDelay = 2000; // Gibt die Verzögerung des Alarms " | ||
+ | unsigned long previousMillis = 0; | ||
+ | |||
+ | // Betriebsstundenzähler und Serviceintervall | ||
+ | float operatingTime; | ||
+ | long lastTime = 0; | ||
+ | float serviceInterval; | ||
+ | bool serviceIntervalReset = false; | ||
+ | |||
+ | // 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, | ||
+ | |||
+ | // Definiert ein Array, aus dem Farbwerte ausgelesen werden. Das Array wird mit PROGMEM in den Flash-Speicher geladen, um dynamischen Speicher zu sparen. | ||
+ | const unsigned int colors[] PROGMEM = {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, | ||
+ | 0xF81F, 0xF81F, 0xF81D, 0xF81B, 0xF81A, 0xF818, 0xF817, 0xF815, 0xF813, 0xF812, 0xF810, 0xF80F, 0xF80D, 0xF80C, 0xF80A, 0xF808, 0xF807, 0xF805, 0xF804, 0xF802, 0xF800}; | ||
+ | |||
+ | // Informationsanzeige | ||
+ | char displayMode; | ||
+ | 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 i = 0; | ||
+ | |||
+ | // Instantiiert ButtonEvents-Objekte | ||
+ | ButtonEvents button1; | ||
+ | ButtonEvents button2; | ||
+ | |||
+ | // Instantiiert ein RunningMedian-Objekt zur Bechnung von Medianwerten | ||
+ | RunningMedian medianDuration(5); | ||
+ | |||
+ | // Instantiirt ResponsiveAnalogRead-Objekte zur Glättung der analogen Inputs | ||
+ | ResponsiveAnalogRead thermistor1(thermistor1Pin, | ||
+ | ResponsiveAnalogRead thermistor2(thermistor2Pin, | ||
+ | ResponsiveAnalogRead thermistor3(thermistor3Pin, | ||
+ | ResponsiveAnalogRead thermistor4(thermistor4Pin, | ||
+ | |||
+ | // Definiert die PID-Regler | ||
+ | double setpointFan, | ||
+ | double fanP = 40, fanI = 1, fanD = 2; | ||
+ | PID fanPID(& | ||
+ | |||
+ | double setpointPeltier, | ||
+ | double peltierP = 30, peltierI = 0.5, peltierD = 0; | ||
+ | PID peltierPID(& | ||
+ | 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 | ||
+ | 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; | ||
+ | |||
+ | |||
+ | // | ||
+ | void setup() { | ||
+ | Serial.begin(115200); | ||
+ | |||
+ | // Initialisiert das OLED-Dispaly | ||
+ | tft.begin(); | ||
+ | |||
+ | // Definiert AREF als externe Referenzspannung | ||
+ | analogReference(EXTERNAL); | ||
+ | |||
+ | // Definiert die Pins | ||
+ | pinMode(tachoPin, | ||
+ | pinMode(powerPin, | ||
+ | pinMode(fanPin, | ||
+ | pinMode(peltierPin, | ||
+ | pinMode(button1Pin, | ||
+ | pinMode(button2Pin, | ||
+ | |||
+ | // Initialisiert die ButtonEvents-Objekte | ||
+ | button1.attach(button1Pin); | ||
+ | button1.debounceTime(10); | ||
+ | button1.doubleTapTime(50); | ||
+ | button1.holdTime(500); | ||
+ | button2.attach(button2Pin); | ||
+ | button2.debounceTime(10); | ||
+ | button2.doubleTapTime(50); | ||
+ | button2.holdTime(500); | ||
+ | |||
+ | // Initialisiert timer1 und timer2 (timer0 bleibt unberührt) | ||
+ | InitTimersSafe(); | ||
+ | |||
+ | // Definiert die Frequenzen für die angegebenen Pins | ||
+ | SetPinFrequencySafe(fanPin, | ||
+ | SetPinFrequencySafe(peltierPin, | ||
+ | |||
+ | // Definiert die ResponsiveAnalogRead-Objekte | ||
+ | thermistor2.setActivityThreshold(6); | ||
+ | thermistor3.setActivityThreshold(6); | ||
+ | thermistor4.setActivityThreshold(6); | ||
+ | |||
+ | /* | ||
+ | bool successFan = SetPinFrequencySafe(fanPin, | ||
+ | bool successPeltier = SetPinFrequencySafe(peltierPin, | ||
+ | |||
+ | //if the pin frequency was set successfully, | ||
+ | if(successFan) { | ||
+ | pinMode(13, OUTPUT); | ||
+ | digitalWrite(13, | ||
+ | Serial.print(" | ||
+ | } | ||
+ | if(successPeltier) { | ||
+ | Serial.print(" | ||
+ | } | ||
+ | */ | ||
+ | // 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); | ||
+ | |||
+ | // Meldung "Klar zum Start!" | ||
+ | Serial.println("< | ||
+ | Serial.print("< | ||
+ | Serial.print("< | ||
+ | |||
+ | // Startbildschirm | ||
+ | tft.fillScreen(BLACK); | ||
+ | tft.setCursor(25, | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setCursor(20, | ||
+ | tft.print(" | ||
+ | tft.setCursor(45, | ||
+ | tft.setTextSize(2); | ||
+ | tft.print(" | ||
+ | if (serviceInterval >= 100.00) { | ||
+ | tft.setCursor(0, | ||
+ | tft.setTextColor(RED, | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.setCursor(10, | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setCursor(10, | ||
+ | tft.print(operatingTime); | ||
+ | |||
+ | delay(2000); | ||
+ | tft.fillScreen(BLACK); | ||
+ | } | ||
+ | |||
+ | |||
+ | // | ||
+ | void loop() { | ||
+ | // Aktuelle Zeit abfragen | ||
+ | unsigned long currentMillis = millis(); | ||
+ | |||
+ | // | ||
+ | |||
+ | |||
+ | // | ||
+ | // Lesen und entprellen des Tasters | ||
+ | button1.update(); | ||
+ | button2.update(); | ||
+ | |||
+ | if (button1.tapped() == true) { | ||
+ | TargetTemp = TargetTemp + 0.5; | ||
+ | EEPROM.updateDouble(addrTargetTemp, | ||
+ | Serial.print(" | ||
+ | refreshTargettemp = true; | ||
+ | if (TargetTemp >= 30.00) { | ||
+ | TargetTemp = 30.00; | ||
+ | } | ||
+ | } | ||
+ | if (button1.held() == true) { | ||
+ | displayMode++; | ||
+ | if (displayMode > 2) { | ||
+ | displayMode = 0; | ||
+ | } | ||
+ | tft.fillScreen(BLACK); | ||
+ | EEPROM.update(addrDisplayMode, | ||
+ | // | ||
+ | refreshPeltier = true; | ||
+ | refreshTargettemp = true; | ||
+ | previousTempCupedge = 150; | ||
+ | refreshFan = true; | ||
+ | } | ||
+ | if (button1.doubleTapped() == true) { | ||
+ | serviceInterval = 0; | ||
+ | EEPROM.updateFloat(addrServiceInterval, | ||
+ | Serial.println(" | ||
+ | serviceIntervalReset = true; | ||
+ | } | ||
+ | |||
+ | if (button2.tapped() == true) { | ||
+ | TargetTemp = TargetTemp - 0.5; | ||
+ | EEPROM.updateDouble(addrTargetTemp, | ||
+ | Serial.print(" | ||
+ | refreshTargettemp = true; | ||
+ | if (TargetTemp <= 0.00) { | ||
+ | TargetTemp = 0.00; | ||
+ | } | ||
+ | } | ||
+ | if (button2.held() == true) { | ||
+ | displayMode--; | ||
+ | if (displayMode < 0) { | ||
+ | displayMode = 2; | ||
+ | } | ||
+ | tft.fillScreen(BLACK); | ||
+ | EEPROM.update(addrDisplayMode, | ||
+ | // | ||
+ | refreshPeltier = true; | ||
+ | refreshTargettemp = true; | ||
+ | previousTempCupedge = 150; | ||
+ | refreshFan = true; | ||
+ | } | ||
+ | |||
+ | |||
+ | // | ||
+ | if ((unsigned long)(currentMillis - previousMillisControllers) >= intervalControllers) { | ||
+ | // Widerstandswerte (gemessen) | ||
+ | // R1 (A1) = 10000, T1 = 9750 | ||
+ | // R2 (A3) = 10500, T2 = 9880 | ||
+ | // R3 (A5) = 10400, T3 = 9860 | ||
+ | // R4 (A7) = 10300, T4 = 9820 | ||
+ | |||
+ | // Thermistor 1: Umgebungstemperatur | ||
+ | thermistor1.update(); | ||
+ | |||
+ | thermistor1Value = thermistor1.getValue(); | ||
+ | // Debug | ||
+ | Serial.print(" | ||
+ | // Berechnung der Temperatur | ||
+ | thermistor1Value = 1023 / thermistor1Value - 1; | ||
+ | thermistor1Value = 10000 / thermistor1Value; | ||
+ | tempAmbient = thermistor1Value / 9750; // (R/Ro) | ||
+ | tempAmbient = log(tempAmbient); | ||
+ | tempAmbient /= 3950; // 1/B * ln(R/Ro) | ||
+ | tempAmbient += 1.0 / (25 + 273.15); | ||
+ | tempAmbient = 1.0 / tempAmbient; | ||
+ | tempAmbient -= 273.15; | ||
+ | |||
+ | |||
+ | // Thermistor 2: Kühlkörper | ||
+ | thermistor2.update(); | ||
+ | |||
+ | thermistor2Value = thermistor2.getValue(); | ||
+ | // Debug | ||
+ | Serial.print(" | ||
+ | // Berechnung der Temperatur | ||
+ | thermistor2Value = 1023 / thermistor2Value - 1; | ||
+ | thermistor2Value = 10500 / thermistor2Value; | ||
+ | tempDissipator = thermistor2Value / 9880; // (R/Ro) | ||
+ | tempDissipator = log(tempDissipator); | ||
+ | tempDissipator /= 3950; // 1/B * ln(R/Ro) | ||
+ | tempDissipator += 1.0 / (25 + 273.15); | ||
+ | tempDissipator = 1.0 / tempDissipator; | ||
+ | tempDissipator -= 273.15; | ||
+ | |||
+ | |||
+ | // Thermistor 3: Kühlbecherboden | ||
+ | thermistor3.update(); | ||
+ | |||
+ | thermistor3Value = thermistor3.getValue(); | ||
+ | // Debug | ||
+ | Serial.print(" | ||
+ | // Berechnung der Temperatur | ||
+ | thermistor3Value = 1023 / thermistor3Value - 1; | ||
+ | thermistor3Value = 10400 / thermistor3Value; | ||
+ | tempCupbase = thermistor3Value / 9860; // (R/Ro) | ||
+ | tempCupbase = log(tempCupbase); | ||
+ | tempCupbase /= 3950; // 1/B * ln(R/Ro) | ||
+ | tempCupbase += 1.0 / (25 + 273.15); | ||
+ | tempCupbase = 1.0 / tempCupbase; | ||
+ | tempCupbase -= 273.15; | ||
+ | |||
+ | |||
+ | // Thermistor 4: Kühlbecherrand | ||
+ | thermistor4.update(); | ||
+ | |||
+ | thermistor4Value = thermistor4.getValue(); | ||
+ | // Debug | ||
+ | Serial.print(" | ||
+ | // Berechnung der Temperatur | ||
+ | thermistor4Value = 1023 / thermistor4Value - 1; | ||
+ | thermistor4Value = 10300 / thermistor4Value; | ||
+ | tempCupedge = thermistor4Value / 9820; // (R/Ro) | ||
+ | tempCupedge = log(tempCupedge); | ||
+ | tempCupedge /= 3950; // 1/B * ln(R/Ro) | ||
+ | tempCupedge += 1.0 / (25 + 273.15); | ||
+ | tempCupedge = 1.0 / tempCupedge; | ||
+ | tempCupedge -= 273.15; | ||
+ | |||
+ | |||
+ | // PID-Regler Lüfter | ||
+ | inputFan = tempDissipator; | ||
+ | setpointFan = tempAmbient + 10.00; | ||
+ | fanPID.Compute(); | ||
+ | pwmWrite(fanPin, | ||
+ | |||
+ | // Wenn der Output des PID-Reglers Null ist, wird der Lüfter ausgeschaltet. | ||
+ | if (outputFan > 0) { | ||
+ | digitalWrite(powerPin, | ||
+ | // | ||
+ | } | ||
+ | else { | ||
+ | digitalWrite(powerPin, | ||
+ | // | ||
+ | } | ||
+ | |||
+ | |||
+ | // PID-Regler Peltier-Element | ||
+ | inputPeltier = tempCupedge; | ||
+ | setpointPeltier = TargetTemp; | ||
+ | peltierPID.Compute(); | ||
+ | pwmWrite(peltierPin, | ||
+ | |||
+ | // Überpüft, ob outputPeltier sich geändert hat. | ||
+ | if (byte(previousOutputPeltier) != byte(outputPeltier)) { // Änderungen hinter dem Komma sollen ignoriert werden | ||
+ | refreshPeltier = true; | ||
+ | // | ||
+ | previousOutputPeltier = outputPeltier; | ||
+ | } | ||
+ | |||
+ | previousMillisControllers = currentMillis; | ||
+ | } | ||
+ | |||
+ | |||
+ | // | ||
+ | // 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(); | ||
+ | high = false; | ||
+ | duration = pulseOff - pulseOn; | ||
+ | if (duration > 7500 && duration < 30000) { // Liegt die Variable über bzw. unter den angegebenen Werten, liegt ein Messfehler vor | ||
+ | medianDuration.add(duration); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Berechnung der Drehzahl des Lüfters | ||
+ | if ((unsigned long)(currentMillis - previousMillisCalculateFanSpeed) >= intervalCalculateFanSpeed) { | ||
+ | |||
+ | // | ||
+ | if (outputFan == 0) { // Wenn der Lüfter ausgeschaltet wird, soll die Drehzahl " | ||
+ | rpm = 0; | ||
+ | } | ||
+ | else { | ||
+ | rpm = float(100000 * 2 * 60 / medianDuration.getMedian()); | ||
+ | } | ||
+ | |||
+ | // 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; | ||
+ | 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; | ||
+ | // | ||
+ | previousRPMMax = rpmMax; | ||
+ | } | ||
+ | |||
+ | |||
+ | / | ||
+ | Serial.print("; | ||
+ | Serial.print("; | ||
+ | Serial.print("; | ||
+ | Serial.print("; | ||
+ | Serial.print("; | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | Serial.print("; | ||
+ | */ | ||
+ | |||
+ | // Meldet eine Fehlfunktion des Lüfters, wenn das Tachosignal " | ||
+ | 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 " | ||
+ | if (millis() - previousMillis > fanAlertDelay) { // ... und eine definierte Zeit verstrichen ist. | ||
+ | previousMillis = millis(); | ||
+ | fanAlert = true; | ||
+ | fanAlertState = true; | ||
+ | Serial.println(" | ||
+ | refreshFan = true; | ||
+ | } | ||
+ | } | ||
+ | else if (rpmMax > 0) { // ... und die gemittelte Drehzahl größer " | ||
+ | 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(); | ||
+ | if (fanAlert != fanAlertState) { | ||
+ | refreshFan = true; | ||
+ | fanAlertState = false; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | previousMillisCalculateFanSpeed = currentMillis; | ||
+ | } | ||
+ | |||
+ | |||
+ | // | ||
+ | // 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, | ||
+ | Serial.print(" | ||
+ | // Serviceintervallzähler | ||
+ | serviceInterval = serviceInterval + 0.25; | ||
+ | EEPROM.updateFloat(addrServiceInterval, | ||
+ | } | ||
+ | |||
+ | |||
+ | // | ||
+ | // Anzeige " | ||
+ | if (serviceIntervalReset == true) { | ||
+ | tft.fillScreen(BLACK); | ||
+ | tft.setCursor(15, | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setCursor(26, | ||
+ | tft.print(" | ||
+ | delay(2000); | ||
+ | tft.fillScreen(BLACK); | ||
+ | refreshPeltier = true; | ||
+ | refreshTargettemp = true; | ||
+ | previousTempCupedge = 150; | ||
+ | refreshFan= true; | ||
+ | serviceIntervalReset = false; | ||
+ | } | ||
+ | |||
+ | // Anzeigemodus " | ||
+ | if (displayMode == 0) { | ||
+ | // Anzeige Infobereich Zieltemperatur | ||
+ | if (refreshTargettemp == true) { // Wenn die Solltemperatur verändert wurde, muss die Anzeige aktualisiert werden. | ||
+ | tft.setCursor(15, | ||
+ | i = TargetTemp * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setCursor(15, | ||
+ | tft.setTextSize(2); | ||
+ | if (TargetTemp < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(TargetTemp); | ||
+ | tft.setTextColor(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, | ||
+ | i = tempCupedge * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setCursor(15, | ||
+ | tft.setTextSize(2); | ||
+ | if (tempCupedge < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(tempCupedge); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | previousTempCupedge = tempCupedge; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Anzeigemodus " | ||
+ | if (displayMode == 1) { | ||
+ | // Anzeige Infobereich Peltier-Element | ||
+ | if ((unsigned long)(currentMillis - previousMillisDisplayCoolingPower) >= intervalDisplayCoolingPower) { | ||
+ | if (outputPeltier > 0) { | ||
+ | if (refreshPeltier == true) { | ||
+ | tft.setCursor(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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(0, | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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, | ||
+ | i = TargetTemp * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setCursor(15, | ||
+ | tft.setTextSize(2); | ||
+ | if (TargetTemp < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(TargetTemp); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | refreshTargettemp = false; | ||
+ | } | ||
+ | |||
+ | // Anzeige Infobereich Isttemperatur | ||
+ | if (tempCupedge != previousTempCupedge) { | ||
+ | tft.setCursor(15, | ||
+ | i = tempCupedge * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setCursor(15, | ||
+ | tft.setTextSize(2); | ||
+ | if (tempCupedge < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(tempCupedge); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | previousTempCupedge = tempCupedge; | ||
+ | } | ||
+ | |||
+ | |||
+ | // Anzeige Infobereich Lüfter | ||
+ | if (fanAlert == true) { | ||
+ | if (refreshFan == true) { | ||
+ | tft.setCursor(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(RED, | ||
+ | tft.print(" | ||
+ | refreshFan = false; | ||
+ | } | ||
+ | } | ||
+ | else if (fanAlert == false) { | ||
+ | if ((unsigned long)(currentMillis - previousMillisDisplayFanSpeed) >= intervalDisplayFanSpeed) { | ||
+ | if (rpmMax > 0) { // Wenn die Drehzahl größer " | ||
+ | if (refreshFan == true) { | ||
+ | tft.setCursor(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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 " | ||
+ | if (displayMode == 2) { | ||
+ | |||
+ | // Anzeige Soll- und Umgebungstemperatur | ||
+ | if (refreshTargettemp == true) { // Wenn die Solltemperatur verändert wurde, muss die Anzeige aktualisiert werden. | ||
+ | // Solltemperatur | ||
+ | tft.setCursor(0, | ||
+ | i = TargetTemp * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | if (TargetTemp < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(TargetTemp); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | refreshTargettemp = false; | ||
+ | } | ||
+ | |||
+ | // Anzeige Isttemperaturen | ||
+ | if (tempCupedge != previousTempCupedge) { | ||
+ | tft.setCursor(0, | ||
+ | i = tempCupedge * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | if (tempCupedge < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(tempCupedge); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | |||
+ | tft.setCursor(0, | ||
+ | i = tempCupbase * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | if (tempCupbase < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(tempCupbase); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | |||
+ | // Umgebungstemperatur | ||
+ | tft.setCursor(0, | ||
+ | i = tempAmbient * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(1); | ||
+ | tft.print(" | ||
+ | if (tempAmbient < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(tempAmbient); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | |||
+ | // Kühlkörper | ||
+ | tft.setCursor(0, | ||
+ | i = tempDissipator * 2; // Berechnet den Index für den Array " | ||
+ | if (i < 0) i = 0; // i soll nicht kleiner als " | ||
+ | if (i > 60) i = 60; // i soll nicht größer als " | ||
+ | // | ||
+ | tft.setTextColor((pgm_read_word_near(colors +i)), BLACK); | ||
+ | tft.setTextSize(1); | ||
+ | tft.print(" | ||
+ | if (tempDissipator < 10.00) { | ||
+ | tft.print(" | ||
+ | } | ||
+ | tft.print(tempDissipator); | ||
+ | tft.setTextColor(BLACK, | ||
+ | tft.print((char)218); | ||
+ | |||
+ | previousTempCupedge = tempCupedge; | ||
+ | } | ||
+ | |||
+ | // Anzeige Kühlleistung | ||
+ | if ((unsigned long)(currentMillis - previousMillisDisplayCoolingPower) >= intervalDisplayCoolingPower) { | ||
+ | if (outputPeltier > 0) { | ||
+ | if (refreshPeltier == true) { | ||
+ | tft.setCursor(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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(0, | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.setTextSize(0); | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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 Lüfter | ||
+ | if (fanAlert == true) { | ||
+ | if (refreshFan == true) { | ||
+ | tft.setCursor(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(RED, | ||
+ | tft.print(" | ||
+ | refreshFan = false; | ||
+ | } | ||
+ | } | ||
+ | else if (fanAlert == false) { | ||
+ | if ((unsigned long)(currentMillis - previousMillisDisplayFanSpeed) >= intervalDisplayFanSpeed) { | ||
+ | if (rpmMax > 0) { // Wenn die Drehzahl größer " | ||
+ | if (refreshFan == true) { | ||
+ | tft.setCursor(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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(0, | ||
+ | tft.setTextSize(0); | ||
+ | tft.setTextColor(WHITE, | ||
+ | tft.print(" | ||
+ | tft.setTextColor(BLACK, | ||
+ | for (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; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | } | ||
+ | // | ||
+ | if ((unsigned long)(currentMillis - previousMillisSerialPrint) >= intervalSerialPrint) { | ||
+ | |||
+ | // Ausgabe für den seriellen Monitor | ||
+ | /* | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | |||
+ | Serial.print("; | ||
+ | // | ||
+ | Serial.print("; | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | |||
+ | // | ||
+ | // | ||
+ | // | ||
+ | Serial.print("; | ||
+ | Serial.print("; | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | // | ||
+ | Serial.print("; | ||
+ | */ | ||
+ | |||
+ | /* | ||
+ | // Ausgabe für SerialComInstruments 4.1 | ||
+ | Serial.print("# | ||
+ | Serial.print("# | ||
+ | Serial.print("# | ||
+ | |||
+ | Serial.print("# | ||
+ | // | ||
+ | Serial.print("# | ||
+ | |||
+ | Serial.print("# | ||
+ | // | ||
+ | |||
+ | Serial.print("# | ||
+ | */ | ||
+ | // | ||
+ | |||
+ | //Speichere die aktuelle Zeit in die zughörige Variable | ||
+ | previousMillisSerialPrint = currentMillis; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | Der Sketch verwendet 25586 Bytes (83%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes. | ||
+ | Globale Variablen verwenden 1504 Bytes (73%) des dynamischen Speichers, 544 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes. | ||
+ | |||
+ | Tags: #Arduino # |
arduino/flaschenkuehler/programmversion_0.5.1504982825.txt.gz · Zuletzt geändert: 18.05.2023 12:16 (Externe Bearbeitung)