Wenn es darum geht, Systeme zu steuern oder zu regeln braucht man effektive Methoden. Sei es die Temperatur des Heizbetts oder der Nozzle im 3D-Drucker, die Drehzahl eines Motors oder auch die Leistung eines Lüfters, je nach Anwendung müssen die Steuerungsprozesse unterschiedlich präzise sein.
Zwei bewährte Prinzipien die in solchen Szenarien zum Einsatz kommen, sind die Hysterese und das PID-Tuning. Beide bieten jeweils ihre eigenen Vor- und Nachteile, je nachdem, wie genau und stabil die Steuerung am Ende sein muss.
Zum Kapitel springen Hysterese
Eine einfache Möglichkeit, Systeme wie Lüfter oder Heizungen zu steuern, ist die Hysterese. Hierbei wird ein Gerät beispielsweise aktiviert, wenn ein bestimmter Schwellenwert überschritten wird, und wieder deaktiviert, wenn der Wert unter eine festgelegte Grenze fällt. Das Prinzip der Hysterese lässt sich gut an einem Wasserbehälter verdeutlichen:
- Ein Sensor erfasst den Füllstand im Behälter
- Ist der maximale Füllstand erreicht, schaltet sich eine Pumpe ein und der Pegel nimmt ab
- Bei minimalem Füllstand wird die Pumpe wieder abgeschaltet und das Wasser kann wieder steigen
Der Wasserpegel pendelt also stets zwischen diesen beiden Werten, ohne kontinuierlich auf einem fest definierten Pegel zu bleiben.
Zum Kapitel springen Beispiel
Nehmen wir mal noch ein anderes Beispiel aus der Praxis: Eine geschlossene und beheizte Kammer wird nur durch einen Ablufventilator gekühlt. Dies passiert, da er die heiße Luft aus der Kammer nach draußen befördert und somit kalte Luft von außen in die Kammer strömt.
Ziel ist es, eine relativ konstante Temperatur zu haben. Hierzu werden bei der Hysterese Schwellwerte festgelegt. Wird ein Schwellenwert überschritten, wird der Lüfter ein- oder ausgeschaltet, was wiederum die Temperatur in der Kammer senkt oder wieder erhöht.
Um die Hysterese mit konkreten Zahlen zu veranschaulichen, definieren wir für die Lüftersteuerung folgende Parameter:
- Schwellenwert: 30°C
- Hysterese: ±10°C
Verhalten der Hysterese-Steuerung:
- Der Lüfter wird eingeschaltet, wenn die Temperatur über 40°C steigt.
- Der Lüfter schaltet sich aus, wenn die Temperatur unter 20°C fällt.
Die Auswirkungen dieser Steuerung werden deutlich, wenn wir uns das folgende Diagramm anschauen, das die Temperaturverläufe und die entsprechenden Einschalt- sowie Ausschaltpunkte des Lüfters zeigt:
Die Hysterese ist also ein Steuerungsverfahren, da unser Steuerungssystem nur auf einen Eingangswert reagiert, ohne direkte Rückmeldung vom Ausgang.
// --- Parameter für Hysterese ---
const int SCHWELLENWERT = 30; // Basiswert
const int HYSTERESE = 10; // ±10°C
// Daraus ergeben sich:
const int EINSCHALT_TEMP = SCHWELLENWERT + HYSTERESE; // 40°C
const int AUSSCHALT_TEMP = SCHWELLENWERT - HYSTERESE; // 20°C
// Variablen
int temperatur = 0; // Aktuelle Temperatur (z. B. vom Sensor)
// --- Hysterese-Logik ---
if ( temperatur > EINSCHALT_TEMP ) {
// Lüfter anschalten
}
if ( temperatur < AUSSCHALT_TEMP ) {
// Lüfter ausschalten
}
Zum Kapitel springen Vor- und Nachteile
Vorteile
- Einfach zu implementieren
- Einfach validier- und testbar
- Teilweise Ressourcenschonend, da Aktoren nicht permanent laufen
Nachteile
- Weniger geeignet für Abstimmung, da sonst ständig getriggert wird
- Kein wirklich konstanter Sollwert
Zum Kapitel springen PID-Tuning
Im Gegensatz zur vergleichsweise einfachen Hysterese arbeitet ein PID-Regler - Proportional-Integral-Differential-Regler - nicht mit festen Schwellwerten, sondern regelt dynamisch anhand eines gesetzten Sollwerts. Dabei wird nicht nur ein fester Schwellwert berücksichtigt, sondern kontinuierlich die Abweichung vom gewünschten Sollwert berechnet.
Dieses Prinzip lässt sich ebenfalls am Wasserbehälter verdeutlichen: Ein Sensor erfasst kontinuierlich den Füllstand. Je nachdem, wie weit der Wasserstand vom Sollwert entfernt ist, wird die Pumpe stärker oder schwächer betrieben - Ziel ist es nämlich, den Pegel möglichst konstant zu halten.
Anders als bei der Hysterese, bei der die Pumpe nur zwischen zwei Werten pendelt, arbeitet der PID-Regler deutlich feinfühliger und gezielter. Dadurch entstehen weniger große Schwankungen, und der Wasserstand bleibt stabiler im gewünschten Bereich. Annahme: Wir möchten einen Wasserpegel von exakt 50% haben.
Nach dem Einschalten oder einer plötzlichen und starken Störung pendelt sich der Wasserstand nicht sofort wieder auf den Sollwert ein, sondern schwankt zunächst um ihn herum - wie in der Abbildung zu sehen. Dieses Verhalten wird als Einschwingen bezeichnet. Je nach Abstimmung des PID-Regler kann dieses Einschwingen unterschiedlich stark ausgeprägt sein. Eine zu aggressive Regelung führt zu starken Überschwingern, während eine zu träge Regelung das Erreichen des Sollwerts verzögert.
Sobald sich das System aber eingependelt hat, läuft es ziemlich stabil und kann den Sollwert präzise halten. Ein gutes Beispiel dafür ist ein 3D-Drucker: Beim Aufheizen der Nozzle kann die Temperatur anfangs stark überschwingen, bevor sie sich dann einpendelt. Danach hält der PID-Regler die Temperatur mit minimalen Schwankungen konstant.
Zum Kapitel springen Beispiel
Nehmen wir auch wieder ein Beispiel aus der Praxis: Eine geschlossene und beheizte Kammer wird nur durch einen Ablufventilator Zirkuliert. Das Ziel ist es, eine relativ konstante Temperatur zu haben. Hierzu wird eine Zieltemperatur festgelegt. Diese wird permanent überwacht und durch anschließendes Gegensteuern wird versucht, diese zu erreichen und anschließend stabil zu halten.
- Soll-Temperatur: 30°C
Der Lüfter passt seine Geschwindigkeit dynamisch an, sobald die Temperatur vom Sollwert abweicht. Je größer die Abweichung, desto stärker wird der Lüfter aktiviert. Durch die kontinuierliche Anpassung wird die Temperatur nahe am Sollwert gehalten, ohne starke Über- oder Unterschwinger nach dem Einschwingen.
Die Auswirkungen dieser Regelung werden im folgenden Diagramm sichtbar:
Das PID-Tuning ist also im Gegensetz zur Hysterese ein Regelungsverfahren, da unser Steuerungssystem den Istwert mit dem gewünschten Sollwert vergleicht und dynamisch reagiert.
#include <PID_v1.h>
// --- PID-Parameter ---
double temperatur = 20.0; // Starttemperatur (Simulation)
double zielTemp = 30.0; // Sollwert
double leistung = 0.0; // PID-Ausgang (0-255)
// --- PID-Faktoren ---
double Kp = 2.0;
double Ki = 0.1;
double Kd = 0.5;
// --- PID-Objekt ---
PID myPID(&temperatur, &leistung, &zielTemp, Kp, Ki, Kd, DIRECT);
void setup() {
myPID.SetOutputLimits(0, 255); // Lüfterleistung 0–255
myPID.SetMode(AUTOMATIC);
}
void loop() {
// PID berechnen und Regeln
myPID.Compute();
// --- Temperatur-Physik ---
// In diesem Fall einfach stumpf simuliert
temperatur += (0.05 * (25 - temperatur)); // Umgebungstendenz
temperatur -= leistung * 0.01; // Lüftereffekt
}
Zum Kapitel springen Vor- und Nachteile
Vorteile
- feinstufige Anpassung
- stabile Werte
- konstante Regelung
Nachteile
- ggf. komplexes Tuning notwendig
- manchmal zusätzliche Anforderungen an die Hardware
Zum Kapitel springen HTML-Code für die Diagramme
Als kleines Zückerli habe ich hier noch die Charts aus den beiden Screenshots bereitgestellt. Es handelt sich hier um HTML-Dateien, die in jedem Webbrowser aufgerufen werden können.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hysterese vs. PID</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation"></script>
</head>
<body>
<div style="display: flex; justify-content: space-around;">
<canvas id="hystereseChart"></canvas>
<canvas id="pidChart"></canvas>
</div>
<script>
function generateHystereseData() {
let time = [...Array(100).keys()];
let fanPower = [ ];
let temperature = [ ];
let temp = 25; // Starttemperatur
let hystereseMin = 20; // Untere Grenze für Lüfter an
let hystereseMax = 40; // Obere Grenze für Lüfter aus
for (let i = 0; i < time.length; i++) {
// Hysterese-Logik: Lüfter schaltet genau dann um, wenn die Grenze erreicht ist
if (temp >= hystereseMax) {
fanPower.push(100); // Lüfter an
} else if (temp <= hystereseMin) {
fanPower.push(0); // Lüfter aus
} else {
fanPower.push(fanPower[i - 1] || 0); // Beibehalten des vorherigen Status
}
// Temperaturberechnung mit sanftem Übergang
temp += fanPower[i] === 100 ? -2 : 2;
temperature.push(temp);
}
// Fan-Power shiften damit die Täler und Kämme übereinstimmen
fanPower.shift();
// Letzten Wert wieder anhängen
fanPower.push(fanPower[fanPower.length - 1]);
return {
labels: time,
datasets: [
{
label: "Temperatur (°C)",
data: temperature,
borderColor: "red",
borderWidth: 2,
fill: true,
backgroundColor: 'rgba(255, 0, 0, 0.2)',
pointStyle: false,
cubicInterpolationMode: "monotone",
tension: 0.8,
},
{
label: "Lüfterleistung (%)",
data: fanPower,
borderColor: "blue",
borderWidth: 2,
fill: true,
backgroundColor: 'rgba(0, 0, 255, 0.05)',
stepped: true,
pointStyle: false
},
],
};
}
function generatePIDData() {
let time = [...Array(50).keys()];
let target = Array(50).fill(50);
let P = time.map(t => 50 + 10 * Math.sin(t / 10));
let I = time.map(t => 50 + 5 * Math.log(t + 1));
let D = time.map(t => 50 + (t % 5 === 0 ? 10 : -10));
return {
labels: time,
datasets: [
{ label: "Zielwert", data: target, borderColor: "black", borderWidth: 2, borderDash: [5, 5], fill: false },
{ label: "P-Anteil", data: P, borderColor: "blue", borderWidth: 2, fill: false },
{ label: "I-Anteil", data: I, borderColor: "green", borderWidth: 2, fill: false },
{ label: "D-Anteil", data: D, borderColor: "orange", borderWidth: 2, fill: false },
],
};
}
window.onload = function() {
let ctx1 = document.getElementById("hystereseChart").getContext("2d");
new Chart(ctx1, {
type: "line",
options: {
plugins: {
annotation: {
annotations: {
line1: {
type: "line",
yMin: 20,
yMax: 20,
borderColor: "gray",
borderWidth: 2,
borderDash: [5, 5], // Gepunktete Linie
label: {
display: false,
content: "Min",
position: "start",
backgroundColor: "rgba(0, 0, 0, 0.2)",
},
},
line2: {
type: "line",
yMin: 40,
yMax: 40,
borderColor: "gray",
borderWidth: 2,
borderDash: [5, 5], // Gepunktete Linie
label: {
display: false,
content: "Max",
position: "start",
backgroundColor: "rgba(0, 0, 0, 0.2)",
},
},
},
},
},
},
data: generateHystereseData()
});
let ctx2 = document.getElementById("pidChart").getContext("2d");
new Chart(ctx2, {
type: "line",
data: generatePIDData()
});
};
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hysterese vs. PID</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation"></script>
</head>
<body>
<div style="display: flex; justify-content: space-around;">
<canvas id="pidChart"></canvas>
</div>
<script>
function generatePIDData() {
let time = [...Array(150).keys()];
let targetTemp = 30;
let temperature = [];
let fanPower = [];
for (let i = 0; i < time.length; i++) {
// Schematische Annäherung an Zieltemperatur mit langsamerem Einschwingen
let temp = targetTemp + 10 * Math.exp(-i / 40) * Math.cos(i / 3);
temperature.push(temp);
// Lüfterleistung nimmt am Anfang stärker zu und stabilisiert sich langsamer
let fan = 100 * Math.exp(-i / 20) * Math.sin(i / 3) + 40;
fanPower.push(Math.max(0, Math.min(100, fan)));
}
return {
labels: time,
datasets: [
{
label: "Temperatur (°C)",
data: temperature,
borderColor: "red",
borderWidth: 2,
fill: false,
pointStyle: false,
cubicInterpolationMode: "monotone",
tension: 0.4,
},
{
label: "Lüfterleistung (%)",
data: fanPower,
borderColor: "blue",
borderWidth: 2,
fill: false,
pointStyle: false,
stepped: false,
cubicInterpolationMode: "monotone",
tension: 0.4,
},
],
};
}
window.onload = function() {
let ctx2 = document.getElementById("pidChart").getContext("2d");
new Chart(ctx2, {
type: "line",
data: generatePIDData(),
options: {
plugins: {
annotation: {
annotations: {
line1: {
type: "line",
yMin: 30,
yMax: 30,
borderColor: "gray",
borderWidth: 2,
borderDash: [5, 5], // Gepunktete Linie
label: {
display: false,
content: "Zieltemperatur",
position: "start",
backgroundColor: "rgba(0, 0, 0, 0.2)",
}
}
}
}
}
}
});
};
</script>
</body>
</html>