Flaschenkühler - Programmversion 0.1
Die erste Version der Software für den Flaschenkühler widmet sich der Regelung des PC-Lüfters. Das erscheint zunächst nur ein Nebenschauplatz zu sein, ermöglicht mir aber Erfahrungen zu sammeln
- mit der Änderung der PWM-Frequenz
- mit der Auswertung des Tachosignals
- mit dem dem Auslesen eines Thermistors
- mit der PID-Regelung
Das Ziel ist zunächst, die Drehzahl eines PC-Lüfters mit 3-Pin-Anschluss über ein Poti zu regeln.1)
Änderung der PWM-Frequenz
Der Arduino Nano hat drei Timer, die sich auf die PWM-Frequenz verschiedener Pins auswirken:
- timer 0: Pins 5 und 6
- timer 1: Pins 9 und 10
- timer 2: Pins 3 und 11
Der timer 0 wirkt sich auf die Befehle millis(); micros() und delay() aus, so dass er nicht verändert werden sollte.
Um den PC-Lüfter mit 25 kHz und das Peltier-Element mit einer anderen PWM-Frequenz ansteuern zu können, wird die PWM-Bibliothek eingebunden. Die Biblothek kann hier geladen werden.) Da die Bibliothek fehlerhaft ist, funktionieren nicht alle Pins.
- Der PC-Lüfter wird an Pin D9 angeschlossen.
- Das Peltier-Element wird an Pin 3 angeschlossen. (Pin 11 gibt kein Signal aus.)
Der hier gepostete Patch funktioniert nicht.
Die PWM-Frequenz kann mit der PWM-Bibliothek nahezu beliebig festgelegt werden. 50 kHz für das Peltier-Element an Pin 3 sind kein Problem.
Tachosignal
Der PC-Lüfter gibt ein Tachosignal aus, das am Pin 2 anliegt. Das Programm bestimmt den Abstand zwischen der ansteigenden und der fallenden Flanke und berechnet daraus die Drehzahl. Es sollte auch möglich sein, die Interruptfähigkeit von Pin 2 dafür zu verwenden, aber mir ist es nicht gelungen.
PID-Regler
Der PID-Regler für den Lüfter (und später auch der für das Peltier-Element) werden mit der Arduino PID-Bibliothek realisiert. Schon mit den Standardwerten für das P-, I- und D-Glied funktioniert die Regelung des Lüfters gut.
Ein- und Ausschalten des Lüfters
Der verwendete PC-Lüfter von BeQuiet! hat eine Sicherheitsfunktionen, die dafür sorgt, dass der Lüfter voll aufdreht, wenn kein PWM-Siganl anliegt. Außerdem kann der Lüfter nicht mit dem PWM-Signal ausgeschaltet werden: Fällt der Duty Cycle unter einen Wert, der einen sauberen Rundlauf nicht gewähren könnte, wird das Signal ignoriert. Damit der Lüfter beim Systemstart nicht unkontrolliert anläuft und im Betrieb vollständig ausgeschaltet werden kann, wird er mit einem MOSFET geschaltet.
// Flaschenkühler - Programmversion 0.1 // Diese Version steuert einen PC-Lüfter mit 4-Pin-Anschluss ... // ... liest das Tachosignal aus ... // ... berechnet die Drehzahl des Lüfters ... // ... liest einen Thermistor aus und berechnet die Temperatur ... // ... stellt über ein Poti die Zieltemperatur ein ... // ... und regelt den Lüfter mit einem PID-Modul. # include <PWM.h> // Bibliothek für Änderung der Frequenz der Timer # include <RunningAverage.h> // Bibliothek für Berechnung von Mittelwerten # include <PID_v1.h> // Bibliothek für PID-Regler // Definition der Ein- und Ausgänge int potiPin = A0; // Input-Pin für den Lüfter int thermistorPin = A1; // Input-Pin für den Thermistor int fanPin = 9; // PWM-Pin für Lüfter int powerPin = 4; // Schaltet den Transistor int tachoPin = 2; // Pin für Tachosignal des Lüfters int peltierPin = 3; // PWM-Pin für Peltier-Element (hier zunächst nur als Funktionstest) // Definition der Variablen float thermistorValue = 0; // Variable in der der Wert des Thermistors gespeichert wird int potiValue = 0; // variable to store the value coming from the sensor uint16_t frequencyFan = 25000; // PWM-Frequenz für den Lüfter (in Hz) uint16_t frequencyPeltier = 50000; // PWM-Frequenz für den Lüfter (in Hz) int tachoSignal; // Tachosignal uint32_t pulseOn = 0; // Ansteigende Signalflanke (Variablenformat nicht verändern!) uint32_t pulseOff = 0; // Abfallende Signalflanke (Variablenformat nicht verändern!) uint32_t 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 temperatur = 0; // Definiert Instanzen zur Berechnung von Mittelwerten RunningAverage averageRPM(10); RunningAverage averageThermistor(10); // Definiert den PID-Regler double Setpoint, Input, Output; double Kp=2, Ki=5, Kd=1; PID fanPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, REVERSE); // Definiert die Tracking-Variablen für die IF-Abfragen unsigned long previousMillisSerialPrint = 0; // Ausgabe an die serielle Schnittstelle // Definiert die Intervalle für die IF-Abfragen in Millisekunden const unsigned long intervalSerialPrint = 250; // Ausgabe an die serielle Schnittstelle void setup() { Serial.begin(115200); // 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 // Initialisiert timer1 und timer2 (timer0 bleibt unberührt) InitTimersSafe(); // Definiert die Frequenzen für die angegebenen Pins 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); // Meldung "Klar zum Start!" Serial.println("<Arduino is ready! Turn the potentiometer, please ...>"); } void loop() { // Aktuelle Zeit abfragen unsigned long currentMillis = millis(); // Lese die analogen Inputs aus potiValue = analogRead(potiPin); // Poti thermistorValue = analogRead(thermistorPin); // Thermistor averageThermistor.addValue(thermistorValue); // Wert wird an Running Average übergeben // Berechnung der Temperatur thermistorValue = averageThermistor.getAverage(); // Der Mittelwert wird eingelesen thermistorValue = 1023 / thermistorValue - 1; thermistorValue = 10000 / thermistorValue; // Der Wert wird in einen Widerstand umgerechnet float temperatur; temperatur = thermistorValue / 10000; // (R/Ro) temperatur = log(temperatur); // ln(R/Ro) temperatur /= 3950; // 1/B * ln(R/Ro) temperatur += 1.0 / (25 + 273.15); // + (1/To) temperatur = 1.0 / temperatur; // Invert temperatur -= 273.15; // convert to C // Regelung des Lüfters (TEST) Input = temperatur; // Input ist die Temperatur des Thermistors in *C Setpoint = 10 + float(potiValue * 0.02); // Setpoint ist die Temperatur zwischen 10 und 30 *C fanPID.Compute(); // PID-Regler wir aufgerufen pwmWrite(fanPin, Output); // Gibt den Output des PID-Reglers an den Lüfter // Wenn der Output des PID-Reglers Null ist, wird der Lüfter ausgeschaltet. if (Output > 0) { digitalWrite(powerPin, HIGH); //Serial.print("HIGH"); Serial.print("; "); } else { digitalWrite(powerPin, LOW); //Serial.print("LOW"); Serial.print("; "); } //use this functions instead of analogWrite on 'initialized' pins //pwmWrite(fanPin, potiValue / 4); // Gibt den Potiwert an den Lüfter pwmWrite(peltierPin, potiValue / 4); // Nur zu Testzwecken // 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 > 7000 && duration < 150000) { // Liegt die Variable über bzw. unter den angegebenen Werten, liegt ein Messfehler vor rpm = float(100000 * 2 * 60 / duration); // Berechnung der RPM } else { rpm = 0; } averageRPM.addValue(rpm); } // Wenn der Lüfter angehalten wird, soll die Drehzahl "0" angezeigt werden if (Output == 0) { rpm = 0; averageRPM.addValue(rpm); } // Ausgabe an die serielle Schnittstelle if ((unsigned long)(currentMillis - previousMillisSerialPrint) >= intervalSerialPrint) { Serial.print("Setpoint: ");Serial.print(Setpoint); Serial.print("; Input: ");Serial.print(Input); Serial.print("; Output: ");Serial.print(Output); //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(averageRPM.getAverage()); Serial.print("; Temperatur: "); Serial.println(temperatur); //Speichere die aktuelle Zeit in die zughörige Variable previousMillisSerialPrint = currentMillis; } }
Der Sketch verwendet 8590 Bytes (27%) des Programmspeicherplatzes. Das Maximum sind 30720 Bytes. Globale Variablen verwenden 579 Bytes (28%) des dynamischen Speichers, 1469 Bytes für lokale Variablen verbleiben. Das Maximum sind 2048 Bytes.
Tags: #Arduino #Flaschenkühler #Nano #PC-Lüfter