Tydzień z Adą - wszystkie wpisy

Po pierwszych próbach z Adą na PC, pora uruchomić ją na mikrokontrolerze. W tym celu posłużę się płytką STM32F4DISCO. W dzisiejszym odcinku dowiesz się, czy trudno jest zacząć z Adą na STM32 i jakimi gotowymi bibliotekami możesz się wspomagać. Zobaczymy też sobie kilka cech Ady ważnych z punktu widzenia embedded jak na przykład deklarowanie zmiennych jako Volatile, czy deklarowanie odpowiedników struktur packed i pól bitowych z C.

Artykuł powstał w ramach „Tygodnia z Adą” podczas którego od poniedziałku do piątku będą ukazywać się materiały na temat języka Ada. Będzie między innymi dlaczego Ada tak dobrze nadaje się do safety-critical, pokażę swoje pierwsze próby z pisaniem programów w Adzie, a także postaram się uruchomić Adę na STM32.

Instalacja toolchaina

Poza toolchainem GNAT-Native, który instalowaliśmy w poprzedniej części potrzebujemy jeszcze toolchaina GNAT-ARM. Oba można ściągnąć ze strony AdaCore. Najlepiej oba toolchainy zainstalować w tym samym folderze. U mnie jest to C:\Ada\GNAT gdzie po instalacji mam 2 foldery:

  • 2018 – z kompilatorem native
  • 2018-arm-elf – z kompilatorem ARM

Poza tym do debugowania będziemy potrzebować również ST-Linka i OpenOCD. Instalację OpenOCD opisywałem już kiedyś przy okazji konfiguracji Eclipse. Driver do ST-Linka w najnowszej wersji możemy natomiast ściągnąć bezpośrednio ze strony producenta.

Konfiguracja projektu

Środowisko GPS ma wbudowany template projektu do migania ledami na STM32F4Discovery. Będzie on idealny do sprawdzenia poprawności konfiguracji sprzętu. Wybierzmy więc ten projekt:

Kompilacja projektu przebiegła bez żadnego problemu, natomiast z wgraniem FLASHa na płytkę, czy z debugowaniem miałem już pewne problemy. Rozwiązaniem było wybranie odpowiednich opcji w ustawieniach projektu. Aby otworzyć okno ustawień projektu klikamy prawym na projekt i wybieramy Project -> Properties. W zakładce Toolchain musiałem ustawić:

Na górze widzimy, że kompilator arm został poprawnie wykryty. W dolnej części musiałem wybrać runtime ravenscar-sfp-stm32f4. Kolejne opcje musiałem jeszcze zmienić w zakładce Embedded:

Jest to konfiguracja OpenOCD. Domyślny port zmieniłem na 3333. Musiałem również zmienić plik konfiguracyjny. Domyślny plik board/stm32f4discovery.cfg jest skonfigurowany pod starą wersje st-linka – korzysta z pliku interface/stlink-v2.cfg. Utworzyłem więc nowy skrypt board/stm32f4discovery-1.cfg, który korzysta z nowszego stlinka – interface/stlink-v2-1.cfg. Wymagało to prostej edycji w jednym miejscu skryptu.

Po zakończeniu konfiguracji możemy zobaczyć, czy OpenOCD działa poprawnie. Aby go uruchomić wybieramy Build->Bareboard->openocd. Jeżeli wszystko przebiegnie pomyślnie, w konsoli powinien ukazać się output podobny do mojego:

Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : Unable to match requested speed 2000 kHz, using 1800 kHz
Info : clock speed 1800 kHz
Error: libusb_open() failed with LIBUSB_ERROR_NOT_SUPPORTED
Info : STLINK v2 JTAG v25 API v2 SWIM v14 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 2.878592
Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
semihosting is enabled

Mogę teraz wgrywać FLASH albo debugować swoją płytkę. Pełen sukces!

Kod przykładu

Możemy podpatrzeć w kodzie kilka ciekawych funkcji Ady do programowania embedded. W pliku stm32f4.ads na przykład widzimy definicje adresów sprzętowych:

   Peripheral_Base : constant := 16#4000_0000#;

   APB1_Peripheral_Base : constant := Peripheral_Base;
   APB2_Peripheral_Base : constant := Peripheral_Base + 16#0001_0000#;
   AHB1_Peripheral_Base : constant := Peripheral_Base + 16#0002_0000#;
   AHB2_Peripheral_Base : constant := Peripheral_Base + 16#1000_0000#;

   GPIOA_Base           : constant := AHB1_Peripheral_Base + 16#0000#;
   GPIOB_Base           : constant := AHB1_Peripheral_Base + 16#0400#;
   GPIOC_Base           : constant := AHB1_Peripheral_Base + 16#0800#;
   GPIOD_Base           : constant := AHB1_Peripheral_Base + 16#0C00#;

Widzimy tutaj między innymi składnię odpowiedzialną za liczby szesnastkowe:

16#<wartość_hex>#

W ten sam sposób możemy definiować liczby w innych systemach liczbowych zamiast początkowego 16 podstawiając bazę systemu liczbowego. Przy okazji duże liczby możemy rozdzielać za pomocą znaku _ zwiększając czytelność.

W pliku stm32f4-gpio.ads natomiast widzimy składnię obsługującą pola bitowe i „alignment” recordów (czyli odpowiedników struktur z C):

   for GPIO_Register use record
      MODER   at 0  range 0 .. 31;
      OTYPER  at 4  range 0 .. 31;
      OSPEEDR at 8  range 0 .. 31;
      PUPDR   at 12 range 0 .. 31;
      IDR     at 16 range 0 .. 31;
      ODR     at 20 range 0 .. 31;
      BSRR    at 24 range 0 .. 31;
      LCKR    at 28 range 0 .. 31;
      AFRL    at 32 range 0 .. 31;
      AFRH    at 36 range 0 .. 31;
   end record;

Mamy więc offset w bajtach jako liczba następująca po at i range określające liczbę bitów.

Następnie zmienne odpowiadające konkretnym rejestrom są deklarowane tak:

   GPIOA : GPIO_Register with
     Volatile,
     Address => System'To_Address (GPIOA_Base);

Mamy tu więc deklarację GPIOA jako volatile i przypisanie konkretnego adresu GPIOA_Base.

Ada Drivers Library

Przykład z template w GPS jest dosyć ubogi. Nie zawiera pełnej biblioteki do obsługi peryferiów, a tylko definicje kilku niezbędnych peryferiów takich jak GPIO, czy RCC. Nie oznacza to na szczęście, że przy większym projekcie musimy wszystko pisać od zera. Możemy się posłużyć biblioteką Ada Drivers Library. Zawiera ona definicje dla rdzeni ARM i RISC-V, a także dla peryferiów STM32 i Nordic oraz dla popularnych płytek jak STM32Discovery, Micro:bit, czy Nucleo.

Ja w celach testowych odpaliłem projekt: examples\STM32F4_DISCO\accelerometer. Odczytuje on dane z akcelerometru i w zależności od wychylenia płytki zapala odpowiednią diodę. Aby wgrywać program i debugować po raz kolejny musiałem zmienić konfigurację openocd.

Zużycie pamięci

Podczas kompilacji powyższych przykładów zaniepokoiło mnie dosyć wysokie zużycie pamięci. Podstawowy LED blinker z szablonu w IDE po kompilacji zajmował:

Możemy w opcjach projektu wyklikać usuwanie nieużywanych sekcji. Zejdziemy wtedy o 5kB FLASHa:

15kB RAM i 11kB FLASH na miganie ledem? Trochę dużo. Musimy jednak pamiętać, że rutime Ady (właśnie ten wyklikany wcześniej w opcjach ravenscar-sfp-stm32f4 ) zawiera w sobie odpowiednik RTOSa zarządzającego na niskim poziomie muteksami, kolejkami itp.

Sytuacja wygląda jeszcze gorzej dla projektu z użyciem Ada Driver Library:

233kB FLASH! Na szczęście po usunięciu zbędnych funkcji podczas linkowania wartość ta zmniejsza się o połowę:

Jednak to nadal bardzo dużo. Mamy również miganie LEDem z Ada Driver Library, które po usunięciu zbędnych funkcji daje wynik zbliżony do akcelerometru:

Zużycie pamięci projektów w Adzie na pewno wymaga dużo bardziej dogłębnej analizy. Możliwe, że to początkowy overhead projektu, a potem dodawanie kolejnego kodu nie będzie powodowało już tak szybkiego wzrostu. Z drugiej strony może to być cena za wykonywanie tych wszystkich dodatkowych sprawdzeń. Na pytanie, czy jesteśmy w stanie ją ponieść dla zwiększenia bezpieczeństwa i niezawodności naszego systemu pewnie w każdym projekcie będziemy musieli odpowiedzieć sobie sami.

Podsumowanie

Start z Adą na STM32 jest w miarę bezbolesny. Mamy gotowe biblioteki do peryferiów i drivery do różnych popularnych boardów i sensorów. Start na innych procesorach np. AVR może być trochę trudniejszy, chociaż tutaj też mamy do dyspozycji jakieś gotowe biblioteki. Do robienia projektów hobbystycznych powinno to zupełnie wystarczyć. Jeżeli jednak myślicie o projekcie komercyjnym, pewnie będziecie musieli przeprowadzić dużo dokładniejszą analizę przed zdecydowaniem się na Adę. Zużycie pamięci na pewno powinno być jednym z jej czynników.

Tydzień z Adą - Nawigacja