PX4 dzieli autopilota na flight stack oraz middleware robotyczny. Flight stack obejmuje estymację, nawigację, generowanie trajektorii, regulatory i alokację sterowania; middleware zapewnia sterowniki, komunikację uORB, parametry, logowanie, uruchamianie modułów i integrację sprzętu. Ten podział wyjaśnia, dlaczego pojedyncza funkcja może działać na NuttX, w procesie POSIX oraz w SITL, ale nie zwalnia z kontrolowania czasu, pamięci i semantyki wiadomości.

Przepływ danych przez flight stack#

Najbardziej użyteczna mapa PX4 prowadzi od sensorów do aktuatorów:

drivers -> sensor_* -> estimator -> vehicle_* state
                                  |
RC / mission / offboard -> flight mode / flight task -> setpoints
                                  |
           position -> attitude -> angular rate controllers
                                  |
       control allocation -> actuator_motors / actuator_servos -> outputs

Nazwy tematów są częścią kontraktu. vehicle_angular_velocity nie jest zamienne z surową próbką żyroskopu, a vehicle_local_position zawiera stan, ważność poszczególnych składowych i odniesienie lokalne. Programista musi sprawdzać timestamp próbki, timestamp publikacji, flagi ważności oraz układ współrzędnych. Sam fakt odebrania nowej wiadomości nie dowodzi, że wszystkie pola są użyteczne.

uORB: magistrala, nie magazyn prawdy#

uORB jest asynchronicznym mechanizmem publish/subscribe używanym wewnątrz PX4. Definicje .msg generują typy, metadane i kod transportowy. Publikujący nie wywołuje bezpośrednio odbiorcy; odbiorca pobiera najnowszą próbkę albo korzysta z kolejki, zależnie od konfiguracji tematu.

Projektując nowy temat, trzeba ustalić:

  • kto jest właścicielem semantyki i jednostek;
  • czy topic reprezentuje pomiar, estymatę, setpoint czy status;
  • jaki timestamp opisuje chwilę pomiaru;
  • czy utrata próbek jest dopuszczalna;
  • jak odbiorca rozpoznaje nieaktualność;
  • czy potrzebne są instancje dla redundantnych urządzeń;
  • czy wiadomość należy wersjonować na granicy systemów.

Publikowanie dużej struktury z dużą częstotliwością kosztuje CPU i pamięć. Dzielenie każdego pola na osobny temat też jest błędem, bo utrudnia spójny snapshot. Granica wiadomości powinna odpowiadać spójnemu zdarzeniu lub stanowi domenowemu.

Narzędzia powłoki pozwalają obserwować system bez dodawania przypadkowych printf:

listener vehicle_local_position 5
uorb top
top
work_queue status

listener pokazuje wartości i timestampy, uorb top obciążenie tematów, top zadania, a work_queue status elementy niewidoczne jako osobne taski.

Task czy work queue#

Moduł może działać jako samodzielny task z własnym stosem albo jako zadanie na współdzielonej work queue. Work queue zmniejsza koszt pamięci i przełączeń kontekstu, ale wymaga kooperacyjnego zachowania: callback nie może spać, wykonywać blokującego I/O ani długo liczyć, ponieważ opóźnia pozostałe elementy kolejki.

Właściwość Osobny task Work queue
stos własny współdzielony dla kolejki
blokowanie możliwe, nadal ryzykowne niedopuszczalne
koszt RAM większy mniejszy
izolacja czasowa lepsza zależy od wszystkich elementów kolejki
typowe użycie cięższa logika, interfejs blokujący krótkie sterowniki i przetwarzanie reaktywne

Wyboru nie należy dokonywać na podstawie długości pliku. Potrzebny jest pomiar WCET, częstotliwość wywołań i wiedza o używanych API. Kod, który zwykle kończy się w 50 µs, lecz czasem czeka 20 ms na magistralę, nie nadaje się na krytyczną work queue.

NuttX i granice zasobów#

Na typowych kontrolerach PX4 działa na NuttX. Taski współdzielą przestrzeń adresową, ale mają osobne stosy i deskryptory. Przepełnienie stosu, fragmentacja sterty lub błąd sterownika może zatem wpływać na cały autopilot. Monitorowanie zapasu stosu i pamięci jest częścią testu, nie optymalizacją wykonywaną po awarii.

Konfiguracja targetu decyduje, które moduły i sterowniki trafią do obrazu. Dodanie funkcji może przekroczyć flash lub RAM starszej płytki. Raport rozmiaru należy porównywać w CI, a target specjalizowany powinien zawierać tylko potrzebne elementy. Kompilacja dla hosta nie sprawdza ograniczeń obrazu NuttX.

Parametry, startup i airframe#

Parametry są trwałym interfejsem konfiguracji. Moduł powinien pobierać ich aktualizacje przez standardowy mechanizm i wykonywać walidację w jednym miejscu. Częste odczyty z trwałego magazynu w szybkiej pętli są zbędne. Zmiana parametru musi mieć opisaną jednostkę, zakres, restart requirement i bezpieczną wartość domyślną.

Airframe configuration ustawia geometrię, mapowanie oraz wartości właściwe klasie pojazdu. Nie jest kalibracją konkretnego egzemplarza. Po wybraniu airframe nadal trzeba sprawdzić orientację sensorów, RC, aktuatory, limity i failsafe. Kopiowanie całego pliku parametrów między wersjami może przenieść nieaktualne identyfikatory i kalibracje.

Sekwencja startowa uruchamia uORB, sterowniki i moduły według skryptów oraz konfiguracji targetu. Błąd „moduł nie publikuje” może wynikać z niewłączenia w build, nieuruchomienia, braku urządzenia, złej instancji albo z niespełnionego warunku inicjalizacji. Diagnostyka powinna przejść te warstwy w tej kolejności.

Estymacja i ważność stanu#

Estimator łączy IMU z GNSS, barometrem, magnetometrem, airspeed i źródłami external vision. Regulatory nie powinny używać surowych sensorów, jeśli kontrakt wymaga estymowanego stanu. Jednocześnie estymata może mieć ważną orientację i nieważną pozycję globalną. Tryb lotu musi reagować na jakość dokładnie tego stanu, którego potrzebuje.

Przy integracji zewnętrznej odometrii należy kontrolować:

  • układ lokalny i kierunek osi;
  • orientację transformacji sensor–body;
  • timestamp próbki i opóźnienie transportu;
  • covariance lub uzasadnione parametry szumu;
  • częstotliwość i przerwy w strumieniu;
  • reset pozycji źródła i zmianę origin;
  • zachowanie po utracie danych.

„Pozycja wygląda dobrze w QGC” nie wystarcza. Log musi pokazać innowacje, opóźnienia, reset estymatora i przejście maszyny stanów po degradacji.

Regulatory i control allocation#

PX4 stosuje kaskadowe regulatory. Warstwa zewnętrzna generuje setpoint dla wewnętrznej; pominięcie ograniczeń prędkości lub przyspieszenia może dostarczyć pętli rate fizycznie nierealne żądanie. Control allocation mapuje żądane siły i momenty na aktuatory, uwzględniając geometrię i ograniczenia.

Alokator nie tworzy brakującego autorytetu. Jeśli aktuator osiągnie limit, regulator nadrzędny powinien otrzymać informację o saturacji, aby ograniczyć windup. Przy awarii napędu wykonalność sterowania zależy od geometrii; samo wyzerowanie jednego wyjścia może prowadzić do nieosiągalnego wektora momentu.

Pierwsza kompilacja i SITL#

Aktualna dokumentacja PX4 zaleca zacząć od symulatora. Po przygotowaniu toolchainu:

git clone https://github.com/PX4/PX4-Autopilot.git --recursive
cd PX4-Autopilot
make px4_sitl gz_x500

Polecenie buduje PX4 dla hosta i uruchamia model x500 w Gazebo. W konsoli PX4 można obserwować moduły, tematy i wykonać kontrolowany start symulacyjny. Pierwszym celem nie jest ręczny lot, lecz uzyskanie reprodukowalnego środowiska: ustalonej rewizji repozytorium, wersji symulatora, świata, modelu i parametrów.

Kompilacja sprzętowa używa nazwy targetu, np.:

make px4_fmu-v5_default
make px4_fmu-v5_default upload

Target musi odpowiadać płycie. Przed uploadem własnego firmware trzeba zachować działający obraz, parametry i procedurę recovery. Firmware z main jest środowiskiem rozwojowym; test platformy operacyjnej powinien bazować na świadomie wybranym wydaniu i zamrożonych zależnościach.

Dodawanie modułu#

Minimalny moduł powinien mieć jednoznaczny lifecycle: start, stop, status, inicjalizację subskrypcji i kontrolowane zakończenie. Kolejność pracy:

  1. zdefiniować wejścia, wyjścia, częstotliwość i timeout;
  2. sprawdzić, czy istniejący topic już ma potrzebną semantykę;
  3. wybrać task albo work queue na podstawie czasu wykonania;
  4. dodać moduł do CMake i właściwej konfiguracji targetu;
  5. napisać test logiki niezależny od sprzętu;
  6. uruchomić w SITL i obserwować topic oraz obciążenie;
  7. przetestować brak danych, stare dane i reset źródła;
  8. dopiero potem uruchomić na FC bez podłączonych śmigieł.

Nowy moduł nie powinien bezpośrednio pisać na wyjście PWM, jeżeli jego rolą jest generowanie setpointu. Zachowanie warstw umożliwia użycie ograniczeń, failsafe, logowania i alokatora.

ULog i diagnostyka regresji#

ULog przechowuje tematy, parametry, zdarzenia i metadane. Telemetria do QGC ma niższą częstotliwość i może tracić pakiety, dlatego nie zastępuje logu pokładowego. Porównanie regresji powinno obejmować te same sygnały oraz identyczny scenariusz.

Przy problemie należy sprawdzić:

  1. zdarzenia i zmianę trybu;
  2. ważność oraz resety estymatora;
  3. setpointy na kolejnych poziomach kaskady;
  4. błąd rate i wkład regulatora;
  5. saturację control allocation;
  6. wyjścia, napięcie i stan napędu;
  7. opóźnienia uORB, obciążenie tasków i kolejki;
  8. utratę MAVLink dopiero jako osobną warstwę.

SITL, HIL i sprzęt#

SITL pokrywa logikę, komunikację i część sterowania. Nie odwzorowuje błędów elektrycznych, rzeczywistego DMA, termiki ani jitteru NuttX. HIL uruchamia firmware na kontrolerze, ale nadal zależy od jakości modelu i interfejsu czasu. Test stołowy bez śmigieł weryfikuje sterowniki, mapowanie, sensory i zasilanie. Dopiero wszystkie warstwy razem tworzą dowód.

Macierz testów powinna zawierać nominalny lot, utratę RC/GCS/GNSS, opóźnioną odometrię, błąd sensora, saturację, restart komponentu i spadek napięcia. Każdy przypadek ma oczekiwany stan, limit czasu i logowane kryterium.

Typowe błędy#

Brak kontroli wieku wiadomości#

Topic może istnieć, lecz ostatnia próbka może być stara. Odbiorca musi porównywać właściwy timestamp z czasem monotonicznym.

Blokowanie work queue#

Odczyt pliku lub oczekiwanie na urządzenie opóźnia inne moduły. I/O należy przenieść do właściwego tasku lub obsłużyć asynchronicznie.

Mieszanie setpointu ze stanem#

Wiadomość żądania nie może udawać pomiaru. To prowadzi do pętli dodatniej i błędnej diagnostyki.

Test tylko w QGroundControl#

Interfejs pokazuje przetworzony podzbiór danych. Do analizy czasu i regulatorów potrzebny jest ULog oraz narzędzia systemowe.

Zły target lub stary submodule#

Repozytorium PX4 ma zależności submodule. Rewizja bez ich identyfikacji nie jest reprodukowalna. Target o podobnej nazwie nie musi odpowiadać PCB.

Moduł, komenda i cykl życia#

Moduł PX4 może działać jako osobny task albo ScheduledWorkItem. Typowy moduł udostępnia komendy start, stop i status, ma kontrolowany stan oraz procedurę sprzątania. Wielokrotne start nie powinno tworzyć niekontrolowanych instancji.

Wejście modułu rejestruje się w systemie build. Sam plik C++ nie trafi do obrazu, jeżeli nie został dodany w odpowiednim CMakeLists.txt i konfiguracji board. Build SITL oraz sprzętowy mogą mieć różne zależności, co musi być jawne.

Zatrzymanie jest trudniejsze niż uruchomienie: callback może być zaplanowany, subskrypcja aktywna, a sterownik używać zasobu. Destruktor nie może uwolnić obiektu, gdy work queue nadal ma do niego wskaźnik. Stosuje się sekwencję request stop → unschedule → release.

uORB i semantyka danych#

Topic jest typem wiadomości generowanym z definicji .msg. Zawiera timestamp oraz pola w określonych jednostkach. Publikacja oznacza nową próbkę, nie trwały wpis w bazie. Subskrybent powinien oceniać świeżość i ważność.

timestamp zwykle wskazuje czas publikacji lub przetworzenia, a timestamp_sample czas pomiaru. Dla kompensacji opóźnień różnica jest krytyczna. Kopiowanie timestampu „teraz” przy przekazywaniu starego sensora ukrywa latency.

Queue depth większe od jednego ma sens, gdy każda wiadomość musi zostać przetworzona. Dla stanu sterowania często ważniejsza jest najnowsza próbka i policzenie utraconych aktualizacji. Kolejka nie powinna służyć do maskowania zbyt wolnego konsumenta.

Multi-instance topics reprezentują kilka sensorów lub źródeł. Wybór instancji należy do modułu selekcji/estymatora; konsument nie powinien bezwarunkowo brać numeru zero, jeżeli potrzebuje źródła aktywnego.

Work queues i deadline#

Work queue współdzieli wątek między zadaniami, ograniczając liczbę stosów i przełączeń. Jednocześnie długie wykonanie jednego elementu opóźnia pozostałe na tej kolejce. Dobór queue jest więc decyzją czasu rzeczywistego.

Callback może być wyzwalany publikacją sensora, co synchronizuje obliczenia z próbką i ogranicza polling. Moduł nadal potrzebuje harmonogramu zapasowego dla timeoutów. W jednej iteracji powinien wykonać ograniczoną ilość pracy bez blokującego I/O.

Pomiar obejmuje średni i maksymalny czas, odstęp, missed deadlines oraz stos. PC SITL daje niewiarygodnie duży zapas względem MCU. Test wykonuje się na najwolniejszej wspieranej płytce z loggerem, MAVLink i sensorami.

Parametry i aktualizacja#

Definicja parametru zawiera nazwę, typ, default, zakres, jednostkę i dokumentację. Nazwa oraz znaczenie są publicznym API konfiguracji. Zmiana typu albo jednostki wymaga migracji i oceny istniejących airframes.

Moduł może cache’ować parametry, ale musi reagować na parameter_update i pobrać je w bezpiecznym punkcie. Aktualizacja nie może zostawić połowy współczynników ze starego, a połowy z nowego zestawu. Warto walidować zależności i odrzucić konfigurację fizycznie niemożliwą.

Parametry tuningowe nie zastępują stanu dynamicznego. Integrator PID, wykryty sensor i bieżący limit należą do danych runtime. Restart z zachowanym parametrem ma dać przewidywalny stan początkowy.

Airframes i startup#

Skrypty airframe ustawiają domyślną konfigurację konkretnej geometrii. Wybór airframe wpływa na alokację, sensory i parametry. Kopiowanie identyfikatora podobnego płatowca bez sprawdzenia geometrii może dać poprawny boot i błędne momenty.

Startup systemu uruchamia sterowniki, estymatory, commander i aplikacje według konfiguracji. Błąd wcześniejszego etapu może ujawnić się później jako brak topicu. Diagnostyka powinna sprawdzać procesy i uorb top, nie tylko ekran QGroundControl.

Własna płytka wymaga board configuration: pinów, magistral, timerów, pamięci, zasilania sensorów i targetu NuttX. Definicja airframe nie zastępuje portu sprzętowego.

Commander, nawigator i tryby#

Commander zarządza stanem uzbrojenia, health checks, failsafe oraz głównym stanem pojazdu. Navigator obsługuje logikę misji i setpointy nawigacyjne. Regulatory nie powinny samodzielnie omijać tych decyzji.

Tryb jest kontraktem wymagań. Position mode potrzebuje ważnego lokalnego stanu, altitude mode wysokości, a manual/acrobatic ma inne wejścia. Gdy źródło traci ważność, commander wybiera skonfigurowaną reakcję. Moduł publikujący valid=true bierze udział w decyzji bezpieczeństwa.

Preflight check powinien odrzucić uzbrojenie z czytelną przyczyną. Wyłączanie checków dla wygody usuwa informację diagnostyczną. Rozwój nowego sensora obejmuje jego health, timeout i komunikat dla operatora.

Estymatory i selekcja sensorów#

EKF2 przewiduje stan z IMU i dołącza obserwacje GNSS, barometru, magnetometru, airspeed, flow lub wizji. Każde źródło ma opóźnienie, wariancję i test innowacji. Publikowanie pozycji bez realistycznej niepewności może nadać złemu sensorowi nadmierną wagę.

Wielokrotne instancje EKF i sensorów pozwalają wykrywać rozbieżności. Selector wybiera rozwiązanie, lecz wspólna awaria — na przykład drgania całej płytki — może dotknąć wszystkie. Loguje się przyczynę zmiany instancji i reset stanu.

Moduł korzystający z vehicle_local_position powinien sprawdzać flagi validity, nie tylko liczby. NaN, reset counter i delta reset są częścią kontraktu przy zmianie źródła.

Control allocation#

Regulatory publikują żądane momenty i ciąg, a control allocator mapuje je na silniki oraz serwa. Geometria efektorów i ich limity są oddzielone od regulatora. Ułatwia to obsługę multirotorów, VTOL i awarii.

Alokator ma problem ograniczonej optymalizacji. Gdy wszystkie żądania nie są wykonalne, ustala priorytety i raportuje unallocated control. Regulator potrzebuje tej informacji dla anti-windup. Obcinanie każdego wyjścia niezależnie zniekształca wektor momentu.

Nowa geometria wymaga testu znaków, zakresów, slew rate i zachowania po utracie efektora. Model SITL powinien mieć tę samą geometrię, ale zgodność symulacji nie zwalnia z testu fizycznych pinów bez śmigieł.

Moduł MAVLink serializuje wybrane topiki do protokołu, obsługuje komendy i strumienie. Rate każdego strumienia ma koszt CPU i łącza. Saturacja telemetrii nie powinna blokować sterowania.

uXRCE‑DDS umożliwia integrację z ROS 2 przez klienta na pojeździe i agenta. Mapowanie topiców jest konfigurowane; nie wszystkie uORB muszą być eksportowane. ROS działa zwykle jako komputer towarzyszący, a krytyczna stabilizacja pozostaje w PX4.

Offboard wymaga regularnych setpointów i heartbeat określonego przez interfejs. Utrata strumienia uruchamia failsafe. Test obejmuje opóźnienie, restart procesu, złe ramki współrzędnych i nieświeże timestampy, nie tylko poprawną trajektorię.

ULog i analiza#

ULog przechowuje topiki, parametry, zdarzenia i metadane. Profil logowania dobiera częstotliwości do zjawiska. Zapis wszystkich topików może przeciążyć medium, a zbyt rzadki log nie pokaże pętli rate.

Do reprodukcji zapisuje się hash firmware, airframe, parametry, wersję modelu i warunki. Analiza łączy setpoint, estimate, actuator controls, outputs, saturation, innowacje i timing. Sam wykres pozycji nie rozstrzyga, czy błąd powstał w nawigatorze, estymatorze czy alokatorze.

Events mają identyfikator i poziom ważności, dzięki czemu GCS może pokazać komunikat bez parsowania dowolnego tekstu. Nowy stan awarii powinien mieć event oraz pola maszynowe w logu.

SITL, testy i CI#

Standardowy build z Gazebo pozwala testować moduł bez śmigieł:

make px4_sitl gz_x500

Test ręczny jest pierwszą kontrolą. Regresja powinna programowo uruchomić scenariusz, czekać na gotowość, wykonać polecenie i sprawdzić tolerancje. Artefakty obejmują pełny ULog i log symulatora.

Fault injection obejmuje utratę topicu, stare timestampy, NaN, reset licznika, saturację i restart modułu. SITL nie sprawdzi DMA, pinów i obciążenia MCU, dlatego następne etapy to HIL i sprzęt.

Testy jednostkowe nadają się dla matematyki, parserów i automatów. Zależność od czasu abstrahuje się tak, aby uniknąć sleep. Deterministyczny test działa szybciej i nie staje się losowo czerwony w CI.

Port na nową płytkę#

Port sprzętowy zaczyna się od schematu i tabeli zasobów. Następnie powstaje konfiguracja NuttX, pinów, magistral, timerów, ADC, storage i kolejności sensorów. Bootloader musi znać layout Flash i sposób odzyskania.

Bring-up przebiega od zasilania i SWD przez zegar, console, storage, magistrale, sensory i wyjścia. Każdy etap ma test. Dopiero potem uruchamia się pełny stack. Próba diagnozy wszystkich urządzeń jednocześnie na pierwszym boot utrudnia lokalizację błędu.

Na sprzęcie mierzy się stack high-water, heap, load, loop latency, błędy bus, resety i temperaturę. Płyta jest wspierana dopiero po zdefiniowaniu procedury flashowania, parametrów domyślnych i testu produkcyjnego.

Ścieżka własnej zmiany#

  1. Zbudować niezmieniony px4_sitl i zapisać log bazowy.
  2. Zidentyfikować topic będący właścicielem wejścia i wyjścia.
  3. Wybrać task/work queue według deadline, nie wygody.
  4. Dodać moduł z start/stop/status i obsługą timeout.
  5. Zdefiniować parametry, eventy i pola diagnostyczne.
  6. Dodać test jednostkowy oraz scenariusz SITL błędu.
  7. Sprawdzić rate, latency i zgubione aktualizacje.
  8. Zbudować cel sprzętowy i ocenić pamięć oraz CPU.
  9. Przejść HIL, test bez śmigieł i ograniczoną próbę.

Najczęstszy błąd architektoniczny to utworzenie równoległego stanu poza uORB i commanderem. Krótszy kod może wtedy omijać health, logowanie i failsafe, co komplikuje cały system.

Powiązane tematy#

Przypisy#

  1. PX4 Architectural Overview — flight stack, middleware, taski i work queues.
  2. PX4 uORB Messaging — API publish/subscribe i definicje wiadomości.
  3. PX4 Building Software — bieżące targety SITL i NuttX.
  4. PX4 Developer Environment — wspierane środowiska i toolchain.
  5. PX4-Autopilot — kod, konfiguracje targetów, testy i wydania.
  6. Gazebo documentation — symulator, modele, sensory i transport.

Źródła z centralnego rejestru

  1. Sumit Sharma, „Drone Development from Concept to Flight” [książka]
  2. PX4 Architectural Overview [dokumentacja]
  3. PX4 uORB Messaging [dokumentacja]
  4. PX4 Simulation [dokumentacja]