Sterowanie PID: czy dodanie opóźnienia przed następną pętlą to dobry pomysł?
Wdrażam sterowanie PID w języku c ++, aby robot z napędem różnicowym obracał się o dokładną liczbę stopni, ale mam wiele problemów.
Wczesne wyjście z pętli sterowania ze względu na szybkie działanie pętli
Jeśli robot zmierzy swój błąd na mniej niż 0,5 stopnia, opuszcza pętlę sterowania i uważa, że zakręt jest „zakończony” (0,5 to losowa wartość, którą mogę zmienić w pewnym momencie). Wygląda na to, że pętla sterowania działa tak szybko, że robot może obracać się z bardzo dużą prędkością, przekroczyć wartość zadaną i wyjść z pętli / wyciąć moc silnika, ponieważ znajdował się w punkcie zadanym przez krótką chwilę. Wiem, że jest to cały cel regulacji PID, aby dokładnie osiągnąć wartość zadaną bez przeregulowania, ale ten problem bardzo utrudnia dostrojenie stałych PID. Na przykład, próbuję znaleźć taką wartość kp, która zapewnia stałe oscylacje, ale nie ma żadnych oscylacji, ponieważ robot myśli, że „skończył” po przekroczeniu wartości zadanej. Aby to naprawić,Wdrożyłem system, w którym robot musi znajdować się na nastawach przez pewien czas przed wyjściem, i to było skuteczne, pozwalając na występowanie oscylacji, ale kwestia wcześniejszego wyjścia z pętli wydaje się być nietypowym problemem i moje rozwiązanie może być niepoprawne.
Termin D nie ma wpływu ze względu na szybki czas działania
Gdy robot oscylował w sposób kontrolowany, używając tylko P, próbowałem dodać D, aby zapobiec przeregulowaniu. Jednak przez większość czasu nie miało to żadnego wpływu, ponieważ pętla sterowania działa tak szybko, że 19 pętli na 20, szybkość zmiany błędu wynosi 0: robot nie poruszył się lub nie poruszył się wystarczająco być mierzone w tym czasie. Wydrukowałem zmianę błędu i człon pochodny każdej pętli, aby to potwierdzić i zobaczyłem, że oba będą wynosić 0 przez około 20 cykli pętli, zanim przyjmą rozsądną wartość, a następnie z powrotem do 0 przez kolejne 20 cykli. Jak powiedziałem, myślę, że dzieje się tak dlatego, że cykle pętli są tak szybkie, że robot dosłownie nie poruszył się wystarczająco, aby uzyskać jakąkolwiek zauważalną zmianę błędu.To był duży problem, ponieważ oznaczało, że termin D zasadniczo nie miał wpływu na ruch robota, ponieważ prawie zawsze wynosił 0. Aby rozwiązać ten problem, próbowałem użyć ostatniej niezerowej wartości pochodnej zamiast dowolnych wartości 0, ale to nie działało dobrze i robot oscylowałby losowo, gdyby ostatnia pochodna nie odzwierciedlała aktualnej szybkości zmian błędu.
Uwaga: używam również małego sprzężenia wyprzedzającego dla statycznego współczynnika tarcia i nazywam to sprzężeniem wyprzedzającym „f”
Czy powinienem dodać opóźnienie?
Zdałem sobie sprawę, że moim zdaniem źródłem obu tych problemów jest pętla działająca bardzo szybko, więc pomyślałem o dodaniu instrukcji wait na końcu pętli. Jednak celowe spowolnienie pętli wydaje się ogólnie złym rozwiązaniem. Czy to dobry pomysł?
turnHeading(double finalAngle, double kp, double ki, double kd, double f){
std::clock_t timer;
timer = std::clock();
double pastTime = 0;
double currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
const double initialHeading = getHeading();
finalAngle = angleWrapDeg(finalAngle);
const double initialAngleDiff = initialHeading - finalAngle;
double error = angleDiff(getHeading(), finalAngle);
double pastError = error;
double firstTimeAtSetpoint = 0;
double timeAtSetPoint = 0;
bool atSetpoint = false;
double integral = 0;
double derivative = 0;
double lastNonZeroD = 0;
while (timeAtSetPoint < .05)
{
updatePos(encoderL.read(), encoderR.read());
error = angleDiff(getHeading(), finalAngle);
currentTime = ((std::clock() - timer) / (double)CLOCKS_PER_SEC);
double dt = currentTime - pastTime;
double proportional = error / fabs(initialAngleDiff);
integral += dt * ((error + pastError) / 2.0);
double derivative = (error - pastError) / dt;
//FAILED METHOD OF USING LAST NON-0 VALUE OF DERIVATIVE
// if(epsilonEquals(derivative, 0))
// {
// derivative = lastNonZeroD;
// }
// else
// {
// lastNonZeroD = derivative;
// }
double power = kp * proportional + ki * integral + kd * derivative;
if (power > 0)
{
setMotorPowers(-power - f, power + f);
}
else
{
setMotorPowers(-power + f, power - f);
}
if (fabs(error) < 2)
{
if (!atSetpoint)
{
atSetpoint = true;
firstTimeAtSetpoint = currentTime;
}
else //at setpoint
{
timeAtSetPoint = currentTime - firstTimeAtSetpoint;
}
}
else //no longer at setpoint
{
atSetpoint = false;
timeAtSetPoint = 0;
}
pastTime = currentTime;
pastError = error;
}
setMotorPowers(0, 0);
}
turnHeading(90, .37, 0, .00004, .12);
Odpowiedzi
Nie odłączaj kontrolera.
Zadaniem regulatora jest nie tylko sterowanie systemem do żądanej wartości zadanej zgodnie z wcześniej określoną dynamiczną reakcją, ale także przeciwdziałanie potencjalnym czynnikom zewnętrznym, które mogą utrudniać to zadanie. Pomyśl o zakłóceniu, które spowoduje oddalenie systemu od wartości zadanej po jej osiągnięciu. W ten sposób kontroler powinien być zawsze sprawny (chyba że trzeba zmienić samo zadanie iw rezultacie zmienić także kontroler).
W tym celu z pewnością należałoby dodać część integralną, która jest odpowiedzialna za osiągnięcie zerowego błędu stanu ustalonego w obecności niezmodelowanych wielkości i zewnętrznych zakłóceń.
To dość pokrewny zasób: https://robotics.stackexchange.com/a/19198/6941.
Pomiń pochodną.
95% regulatorów PID w przemyśle to regulatory PI (patrz „Systemy sprzężenia zwrotnego” Astrom, Murray), ponieważ część D może odgrywać znaczącą rolę tylko w przypadku powolnych procesów (np. Związanych z regulacją temperatury i poziomu w zbiorniku). To zdecydowanie nie jest twój przypadek. W jakiś sposób powiązane zasoby dotyczące trudności terminów pochodnych to:
- https://robotics.stackexchange.com/a/21556/6941.
- https://robotics.stackexchange.com/a/21555/6941.
Gdy robot oscylował w sposób kontrolowany, używając tylko P, próbowałem dodać D, aby zapobiec przeregulowaniu.
Wygląda na to, że stosujesz się do zaleceń Zieglera-Nicholsa, aby dostroić kontroler. Są tabele, których musisz się trzymać, podając szacunki zysków. Są to jednak działania heurystyczne i prawdopodobnie nie zadziałają w Twoim przypadku.
Po prostu upuść część D i skup się na kontrolerze PI. Istnieje również inny wariant ZN, który nie opiera się na celowo generowanych oscylacjach:https://robotics.stackexchange.com/a/21616/6941.
Nigdy, przenigdy nie dodawaj opóźnień do systemu.
Opóźnienia to źli ludzie, z którymi trzeba sobie poradzić w pętli sterowania, a inżynierowie z najgorszymi bestiami muszą walczyć ( patrz 🎥 ), ponieważ znacznie zmniejszają margines fazowy, popychając cały system w kierunku niestabilności.
Jeśli uważasz, że twoja pętla jest zbyt szybka, zastosuj kształtowanie sygnału wejściowego do wartości zadanej (na przykład z trajektorią minimalnego szarpnięcia ), aby wygładzić początek gwałtownego kroku. System z zamkniętą pętlą będzie reagował bardziej wdzięcznie. Inną możliwością jest to, że twoje wzmocnienie P jest zbyt wysokie: po prostu przestrój kontroler.
W związku z tym, jeśli wstawisz człon całkowy I, będziesz musiał rozważyć czas próby $T_s$ także.
Znacznie lepiej byłoby określić stałą częstotliwość próbkowania dla twojego kontrolera. Naprawdę przybliżona zasada jest taka, że bez względu na potrzebny czas ustalania poza pętlą, gdy działa ona w trybie liniowym , interwał próbkowania powinien być od 10 do 100 razy krótszy niż czas ustalania. Innymi słowy, częstotliwość próbkowania powinna być od 10 do 100 razy większa niż pożądana szerokość pasma pętli.
Jako mały dodatkowy bonus oznacza to, że możesz obliczyć dt poza pętlą
Preferowane jest, aby częstotliwość próbkowania była narzucana sprzętowo, np. Przez zegar sprzętowy, a jeszcze lepiej próbkowanie pozycji powinno być wykonywane sprzętowo, wyzwalane przez timer. To znacznie zmniejsza ograniczenia oprogramowania w czasie rzeczywistym.
Jeśli używasz rozsądnej częstotliwości próbkowania i przeważnie nie rejestrujesz zmian w koderze, oznacza to, że koder nie ma wystarczającej liczby kroków.
Nie zgodzę się z @Ugo_Pattachini, ponieważ jeśli pętla obejmuje silnik, a kontroler jest wystarczająco czysty, niektóre działania różnicowe mogą być korzystne. Ale jest duża szansa, że musi być ograniczony pasmem (tj. Potrzebujesz kontrolera opóźnienia), a jeśli robisz strojenie PID w spodniach, istnieje duża szansa, że nie masz narzędzi do ustawić prawidłowo ograniczenie pasma.
Jeśli jednak nie możesz wymusić bardzo krótkiego czasu próbkowania kodera, nie próbuj nawet używać sterowania pochodnego. Nieregularne czasy próbkowania oraz pewna prędkość na silniku spowodują dodatkowy szum, a sterowniki pochodne mają tendencję do wzmacniania szumu. Jeśli nie możesz ustawić czystego sygnału do sterowania, sterowanie pochodną nie będzie odpowiednie.