Hi,
ich möchte auf einem Mikrocontroller einen PID-Regler implementieren:
abhängig von einem analogen Eingangswert (0..1023) soll ein
Digitalausgang an- oder ausgeschaltet werden.
Der Hardwareteil ist kein Problem, ich würde jetzt nur eine fertige
PID-Implementierung suchen, bei der man entsprechende Parameter
dynamisch übergeben kann (um das Rad nicht noch mal komplett neu
erfinden zu müssen).
Deswegen meine Frage: kennt jemand eine passend Implementierung in C?
Danke!
An sich ist das eine ganze einfache Gleichung, die man in C oder den
meisten höheren Programmiersprachen nahezu wortwörtlich hinschreiben
kann.
Der folgende Halbsatz: "... bei der man entsprechende Parameter
dynamisch übergeben kann (um das Rad nicht noch mal komplett neu
erfinden zu müssen)."
deutet an, dass für Dich dennoch ein Problem zu bestehen scheint.
Der einzige Anhaltspunkt scheint mir das Wort "dynamisch" zu sein. Kann
es sein, dass Du auch die Reglerparameter programmatisch bestimmen
willst?
Vielleicht schreibst Du mal ausführlicher, worum es Dir geht.
Stokmarknes schrieb:
(um das Rad nicht noch mal komplett neu
> erfinden zu müssen).
Nenn das Kind doch beim Namen: Weil du dich nicht mit programmieren oder
der Mathematik dahinter beschäftigen willst.
Das sollte wie oben beschrieben kein Problem sein, denn das gibts
bestimmt schon tausend mal im Netz
Stokmarknes schrieb:> bei der man entsprechende Parameter> dynamisch übergeben kann
Was meinst du damit. Es sollte ja kein Problem sein, daß die Konstanten
als 8 oder 16 Bit Werte in einer Speicherzelle stehen.
Willst du sie jetzt im Programm verändern können und neu flashen oder
sollen sie während des Programmablaufs geändert werden?
Und wenn, dann wie? Mit Potis? Mit einer RS232 Terminalanbindung? ...
Und hier wirds schwieriger mit einem fertigen Programm
Theor schrieb:> Der einzige Anhaltspunkt scheint mir das Wort "dynamisch" zu sein. Kann> es sein, dass Du auch die Reglerparameter programmatisch bestimmen> willst?
Genau das, es geht um eine generische Implementierung, bei der der
Anwender die (PID-)Parameter entsprechend seiner Umgebung selber
verändern kann.
Stokmarknes schrieb:> Theor schrieb:>> Der einzige Anhaltspunkt scheint mir das Wort "dynamisch" zu sein. Kann>> es sein, dass Du auch die Reglerparameter programmatisch bestimmen>> willst?>> Genau das, es geht um eine generische Implementierung, bei der der> Anwender die (PID-)Parameter entsprechend seiner Umgebung selber> verändern kann.
Das sind nach meinem Verständnis zwei verschiedene Vorgänge.
Einmal ist der Anwender der Akteur, im anderen Fall das Programm.
1. Der Anwender verändert die Parameter.
Das würde an meiner Grundaussage nichts ändern. Die PID-Gleichung ist
einfach hin zu schreiben. Hinzufügen ist nur eine Eingabemöglichkeit.
Das ist sehr einfach und völlig unproblematisch - was das Programm
betrifft.
2. Das Programm ermittelt (bestimmt) die Parameter.
Das ist schon wesentlich komplizierter.
Nun ja, dann hast du eben zwei Teilprobleme. Einmal den PID-Regler an
sich und zum anderen das dynamische übertragen von Werten. Für letzteres
gibt es viele Möglichkeiten, da musst du schon eher sagen wo es hingehen
soll. Drehencoder mit Display, UART-Terminal, rein analoge Drehpoties...
Die Aufgabenstellung ist derart megatrivial, dass es eine einfache
Uebungsaufgabe darstellt. Mach mal und komm dann wieder. Suchen,
anpassen und verstehen sind schwieriger als selbst neu schreiben.
Christopher J. schrieb:> Für letzteres> gibt es viele Möglichkeiten, da musst du schon eher sagen wo es hingehen> soll. Drehencoder mit Display, UART-Terminal, rein analoge Drehpoties...
Jemand, der mit der Materie erst anfängt, weiß zunächst noch gar nicht,
womit er(sie) anfangen soll und fragt deshalb hier im Forum, was für
einen Anfänger am einfachsten umzusetzen ist. (Stichwort: Fahrrad neu
erfinden)
Meine Fresse, seitenweise großkotzige Selbstdarstellung aber nicht eine
einzige Antwort dabei, die irgendwie weiter helfen würde. Was habt ihr
alle für ein Problem mit eurem Ego? Seid und dürft ihr im realen Leben
sonst nix? Könnt ihr euch nicht lieber einen SUV kaufen statt hier
rumzuprollen?
An den Thread-Opener: schau dir mal
https://homepages.uni-regensburg.de/~erc24492/PID-Regler/AVR221/IAR/doxygen/pid_8c.html
an, ich habe zwar keine Idee, was die Reset-Funktion machen soll, aber
das dürfte das sein, was du suchst.
Zorg3000 schrieb:> aber nicht eine> einzige Antwort dabei, die irgendwie weiter helfen würde.
Doch, natürlich sind da brauchbare Antworten bei. Hier ist z.B. eine:
M. K. schrieb:> AVR221 von Atmel. Sehr empfehlenswert ;)
Man sollte vllt. noch erwähnen, das damit eine Application Note von
Atmel/Microchip und eine Impementation in C gemeint ist.
Und noch eine:
Bastler schrieb:> Schon mal bei Arduino nachgeschaut?>> https://playground.arduino.cc/Code/PIDLibrary
Du Spassvogel verlinkst ja selber auf die AVR221:
Zorg3000 schrieb:> An den Thread-Opener: schau dir mal> https://homepages.uni-regensburg.de/~erc24492/PID-Regler/AVR221/IAR/doxygen/pid_8c.htmlZorg3000 schrieb:> ich habe zwar keine Idee, was die Reset-Funktion machen soll
Es ist sinnvoll, nach einer Änderung von P,I oder D den Regler
zurückzusetzen. Schadet auch nicht bei der Initialisierung.
Einfach mal AVR221 lesen.
Hallo,
hab den Rest der Beiträge nicht gelesen, aber hab mir mal die Formeln
aus Wikipedia besorgt und nach programmiert ggf hilft es dir ja.
float PIDRegler(float T1,float T2) // PID-Regler
{
// Empfindlichkeit des PID-Reglers
float Tn = 1.0; // dient zur Einstellung des PID Reglers
float Tv = 1.0; // dient zur Einstellung des PID Reglers
float Kp = 1.0; // dient zur Einstellung des PID Reglers
// Variablen für P,I,D
float P,I,D;
// Zeitkostente
float delta_t=0.5;
En = T1 - T2; // T1 = Temperatur_1 = Sollwert hier
// T2 = Temperatur_2 = Istwert
// En = Regelabweichung
// Berechnugn P I D
P = En - en1;
D = (delta_t / Tn) * En;
I = Tv * (En - 2 * en1 + en2);
// Berechnung neuer Stellgröße
delta_y = Kp * ( P + D + I);
Yn = yn1 + delta_y;
// Speichert alte Werte
en2 = en1;
en1 = En;
yn1 = Yn;
// Begrenzt Yn auf 100
if (Yn > 100.0) Yn=99.99;
if (Yn < 0.0) Yn= 0.0;
return Yn; // Gibt Stellgröße zurück und springt ins
Hautprogramm zurück
}
DIE FUNKTION MUSS ALLE 0.5 SEKUNDEN AUFGERUFEN WERDEN DAS ES
FUNKTIONIERT.
falls du dich nicht so auskennst. TV, TN , KP kann man nachgoogln .. zu
not auch der Funktion übergeben.
Hoffe konnte dir helfen.
Gruß
P.s
Bei meinem alten Programm sind en1,en2, etc als Globale Varirablen
deklaiert. Endweder machst du das auch oder deklaierst die in der
Funktion mit -> static da die Werte beim wiederaufruf vorhanden sein
müssen.
static float en1,en2,etc.;
oder vorm Hauptprogramm
float en1,en2,etc.;
Gruß
Vergiss float. Longint ist besser geeignet und schneller zu rechnen.
Denn Float hat 5-6 signifikante Stellen, waehrend longint 9 signifikante
Stellen hat.
Michael V. schrieb:> aber hab mir mal die Formeln aus Wikipedia besorgt und nach programmiert> ggf hilft es dir ja.
Wobei erwähnt werden sollte, dass das lediglich für einfache/träge
Strecken erfolgreich ist, auf diese gezeigte Weise von analog auf
digital umzusteigen.
Wenn phasenreserve und Dynamik kritisch sind, wird meist Tustin
Transformation (bilinear Transformation) verwendet.
Gruß,
Al3ko -. schrieb:> Wobei erwähnt werden sollte, dass das lediglich für einfache/träge> Strecken erfolgreich ist, auf diese gezeigte Weise von analog auf> digital umzusteigen.>> Wenn phasenreserve und Dynamik kritisch sind, wird meist Tustin> Transformation (bilinear Transformation) verwendet.
Gute Ergänzung :)
Stokmarknes schrieb:> abhängig von einem analogen Eingangswert (0..1023) soll ein> Digitalausgang an- oder ausgeschaltet werden.
Würde da nicht ein Zweipunkt-Regler ausreichen ?
Gruß
> aber long int haben doch keine Kommastellen ?
Was willst du denn mit Kommastellen ?
Nehmen wir einen Temperaturregler :
ADC : -32768 .. 32767 , entsprechend zB -30..400 Grad, wie auch immer
linearisiert : mit 20 Schritt pro Grad.
Solltemp = 2452, entsprechend (irgendwas), in der ADC skala
Isttemp = 1127
Fehler = 1325
Proportional gain= 150 -> P Anteil = 198750
Integral gain = 10
Integrator = Integrator + 10* Fehler
sei der Integrator bei 2134436 -> I Anteil = 2134436
Ausgang Regler = P Anteil + I Anteil = 2333186
Das Stellglied, dh die Heizung ist PWM mit 0-4095 (12bit)
Wir bilden den Reglerausgang auf das Stellglied ab, mit
2^24 = 16777216 -> 2^12 = 4096
als div 4096, heisst als shift rechts 12.
Ausgang Regler (2333186) -> Stellglied = 569
Fertig. Du benoetigst keine Kommastellen.
Zitronen F. schrieb:> Fertig. Du benoetigst keine Kommastellen.
Klar so gehts auch, aber benutzt man die Umgewandelten Temperaturen (z.B
23.10°C) und nicht den ADC-Wert... halt nicht.
Naja ich wollte hier nur den Fragesteller helfen.
Viele Wege führen nach Rom :)
Gruß
Michael V. schrieb:> Klar so gehts auch, aber benutzt man die Umgewandelten Temperaturen (z.B> 23.10°C) und nicht den ADC-Wert... halt nicht.
Falls man für den Benutzer da was anzeigen muss, kann man immer noch
eine Rechenroutine für eine Benutzerschnittsstelle zu machen, die dann
die gewohnte Temperatur errechnet.
Aber intern reicht es, ganzzahlig zu arbeiten, auch weil der PID Regler
ja am besten eine gleichmässige Durchlaufzeit haben sollte. Wie o.a.
enthält AVR221 auch schöne Grafiken und ist nicht nur für AVR Anwender
nützlich. Bei mir läuft dieser PID Regler auch auf XMega und STM32.
Michael V. schrieb:> Würde da nicht ein Zweipunkt-Regler ausreichen ?
Sollte man als erstes mal checken.
Die float-Zahl fuer den Benutzer wird ja eher selten benoetigt. Einmal
zur Eingabe des Vorgabewertes, und repetitiv zur Anzeige. Wobei je nach
Zeitkonstante die Anzeige limitiert schnell sein soll. Mehr als 10
Anzeigewerte pro Sekunde werden eh nichts.
Zitronen F. schrieb:> Vergiss float. Longint ist besser geeignet und schneller zu rechnen.
Nö, float ist schneller, da es nur 24Bit berechnen muß.
Zitronen F. schrieb:> Denn Float hat 5-6 signifikante Stellen, waehrend longint 9 signifikante> Stellen hat.
Von den 9 Stellen bleibt aber kaum was übrig, wenn die Koeffizienten
sehr klein werden. Float rechnet dagegen über einen großen Bereich
genau.
Ich hab deshalb den Ärger mit Ganzzahlen hinter mir gelassen.
Peter D. schrieb:> Zitronen F. schrieb:>> Vergiss float. Longint ist besser geeignet und schneller zu rechnen.>> Nö, float ist schneller, da es nur 24Bit berechnen muß.
Ähhhm, NÖ!
Bevor ein float berechnet werden kann muss es ersteinmal ausgepackt und
normalisiert werden.
Guck dir im objdump mal ein float MUL und ein 32Bit int MUL an.
Da staunste Bauklötze bei den Berechnungen.
Statt longint und Konsorten sollten stdint.h genutzt werden, also
uint32_t.
Da hat der 8Bit Prozessor (z.B. nen AVR) die 32Bit mul/div/add/sub schon
alle durch bevor der float überhaupt erstmal ausgepackt wurde.
Ein 32Bit Prozessor hat den 32Bit Wert noch schneller durch.
Float wird erst schnell mit einer FPU.
Danke für die brauchbaren Hinweise. Was mir noch unklar ist: im Beispiel
oben gibt es diese Werte hier:
Michael V. schrieb:> float Tn = 1.0; // dient zur Einstellung des PID Reglers> float Tv = 1.0; // dient zur Einstellung des PID Reglers> float Kp = 1.0; // dient zur Einstellung des PID Reglers
und in pid.c einen SCALING_FACTOR. Die scheinen die
Reaktionsgeschwindigkeit des Reglers zu beeinflussen. Ich hätte das aber
jetzt so verstanden, dass man das eigentlich mit den PID-Werten macht -
wieso gibt es die noch mal zusätzlich?
Die AVR221 ist ein PID der mit Integer Werten arbeitet. Du müsstest also
die entsprechenden Werte für TN, TV und KP als Integer Werte umrechnen
(im einfachsten Fall einfach die Nachkommastellen abschneiden).
Der Scaling Factor ist dazu zu, die Genauigkeit zu erhöhen. Dazu ein
Beispiel:
Du hast KP ermittelt und festgestellt dass z.B. 1.25 ein guter KP-Wert
wäre. Der PID aus der AVR221 rechnet/arbeitet aber nur mit Interger
Werten. Willst du also die Nachkommastellen mit benutzen im PID dann
musst du das Komma um mindestens zwei Stellen nach rechts schieben, also
mindestens mit 100 multiplizieren.
Genau das macht der Scaling Factor in der AVR221. Man rechnet dann in
der PID-Routine/Funktion mit den um den Scaling Factor multiplizierten
Werten und dividiert den Rückgabewert später wieder durch den Scaling
Factor.
In der AVR221 wurde nur nicht 100 als Scaling Factor gewählt sondern
128. 128 ist eine Zweierpotenz, jeder C-Compiler sollte das heute
erkennen und so wird aus einer Multiplikation/Division eine
Shift-Operation die erheblich schneller ausgeführt werden kann als eine
Multiplikation/Division.