W poprzednim wpisie opisałem proces generowania runtime Ady dla STM32F401. Myślałem, że to już koniec przeprawy z konfiguracją, ale okazuje się, że nie. Pliki wygenerowane zgodnie z instrukcją z poprzedniego postu wymagają jeszcze małej modyfikacji. Poza tym jeżeli chcemy skorzystać również z Ada Drivers Library czeka nas kolejna runda walki ze skryptami. Udało mi się doprowadzić do stanu, gdzie program na STM32F401 się kompiluje, uruchamia na debugerze i poprawnie steruje peryferiami. Z tego postu dowiesz się jakie kroki musiałem wykonać, żeby do niego dojść.

Pamięć CCM

Jedną z różnic między STM32F401 a F405, czy F407 jest brak pamięci CCM (Core Coupled Memory). Jest to rodzaj pamięci RAM, która jest szybsza od zwykłej, ale nie można na niej wykonywać operacji DMA. Znajduje się ona w innym miejscu przestrzeni adresowej niż zwykły RAM (adresy CCM zaczynają się od 0x10000000 a RAM od 0x20000000). Region CCM jest definiowany w skrypcie linkera, a podczas inicjalizacji odwołuje się do niego kod asemblerowy.

Usuwanie CCM zaczynamy od pliku linkera z mapą pamięci. W repo bb-runtimes znajdziemy go w arm\stm32\<folder_procesora>\memory-map.ld. Jeżeli konfigurację nowego runtime’u robiliście zgodnie z instrukcją z repo, o której wspominałem w poprzednim wpisie, folder waszego procesora będzie nazywać się mystm32 i będzie kopią floderu stm32f40x z modyfikacjami odzwierciedlającymi różnice między tymi procesorami. Zawartość pliku memory-map.ld powinna wyglądać mniej więcej tak:

MEMORY
{
  flash   (rx) : ORIGIN = 0x08000000, LENGTH = 128K
  sram12 (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

REGION_ALIAS("sram_tx", sram12)
REGION_ALIAS("sram_ro", sram12)
REGION_ALIAS("sram_bs", sram12)
REGION_ALIAS("sram_da", sram12)

W porównaniu z plikiem z stm32f40x (można go znaleźć tutaj), poza innymi rozmiarami flash i ram, nie ma definicji regionu CCM i aliasu.

Odwołania do CCM znajdują się również w plikach linkera common-RAM.ld i common-ROM.ld oraz plikach asemblerowych start-ram.S i start-rom.S. Te pliki w folderze arm\stm32\ są jednak współdzielone przez wszystkie procesory. Ich edytowanie tutaj nie będzie najlepszym pomysłem. Najlepszym co możemy zrobić jest więc wygenerowanie plików runtime za pomocą skryptu build_rts (zgodnie z instrukcją w repo). Dalszych edycji dokonamy w tych wygenerowanych plikach. Jeżeli użyliśmy domyślnych ścieżek znajdziemy je w repo bb-runtimes w folderze build/BSPs/. Musimy jedynie pamiętać, żeby nie generować w tym samym folderze runtime’ów dla procesorów korzystających z domyślnych plików, bo zamiast nich wezmą wersje nadpisane. Może kiedyś Adacore – maintainer repo bb-runtimes – upora się z tym problemem.

Zaczniemy od plików linkera. Znajdziemy je w folderze build\BSPs\cortex-m\armv7-m\stm32\link. W obu plikach (common-RAM.ld i common-ROM.ld) należy usunąć fragment:

  .ccmdata :
  {
    __ccmdata_start = .;
    *(.ccmdata .ccmdata.*)

    /* Ensure that the end of the data section is always word aligned.
       Initial values are stored in 4-bytes blocks so we must guarantee
       that these blocks do not fall out the section (otherwise they are
       truncated and the initial data for the last block are lost). */

    . = ALIGN(0x4);
    __ccmdata_end = .;
  } > ccm_da

Następnie z pliku asemblerowego start-rom.S znajdującego się w build\BSPs\cortex-m\armv7-m\stm32\src\crt0 usuwamy:

1:
	/* Copy .ccmdata */
	movw	r0,#:lower16:__ccmdata_start
	movt	r0,#:upper16:__ccmdata_start
	movw	r1,#:lower16:__ccmdata_words
	movw	r2,#:lower16:__ccmdata_load
	movt	r2,#:upper16:__ccmdata_load
	cbz	r1,1f
0:	ldr	r4,[r2],#4
	str	r4,[r0],#4
	subs	r1,r1,#1
	bne	0b

Pliku start-ram.S nie trzeba modyfikować, bo nie zawiera odniesień do CCM.

Po tych zmianach można wykonać zgodnie z instrukcją operacje gprbuild i gprinstall. Nasz runtime dla STM32F401 jest gotowy.

Ada Drivers Library

Mając odpowiedni runtime przystąpiłem do dostosowywania Ada Drivers Library i trafiłem dokładnie na taką samą ścianę. Są wykorzystywane pliki svd, a niektóre peryferia z F407 nie są obecne na F401 jak na przykład RNG. Stwierdziłem, że najłatwiej będzie uporać się z tymi problemami kopiując foldery stm32f40x i wykonując własne modyfikacje.

Na początek więc w Ada_Drivers_Library\arch\ARM\STM32\devices skopiowałem folder stm32f40x zawierający pliki stm32-device i stm32-rcc i nazwałem go mystm32. Na modyfikacje przyjdzie czas później. Kolejny skopiowany folder to Ada_Drivers_Library\arch\ARM\STM32\drivers. Potrzebuję jeszcze folderu SVD, który muszę wygenerować za pomocą svd2ada. W tym celu użyłem komendy:

$(SVD2ADA_DIR)/svd2ada $(CMSIS_SVD_DIR)/data/STMicro/STM32F401.svd --boolean -o svd/mystm32 -p STM32_SVD --base-types-package HAL --gen-uint-always

Komendę tę znalazłem w pliku Ada_Drivers_Library\arch\svd.mk i dostosowałem do swoich potrzeb.

Następnym krokiem jest wygenerowanie projektu bazowego dla nowej konfiguracji. Robię to przy pomocy skryptu Ada_Drivers_Library\scripts\project_wizard.py. Skrypt dosyć łatwo się wywala jeżeli wpiszemy coś nie tak, ale w końcu udało mi się z nim uporać. Kolejne opcje jakie w nim wybieram to:

  1. Board: Custom_Board
  2. Architecture: ARM
  3. Vendor: STMicro
  4. Device_Family: STM32F4
  5. Device_Name: STM32F407VGTx
  6. High_Speed_External_Clock: 8000000
  7. Has_<ravenscar_sfp/ravenscar_full/zfp>_runtime: True
  8. Reszta na domyślne wartości

Utworzy nam się folder Ada_Drivers_Library, a w nim folder src zawierający plik z danymi konfiguracyjnymi i projekt ada_drivers_library.gpr. Jeżeli potrzebujemy, możemy sobie zmienić ich nazwy. Następnie przystępujemy do edycji.

W sekcji zawierającej wyrażenia for … use … dopisujemy:

   for Runtime ("Ada") use "<nazwa_runtime>";
   for Create_Missing_Dirs use "True";

W miejsce <nazwa_runtime> wpisujemy runtime sfp wygenerowany przez bb-runtimes. U mnie ta nazwa to ravenscar-sfp-mystm32. Kolejna sekcja pliku zawierająca przypisania zmiennych u mnie wygląda tak:

   Vendor                         := "STMicro";           -- From user input
   Max_Mount_Points               := "2";                 -- From user input
   Max_Mount_Name_Length          := "128";               -- From user input
   Runtime_Profile                := "ravenscar-sfp";     -- From command line
   Device_Name                    := "STM32F401RBTx";     -- From user input
   Device_Family                  := "STM32F4";           -- From user input
   Runtime_Name                   := "ravenscar-sfp-mystm32"; -- From default value
   Has_Ravenscar_Full_Runtime     := "True";              -- From user input
   CPU_Core                       := "ARM Cortex-M4F";    -- From mcu definition
   Board                          := "Custom_Board";      -- From user input
   Has_ZFP_Runtime                := "True";              -- From user input
   Has_Ravenscar_SFP_Runtime      := "True";              -- From user input
   High_Speed_External_Clock      := "8000000";           -- From user input
   Max_Path_Length                := "1024";              -- From user input
   Runtime_Name_Suffix            := "mystm32";           -- From board definition
   Architecture                   := "ARM";               -- From user input

W następnej sekcji zawierającej ścieżki dokonujemy modyfikacji, żeby wskazać utworzone wcześniej ścieżki z driverami dla nowego targetu. Ja również usunąłem wszystkie ścieżki do middleware, ponieważ nie będę go potrzebował.

Tworzenie pliku projektu

Mając ten projekt bazowy możemy utworzyć plik konkretnego projektu. Ja posłużyłem się istniejącymi przykładami w Ada Drivers Library, które zedytowałem. Mój plik projektu ma następującą zawartość:

with "mystm32/ravenscar_sfp_mystm32.gpr";

project <nazwa_projektu> extends "Ada_Drivers_Library/examples/shared/common/common.gpr" is

	for Runtime ("Ada") use ravenscar_sfp_mystm32'Runtime("Ada");
	for Target use "arm-eabi";
	for Main use ("src/main.adb");
	for Source_Dirs use ("src");
	for Object_Dir use "obj";
	for Create_Missing_Dirs use "True";

	package Compiler renames ravenscar_sfp_mystm32.Compiler;

	-- Debugger configuration
	package Ide is
		for Connection_Tool use "openocd";
		for Connection_Config_File use "board/stm32f4discovery.cfg";
		for Program_Host use "localhost:3333";
		for Communication_Protocol use "remote";
	end Ide;

	package Emulator is
		for Board use "STM32F4";
	end Emulator;

end <nazwa_projektu>;

Na początku za pomocą słówka with includujemy projekt bazowy. U mnie ma on nazwę ravenscar_sfp_mystm32. Dalej definiujemy nasz konkretny projekt, który dziedziczy po projekcie common.gpr z przykładów. W common.gpr znajduje się kilka ustawień kompilacji jak na przykład wybór buildu produkcyjnego/debugowego. Docelowo będę się chciał go pozbyć.

Usuwanie błędów kompilacji

Tak utworzony projekt początkowo nie będzie się kompilować. Wszystko przez drivery do peryferiów, które nie są obecne na STM32F401. Niestety tutaj czeka nas żmudna praca. Musimy kompilować projekt i czytać komunikaty o błędach. Następnie usuwać całe drivery, które nie są wspierane (np. RNG, DCIM, SD), albo kod odwołujący się do nieistniejących na naszym procku peryferiów (np. GPIOF, TIM6, UART4). W moim przypadku było kilka złośliwych miejsc jak choćby ADC. Na F401 jest jedno ADC, a na F407 są 3, które mogą działać w multi mode. W efekcie musiałem usuwać niektóre bity w rejestrach, których nie ma na F401. Na szczęście nie musiałem dopisywać żadnych nowych peryferiów. Z tym pewnie byłoby dużo więcej roboty. W każdym razie błędy kompilacji da się stosunkowo łatwo wyeliminować. W ten sposób otrzymujemy gotową wersję driverów do naszego procka.

I to już wszystko – po tych zabiegach udało mi się skompilować i zdebugować na STM32F401 kod w Adzie.

Podsumowanie

Jak widać problemów z konfiguracją Ady jest co nie miara. Jeżeli tylko zejdziemy z happy patha i potrzebujemy coś, co nie jest od początku wspierane, musimy szykować się na ciężką walkę. Community AdaCore robi co może, aby ułatwić start z Adą jednak jeszcze daleka droga, aby osiągnąć łatwość obsługi narzędzi taką jak w C. Z podobnymi problemami zmierzymy się jednak również używając rozwijanych przez community bibliotek do peryferiów w C++ czy Rust. Bez wsparcia producentów układów pewnie sytuacja się szybko nie poprawi.

Dotychczasowe doświadczenia z Adą na mikrokontrolery sprawiają, że raczej bym jej nie polecił do dużego komercyjnego projektu. A jeśli już to tylko mając wykupiony dodatkowy support. Jednak istnieje ogromna ilość poważnych projektów napisanych w Adzie. Listę możecie znaleźć tutaj. Zastanawia mnie więc jak tam sobie poradzili z tymi problemami.