Ich bin mir theoretisch bewusst, wie ein PID-Controller funktioniert, habe noch nie einen implementiert. Ich implementiere eine Steuermethode zum Ansteuern eines Ventils über PWM.
Details zum Anwendungsfall: Das System verfügt über zwei ADC-Kanäle, einen für die Eingabe und den anderen für die Rückmeldung. Das Lesen von ADC-Kanälen ist freilaufend, wobei ausreichend Proben genommen werden.
Vorhandene Implementierung: Es gibt eine Endlosschleife, die nur zwei Aufgaben erledigt: ADC-Werte lesen und PWM erzeugen. Es gibt einen Timer-Interrupt, der so konfiguriert ist, dass er bei 20 ms aufgerufen wird. Also 'Ist die Zeit abgelaufen?' im Flussdiagramm unten wird alle 20 ms mit „Ja“ bewertet. Unten ist das Flussdiagramm dessen, was ich jetzt mache.
Folgendes ist das Programm, das ich mir anschaue:
/*
Some information on variables that are being used:
CURR_OUT_CH is Feedback channel
CMD_INP_CH is the channel where external input is applied.
So, ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] is where I am receiving the value of feedback
And, ADC_Val.fADC_Final_mAVal[CMD_INP_CH ] is where I am receiving the value of external input that I am trying to achieve
MAX_ALLOWABLE_DUTY_CYCLE is a macro set to ((uint16_t)480) which Maps to 60% - This is a requirement.
(Op-Amp output is in mV, I convert it into mA based on resistor values)
(Config[chUser_Config_Mode].uiMaxCMD_I) is 350. (max current allowed through, in mA)
*/
#define RESTRICT(x, low, high) (x = (x)<(low)?(low):((x)>(high)?(x=high):(x)))
typedef struct {
float fFeedback;
float fOutput;
float Kp;
float Ki;
float fIntegralError;
float fSetpoint;
} PIControl_t;
PIControl_t PI;
uint16_t Load_Dutycount;
void PICompute(PIControl_t *pPI)
{
// I know that if PI is already a global, then taking the pointer doesn't make sense here,
// but, I may have to add another PI for a different sensor here, that is why I have used
// it this way!
// Instantaneous error is always local
float fError = 0.0;
// The classic PID error term
fError = pPI->fSetpoint - pPI->fFeedback;
// Compute the integral term
pPI->fIntegralError += (pPI->Ki * fError);
// Run all the terms together to get the overall output
pPI->fOutput = (pPI->Kp * fError) + (pPI->fIntegralError);
}
void Update_PWM_Module(void)
{
// Might want to get rid of this fCount, lets see.
float fCount = 0.0;
// Timer hasn't generated an interrupt yet (Integration time hasn't elapsed)
// ISR sets the bCompute variable - Flags are Not the best way, but does what it should.
// And, Timer doesn't start counting if bCompute is set
if(!bCompute)
{
// No control action needed, return!
return;
}
// Assign the feedback value read for PI output computation
PI.fFeedback = ADC_Val.fADC_Final_mAVal[CURR_OUT_CH];
// Compute the PI Controller output
PICompute(&PI);
// Formulate the value to be used to generate PWM
ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] = ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] + PI.fOutput;
// Map Output to no. of counts
fCount = (float) ((ADC_Val.fADC_Final_mAVal[CURR_OUT_CH] * MAX_ALLOWABLE_DUTY_CYCLE) / (float)(Config[chUser_Config_Mode].uiMaxCMD_I));
// Convert into compatible Duty Count type - uint16_t
Load_Dutycount = (uint16_t) fCount;
// Bound the output count between worst case lower and higher points
RESTRICT(Load_Dutycount, MIN_DUTY_CYCLE_COUNT, MAX_ALLOWABLE_DUTY_CYCLE);
// Generate PWM
Generate_PWM(Load_Dutycount);
// Assign the latest external input value read from ADC as the Setpoint for PI computation
PI.fSetpoint = ADC_Val.fADC_Final_mAVal[CMD_INP_CH] ;
// Not sure about this --- Because I think with a new Setpoint, the integrated error (which was developed based on previous Setpoints) will have no significance.
PI.fIntegralError = 0.0;
// Start integration all over again (Timer doesn't start counting if bCompute is set)
bCompute = false;
}
int main(void)
{
// Some code for Power-ON initialization like,
// ADC
// Timer
// PWM
// PI variables
// Everything else which needs one-time initialization before going into the infinite loop
while(1)
{
Read_ADC();
Update_PWM_Module();
}
}
Sobald die PWM erzeugt ist, läuft sie frei. Das Tastverhältnis bleibt konstant, es sei denn, ich ändere es, daher ändert es sich nur periodisch basierend auf der PI-Berechnung.
Zur Verdeutlichung, wenn ich sage "den Wert des integrierten Fehlers aufheben", meinte ich pPI->integralError = 0.0;
das C-Programm.
Problemstellung: Die Gesamtzeit für die Ausführung der Schleife, wenn der Timer nicht abgelaufen ist, beträgt ungefähr 2 ms. Die Ausführungszeit erhöht sich natürlich, wenn die PI-Berechnung abgeschlossen ist und die PWM-Erzeugungsfunktion aufgerufen wird.
Ich prüfe die beiden Signale:
- Ausgang der Rückkopplung am Ausgang des verwendeten Operationsverstärkers.
- Eingabe in das System.
Meine Frage ist, ist der Betriebsablauf korrekt? Habe ich Recht, PWM erst nach Abschluss der PI-Berechnung zu erzeugen und den Wert des integrierten Fehlers auf 0,0 zurückzusetzen, wenn ein neuer Sollwert zugewiesen wird? Beim Testen mit einem Schritteingang von 0-4 V, 0,5 Hz auf dem Oszilloskop sehe ich, dass das System etwa 120 ms benötigt, um seinen Ausgang auf den Eingang anzuheben. Ich kann korrelieren, dass die P- und I-Werte angepasst werden müssen, um die Zeit zu verbessern. In diesem Beitrag geht es nicht viel um die Abstimmung der Werte der P- und I-Faktoren.
Verwandte Lektüre: Fragen zu electronic.stackexchange, die ich durchgelesen habe und eng damit verbunden sind:
Ich: Aber Sie haben nicht erklärt, warum Sie den Integralterm zurücksetzen / annullieren.
Weda: Weil ich dachte, dass bei einem neuen Sollwert der integrierte Fehler (der auf der Grundlage früherer Sollwerte entwickelt wurde) keine Bedeutung hätte. Ich denke, hier habe ich grundlegende Verwirrung / Unverständnis.
Ich gebe Ihnen mein Beispiel „PI für Anfänger“, das einigen bei der Arbeit zu helfen scheint:
Nur proportionale Steuerung
Abbildung 1. Reaktion des Nur-P-Tempomaten. Beachten Sie, dass die Sollgeschwindigkeit von 80 km/h niemals erreicht wird.
Wir schalten den Tempomat ein. Er beschleunigt auf 70 km/h bei 100 % Gas. Es sollte schon klar sein, dass wir niemals 80 km/h erreichen werden, denn mit Roll- und Windwiderstand können wir 80 km/h nicht ohne Leistung halten. Nehmen wir an, er pendelt sich bei 77 km/h bei 30 % Leistung ein. Das ist das Beste, was wir mit der Nur-P-Steuerung erreichen können.
Proportional-Integral-Regelung
Abbildung 2. Die Antwort mit der Hinzufügung einer integralen Steuerung.
Wenn die integrale Aktion hinzugefügt wird, steigt der integrale Term weiterhin mit einer Rate an, die proportional zum Fehler ist. Dies ist in der Integralkurve von Abbildung 2 als eine hohe Anfangsanstiegsrate aufgrund des großen Anfangsfehlers zu sehen, der auf einen Nullanstieg (Pegellinie) abfällt, wenn der Fehler schließlich eliminiert wird.
Abbildung 3. Die klassische PID-Regelfunktion. Quelle: Wikipedia - PID-Regler .
Eine Sache, die mir ziemlich spät im Leben dämmerte, war, dass, wenn die integrale Aktion den Ausgang korrigiert, der Fehler auf Null fällt, sodass der Beitrag der Proportionalsteuerung ebenfalls auf Null fällt. Die Ausgabe, wenn der Fehler Null ist, wird ausschließlich durch die Integralwirkung aufrechterhalten.
Beachten Sie, dass, wenn sich der Sollwert oder die Belastung ändert (das Auto trifft auf einen Hügel oder Gegenwind), der Fehler auf einen Wert ungleich Null wechselt, der P-Regler sofort von Null ansteigt und die Integralaktion von ihrem aktuellen Wert aus fortgesetzt wird - nicht von Null.
Bei Engineers Excel gibt es einen einfachen Excel- PI-Simulator , der möglicherweise von Nutzen ist. Ich weiß nicht, ob es das Beste ist.
Spehro Pefhany
Weda Pashi
Transistor
Weda Pashi
pPI->integralError
Zuweisung0.0
Harry Swensson
Transistor
Weda Pashi
Transistor
Weda Pashi
Lundin
Lundin
Weda Pashi
Lundin
Weda Pashi
Lundin
Weda Pashi
Abdel Alem
Lundin
Abdel Alem
Abdel Alem
Lundin
Abdel Alem
Lundin