Po zainstalowaniu toolchaina GNAT dla ARMów otrzymujemy wsparcie jedynie dla niewielkiej liczby procesorów. Aby wygenerować pliki runtime dla innych, musimy skorzystać ze skryptów dostępnych w repozytorium bb-runtimes od AdaCore. Ostatnio potrzebowałem wygenerować je dla nowego procka i trochę musiałem się namęczyć. Dlatego teraz opiszę do czego udało mi się dojść.

Co to jest ten runtime?

Runtime Ady to zestaw plików potrzebnych do uruchomienia Ady na konkretnym procesorze. Dla mikrokontrolerów składa się on z mapy pamięci dla linkera, procedur startowych napisanych w asemblerze, kilku plików C i plików Ady z definicjami specyficznymi dla procesora (np. rejestry peryferiów), deklaracjami przerwań i inicjalizacją programu (w Adzie nazywamy ją Elaboration).

Dla różnych procesorów pliki te będą się różniły. Nawet wewnątrz tej samej rodziny poszczególne procesory mają inne rozmiary pamięci i różną ilość peryferiów. Ten sam problem mają na przykład twórcy bibliotek do C++ jak Modm. Aby ułatwić im pracę, ARM stworzył format plików CMSIS-SVD (System View Description) standaryzujący sposób opisywania peryferiów. Dzięki niemu informacje o adresach rejestrów i wykorzystywanych w nich bitach są zapisywane w ujednoliconej formie przez wszystkich producentów ARMów. Na podstawie tych plików można generować biblioteki do konkretnych procesorów. Do generowania plików Ady służy narzędzie Svd2Ada.

Próby z istniejącym runtime’m

Chciałem napisać coś w Adzie na STM32F401RB. Myślałem, że nie będzie z tym problemu, bo przecież jest domyślny runtime dla STM32F4 i uruchomiałem już nawet na nim programy na płytce Discovery. Okazało się jednak, że jeśli chcę użyć F401, nie jest tak różowo. Domyślny runtime STM32F4 jest tak naprawdę dla STM32F407VG. Mój procesor ma dużo mniej pamięci (128kB FLASH 64kB RAM, a w Discovery 1024kB FLASH, 128kB RAM 64kB CCM RAM) i niższą częstotliwość zegara (84MHz vs 168MHz). Próba użycia istniejącego runtime’u kończy się na inicjalizacji PLL – aplikacja wchodzi w HardFault przez zbyt duże taktowanie. Muszę więc wygenerować swoje runtime’y. Jeżeli bym chciał użyć procka o tym samym taktowaniu co F407VG to pewnie istniejący runtime by się sprawdził, ale i tak lepiej wygenerować swój choćby ze względu na pamięć i peryferia.

Generowanie według instrukcji

W repo bb-runtimes możemy znaleźć ładną instrukcję jak generować własne runtime’y. W tym celu należy:

  1. Ściągnąć bb-runtimes.
  2. Ściągnąć svd2ada.
  3. Skopiować pliki dla istniejącej konfiguracji.
  4. Dodać nową konfigurację do skryptów pythonowych build_rts i cortexm.
  5. Zaktualizować mapę pamięci dla linkera.
  6. Wygenerować pliki z SVD.
  7. Zedytować ustawienia taktowania.
  8. Wywołać skrypt pythonowy build_rts.
  9. Zbudować projekt.
  10. Zainstalować nowy runtime w toolchainie.

Dokładne komendy, jakie należy wywołać w każdym z tych kroków można znaleźć w podlinkowanej wyżej instrukcji. Jak pewnie możecie się domyślić – u mnie nie zadziałało. Wywaliła się kompilacja projektu:

s-stm32.adb:53:57: expected an integer type s-stm32.adb:53:57: found type "Interfaces.Stm32.Rcc.CFGR_SWS_Field"

setup_pll.adb:105:36: expected type "Interfaces.Stm32.Rcc.CFGR_SW_Field" setup_pll.adb:105:36: found type universal integer setup_pll.adb:219:23: expected type "Interfaces.Stm32.Rcc.PLLCFGR_PLLM_Field" setup_pll.adb:219:23: found type "Interfaces.Stm32.UInt6" setup_pll.adb:220:23: expected type "Interfaces.Stm32.Rcc.PLLCFGR_PLLN_Field" setup_pll.adb:220:23: found type "Interfaces.Stm32.UInt9" setup_pll.adb:221:23: expected type "Interfaces.Stm32.Rcc.PLLCFGR_PLLP_Field" setup_pll.adb:221:23: found type "Interfaces.Stm32.UInt2" setup_pll.adb:222:23: expected type "Interfaces.Stm32.Rcc.PLLCFGR_PLLQ_Field" setup_pll.adb:222:23: found type "Interfaces.Stm32.UInt4" setup_pll.adb:272:43: invalid operand types for operator "=" setup_pll.adb:272:43: left operand has type "Interfaces.Stm32.Rcc.CFGR_SWS_Field" setup_pll.adb:272:43: right operand has type universal integer

Poprawki

Ostatecznie po długiej walce udało mi się zbudować runtime. Okazało się, że pliki z bb-runtimes są kompatybilne ze starszą wersją svd2ada. Aktualnie niektóre pola bitowe są reprezentowane za pomocą variant record., w którym w zależności od wartości jednego pola, pozostałe pola mogą się różnić. Do tych pól bitowych dodano parametr boolowski As_Array i jeżeli ma wartość True, pole jest reprezentowane jako tablica bitów, a w przeciwnym wypadku jako pojedyncza wartość. Efekt jest taki, że w miejscach użycia tych zmiennych typy się nie zgadzają i mamy błąd kompilacji.

Aby projekt się skompilował musimy zmodyfikować pliki s-stm32.ads:

    function System_Clocks return RCC_System_Clocks
    is
       Source       : constant SYSCLK_Source :=
-                      SYSCLK_Source'Val (RCC_Periph.CFGR.SWS);
+                      SYSCLK_Source'Val (RCC_Periph.CFGR.SWS.Val);
       Result       : RCC_System_Clocks;

    begin
package body System.STM32 is

          when SYSCLK_SRC_PLL =>
             declare
-               Pllm   : constant UInt32 := UInt32 (RCC_Periph.PLLCFGR.PLLM);
-               Plln   : constant UInt32 := UInt32 (RCC_Periph.PLLCFGR.PLLN);
-               Pllp   : constant UInt32 := UInt32 (RCC_Periph.PLLCFGR.PLLP);
+               Pllm   : constant UInt32 :=
+                   UInt32 (RCC_Periph.PLLCFGR.PLLM.Val);
+               Plln   : constant UInt32 :=
+                   UInt32 (RCC_Periph.PLLCFGR.PLLN.Val);
+               Pllp   : constant UInt32 :=
+                   UInt32 (RCC_Periph.PLLCFGR.PLLP.Val);
                Pllvco : UInt32;

             begin

i setup_pll.adb:

procedure Setup_Pll is
                        else (if HSE_Enabled then SYSCLK_SRC_HSE
                              else SYSCLK_SRC_HSI));
       SW_Value    : constant CFGR_SW_Field :=
-                      SYSCLK_Source'Enum_Rep (SW);
+                      (As_Array => False, Val => SYSCLK_Source'Enum_Rep (SW));

       SYSCLK      : constant Integer := (if Activate_PLL
                                          then PLLCLKOUT
procedure Setup_Pll is
          --  Configure the PLL clock source, multiplication and division
          --  factors
          RCC_Periph.PLLCFGR :=
-           (PLLM   => PLLM,
-            PLLN   => PLLN,
-            PLLP   => PLLP,
-            PLLQ   => PLLQ,
+           (PLLM   => (As_Array => False, Val => PLLM),
+            PLLN   => (As_Array => False, Val => PLLN),
+            PLLP   => (As_Array => False, Val => PLLP),
+            PLLQ   => (As_Array => False, Val => PLLQ),
             PLLSRC => (if HSE_Enabled
                        then PLL_Source'Enum_Rep (PLL_SRC_HSE)
                        else PLL_Source'Enum_Rep (PLL_SRC_HSI)),
procedure Setup_Pll is

       if Activate_PLL then
          loop
-            exit when RCC_Periph.CFGR.SWS =
+            exit when RCC_Periph.CFGR.SWS.Val =
               SYSCLK_Source'Enum_Rep (SYSCLK_SRC_PLL);
          end loop;

Zmiany można też podejrzeć w commicie na moim forku bb-runtimes.

Przy okazji zabawy z runtime’ami zauważyłem jedną poważną wadę – jest tam konfigurowane taktowanie zegara. Nie bawiłem się tym jeszcze, więc nie wiem, czy tą częstotliwość da się gdzieś jeszcze nadpisać. Mam nadzieję, że tak, bo inaczej oddzielny runtime musiałby być generowany dla każdego potrzebnego taktowania.

Mam nadzieję, że udało mi się już uporać ze wszystkimi narzędziami i teraz będę mógł w końcu coś popisać na STMa, żeby zobaczyć, jak Ada sprawuje się w akcji.

EDIT: Okazało się jednak, że nie jest to koniec walki z konfiguracją. Potrzebne są jeszcze pewne zmiany w bb-runtime i podobna walka ze skryptami Ada Drivers Library, aby poprawnie uruchomić swój projekt w Adzie na innym mikrokontrolerze. Po szczegóły zapraszam do kolejnego wpisu: