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:
- Board: Custom_Board
- Architecture: ARM
- Vendor: STMicro
- Device_Family: STM32F4
- Device_Name: STM32F407VGTx
- High_Speed_External_Clock: 8000000
- Has_<ravenscar_sfp/ravenscar_full/zfp>_runtime: True
- 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.
Dodaj komentarz