Programmierung
Anmerkung
Wie zuvor schon angesprochen, wird die Maschine von einem Arduino gesteuert, der auch die gesamte Logik zur
Temperatursteuerung enthält. Der hier abgebildete Code kann sich jederzeit ändern und wird
dies in näherer Zukunft auch häufiger tun.
Dieser Code ist "Work-in-Progress" und steht hier ohne Garantie zur Einsicht.
Die Nutzung erfolgt auf eigenes Risiko.
Programmcode
/* ABOUT ============================================== */
/* Espressomaschinen-Steuerung */
static String versionString = "ver25 2016-04-10";
/* Jan Kube - www.jankube.de */
#include "max6675.h"
#include "KUBE_PID.h"
/* EINSTELLUNGEN ====================================== */
// Timing-Allgemein
static int ZEIT_kaffeeBezug = 30000; // 30sek Kaffeebezug
static int ZEIT_dampfPause = 500; // Pumpenwartezeit bei Dampfbezug
static int ZEIT_entlueftung = 7500; // Millisekunden zwischen Entlueftung
static long ZEIT_automatischeAbschaltung = 600000; // auto aus nach 10 Minuten Untaetigkeit
static long ZEIT_letzteAktion = 0; // Nach 10 Min Untaetigkeit soll automatisch abgeschaltet werden
// Temperatur-Variablen
static double TEMPERATUR_Ziel_Heizung_Wasser = 105.0; // ZielTemperatur fuer Wasser-Heizung bei Kaffeebezug
static double TEMPERATUR_Ziel_Heizung_Dampf = 95.0; // ZielTemperatur fuer Dampf-Heizung bei Kaffeebezug
static double TEMPERATUR_Ziel_Heizung_BG = 95.0; // ZielTemperatur fuer BG-Heizung bei Kaffee- und Dampfbezug
static double TEMPERATUR_Ziel_Heizung_Wasser_Dampfbezug = 140.0; // ZielTemperatur fuer Wasser-Heizung bei Dampfbezug
static double TEMPERATUR_Ziel_Heizung_Dampf_Dampfbezug = 130.0; // ZielTemperatur fuer Dampf-Heizung bei Dampfbezug
static double TEMPERATUR_max = 175.0; // Hoechst-Temperatur
static double TEMPERATUR_min = 5.0; // Niedrigst-Temperatur
// Temperatur-Arbeitsvariablen
static double SENSOR_Heizung_Wasser = 20.0; // Messwert von Temperatursensor an Wasserheizung
static double SENSOR_Heizung_Dampf = 20.0; // Messwert von Temperatursensor an Dampfheizung
static double SENSOR_Heizung_BG = 20.0; // Messwert von Temperatursensor an BG
// Fehler
static boolean FEHLER = false; // Allgemeine Variable fuer Fehler-Alarm
/* WASSER ======================================== */
// PID Einstellungen
static double PID_Heizung_Wasser_Eingabe = 0.0; // Momentaner Temperaturzustand als Eingabewert fuer PID
static double PID_Heizung_Wasser_Ziel = TEMPERATUR_Ziel_Heizung_Wasser; // Sollwert
static double PID_Heizung_Wasser_kp = 20.0; // Proportional (Gegenwart) 55.0
static double PID_Heizung_Wasser_ki = 0.0; //0.0001; // Integral (Vergangenheit) 0.0001
static double PID_Heizung_Wasser_kd = 90.0; // Derivativ (Zukunft) 70.0
/* PID DAMPF ======================================== */
// PID Einstellungen
static double PID_Heizung_Dampf_Eingabe = 0.0; // Momentaner Temperaturzustand als Eingabewert fuer PID
static double PID_Heizung_Dampf_Ziel = TEMPERATUR_Ziel_Heizung_Dampf; // Sollwert
static double PID_Heizung_Dampf_kp = 25.0; // Proportional (Gegenwart) 25.0
static double PID_Heizung_Dampf_ki = 0.0; // Integral (Vergangenheit) 0.6
static double PID_Heizung_Dampf_kd = 95.0; // Derivativ (Zukunft) 24.0
// PID Dampf Arbeitsvariablen
/* PID BG ========================================== */
// PID Einstellungen
static double PID_Heizung_BG_Eingabe = 0.0; // Momentaner Temperaturzustand als Eingabewert fuer PID
static double PID_Heizung_BG_Ziel = TEMPERATUR_Ziel_Heizung_BG; // Sollwert
static double PID_Heizung_BG_kp = 60.0; // Proportional (Gegenwart) 11.0
static double PID_Heizung_BG_ki = 0.0; // Integral (Vergangenheit) 0.001
static double PID_Heizung_BG_kd = 50.0; // Derivativ (Zukunft) 24.0
/* ANSCHLUESSE ======================================= */
// Ausgaenge
static int PIN_Pumpe = 7; // Pumpenanschluss
static int PIN_Ventil_BG = 8; // 3-Wege-Ventil, welches an der Bruehgruppe sitzt
static int PIN_Ventil_Wasser = 6; // Ventil, welches den Weg zur BG oeffnet
static int PIN_Ventil_Dampf = 16; // Ventil, welches den Weg zur Dampflanze oeffnet
static int PIN_Heizung_Wasser = 9; // Hauptheizung fuer Wasser (mit Temperatursensoren, 1400W)
static int PIN_Heizung_Dampf = 5; // Zusatzheizung fuer Dampf (der Hauptheizung vorgeschaltet, 900W)
static int PIN_Heizung_BG = 4; // TODO: Heizung der Bruehgruppe (250W)
// Eingaenge
static int PIN_Schalter_Wasser = 11; // Schalter fuer den Bezug von Wasser aus der Dampflanze
static int PIN_Schalter_Dampf = 10; // Schalter fuer den Bezug von Dampf aus der Dampflanze
static int PIN_Schalter_Kaffee = 15; // Schalter fuer den Bezug von Kaffee (Wasser aus der BG)
// Temperatursensoren
static int Sensor_DO = 12; // MISO: Sensor Data Out (gemeinsam)
static int Sensor_CLK = 13; // Clock (gemeinsam)
static int Sensor1_CS = 19; // Select Chip 1 // Dampf
static int Sensor2_CS = 18; // Select Chip 2 // BG
static int Sensor3_CS = 17; // Select Chip 3 // Wasser
/* BIBLIOTHEKEN ======================================= */
/* TEMPERATURSENSOREN */
MAX6675 Sensor1(Sensor_CLK, Sensor1_CS, Sensor_DO);
MAX6675 Sensor2(Sensor_CLK, Sensor2_CS, Sensor_DO);
MAX6675 Sensor3(Sensor_CLK, Sensor3_CS, Sensor_DO);
/* PIDs */
// PID Wasser
KUBE_PID PID_Heizung_Wasser (
&PID_Heizung_Wasser_Eingabe,
&PID_Heizung_Wasser_Ziel,
PID_Heizung_Wasser_kp,
PID_Heizung_Wasser_ki,
PID_Heizung_Wasser_kd,
PIN_Heizung_Wasser
);
// PID Dampf
KUBE_PID PID_Heizung_Dampf (
&PID_Heizung_Dampf_Eingabe,
&PID_Heizung_Dampf_Ziel,
PID_Heizung_Dampf_kp,
PID_Heizung_Dampf_ki,
PID_Heizung_Dampf_kd,
PIN_Heizung_Dampf
);
// PID BG
KUBE_PID PID_Heizung_BG (
&PID_Heizung_BG_Eingabe,
&PID_Heizung_BG_Ziel,
PID_Heizung_BG_kp,
PID_Heizung_BG_ki,
PID_Heizung_BG_kd,
PIN_Heizung_BG
);
/* SETUP ============================================== */
void setup() {
// Kommunikation mit dem Rechner beginnen
Serial.begin(9600);
nachricht("I Jan Kube");
nachricht("I Espressomaschine Steuerung " + versionString);
nachricht("I www.jankube.de");
nachricht("I Beginne mit der Konfiguration");
// PINS KONFIGURIEREN
// Ausgaenge
pinMode(PIN_Pumpe, OUTPUT);
pinMode(PIN_Ventil_BG, OUTPUT);
pinMode(PIN_Ventil_Wasser, OUTPUT);
pinMode(PIN_Ventil_Dampf, OUTPUT);
pinMode(PIN_Heizung_Wasser, OUTPUT);
pinMode(PIN_Heizung_Dampf, OUTPUT);
pinMode(PIN_Heizung_BG, OUTPUT);
pinMode(Sensor1_CS, OUTPUT); // Select Chip 1
pinMode(Sensor2_CS, OUTPUT); // Select Chip 2
pinMode(Sensor3_CS, OUTPUT); // Select Chip 3
// Eingaenge
pinMode(PIN_Schalter_Wasser, INPUT_PULLUP);
pinMode(PIN_Schalter_Dampf, INPUT_PULLUP);
pinMode(PIN_Schalter_Kaffee, INPUT_PULLUP);
// ALLES AUSSCHALTEN
allesAus();
// Durchspuelen
digitalWrite(PIN_Ventil_Dampf, HIGH);
digitalWrite(PIN_Pumpe, HIGH);
delay(2000);
// ALLES AUSSCHALTEN
allesAus();
// Status ausgeben
nachricht("I Konfiguration abgeschlossen");
}
/* METHODEN =========================================== */
void allesAus() {
// Nacheinander alles abschalten, die Ventile zuletzt.
digitalWrite(PIN_Pumpe, LOW);
delay(10);
digitalWrite(PIN_Heizung_Wasser, LOW);
delay(10);
digitalWrite(PIN_Heizung_Dampf, LOW);
delay(10);
digitalWrite(PIN_Heizung_BG, LOW);
delay(10);
digitalWrite(PIN_Ventil_BG, LOW);
delay(10);
digitalWrite(PIN_Ventil_Wasser, LOW);
delay(10);
digitalWrite(PIN_Ventil_Dampf, LOW);
delay(10);
// Zaehler fuer automatische Abschaltung zuruecksetzen
ZEIT_letzteAktion = millis();
}
void nachricht(String msg) {
// Serielle Nachricht nur dann schicken, wenn ueberhaupt eine Verbindung besteht
if (Serial) {
// Nachricht seriell verschicken
Serial.println(msg);
}
}
void bezugWasser() {
// Status ausgeben
nachricht("I Starte Wasserbezug");
if (!FEHLER) {
// Leitung oeffnen, Wasser pumpen
digitalWrite(PIN_Ventil_Dampf, HIGH);
digitalWrite(PIN_Pumpe, HIGH);
// Wasser (aus Dampfrohr) ausgeben, solange Schalter an ist (LOW = AN)
while ( digitalRead(PIN_Schalter_Wasser)==LOW && !FEHLER ) {
// Temperaturen mit PID regeln
pid_wasser();
pid_dampf();
pid_bg();
}
}
// Schalter aus
allesAus();
// Status ausgeben
nachricht("I Wasserbezug abgeschlossen");
}
void bezugDampf() {
// Status ausgeben
nachricht("I Starte Dampfbezug");
// Temperaturen nach oben korrigieren (fuer Dampfbezug)
PID_Heizung_Wasser_Ziel = TEMPERATUR_Ziel_Heizung_Wasser_Dampfbezug;
PID_Heizung_Dampf_Ziel = TEMPERATUR_Ziel_Heizung_Dampf_Dampfbezug;
if (!FEHLER) {
// Leitung oeffnen, Dampheizung anschalten
digitalWrite(PIN_Ventil_Dampf, HIGH);
digitalWrite(PIN_Heizung_Dampf, HIGH);
// Impuls-Pumpensteuerung vorbereiten
long pumpeSchaltZeitpunkt = millis(); // Zeit fuer Pumpensteuerung ohne delay()
// Dampf ausgeben, solange Schalter an ist (LOW = AN)
while ( digitalRead(PIN_Schalter_Dampf)==LOW && !FEHLER ) {
// Temperaturen mit PID regeln
pid_wasser();
pid_dampf();
pid_bg();
// Pumpen-Impulssteuerung: Aus-Zeit berechnen
long schaltzeit = millis() - pumpeSchaltZeitpunkt;
// Anschalten?
if (schaltzeit>ZEIT_dampfPause) { // Pumpe lange genug aus -> Impuls geben
digitalWrite(PIN_Pumpe, HIGH); // an
delay(100);
digitalWrite(PIN_Pumpe, LOW); // aus
// Zeitsteuerung aktualisieren
pumpeSchaltZeitpunkt = millis();
}
}
}
// Schalter ist aus
allesAus();
// hohe Dampf-Temperaturen wieder nach unten korrigieren (fuer Wasser-/Kaffeebezug)
PID_Heizung_Wasser_Ziel = TEMPERATUR_Ziel_Heizung_Wasser;
PID_Heizung_Dampf_Ziel = TEMPERATUR_Ziel_Heizung_Dampf;
// Status ausgeben
nachricht("I Dampfbezug abgeschlossen");
}
void bezugKaffee() {
if (!FEHLER) {
// Status ausgeben
nachricht("I Starte Kaffeebezug");
// Ventile auf
digitalWrite(PIN_Ventil_BG, HIGH); // BG-Ventil oeffnen
digitalWrite(PIN_Ventil_Wasser, HIGH); // Ventil auf dem Wasserweg zur BG oeffnen
// PRAEINFUSION: Startzeit merken
long startzeit = millis();
// Pumpe fuer Praeinfusion an
digitalWrite(PIN_Pumpe, HIGH);
// 3 Sek. lang pumpen, aber PID weiter regeln
while (millis() - startzeit < 3000) {
// Temperaturen mit PID regeln
pid_wasser();
pid_dampf();
pid_bg();
}
// Pumpe fuer Praeinfusion an
digitalWrite(PIN_Pumpe, LOW);
// weitere 2 Sek. lang warten, um Puck quellen zu lassen (also insgesamt 5 Sek. Praeinfusion)
while (millis() - startzeit < 5000) {
// Temperaturen mit PID regeln
pid_wasser();
pid_dampf();
pid_bg();
}
// KAFFEEBEZUG starten
digitalWrite(PIN_Pumpe, HIGH);
// Kaffee ausgeben, solange Schalter an ist (LOW = AN)
while ( digitalRead(PIN_Schalter_Kaffee)==LOW && !FEHLER ) { // && (millis()-kaffeeBezugsstart<ZEIT_kaffeeBezug) ) {
// Temperaturen mit PID regeln
pid_wasser();
pid_dampf();
pid_bg();
}
// Schalter ist aus
allesAus();
// Status ausgeben
nachricht("I Kaffeebezug abgeschlossen");
}
}
void pid_wasser() {
// SCHRITT 1: Temperatur aktualisieren
SENSOR_Heizung_Wasser = Sensor3.readCelsius();
Serial.print(" \t Heizung Wasser: ");
Serial.print(SENSOR_Heizung_Wasser);
// Auf Fehler pruefen: Zu warm oder zu kalt?
if (SENSOR_Heizung_Wasser > TEMPERATUR_max || SENSOR_Heizung_Wasser < TEMPERATUR_min) {
FEHLER = true;
}
// SCHRITT 2: PID-Ausgabe berechnen
PID_Heizung_Wasser_Eingabe = SENSOR_Heizung_Wasser;
PID_Heizung_Wasser.ausgabeBerechnen();
// SCHRITT 3: Heizungs-SSR steuern
PID_Heizung_Wasser.relaisSteuern();
}
void pid_dampf() {
// SCHRITT 1: Temperatur aktualisieren
SENSOR_Heizung_Dampf = Sensor1.readCelsius();
Serial.print(" \t Heizung Dampf: ");
Serial.print(SENSOR_Heizung_Dampf);
// Auf Fehler pruefen: Zu warm oder zu kalt?
if (SENSOR_Heizung_Dampf > TEMPERATUR_max || SENSOR_Heizung_Dampf < TEMPERATUR_min) {
FEHLER = true;
}
// SCHRITT 2: PID-Ausgabe berechnen
PID_Heizung_Dampf_Eingabe = SENSOR_Heizung_Dampf;
PID_Heizung_Dampf.ausgabeBerechnen();
// SCHRITT 3: Heizungs-SSR steuern
PID_Heizung_Dampf.relaisSteuern();
}
void pid_bg() {
// SCHRITT 1: Temperatur aktualisieren
SENSOR_Heizung_BG = Sensor2.readCelsius();
Serial.print(" \t Heizung BG: ");
Serial.println(SENSOR_Heizung_BG);
// Auf Fehler pruefen: Zu warm oder zu kalt?
if (SENSOR_Heizung_BG > TEMPERATUR_max || SENSOR_Heizung_BG < TEMPERATUR_min) {
FEHLER = true;
}
// SCHRITT 2: PID-Ausgabe berechnen
PID_Heizung_BG_Eingabe = SENSOR_Heizung_BG;
PID_Heizung_BG.ausgabeBerechnen();
// SCHRITT 3: Heizungs-SSR steuern
PID_Heizung_BG.relaisSteuern();
}
void automatischAbschalten() {
// Automatische Abschaltung nach 10 min des Nichtstuns
if (millis() - ZEIT_letzteAktion > ZEIT_automatischeAbschaltung) { // 10 min
while (true) {
allesAus();
Serial.println(" AUS NACH ZU LANGE AN ");
Serial.print(" millis: ");
Serial.print(millis());
Serial.print(" ZEIT_letzteAktion: ");
Serial.print(ZEIT_letzteAktion);
Serial.print(" ZEIT_automatischeAbschaltung: ");
Serial.println(ZEIT_automatischeAbschaltung);
delay(10000);
}
}
}
void fehlerAbschalten() {
while (true) {
allesAus();
Serial.println("AUS NACH FEHLER");
delay(10000);
}
}
/* LOOP =============================================== */
void loop() {
// Solange kein Fehler passiert ist...
if (!FEHLER) {
// Temperaturen mit PID regeln
pid_wasser();
pid_dampf();
pid_bg();
// Schalter abfragen (LOW=AN)
if (digitalRead(PIN_Schalter_Wasser) == LOW) {
bezugWasser();
}
if (digitalRead(PIN_Schalter_Dampf) == LOW) {
bezugDampf();
}
if (digitalRead(PIN_Schalter_Kaffee) == LOW) {
bezugKaffee();
}
// automatisch abschalten? (bei 10 Min keine Aktion)
automatischAbschalten();
// Wenn ein Fehler passiert ist, nichts mehr machen.
} else {
fehlerAbschalten();
}
}
Sie können die komplette Arduino-Programmierung (Version 25)
hier herunterladen
.
KUBE_PID Bibliothek
/**********************************************************************************************
* Arduino KUBE_PID Library - Version 0.1
* by Jan Kube <kontakt@jankube.de> http://jankube.de/
*
* based on PID Library by Brett Beauregard <br3ttb@gmail.com> brettbeauregard.com
*
* This Library is licensed under GPLv3
**********************************************************************************************/
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <KUBE_PID.h>
/*Constructor (...)*********************************************************/
KUBE_PID::KUBE_PID(double* Eingabe, double* Ziel, double Kp, double Ki, double Kd, int pin)
{
// Einstellungen im Constructor uebernehmen
// Verweise
PID_Eingabe = Eingabe;
PID_Ziel = Ziel;
// Anschluesse
PID_SSR_PIN = pin;
// Einstellungen
PID_kp = Kp; // Proportional
PID_ki = Ki; // Integral
PID_kd = Kd; // Derivativ
PID_Taktlaenge = 50; // In welchen Intervallen soll (ggf.) berechnet werden?
PID_Relais_Taktlaenge = 1000; // In welchen Intervallen soll (ggf.) geschaltet werden?
PID_MIN = 0; // Untergrenze fuer PID_Ausgabe
PID_MAX = 1000; // Obergrenze fuer PID_Ausgabe
// Arbeitsvariablen
PID_Ausgabe = 0;
PID_Schaltzeitpunkt = millis();
PID_Relais_Schaltzeitpunkt = 0; // Wann wurde zuletzt geschaltet?
PID_Fehlersumme = 0;
PID_letzterFehler = 0;
PID_Relais_Strom = LOW; // Strom-Zustand merken
}
/* ausgabeBerechnen() **********************************************************************/
bool KUBE_PID::ausgabeBerechnen()
{
/**
* PID-ZIELWERT BERECHNEN
*/
// Zeit seit letzter Berechnung
long PID_Berechnungsstart = millis();
long PID_ZeitAenderung = (PID_Berechnungsstart - PID_Schaltzeitpunkt);
// Schon wieder neu berechnen?
if (PID_ZeitAenderung >= PID_Taktlaenge) {
// alle verschiedenen Fehler berechnen
double PID_Fehler = *PID_Ziel - *PID_Eingabe;
PID_Fehlersumme = PID_Fehlersumme + (PID_Fehler * PID_ZeitAenderung);
double PID_FehlerAenderung = (PID_Fehler - PID_letzterFehler) / PID_ZeitAenderung;
// I-Wert (Fehlersumme) limitieren (Min und Max auf je 5-Fache Taktlaenge)
// damit I nicht voellig uebertrieben gross wird.
if (PID_Fehlersumme < (0-PID_MAX)*5/PID_ki) {
PID_Fehlersumme = (0-PID_MAX)*5/PID_ki;
} else if (PID_Fehlersumme > (PID_MAX)*5/PID_ki) {
PID_Fehlersumme = PID_MAX*5/PID_ki;
}
// PID Ausgaben berechnen
double PID_P = (PID_kp * PID_Fehler); // P
double PID_I = (PID_ki * PID_Fehlersumme); // I
double PID_D = (PID_kd * PID_FehlerAenderung); // D
// Ausgaben kombinieren
PID_Ausgabe = PID_P + PID_I + PID_D;
// Fehler und Schaltzeit fuer naechsten Takt merken
PID_letzterFehler = PID_Fehler;
PID_Schaltzeitpunkt = PID_Berechnungsstart;
// Min und Max einhalten:
if (PID_Ausgabe > PID_MAX) {
PID_Ausgabe = PID_MAX;
} else if (PID_Ausgabe < PID_MIN) {
PID_Ausgabe = PID_MIN;
}
/*
Serial.print(" \tFehler: ");
Serial.print(PID_Fehler);
Serial.print(" \tkp: ");
Serial.print(PID_kp);
Serial.print(" \tki: ");
Serial.print(PID_ki);
Serial.print(" \tkd: ");
Serial.print(PID_kd);
Serial.print(" \tP: ");
Serial.print(PID_P);
Serial.print(" \tI: ");
Serial.print(PID_I);
Serial.print(" \tD: ");
Serial.print(PID_D);
Serial.print(" \tAusgabe PID: ");
Serial.println(PID_Ausgabe);
*/
}
}
/* relaisSteuern() *************************************************************************/
bool KUBE_PID::relaisSteuern()
{
/**
* VORBEREIUNG
*/
// Heizung steuern: Ziel-Status ermitteln
int ziel_zeit_an = PID_Ausgabe;
int ziel_zeit_aus = PID_Relais_Taktlaenge - ziel_zeit_an;
long taktStartzeit = millis(); // Startzeit des aktuellen Taktes merken
// Zeit seit letztem Schalten berechnen
long zeitdifferenz = taktStartzeit-PID_Relais_Schaltzeitpunkt;
/**
* ZIEL-STATUS ERMITTELN
*/
// Immer an
if (ziel_zeit_an>0.95*PID_Relais_Taktlaenge) {
// Stromstatus und Schaltzeit merken
PID_Relais_Strom = HIGH;
PID_Relais_Schaltzeitpunkt = taktStartzeit;
}
// Immer aus
else if (ziel_zeit_aus>0.95*PID_Relais_Taktlaenge) {
// Stromstatus und Schaltzeit merken
PID_Relais_Strom=LOW;
PID_Relais_Schaltzeitpunkt = taktStartzeit;
}
// aus und lange genug aus? -> Anschalten
if (PID_Relais_Strom==LOW && zeitdifferenz>ziel_zeit_aus) {
// Stromstatus und Schaltzeit merken
PID_Relais_Strom = HIGH;
PID_Relais_Schaltzeitpunkt = taktStartzeit;
}
// an und lange genug an? -> Ausschalten
else if (PID_Relais_Strom==HIGH && zeitdifferenz>ziel_zeit_an) {
// Stromstatus und Schaltzeit merken
PID_Relais_Strom = LOW;
PID_Relais_Schaltzeitpunkt = taktStartzeit;
}
//Schalten
digitalWrite(PID_SSR_PIN, PID_Relais_Strom);
}
Sie können die komplette Arduino-Bibliothek zur zeitdiskreten PID-Steuerung
hier herunterladen
.