Distortos – pierwsze kroki

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:

  1. Ściągnąć repo
  2. Wygenerować konfigurację dla naszego HW, albo użyć jednej z istniejących.
  3. Uruchomić cmake w celu wygenerowania plików do builda.
  4. Dopasować konfigurację używając cmake-gui.
  5. 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.

1 Comment

  1. 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.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *