Distortos to system operacyjny czasu rzeczywistego (RTOS) napisany w C++ z myślą o procesorach ARM Cortex-M, a szczególnie STM32. Pisałem już o nim przy okazji ciekawych projektów C++ Embedded i łazika na NASA Space Apps. Aplikacja na STM w łaziku chyba jednak będzie za prosta, żeby dawać do niej RTOSa, ale ostatnio zacząłem przepisywać Micromouse do C++ i tam już się nada. Źródła distortosa możecie znaleźć na GitHubie, a dokumentację na stronie projektu. Jest on jeszcze w dosyć wczesnej fazie rozwoju, dlatego nie posiada aż tak rozbudowanej dokumentacji i przykładów. Może to być pewną barierą na początku, jednak z czasem na pewno to się zmieni.
Po co nam C++ w RTOSie?
Największą zaletą RTOSa napisanego w C++ jest wykorzystanie RAII (Resource Acquisition is Initialization). Ktoś kiedyś powiedział, że RAII jest idealnym podsumowaniem C++. Idealnie łączy w sobie ohydę nic nie mówiącej nazwy z niesamowicie przydatną funkcjonalnością. No ale do rzeczy – RAII polega na tym, że operacje związane z zabieraniem i oddawaniem zasobu są umieszczone w konstruktorze i destruktorze. Dzięki temu mamy pewność, że zasób zostanie oddany w każdej ścieżce wyjścia z funkcji. Najlepiej pokazać to na przykładzie:
void function_using_mutex()
{
os::Lock lock(getMutex());
... //some processing
if (...)
{
... //some more processing
return;
}
}
Dzięki RAII mamy pewność, że mutex zostanie oddany zarówno jeśli program wyjdzie z funkcji na końcu, jak i w ifie. Dzięki temu nie musimy pamiętać o dodawaniu zwalniania mutexu w każdym możliwym miejscu.
Inne przydatne elementy C++ do wykorzystania w RTOSie to przeciążanie funkcji i templaty. Dzięki templatom możemy na przykład przekazać do funkcji rozmiar stosu dla wątku w czasie kompilacji. W Distortosie wygląda to następująco:
constexpr size_t STACK_SIZE = 1024;
constexpr uint8_t PRIORITY = 1;
auto thread = distortos::makeAndStartStaticThread(PRIORITY, taskFunction);
Przy okazji widzimy tutaj użycie auto do typu zmiennej, które pozwala nam pisać bardziej odporny na zmiany kod i ukrywać szczegóły, które w danym kontekście nie są interesujące.
Konfiguracja i kompilacja
W opisie projektu na GitHubie mamy instrukcję z rozpisanymi krokami potrzebnymi do konfiguracji i kompilacji systemu. Musimy:
- Ściągnąć repo
- Wygenerować konfigurację dla naszego HW, albo użyć jednej z istniejących.
- Uruchomić cmake w celu wygenerowania plików do builda.
- Dopasować konfigurację używając cmake-gui.
- Uruchomić build przy użyciu Ninja.
Ja w kroku 2 potrzebowałem konfiguracji dla STM32F401RB. Wygenerowałem ją używając komendy:
python scripts/generateBoard.py source/chip/STM32/STM32F4/chipYaml/ST_STM32F401RB.yaml -o generated/chip
Wywołałem ją z głównego katalogu distortosa. Wywołanie z wyższego katalogu (np. z poziomu głównego katalogu projektu używającego distortos) powodowało błędy w skrypcie.
Następnie w kroku 3 podaję cmake’owi ścieżkę do wygenerowanego pliku toolchain:
cmake .. -DCMAKE_TOOLCHAIN_FILE={PATH_TO_DISTORTOS}/generated/chip/Toolchain-ST_STM32F401RB.cmake -GNinja
Próbowałem bawić się cmakiem, aby potrafił sam sobie wygenerować plik Toolchain i wykorzystać go dalej, ale na razie mi się nie udało. Jeżeli nie podamy tego pliku na początku, CMake używa domyślnego toolchaina.
Następnie w cmake-gui możemy wybrać opcje distortosa, które nas interesują. Jest ich całkiem sporo, dlatego próba zarządzania nimi z poziomu plików .cmake jest dużo trudniejsza.
Dzięki tej konfiguracji możemy na przykład wyklikać, czy chcemy sam scheduler, czy również inicjalizację zegara albo drivery peryferiów.
Następnie w kroku 5 wywołujemy ninję i budujemy distortosa do biblioteki statycznej, a także unit testy i hw testy. Hw testy możemy od razu wgrać sobie na płytkę i zobaczyć czy wszystko działa. Jak chcemy jakieś inne przykładowe programy na Distortosie, możemy zobaczyć oddzielne repo z przykładami. Na razie jest tam kilka różnych sposobów świecenia LEDami.
Co zawiera distortos?
Distortos to nie tylko scheduler, to cały framework do STM32. Zawiera także między innymi:
- Skrypty linkera i pliki startupowe.
- Stuby dla syscalli z newliba.
- Drivery do peryferiów.
Automatyczne generowane skrypty linkera i pliki startupowe stanowią dużą wartość. ST w swoich przykładach z not aplikacyjnych zwykle daje tylko skrypty, które nadają się do C. Brakuje niektórych sekcji do C++, a szczególnie do inicjalizacji statycznych obiektów. W distortosie te sekcje są i działają. Co więcej mamy makra umożliwiające przesunięcie niektórych funkcji inicjalizacyjnych przed funkcję main. Dlatego na przykład inicjalizacja RCC i niektórych peryferiów może odbywać się tam. Nie wnikałem w to dokładnie, ale chyba funkcja main jest częścią wątku MainThread i jest wywoływana już po starcie schedulera.
Drivery do peryferiów są na razie w trakcie tworzenia. Istnieją już drivery dla GPIO, USART, DMA, SPI, ale dużo peryferiów nie jest wspieranych. I nie ma w tym nic dziwnego biorąc pod uwagę wszystkie rodzaje STM32 i ich peryferia. Na razie testowałem tylko driver GPIO i całkiem przyjemnie się go używa:
//PA0 - PWM pin for right motor
//PA1 - PWM pin for left motor
distortos::chip::configureAlternateFunctionPin(distortos::chip::Pin::pa0,
distortos::chip::PinAlternateFunction::af1);
distortos::chip::configureAlternateFunctionPin(distortos::chip::Pin::pa1,
distortos::chip::PinAlternateFunction::af1);
Jak ktoś potrzebuje pełnych bibliotek do peryferiów w C++, powinien się zainteresować projektem Modm.
Podsumowanie
Na razie to tyle jeżeli chodzi o moje początki z distortosem. Napisałem do tej pory tylko kilka prostych programów wykorzystujących StaticThready tworzone tak jak w przykładzie kodu wyżej. Żeby omówić bardziej zaawansowane funkcje muszę jeszcze trochę go poznać. Jednak już teraz mogę powiedzieć, że projekt jest ciekawy i stanowi fajną alternatywę np. dla FreeRTOSa. Przy okazji warto pamiętać, że na razie distortos jest jeszcze w wersji dosyć eksperymentalnej i pewnie będzie się zmieniać na lepsze.
27 maja 2019 at 17:10
Dzięki za artykuł!
We fragmencie kodu `auto thread = distortos::makeAndStartStaticThread(PRIORITY, taskFunction);` chyba wycięło argumenty dla template’a („) – pewnie potraktowało to jako HTML i wywaliło jako nieznany tag.
> Nie wnikałem w to dokładnie, ale chyba funkcja main jest częścią wątku MainThread i jest wywoływana już po starcie schedulera.
Tak właśnie jest – funkcja main() jest najzwyklejszym wątkiem i można w niej robić wszystko to co w innych wątkach (no może poza wyjściem z niej [; ). Można używać funkcji „sleep”, można używać funkcji blokujących, czekać na dane z kolejek itd.