Produkty Novinky Články Návody Kontakty

Překlad a program Make

Nebudeme se zabývat triviálním spuštěním překladače pro kompilaci a sestavení programu, který je obsažen v jediném zdrojové souboru. Připravíme si jednoduchý rámec, který můžeme použít pro kompilování dalších příkladů.
Program napsaný v programovacím jazyku C musíme přeložit překladačem do strojového kódu cílového procesoru, v našem případě do strojového kódu procesoru Cortex-M3. Většinou je program rozložen kvůli přehlednosti do více zdrojových souborů (modulů), může používat funkce z podpůrných knihoven, je třeba definovat mapu cílového paměťového prostoru, atd.
Prvním krokem je kompilace souborů se zdrojovým kódem. Vzniklé object soubory je třeba pomocí spojovacího programu, linkeru, spojit s funkcemi z knihoven a umístit v paměti. Výsledný soubor obsahuje obraz paměti pro cílový procesor. Podle toho, jakým způsobem zapisujeme binární obraz do paměti cílového procesoru, může být nutné provést jeho konverzi například do .bin formátu nebo .hex formátu.

Make a makefile

Celý proces překladu je vhodné zautomatizovat. Bylo by možné použít dávkové soubory, ale pro automatizaci překladu existuje speciální nástroj, program make. Tento program má původ ve světě operačního systému UNIX, my použijeme verzi programu make, která je součástí Sourcery G++ a jmenuje se cs-make.
Program make se po spuštění pokusí otevřít v aktuálním adresáři soubor makefile a interpretuje jeho obsah. Parametrem -f jmeno_souboru lze programu make zadat jméno souboru, který má být interpretován.
Tento příkaz provádí obsah souboru makefile:
C:\Develop\ARM\projects\ucsimply\cm3\blink_led_cli>cs-make
Tento příkaz provádí obsah souboru mymkfile:
C:\Develop\ARM\projects\ucsimply\cm3\blink_led_cli>cs-make -f mymkfile
Vytvoříme si jednoduchý soubor makefile, který nám s minimálními změnami může sloužit pro další projekty a vysvětlíme jeho obsah řádek po řádku. Pokud budete chtít proniknout hlouběji do tajů programu make, na internetu existuje řada materiálů, které vysvětlují pravidla pro vytváření makefilů a používání programu make.
Pro psaní souborů makefile je třeba mít na paměti jednu důležitou skutečnost. Všechny příkazy, které jsou součástí pravidel, musí začínat znakem tabulátoru (0x09), jinak se program make zlobí a nechce pracovat. Dávejte si proto dobrý pozor na editory, které nahrazují znak tabulátoru mezerami.
Součástí jednoduchého příkladu je i soubor makefile. Projdeme si celý skript a vysvětlíme si jeho části. Znak # v souborech makefile označuje komentář, text od znaku # do konce řádku program make interpretuje jako komentář.
Téměř polovina našeho skriptu obsahuje definice proměnných, které budeme ve skriptu používat. Proměnné se ve skriptu použíjí zápisem $(NAZEV_PROMENNE), program make na místo zápisu dosadí obsah proměnné.
Hned na začátku skriptu definujeme proměnnou RESULT, která obsahuje název projektu. Proměnnou použijeme při generování názvu výsledných souborů (.elf, .bin atd.) sestaveného programu. Proměnnou DEBUG použijeme pro indikaci, zda má být sestavena ladicí verze nebo provozní verze programu. Stačí odstranit znak komentáře a program bude sestaven v ladicí verzi. Další sada proměnných definuje programy, které bude skript spouštět v příkazech pravidel. Proměnná CC obsahuje název spouštěného kompilátoru, proměnná AS název assembleru, LD obsahuje linker atd.
​
# program 
RESULT = blink_led_cli
​
# DEBUG = 1
​
CC      = arm-none-eabi-gcc
AS      = arm-none-eabi-as
LD      = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
OBJDUMP = arm-none-eabi-objdump
RM      = cs-rm 
​
Proměnná SRCDIR definuje adresáře, ve kterém máme umístěné zdrojové soubory, proměnná INCDIR adresáře, kde jsou hlavičkové soubory. Do proměnné INCLUDES dosadíme parametr -I, který dále použijeme při definici příkazové řádky pro kompilátor. Tento parametr říká kompilátoru, které adresáře má procházet, když hledá hlavičkové soubory. V proměnné SOURCES budeme mít seznam zdrojových souborů, které se budou kompilovat. Obrácené lomítko na konci řádku umožňuje rozložit zápis dlouhého příkazu na více řádků a tím zpřehlednit skript. Direktivou vpath říkáme programu make, ve kterých adresářích má hledat soubory s příponou .c, tedy zdrojové soubory.
Jednoduchou náhradou přípony .c za příponu .o dosadíme do proměnné OBJECTS seznam object souborů, které budou generovány kompilátorem a do proměnné LDSCRIPT název skriptu pro linker.
​
SRCDIR = src 
INCDIR = inc 
​
# include header files 
INCLUDES = -I $(INCDIR)
​
# Source files 
SOURCES = \
        vector.c        \
        main.c          \
        mcu_init.c      \
        helpers.c
​
vpath %.c $(SRCDIR) 
​
# Object files 
OBJECTS = $(SOURCES:.c=.o)
​
# Linker script
LDSCRIPT = lm3s800.ld
​
​
Do proměnných WARNS_RUN a WARNS_DEBUG si nastavíme warning příznaky pro kompilátor. Uděláme dvě verze, jednu pro provozní a druhou pro ladicí verzi programu. Jejich uplatnění je zřejmé z následujících řádků. Pokud je definována proměnná DEBUG, tak je do proměnné WARNS dosazen obsah proměnné WARNS_DEBUG, pokud DEBUG není definována, do proměnné WARNS je dosazen obsah WARNS_RUN. Současně definujeme dvě verze proměnné OPTIM, která specifikuje druh optimalizace, která mám být uplatněna při překladu. Pro produkční verzi budeme chtít optimalizaci velikosti výsledného kódu (parametr -Os). Pro ladicí verzi nebude generovaný kód optimalizován (parametr -O0) a budou generovány ladicí informace (parametr -g).
​
WARNS_RUN   = -Wall 
WARNS_DEBUG = -Wall -Wunused -Wextra -Wshadow -Wpointer-arith \     
        -Wbad-function-cast -Wsign-compare -Waggregate-return \     
        -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations
​
ifdef DEBUG 
OPTIM = -O0 -g 
WARNS = $(WARNS_DEBUG) 
else 
OPTIM = -Os 
WARNS = $(WARNS_RUN) 
endif
​
Do proměnné CFLAGS shromáždíme parametry pro překlad zdrojových kódů. Budeme kompilovat pro procesor Cortex-M3 a provozní režim THUMB. Přidáme také warning parametry, které obsahuje proměnná WARNS, požadavky na optimalizaci generovaného kódu, které jsou definovány v proměnné OPTIM a seznam adresářů, ve kterých budou hledány hlavičkové soubory.
​
CFLAGS = -mthumb -mcpu=cortex-m3 -std=c99 -pedantic \
        $(WARNS) $(OPTIM) $(INCLUDES)
​
Pro linker naplníme proměnnou LDFLAGS. Název skriptu pro linker definuje parametr -T, parametrem -Map si vyžádáme generování paměťové mapy sestaveného programu do souboru .map.
​
LFLAGS = -T $(LDSCRIPT) -Map=$(RESULT).map
​
Nyní se už dostáváme k pravidlům, která řídí výkonnou část programu make. Každe pravidlo definuje cíl, závislost a prováděné příkazy. Na řádku je nejdříve definován cíl, následuje dvojtečka jako oddělovač a dál seznam objektů, na kterých je cíl závislý. Pod tímto řádkem mohou následovat příkazy, které se při zpracování pravidla mají provést. Každý příkaz musí začínat znakem tabulátoru (0x09).
Nejdříve si ještě nadefinujeme obecné pravidlo, které se vyhodnocuje pro všechny cíle s příponou .o, tedy pro object soubory. Každé reálné pravidlo, které jako cíl obsahuje object soubor, je závislé na odpovídajícím zdrojovém souboru a po jeho vyhodnocení je proveden příkaz na následujícím řádku. Příkaz spustí kompilátor GCC, definovaný obsahem proměnné CC, předá mu parametry překladu nadefinované v proměnné CFLAGS, parametrem -c si vyžádá kompilaci, jako vstup si vezme obsah automatické proměnné $<. Výstup kompilace (parametr -o) zapíše do souboru s názvem, který je obsažen v automatické proměnné $@.
Automatická proměnná $< obsahuje první řetězec závislosti, v našem případě tedy název zdrojového souboru. Automatická proměnná $@ obsahuje cíl pravidla, tedy název object souboru.
​
# obj file general rule 
%.o : %.c
        $(CC) $(CFLAGS) -c $< -o $@ 
​
A toto je už skutečně výkonná část našeho makefilu.
​
all : $(RESULT).bin
​
$(RESULT).bin : $(RESULT).elf   
        $(OBJCOPY) -O binary $(RESULT).elf $(RESULT).bin        
        $(OBJCOPY) -O ihex $(RESULT).elf $(RESULT).hex  
        $(OBJDUMP) -S $(RESULT).elf >$(RESULT).lst
​
$(RESULT).elf : $(OBJECTS)      
        $(LD) $(LFLAGS) $^ -o $@
​
-include dependency.list
​
clean :
        $(RM) -f *.o
        $(RM) -f $(RESULT).*
​
dep : $(SOURCES)
        $(CC) -E -MM $(INCLUDES) $^ >dependency.list
​
.PHONY : all, clean, dep
​