PID - jeszcze raz

Jan 22, 2026 Last reply: 5 miesięcy temu 59 Replies

Ale przy takim błędzie, człon P też powinieni stukać.

Nie wiem, na ile dobrze masz zrobioną obslugę enkodera w PIO, ale potrafię sobie wybrazić, że jak jest gdzieś na granicy, to może jeden sygnał dużo drgań wytwarzać, a kiepski program do enkodera (inkrementalnego?)sobie z tym nie radzi. Ale wtedy:

-błędy pozycji byłyby, a nie masz,

-błędy byłyby jakieś losowe, a nie tak 5, 6, 5, 56 ..

Jak Ty go sterujesz? Wysyłasz mu nową pozycję, on ma dojsc i stać, a pod koniec generuje taki ciąg błędów?

Czy mu wysyłasz narastające pozycje, 5, 10, 15, 20, 25, ... i patrzysz co się dzieje.

Czy może mu wysłasz polecenie zmiany: "+5" .... "+5" ...."-7" ?

Bo może problem jest gdzies tu od strony zadawania- wysyłasz

20 25 30 35 40 45 cos ginie, czy przekłamuje, RPI odbiera np 20 25 90 35 40

i bład 55 przez chwile jest. Ale to raczaj znów nie pasuje, to tego co pisałes.

A to "Print(DeltaError)" to jak pokazuje? port COM symulowany na USB? Może tu jest jakiś problem - nie nadąża wypluc, i gdzies z dwóch robi się jeden komunikat ... ale chyba nie - bo małych błędów nie raportujesz.

No i jeszcze jedno może to "drukowanie" czasem czeka na transmisję, zatrzymuje pętlę regulatora, silnik się dalej kręci, i za chwilę regulator wypluje duży błąd ... ale znow jakiś bardziej losowy powinien być ...

J.

W dniu 5.02.2026 o 08:38, J.F pisze:

Nie stuka, bo Kp jest niewielkie - przy Kd=0 musi być niewielkie dla stabilności. Ale przy takim niewielkim wystarczy wprowadzić niewielkie Kd i już słychać stukanie.

Przecież pisałem - dodaję stałą liczbę co cykl.

To bez znaczenia. błędy są czy się je printuje czy nie. Stuka tak czy siak.

Nic nie czeka. Pętla ma 1ms czy drukuję czy nie. Bufor drukowania chodzi osobno i fakt, nie nadąża jak za dużo drukuję na raz, ale to nadal nie zmienia czasu pętli.

Dobra, dziś spędziłem nad tym kilka godzin. Generalnie wygląda na to, że te błędy to są jakieś skumulowane (zbuforowane?) impulsy z enkodera. Czyli: W pierwszej kolumnie mamy licznik odczytów z fifo PIO, w drugiej odczytaną wartość, w trzeciej różnicę do poprzedniej - żeby było łatwo patrzeć. Teraz steruję PWM minimalnie żeby tylko się silnik kręcił (pid nie działa - daję stałą wartość do PWM) i mamy: (tylko interesujący fragment)

1922 1922 1 1923 1923 1 1924 1924 1 1925 1925 1 1926 1928 3 1927 1929 1

kręcimy trochę szybciej i mamy:

1920 10714 7 1921 10722 8 1922 10730 8 1923 10738 8 1924 10746 8 1925 10753 7 1926 10919 166 1927 10926 7

Dodatkowo - testowałem to trzy razy - wartość mimo tych błędów odpowiada położeniu enkodera. Enkoder daje 800 impulsów na obrót* - zadaję mu

800000 kroków, błędy sobie radośnie stukają a on po tych 100 obrotach staje dokładnie tam gdzie był w zerze. Czyli na mój gust są to kroki, które powinny się pojawić po jednym gdzieś we wcześniejszych odczytach ale się nie pojawiają. Czy są to kolejne cyfry złożone w liczbę, tak jak się wydawało? - nie, to tylko przypadek. Czy liczba 1926 odczytów ma tu jakieś znaczenie? - Tak jest tylko na początku, potem już różnie. Tutaj odczyty tylko błędów, czyli printuje jeśli różnica do poprzedniej większa od 10 (pokrywa się ze stukaniem), druga kolumna tym razem co ile odczytów: 17329 2515 18 19848 2519 18 22361 2513 17 24882 2521 18 27398 2516 17 29914 2516 18 32435 2521 18 34947 2512 17 37470 2523 18 39977 2507 17

Czyli coś źle działa i nie wiemy dlaczego. Nawet nie mam teorii jak taki błąd mógłby powstać. Próbowałem wgrywać nowego MicroPythona, najnowsze jakieś nieoficjalne wydania, następnie najstarsze jakie tylko mam - dzieje się to samo.

Znalazłem jeszcze to:

formatting link
łem tą klasę, odpaliłem i już się zacząłem cieszyć, bo okazało się, że jednak z tamtym odczytem było coś nie tak, bo mój enkoder ma jednak 1600 imp/obrót (stąd gwiazdka wcześniej)... ale błędy i stukanie nadal jest.

Trudno mi to komentować, dlatego powstrzymam się.

I jakoś wysyłasz tę powiekszoną liczbę? Binarnie, tekstowo?

Ale jak nie nadąża ... albo funkcja Print czeka, albo cos się z bufora gubi ...

Ale na początku jest zawsze 1926? To musi miec jakis powód.

Pierwsze poderzenie, które bym miał, to jednak to, że cos zatrzymuje główną pętlę,

Ale ciekawe bardzo

J.

W dniu 5.02.2026 o 20:34, Mirek pisze:

Gdyby to był uszkodzony w jednym miejscu enkoder to błąd by siię pojawiał przy mniejszej liczbie kroków bo0 jego rozdzielczość jak pisałeś to 1600 imp/obrót, więc to nie to. Ale jest inny trop, na pi siedzi linux z calym swoim bagażem i tutaj tym 'przeskadzaczem' może być przerwanie zegarowe np co sekundę. Stąd taka powtarzalność, pytanie czy można go wyłączyć w systemie. Albo wybrać inną płytkę, np arduino na avr-e gdzie całkowicie panujemy nad systemem i przepisać program w C i tam uruchomić. Od biedy ESP, ale one też mają jakiego rt-sa który jest nadrzędny nad programem, więc i tam może też być przerwanie które zatrzymuje wszystko.

to by potwierdzało moje przypuszczenia.

Zależą? bo w dwóch przykładach mamy inne liczby wywołań. Ale wywołania skorelowane z czasem ... chyba.

Temat wart dalszego zbadania.

J.

W dniu 6.02.2026 o 20:39, J.F pisze:

No nie do końca są skorelowane, bo tam jest sprawdzanie, czy bufor jest pełny i jak nie jest pełny to nie ma wywołania. Jeśli zrobi co najmniej jeden krok na pętlę, to wywołanie jest co pętlę (1ms) i wtedy faktycznie jakby błąd pojawia się około 2 na sekundę. No ale jeśli kręcę szybciej to błąd jest częściej, a wywołań tyle samo, więc już nie wiem.

Z resztą tak było w pierwszej wersji programu PIO. W drugiej bufor napełnia się na żądanie sm.put(1) i później sm.get(), więc może to być inaczej - i tutaj przyznaję się, że nie sprawdzałem dokładnie czy zachowuje się to identycznie - jak tylko zobaczyłem, że stukanie i błędy są nadal to zacząłem szukać innego rozwiązania.

W dniu 6.02.2026 o 20:32, Mirek pisze:

No to masz sukces, pid-y masz gotowe w C tylko wsadzić funkcję i odpowiednio wywoływać. Nawet ten co wkleiłem jest kompletny i możesz go spokojnie użyć.

W dniu 6.02.2026 o 22:09, Janusz pisze:

Ale nie powinien zatrzymywać licznika micros(), a właściwie time.ticks_us() - bo taki użyłem do sprawdzenia pętli. Pętlę wyzwalam z timera, który też nie powinien mieć czkawki, pętla się wykonuje za każdym wyzwoleniem z timera - to też sprawdzałem. Mimo wszystko zatrzymywanie się pętli pasowało by do objawów. Powinienem to sprawdzić oscyloskopem, albo chociaż na słuch, bo jeśli z

1ms robi się nagle 10ms to powinno to być słychać jako trzask.

W dniu 8.02.2026 o 12:25, Mirek pisze:

Można to na dwa sposoby spróbować zmierzyć Mierzyć odstęp między tymi zatrzymaniami, zobaczymy jaki to czas, może się do czegoś dopasuje, być może jak "micros()" też się zatrzymuje to przy dłuższym pomiarze mocno nie zafałszuje pomiaru czasu, albo jak wystąpi zatrzymanie wysterować nogą PIO, a jak ruszy to skasować i obserwować przebieg na oscyloskopie. Bedzie okres między zdarzeniami i może uda się uchwycić jak długo ono trwa.

Sensowne byłoby, gdyby pętla nie była wykonywana, jak już jest wykonywana. Cos długiego w pętli - ta czeka, przerwania są albo zablokowane, albo nie uruchamiają pętli.

No ale masz ten licznik micros() - i tak się niby nie dzieje.

Jesli wierzyć odczytom z enkodera, to np zamiast 7 impulsów (w 1ms) zlicza 166, co sugeruje zatrzymanie na ok 23ms.

Choć ten inny test wychwytywał ok 17-18 impulsów. No i to się zdarzało co ok 2500 odczytów ... co 2.5s ?

256*10ms ? Nie ma jakiegoś przerwania 100Hz ?

J.

W dniu 6.02.2026 o 22:11, Janusz pisze:

Do sukcesu jeszcze daleko. Wzorowałem się tym razem na tym:

formatting link
tutaj też jest filtr na D i o dziwo ten filtr mi zadziałał, tzn byłem uparty, i po tym jak zwiększyłem stałą czasową tak, że zaczął wprowadzać oscylacje, zwiększyłem radykalnie kd i okazało się, że z filtrem można jeszcze zwiększać, a bez filtra już nie. Mimo wszystko nie jest tak, jak powinno być. Nie udaje mi się szybko zejść do zerowego błędu bez kołysania, a jeśli już to przypadkiem raz na kilka jazd. W ogóle mam wrażenie jakby teraz pod C strasznie szumi ten PWM, nie stuka, ale szum jest taki jak audio na 4-ech bitach. To tak wygląda, jakby człon P dawał normalne PWM, natomiast D daje szpile losowo i jakoś to się stabilizuje. Jakoś, bo to dojście do zerowego błędu wygląda tak, że np, jest +1, prawie stoi, narasta pisk, trzask i jest na -12. znowu narasta, trzask i jest +2. Za którymś razem jak mi się nie znudzi to stanie na 0. A ja chciałbym żeby mi to chodziło tak jak na końcu tego filmu:
formatting link
ągle mam nadzieję, że znajdę jakąś wyspę stabilności.

W dniu 10.02.2026 o 21:32, Mirek pisze:

Szczerze? nie podoba mi sie ten kod, ktoś tu poszedł na skróty względem normalnego pid-a

/* Controller parameters */ #define PID_KP 2.0f #define PID_KI 0.5f #define PID_KD 0.25f

#define PID_TAU 0.02f

#define PID_LIM_MIN -10.0f #define PID_LIM_MAX 10.0f

#define PID_LIM_MIN_INT -5.0f #define PID_LIM_MAX_INT 5.0f

float PIDController_Update(PIDController *pid, float setpoint, float measurement) { float error = setpoint - measurement;

float proportional = pid->Kp * error;

pid->integrator = pid->integrator + 0.5f * pid->Ki * pid->T * (error + pid->prevError);

/* Anti-wind-up via integrator clamping */ if (pid->integrator > pid->limMaxInt) { pid->integrator = pid->limMaxInt; } else if (pid->integrator < pid->limMinInt) { pid->integrator = pid->limMinInt; } /* * Derivative (band-limited differentiator) */ pid->differentiator = -(2.0f * pid->Kd * (measurement - pid->prevMeasurement) /* Note: derivative on measurement, therefore minus sign in front of equation! */ + (2.0f * pid->tau - pid->T) * pid->differentiator) / (2.0f * pid->tau + pid->T);

/* * Compute output and apply limits */ pid->out = proportional + pid->integrator + pid->differentiator;

if (pid->out > pid->limMax) {

pid->out = pid->limMax;

} else if (pid->out < pid->limMin) {

pid->out = pid->limMin;

}

W dniu 11.02.2026 o 09:22, Janusz pisze:

Nie zrobiłem jeszcze. Tamto jest w Pythonie, a na razie skupiłem się na C, i nie wiem czy już jednej płytki nie zajechałem, bo się jakieś cuda dzieją - nie zawsze wstaje na USB, nie zawsze program rusza po wgrywaniu.

W dniu 11.02.2026 o 09:34, Janusz pisze:

A co mu tam brakuje? Mi nie podoba się tylko ten integrator: pid->integrator = pid->integrator + 0.5f * pid->Ki * pid->T * (error + pid->prevError);

- czyli gościu dodaje obecny error do poprzedniego i dzieli przez dwa... dla wygładzenia? - nie wiem czy ma to jakikolwiek sens przy integratorze, zresztą spróbowałem to wywalić i niewiele się zmienia. No i limit ustawił na sztywno dla integratora - w sumie to nie próbowałem tak, zrobiłem po swojemu, czyli limituje jak w poprzednim cyklu moc zadana na silnik jest maksymalna.

W dniu 11.02.2026 o 18:47, Mirek pisze:

Brakuje I. Czyli nie uzględnia części narastającej błędu z P.

No te stałe które są w kodzie to też kiepski pomysł, widać że strojone pod konkretną rzecz.

nie tyle dla wygładzenia, co dla lepszego całkowania. obecny błąd wynosi np 10, poprzedni wynosił 13, ile dodać do całki za cały okres? średnia z obu wydaje się dobra, zresztą nazywa sie to metoda trapezów.

Na ile to istotne w przypadku PID ... chyba znikomo, może nawet szkodzi, bo na bardzo dokładnej wartości całki wcale nam nie zależy, za to powyzsze wprowadza "opóźnienie", które może byc przyczyną większych problemó.

I zastanowiłbym się nad dalszym rozszerzeniem tego. Całkowanie dużego błedu ma mały sens.

J.

W dniu 12.02.2026 o 14:40, J.F pisze:

No ale teoria sterowania nic o tym nie mówi. Z resztą teraz mam inne problemy. Wygląda na to, że printowanie przez USB w C jednak zakłóca przebieg normalnej pętli. Niby nie muszę printować w czasie działania PID-a, ale fajnie się stroi "na żywo", a podłączenie komputera przez USB wydaje się naturalne i najwygodniejsze. Próbowałem wyrzucić PID-a do osobnego wątku na drugim rdzeniu. Kilka razy mi to zadziałało pięknie, ale za którymś razem zaczęło się coś wieszać, w ogóle nie zgłaszał się na USB albo tak jakby nie ruszał ten drugi rdzeń. Myślałem że flash się już poddaje, bo było wgrywane już z 500 razy, ale podpiąłem nową płytkę i to samo. Najciekawsze jest to, że czasem się uda skompilować i wgrać i ruszy. Dziś wiele nie poszalałem, bo wpadł do naprawy jeszcze falownik - też ciekawy przypadek - może nowy wątek założę.

Wow, a jak to działa na PICO? Bo tam program przecież z szeregowego eproma ... na jeden rdzen za wolno :-)

Przepisują sobie do RAM ? Kod PID krótki.

J.

W dniu 12.02.2026 o 22:36, J.F pisze:

Ta pamięć wcale nie taka wolna jest, do tego jest cachowana, więc pewnie sobie dociąga blokami i jakoś się kręci. Najciekawsze, że ja wcale nie widzę jakiegoś szalonego przyspieszenia dla C w stosunku do MicroPythona. W sumie pod MicroPythonem też używałem drugiego rdzenia i problemów nie było. Przyspieszenie widziałem w Raspberry Pi z Raspbianem, gdzie napisałem sztuka dla sztuki obsługę wyświetlacza LCD 122x32 w Pythonie. Żeby zapalić wszystkie pikselki na czarno to było ze 3 sekundy, potem 3 sekundy na biało, potem się rysowały po kolei cyferki, które chciałem wyświetlić. Po przerobieniu na C po prostu pojawiały się od razu cyferki, a i tak musiałem sygnał strobe wydłużyć, bo nie łapał.

Nie to raczej nie ta architektura.

To byłoby istotnie ciekawe, bo chyba ten Python ciągle nie jest kompilowany na kod ARM, ale ... przecież Ty masz nieobciązony procesor, bo pętla regulatora uruchamiana w przerwaniu.

napisz drugi program, który bedzie funkcję regulatora wywoływał w pętli programowej, i co milion wywołań wypisze cos przez USB, czy diodą mrugnie ... albo nastawe zwiększy o 1.

Zwykły Python na Raspberry jest chyba interpretowany, czy połowicznie kompilowany w locie, cos w stylu Javascriptu. I to szybkie nie jest.

Architektura chyba dobra, tylko RAM mało :-)

Na jedną pętle regulatora powinno jednak starczyc :-)

J.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required