PID Regler - Hilfe, ich finde den Fehler nicht

Hallo,

ich habe meine Wärmeschranksteuerung nun von 2-Punktregelung auf PID umstellen wollen. Die Implementation ging schneller, als vermutet, aber das Ding heizt, als gäbe es kein Morgen! :stuck_out_tongue_closed_eyes:
Ich habe schon zig mal drübergeschaut und auch Kp, Ki und Kd gedreht - erfolglos!
Habe die Initialisierung meiner Werte von float auf double umgestellt, weil die Werte im Beispielsketch der PID Lib so initialisiert wurden und ich die Temperatur übergeben muß. Falsche Werte auf Grund von unterschiedlichen Datentypen beim Rechnen damit hoffe ich damit ausgeschlossen zu haben.

/*
Example 39.3 - NXP SAA1064 I2C LED Driver IC Demo III
Displaying numbers on command
http://tronixstuff.com/tutorials > chapter 39
John Boxall July 2011 | CC by-sa-nc
*/
#include <PID_v1.h>
#include "Wire.h" // enable I2C bus
#include <LiquidCrystal_I2C.h>
#define sensorPin 0    // Verbunden mit LM35 Ausgang
#define DELAY1 10      // kurze Wartezeit beim Messen
#define DELAY2 500     // kurze Wartezeit beim Anzeigen
#define heaterPin 9     // Heizung-Pin
#define threshold 40   // Schalt-Temperatur für Lüfter (40 Grad Celsius)
#define hysterese 0.1  // Hysterese-Wert (0.1 Grad Celsius)
#define Kp 2.5
#define Ki 0.01
#define Kd 0

const int cycles = 50; // Anzahl der Messungen
LiquidCrystal_I2C lcd(0x20, 16, 2); // Adresse auf 0x27 für 16 Zeichen/2 Zeilen

byte saa1064 = 0x70 >> 1; // define the I2C bus address for our SAA1064 (pin 1 to GND)
int digits[12]={
63, 6, 91, 79, 102, 109, 125,7, 127, 111, 204, 0};
// these are the byte representations of pins required to display each digit 0~9, °C sign, and blank digit

//Define Variables we'll be connecting to
double Setpoint, Input, Output;

//Specify the links and initial tuning parameters
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);

int WindowSize = 5000;
unsigned long windowStartTime;

// right hand digit 180° flip - DP ist used as ° symbol :-) 204 is °c for big C use 143
void setup()
{
   windowStartTime = millis();
  
  //initialize the variables we're linked to
  Setpoint = threshold;

  //tell the PID to range between 0 and the full window size
  myPID.SetOutputLimits(0, WindowSize);

  //turn the PID on
  myPID.SetMode(AUTOMATIC);
  
  lcd.init(); // LCD initialisieren
  lcd.backlight(); // Hintergrundbeleuchtung aktivieren  
pinMode(heaterPin, OUTPUT);
Wire.begin(); // start up I2C bus
delay(500);
initDisplay();
}
void initDisplay()
// turns on dynamic mode and adjusts segment current to 12mA
{
Wire.beginTransmission(saa1064);
Wire.write(B00000000); // this is the instruction byte. Zero means the next byte is the control byte
Wire.write(B01110111); // control byte (dynamic mode on, digits 1+3 on, digits 2+4 on, 21mA segment current
// left 111 is relevant for current left hand "1" 12mA, middle 6mA and right 3mA - so you can tewak it
Wire.endTransmission();
}
void clearDisplay()
{
Wire.beginTransmission(saa1064);
Wire.write(1); // start with digit 1 (right-hand side)
Wire.write(0); // blank digit 1
Wire.write(0); // blank digit 2
Wire.write(0); // blank digit 3
Wire.write(0); // blank digit 4
Wire.endTransmission();
}
void displayInteger(int num, int zero)
{
int hundred, ten, one;
// breakdown number into columns
hundred = num/100;
ten = (num-(hundred*100))/10;
one = num-((hundred*100)+(ten*10));
if (zero==1) // yes to leading zero
{
Wire.beginTransmission(saa1064);
Wire.write(1);
Wire.write(digits[hundred]);
Wire.write(digits[ten]+128); // 128 turns on DP
Wire.write(digits[one]);
Wire.write(digits[10]); // print position 10 from arry - °C
Wire.endTransmission();
delay(10);
}
else
if (zero==0) // no to leading zero
{
if (hundred==0) { hundred=11; }
if (hundred==0 && num<100) { hundred=16; }
if (ten==0 && num<10) { ten=16; }
Wire.beginTransmission(saa1064);
Wire.write(1);
Wire.write(digits[hundred]);
Wire.write(digits[ten]+128);
Wire.write(digits[one]);
Wire.write(digits[10]);
Wire.endTransmission();
delay(10);
}
}
void loop()
{
double resultTemp = 0.0;
for(int i = 0; i < cycles; i++){
int analogValue = analogRead(sensorPin);
double temperature = (5.07 * 100.0 * analogValue) / 1024; // reale Spannung Ub (theoretisch 5.00V) 
resultTemp += temperature; // Aufsummieren der Messwerte
delay(DELAY1);
  } 
resultTemp /= cycles; // Berechnung des Durchschnitts
lcd.clear(); // clear-Methode löscht LCD Inhalt
lcd.print("Temp: "); // print-Methode schreibt LCD Inhalt
lcd.print(resultTemp);

 Input = (resultTemp);
  myPID.Compute();
  
lcd.setCursor(10, 0);
#if ARDUINO < 100
lcd.print(0xD0 + 15, BYTE); // Grad-Zeichen (Arduino 0022)
#else
lcd.write(0xD0 + 15); // Grad-Zeichen (Arduino 1.00)
#endif
lcd.print("C");
lcd.setCursor(0, 1); // setCorsor-Methode positioniert LCD-Cursor
lcd.print("Heizung: ");
{
delay(10);
}
clearDisplay();
{
 /************************************************
   * turn the output pin on/off based on pid output
   ************************************************/
  if(millis() - windowStartTime>WindowSize)
  { //time to shift the Relay Window
    windowStartTime += WindowSize;
  }
  if(Output < millis() - windowStartTime) digitalWrite(heaterPin,HIGH);
  else digitalWrite(heaterPin,LOW);
  
displayInteger(resultTemp * 10 ,0); //for LED-Display x10 - DP is hardwired
 delay(5);
// if(resultTemp < (threshold - hysterese)) // Temperaturvergleich pos.
// digitalWrite(heaterPin, HIGH);           // Heizung anschalten
// else if(resultTemp > (threshold + hysterese)) // Temperaturvergleich neg.
// digitalWrite(heaterPin, LOW);                 // Heizung abschalten
lcd.print(digitalRead(heaterPin) == HIGH?"an":"aus");
// delay(DELAY2);
}
}

Kompilieren tut er alles, aber irgendwo steckt ein logischer Fehler drin, den ich nicht finde :-/
Da der PID quasi eine Blackbox ist, weiß ich auch nicht, wo ich mit Auslesen von Zwischenergebnissen üner die serielle Schnittstelle ansetzen soll.

Gruß Gerald

Zwischenergebnisse wüsst ich auch nicht, aber du kannst dir den jeweiligen Wert (Output) problemlos ausgeben lassen.
Hast dir mal das Beispiel: Arduino Playground - PIDLibraryRelayOutputExample
angesehen?
Das dürfte weitgehend das tun, was du vorhast...wenn ich dein Programm richtig überflogen habe.

Hm, das dachte ich auch, aber irgendwie ist der ATMEL da anderer Meinung.
Ich habe jetzt mal den Vorschlag von Jurs aufgegiffen und seine % Steuerung mit PWM implementiert.
Sieht recht vielversprechend aus. Ich mußte nur noch einen Bug in meiner Formel entfernen. Demnach hat er bei 37,5° angefangen mit 25% zu pulsen, aber zwischen 40° und 40,1° im Hysteresebereich von unten her dann nochmal mit 100% geheizt, wie ein Mann. :smiley:
Das habe ich ihm jetzt vermutlich ausgetrieben. Flashen tu ich das neue File aber erst nach dem Aufstehen.
Wobei PID schon cooler wäre...
Aber egal. Es funktioniert erst mal besser als der Zweipunktregler.
Jetzt gehe ich erstmal an der Matratze horchen :wink:

Such mal nach PID etwas herum. Es gibt irgendwo ein Forum, wo es scheinbar sehr beliebt ist, irgendwelche Espressomaschinen mittels PID zu regeln-das dürfte deiner Aufgabenstellung ziemlich nahe kommen.
Da steht auch einiges zum Justieren, sowie zur Programmierung.

Hexenwerk isses nicht, wenn man sich mal etwas reingefuchst hat.
Eventuell mal schnell nen Testaufbau zimmern, mit ner LED als Heizung und nem Fotowiderstand als Geber oder so, da kannst du nachvollziehen, wann sich was tut.

Ist denn die Heiz-logik richtig? also: heaterPin HIGH bedeutet heizen?

ansonsten hilft mir immer recht gut, wenn ich mir die Kenngrößen im Sermon ausgeben lassen und die dann im Excel als Grafik ansehe.
Diesen Code in die loop(), der gibt alle 100ms die Kennwerte aus, Tabgetrennt.

static unsigned long TS;
if (millis() -TS > 100){
  TS = millis();
  Serial.print(Input);  Serial.print("\t"); 
  Serial.print(Output);  Serial.print("\t"); 
  Serial.print(Setpoint);  Serial.println();
}

Den Inhalt vom Monitor kannst du dann ins Excel kopieren und dir die Kurven ansehen. Dann erkennst du auch, ob der Regler schwingt.