Produkty Novinky Články Návody Kontakty

Struktura programu (co jsme zatajili)

V předchozím článku jsme rozebrali strukturu zdrojáků jednoduchého programu pro mikrokontrolér LPC1115, ale pár věcí jsme přešli jako samozřejmost, kterou není potřeba moc vysvětlovat. Třeba odkud se vzaly definice registrů LPC1115 nebo co je vlastně v tom mejkfajlu. Proto jsme napsali tento článek.

Kde jsme vzali definice registrů

Ve zdrojácích, ve kterých zapisujeme hodnoty přímo do registrů mikrokontroléru LPC1115 nebo čteme jejich obsah, používáme názvy registrů a nijak se nepídíme po vysvětlení, jak jsou ty názvy svázány s adresami v paměťovém prostoru. On to ten kompilátor asi nějak udělá. No neudělá, stará se o to direktiva include, která překladači podstrčí hlavičkové (header) soubory s definicemi používaných symbolů.
V souboru lowinit.c vkládáme hlavičkový soubor chip.h, který definuje základní adresy registrů periferií a na konci natahuje další hlavičkové soubory s definicemi pro jednotlivé periferie.
...
#include "pmu_11xx.h"
#include "fmc_11xx.h"
#include "sysctl_11xx.h"
#include "clock_11xx.h"
#include "iocon_11xx.h"
#include "timer_11xx.h"
#include "uart_11xx.h"
#include "wwdt_11xx.h"
#include "ssp_11xx.h"
#include "romapi_11xx.h"
...
Hlavičkové soubory, které jsou určené pro řadu mikrokontrolérů LPC11xx, najdete v podadresáři include/lpc11xx a rozhodně stojí za prohlédnutí.
Jak se překladač dozví, kde ty hlavičkové soubory najde? Sdělíme mu to parametrem -I spolu s názvem cesty k souborům. Proto v souboru makefile definujeme seznam adresářů s hlavičkovými soubory, který pak použijeme při sestavení sady parametrů pro kompilátor.

Ještě ten makefile

Nebojte se, nebudeme tady opakovat stokrát jinde řečené. Jak funguje program make a skript makefile, najdete na Internetu na řadě míst, stačí jen požádat o pomoc Google. Jeden tutoriál o programu make v češtině je na našem webu, další popis přináší články Správa projektů pomocí programu Make nebo GNU Make.
O čem tedy bude řeč? Stručně popíšeme, jak jsme poskládali náš makefile, protože jej s drobnými úpravami budeme používat pro všechny následující příklady.
Základem souboru makefile jsou cíle (targets), ty definují, jak jej budeme používat. První defaultní cíl makefile se postará o kompilaci zdrojáků, sestavení linkerem a vytvoření binárního (a hexadecimálního) obrazu programu. Provedení cíle dosáhneme na příkazové řádce příkazem
make
nebo
make all
Další cíl clean se postará o smazání produktů předchozí kompilace a buildování. Provede se jednoduše příkazem
make clean
Poslední cíl pro příkazovou řádku se jmenuje dep a vytvoří soubor pravidel a závislostí pro kompilaci zdrojových souborů, který se do souboru makefile vkládá direktivou include. Provede se příkazem (jak určitě správně tušíte)
make dep
Dvojici příkazů
make clean
make dep
byste měli provést pokaždé, když přidáte nebo odeberete zdrojový nebo hlavičkový soubor.
Obecné pravidlo definuje, jak se ze zdrojového souboru stane object soubor, to jest, jakým programem se bude zdroják kompilovat a jaké parametry budou na příkazové řádce kompilátoru.
No a co je na začátku souboru makefile? Tam se definují proměnné, které se používají dál v souboru, postupně se definuje jejich obsah. Proměnné VERBOSE a DEBUG jsme si připravili, aby se z příkazové řádky dalo řídit, jestli výpis při kompilaci bude ukecaný a zda překládáme ladicí nebo provozní verzi programu. Z příkazového řádku se proměnné nastaví při spuštění programu make
make VERBOSE=0 DEBUG=0
nebo
make VERBOSE=0 DEBUG=0 all
Do proměnné INCDIR se nastavuje seznam adresářů, ve kterých se budou hledat hlavičkové soubory. Do proměnné SRCDIR se nastavuje seznam adresářů se zdrojovými soubory. Do proměnné SOURCES se nastavuje seznam zdrojových souborů, ze kterých se program sestaví. Na základě obsahu proměnných VERBOSE a DEBUG se nastavují proměnné pro spuštění kompilátoru a úrovně optimalizace překladu.
Do proměnné CFLAGS se nastavují parametry pro kompilátor a do proměnné LDFLAGS se nastavují parametry pro linker. Tady linkeru říkáme, který skript pro uložení objektů na správné adresy v paměti má použít, že chceme, aby vypsal do souboru mapu obsazení paměti a parametrem -nostartfiles linkeru sdělíme, že nechceme, aby přilinkoval standardní startup kód.
Do proměnné OBJECTS se transformuje seznam zdrojáků s příponou *.c na seznam object souborů s příponou *.o a direktiva vpath určuje, kde se budou hledat soubory se jmenovanou příponou.

Parametr -nostartfiles

Zmínili jsme parametr linkeru -nostartfiles. Tento parametr linkeru přikazuje, aby k našemu programu nelinkoval startup kód z crt0.o a dalších object souborů. Říkáme tím, že se o nastavení vektorů přerušení, zásobníku, o nastavení počátečních hodnot proměnných, o další počáteční inicializaci procesoru a o skok na funkci main() postaráme sami. Proč si takhle komplikujeme život a nenecháme linker, aby vše zařídil? Protože chceme mít ve svých rukou, z jakého kódu se náš program skládá, chceme, aby byl co možná nejmenší a neobsahoval žádné zbytečnosti.
Proto například namísto funkce printf(), jejíž implementace je ve standardní knihovně rozsáhlá a zabírá v paměti mikrokontroléru hodně místa, budeme používat mnohem jednodušší implementaci xprintf() od Elm-Chan, která našim potřebám bude bohatě stačit.

Prodleva a SysTick timer

Nakonec jsme ještě trochu změnili program. Prodlevu mezi přepnutím LEDek děláme čekací smyčkou s dekrementací počítadla. Je to jednoduché, ale hodnotu konstanty, která definuje dobu prodlevy, musíme stanovit zkusmo. Abychom mohli stanovit délku prodlevy přesně, použili jsme časovač SysTick. Tento timer není specialitou určité řady mikrokontrolérů, ale je součástí procesoru ARM Cortex-M0 (a taky Cortex-M0+/M3/M4/M7) a tak jej v každém mikrokontroléru, který je postaven okolo některého ze zmíněných procesorů, budeme mít určitě k dispozici.
Postup byl jednoduchý. Udělali jsme kopii příkladu do nového adresáře systick_blinky. Přibyl nový zdrojový soubor systick.c spolu s hlavičkovým souborem systick.h, do kterého jsme dali obsluhu timeru SysTick. Název souboru systick.c jsme přidali také do seznamu zdrojáků v souboru makefile, aby se zúčastnil překladu a sestavení programu.
V souboru jsou tři funkce. První z nich, systick_init(), nastavuje hodnoty registrů timeru SysTick tak, aby každých 10 ms vygeneroval přerušení. Druhá funkce systick_ienb() povolí nebo zakáže přerušení od časovače. Obě dvě funkce voláme na začátku main().
Třetí je obsluha přerušení systick_handler(). Adresa handleru musí být na správné pozici tabulky vektorů přerušení.
void systick_init(void)
{
      SysTick->LOAD  = SYSTICK_RELOAD;
      SysTick->VAL   = 0;
      SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
                       SysTick_CTRL_ENABLE_Msk;
}

void systick_ienb(int enable)
{
    if (enable)
        SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
    else
        SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk;
}

/* SysTick interrupt handler */
void systick_handler(void)
{
    systick_func();
}
Handler přerušení systick_handler() pouze volá funkci systick_func(), která je definovaná ve zdrojovém souboru main.c. Funkce systick_func() odpočítává 10 ms přerušení a když jich proběhne dostatek, nastaví příznak flag_period_pass. Čekací cyklus namísto dekrementování počítadla jenom čeká, až je příznak flag_period_pass nastaven. A jak se dočká, musíme příznak hned shodit.