Regulatory PID na Arduino. Wstęp

W tym tekście postaram się przybliżyć ideę regulatorów PID, ich zastosowania oraz sposób implementacji w języku C. Jest to wstęp do cyklu wpisów o regulatorach w robotyce. W tej serii nie będę się zagłębiał w Teorię Sterowania, zamiast tego skupię się na wykorzystaniu omówionych regulatorów w praktyce.

Idealny regulator PID
Idealny regulator PID

Czym to się je, czyli co to jest regulator

W Teorii Sterowania istnieje pojęcie układu regulacji automatycznej (URA). Schemat blokowy takiego układu wygląda tak:

Układ regulacji

Sygnał zadany to np. napięcie zasilania silnika elektrycznego lub serwa. Analogicznie może być to zasilanie pompy wodnej, dopływ paliwa do silnika spalinowego itp. Obiekt to rzecz, która ma być kontrolowana np. silnik. Sygnał wyjściowy to np. prędkość obrotowa wału silnika. Dodanie regulatora i sprzężenia zwrotnego ma celu stworzenie sytuacji, w której mimo pojawienia się zakłóceń (np. zmiana obciążenia silnika) układ sam będzie wracał do stanu określonego przez sygnał zadany. Ot cała filozofia ;)

Nazwa regulatora PID pochodzi od nazw jego poszczególnych członów:

  • Proporcjonalnego (P – proportional)
  • Całkującego (I – integral)
  • Różniczkującego (D – derivative)

Każdy z członów może z powodzeniem występować sam jako regulator lub w innej kombinacji, stąd powszechne występowanie regulatorów P, PI, PD. Regulator na wyjściu daje nam „u” czyli tzw. sygnał sterujący, a pracuje na uchybie „e” – czyli różnicy aktualnej wartości sygnału od wartości zadanej. Żeby nie zagłębiać się w teorię nie będę podawał wzorów opisujących regulator PID ;) Postaram się wytłumaczyć to intuicyjnie. Wystarczy pamiętać, że są cztery istotne wielkości: „Kp”, „Ki”, „Kd” – czyli wzmocnienia poszczególnych części regulatora oraz „dt”, czyli co jaki czas pobiera się próbkę. Możecie się też spotkać z innymi parametrami opisującymi regulator, np na Wikipedii, czyli „Ti” i „Td”. „Ki” to iloraz „Kp” i „Ti” pomnożony przez „dt”, a „Kd” to iloczyn „Kp” i „Td” podzielony przez „dt” :) Opis jest długi, ale te parametry stosuje się wymiennie, w zależności od potrzeb. Tak samo jak rezystancję i konduktancję czy impedancję i admitancję, stosuje się je wymiennie dla wygody – by zastąpić dzielenia mnożeniami.

Działanie regulatora PID postaram się zaprezentować przy pomocy kilku charakterystyk przedstawiających zmianę regulowanej wartości w czasie. Są to tzw. odpowiedzi skokowe, ponieważ na wejście regulatora podajemy skok od 0 do 1 (fioletowy wykres), a na wyjściu dostajemy coś… innego. Zadanie regulatora jest proste: sprawić, by to co było na wyjściu było takie samo jak na wejściu.

Odpowiedź skokowa obiektu
Odpowiedź skokowa obiektu

 

Odpowiedź skokowa układu regulacji
Kp = 10 || Ki = 0 || Kd = 0

 

Odpowiedź skokowa układu regulacji
Kp = 10 || Ki = 0 || Kd = 1.5

 

Odpowiedź skokowa układu regulacji
Kp = 10 || Ki = 1.5 || Kd = 0

 

Odpowiedź skokowa układu regulacji
Kp = 10 || Ki = 1.5 || Kp = 1.5

 

Odpowiedź skokowa układu regulacji
Kp = 20 || Ki = 1.5 || Kd = 2.5

 

Odpowiedź skokowa układu regulacji
Kp = 20 || Ki = 4 || Kd = 2.5

Jak widać, zależnie od wartości Kp , Ki oraz Kd zmienia się charakterystyka sygnału wyjściowego. Został zadany sygnał o wartości 1, cała rzecz polega na tym by jak najszybciej na wyjściu też dostać 1. Bez żadnego regulatora, bez sprzężenia, sygnał stabilizował się na poziomie 0.25 – to za mało. Wzmocnienie członu proporcjonalnego zwiększa siłę sygnału wyjściowego i przyspiesza osiąganie wartości zadanej, a człon całkujący pozwala na pozbycie się uchybu całkowitego (wartość sygnału w nieskończoności osiąga wartość zadaną, błąd wynosi zero). Stąd przy wysokiej wartości Ki sygnał pod koniec czasu trwania symulacji był dosyć blisko wartości zadanej. Zadałem zbyt dużą wartość Kd i dlatego pojawiły się zniekształcenia. Niemniej, znacznie przyspieszył osiągnięcie wartości zadanej. Podsumowując, człon P regulatora odpowiada za korektę błędu z teraźniejszości, człon I – błędu z przeszłości, a człon D stara się pozbyć błędu jaki będzie w przyszłości. 

Regulatory PID – zastosowanie

W zależności od zastosowania wykorzystuje się różne regulatory. Człon całkujący jest bardzo wolny, nie przydaje się tam gdzie coś się szybko zmienia. Dlatego regulatory PI i PID znajdują zastosowanie przy regulacji temperatury, przy trymowaniu pracy pompy ciepła, itp. Mają one na tyle dużą inercję, że mała szybkość reakcji członu całkującego nie jest problemem, a pozwala cieszyć się ze swojej właściwości – likwidowania uchybu do zera. W ten sposób, po ustawieniu termostatu na 20 stopni, po pewnym czasie faktycznie w pokoju jest 20 stopni, nie mniej i nie więcej. Prędkość robota zmienia się dosyć szybko, w związku z tym w robotyce stosuje się głównie regulatory PD. Oczywiście regulatory P, PI, PD i PID to nie jedyne rodzaje regulatorów, są jeszcze regulatory histerezowe, dwupołożeniowe, step-forward i wiele, wiele więcej.

Implementacja

Wzór opisujący mechanikę regulatora PID, opisany wyżej, należy teraz przedstawić w sposób zjadliwy dla Arduino. Cytując Abrahama Lincolna: „Każda praca jest możliwa do wykonania jeśli podzielić ją na małe odcinki.”. Zgodnie z opisem wyżej, regulator ma trzy niezależne człony, rozdzielmy je zatem. Zacznijmy od najtrudniejszego – członu całkującego.

Trzeba zaimplementować w programie całkę. Co to jest całka? Jest to suma nieskończenie wielu, nieskończenie małych fragmentów. Intuicyjnie, całkę oznaczoną można rozumieć jako pole między wykresem funkcji f(x) a osią x. Funkcją f(x) może być np. zmiana temperatury czy prędkości w czasie, zmiana wychylenia w funkcji położenia, etc. Właśnie to pole pod wykresem trzeba policzyć. Jest wiele metod numerycznych, opracowanych przez ludzi znacznie mądrzejszych niż ja, do liczenia wartości całek funkcji. Każda metoda niesie ze sobą pewien błąd, niestety – z reguły im metoda jest prostsza tym błąd jest większy. Najpopularniejsze w prostych zastosowaniach, gdzie taki błąd można tolerować, jest całkowanie metodą prostokątów i trapezów.

Metoda prostokątów

Zgodnie z rysunkiem, pole pod wykresem dzieli się na prostokąty.

Metoda prostokątów

Powstające po połączeniu „ząbki” to błąd tej metody. Pole pod wykresem funkcji można policzyć sumując pola wszystkich prostokątów. Szerokością prostokąta jest malutki fragment osi rzędnych, z kolei wysokością – wartość funkcji, np. temperatura wody w danej chwili. Zmniejszając szerokość „fragmentu” na osi czasu, zwiększa się gęstość próbkowania co poprawia dokładność całkowania.

int dt = 20; //podstawa prostokąta, co ile pobiera się próbkę
int e; //uchyb
int y; //wynik całkowania
...
void loop()
{
 e = analogRead(2); //tutaj założyłem, że wartość uchybu poznajemy za pomocą odczytu z pinu A2
 y += e*dt;
}

Metoda trapezów jest analogiczna do metody prostokątów, z tą różnicą, że zamiast liczenia pól prostokątów – oblicza się pola trapezów.

 Metoda trapezów

int dt = 20; //co ile pobiera się próbkę
int ep; //uchyb poprzedni
int en; //uchyb następny
int y; //wynik całkowania
...
void loop()
{
 en = analogRead(2); //tutaj założyłem, że wartość uchybu poznajemy za pomocą odczytu z pinu A2
 y += ((ep + en)/2)*dt;
 ep = en;
}

Człon różniczkujący

Opis wzorem różniczki wygląda następująco:

Różniczka

gdzie f’(x) to pochodna opisana wzorem:

Pochodna

Te dwa dziwne wzory najprościej można wytłumaczyć tak: różniczka to nieskończenie mała zmiana danej zmiennej. Taka jakby mała różnica ;) Dlatego można pokusić się o taki zapis:

Zaokrąglenie definicji różniczki

Pod warunkiem, że nie dzieli tych kolejnych wartości duża odległość wzdłuż osi x.

int dt = 20; //co ile pobiera się próbkę
int ep; //uchyb poprzedni
int en; //uchyb następny
int y; //różniczka
...
void loop()
{
 en = analogRead(2); //tutaj założyłem, że wartość uchybu poznajemy za pomocą odczytu z pinu A2
 y = (en - ep)/dt;
 ep = en;
}

Nadszedł czas by zebrać wszystko w całość.

int dt = 20; //co ile pobiera się próbkę
int ep; //uchyb poprzedni
int en; //uchyb następny
int U; //sygnał sterujący
int C; //część całkująca
int Kp; //wzmocnienie
int Ti; //stała całkowania
int Td; //stała różniczkowania
...
void loop()
{
 en = analogRead(2); //tutaj założyłem, że wartość uchybu poznajemy za pomocą odczytu z pinu A2
 C += ((ep + en)/2)*dt;
 U = Kp*(en + (1/Ti)*C/1000 + Td*(en - ep)*1000/dt)
 ep = en;
}

Skąd się wzięło to mnożenie i dzielenie przez 1000? Chodzi o zamianę jednostek na jednostki SI, z milisekund na sekundy. Kod wycinałem z pewnego projektu nad którym ugrzęzłem i ta zamiana to pozostałość po nim.

W następnym artykule postaram się omówić różne metody doboru nastaw regulatora, a trzeba przyznać, że nie jest to rzecz lekka ;) Ale warta zachodu, gdyż dobrze ustawiony regulator potrafi zdziałać cuda.