Cortex-M3: pierwszy projekt w Open Source
Spróbujemy teraz uruchomić debugger: w tym celu uruchamiamy program OpenOCD (klikając na ikonę launch External Tool ), następnie ikonę z symbolem „robaka”, co powinno zaowocować uruchomieniem programu w trybie Debug oraz zatrzymaniem się aplikacji w funkcji main() (rys. 10). Przy pierwszym uruchomieniu sesji należy wybrać opcję Menu->Debug Configuration , wybrać program ledsDBG , następnie wcisnąć przycisk Run .
Rys. 10. Wygląd okna Eclipse z uruchomionym debugowaniem przykładowej aplikacji
Teraz wciskając klawisz F6 ( Step Over ) możemy śledzić wykonanie poszczególnych linii programu. Eclipse umożliwia również ustawianie pułapek w określonych miejscach programu, poprzez kliknięcie myszką na niebieskim obszarze przed wybraną linią, umożliwia śledzenie zmiennych. Z innych użytecznych skrótów klawiszowych dostępny jest F5 ( Step Into ), opcja umożliwiająca zagłębienie się do wnętrza funkcji.
Budowa projektu
Wykorzystując zintegrowane środowiska programistyczne jedynym zmartwieniem użytkownika jest utworzenie plików źródłowych, ponieważ wszystkie skrypty potrzebne do pracy są automatycznie generowane przez środowisko. Podejście takie zazwyczaj jest zadowalające jeżeli wszystko działa poprawnie, natomiast w przypadku, gdy pojawią się jakieś problemy, użytkownik tak naprawdę nie wie w jaki sposób działają narzędzia ani nie ma wpływu na działanie kreatorów, co może być przyczyną wielu problemów.
Środowisko Eclipse nie zapewnia żadnego specjalistycznego wsparcia w przypadku projektów dla mikrokontrolerów, w związku z tym wszystkie pliki należy stworzyć samodzielnie. Przy tworzenia wszystkiego od początku może to być zadaniem stosunkowo skomplikowanym, dlatego najwygodniej będzie skorzystać z gotowego bazowego projektu dla danej rodziny mikrokontrolerów, i zmienić tylko potrzebne opcję. Zaprezentowany projekt bazowy dla mikrokontrolerów STM32 posiada strukturę hierarchiczną, pokazaną na rys. 11.
Rys. 11. Struktura przykładowego projektu
W katalogu scripts sązawarte skrypty odpowiedzialne za przebieg kompilacji oraz programowanie urządzenia docelowego. Plik stm32.mk jest plikiem dla narzędzia make zapewniającym automatyzację procesu kompilacji. Stanowi on bazę dołączaną do głównego pliku makefile i został on napisany w taki sposób, aby wszystkie pliki źródłowe C, C++ oraz S (assembler), utworzone w pliku projektu były kompilowane automatycznie.
Plik stm32.cfg jest plikiem konfiguracyjnym dla programu debugera OpenOCD i zawiera opis konfiguracji dla interfejsu zgodnego z ocdlink (np. BF-30) oraz konfigurację dla mikrokontrolerów rodziny STM32. Plik stm32.ld jest skryptem linkera, w którym mamy możliwość zdefiniowania wielkości pamięci, która może być różna w zależności od wybranego układu z danej rodziny. W linii:
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 48K
możemy definiować adres bazowy oraz rozmiar wielkości pamięci RAM, natomiast w linii:
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 128K
możemy zdefiniować adres bazowy oraz wielkość pamięci Flash w zależności od używanego układu.
W katalogu lib-stm32 znajduje się część biblioteki STM32 Perhipheral library , z której wyciągnięto pliki nagłówkowe(katalog inc ) oraz skrypty dla linkera ( *.ld ).
Pliki nagłówkowe zmodyfikowano tak, aby zawierały tylko struktury opisujące układy peryferyjne. Właściwy przykładowy program migający diodami LED zawarto w katalogu ex-leds . Głównym plikiem sterującym przebiegiem kompilacji projektu jest plik makefile . Zawiera on kilka użytecznych opcji, które mogą być zmieniane przez użytkownika w zależności wymagań. Najistotniejszy z punktu widzenia użytkownika fragment tego pliku pokazano na list. 1.
List. 1. Fragment pliku makefile
# Automatic makefile for GNUARM (C/C++) # Copyright (C) Lucjan Bryndza # http://www.boff.pl #tutaj wpisz nazwe pliku hex TARGET = leds-example #Format wyjsciowy (moze byc elf,hex,bin) FORMAT = hex #Optymalizacja [0,1,2,3,s] # 0 - brak optymalizacji, s -optymalizacja rozmiaru OPT = 2 #Common flags COMMON_FLAGS = -Wall -pedantic -DSTM32F10X_CL
W zmiennej target mamy możliwość wpisania nazwy pliku wynikowego, jaki powstanie w wyniku kompilacji projektu. Pole format umożliwia określenie rodzaju pliku wynikowego wygenerowanego w wyniku kompilacji. Skrypt przewiduje możliwość generowania pliku w formatach ELF , Intel Hex lub bezpośrednio w formacie binarnym.
Kolejną istotną opcją jest wspomniany wcześniej tryb optymalizacji kodu wynikowego. Mamy możliwość sterowania optymalizacją od poziomu 0 (brak optymalizacji), poprzez opcję 3 (maksymalna optymalizacja na szybkość programu), aż po opcję s (optymalizującą program pod kątem jak najmniejszego miejsca zajmowanego w pamięci).
Ostatnią istotną zmienną jest COMMON_FLAGS , która umożliwia podanie dodatkowych wspólnych opcji dla kompilatora C oraz C++. Istotna tutaj jest definicja -DSTM32F10X_CL , która powoduje zdefiniowanie typu mikrokontrolera dla biblioteki STM32 Standard Perhipheral Library . Mamy możliwość zdefiniowania następujących symboli:
– STM32F10X_LD – mikrokontrolery STM32 Low Density Line ,
– STM32F10X_MD – mikrokontrolery STM32 Medium Density Line ,
– STM32F10X_HD – mikrokontrolery STM32 Hi Density Line ,
– STM32F10X_CL – mikrokontrolery STM32 Connectivity Line – stosowane w STM32Butterfly.
Pozostałe pliki są plikami źródłowymi w języku C, i odpowiadają bezpośrednio za działanie aplikacji. Warto tutaj wspomnieć o pliku startup.c , w którym została zdefiniowana globalna tablica przypisująca funkcje do wektorów przerwań (list. 2).
List. 2. Globalna tablica przypisująca funkcje do wektorów przerwań
/*---------------------------------*/ //Interrupt vector table __attribute__ ((section(".isr_vector"))) void (* const exceptions_vectors[])(void) = { &_estack, // The initial stack pointer reset_handler, // The reset handler unused_vector, //NMIException unused_vector, //HardFaultException unused_vector, //MemManageException unused_vector, //BusFaultException unused_vector, //UsageFaultException
W pliku należy przypisać funkcję, która będzie odpowiedzialna za realizację obsługi danego przerwania. Domyślnie wszystkie wektory mają zdefiniowaną funkcję unused_vector() , która zawiera nieskończoną pętlę while(1) . W naszym prostym przykładzie nie wykorzystujemy żadnych przerwań więc nie ma potrzeby dopisywania tutaj żadnych funkcji.