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