Struktura programu pro LPC1115
Program pro LPC1115, který na Lipicanu bliká LEDkami, je opravdu velice jednoduchý. Než se ale procesor dostane k samotnému rozsvěcování a zhasínání LEDek, uděje se řada věcí. Projdeme si strukturu zdrojového kódu programu a probereme děje, které předchází spuštění hlavní funkce main().
Program simple_blinky by se určitě dal napsat jednoduššeji. Vše by mohlo být v jednom zdrojovém souboru, místo kompletní tabulky vektorů přerušení by nám stačila jen adresa vrcholu zásobníku a adresa skoku po resetu, těch pár řádků kódu bychom mohli nacpat do funkce obsluhy resetu a kompilaci a sestavení by šlo provést krátkým baťákem namísto kryptického souboru makefile.
Zvolili jsme ale jinou cestu. Jednotlivé části programu jsme rozdělili do samostatných zdrojových souborů a vytvořili jsme šablonu (template) projektu, kterou budeme používat i v dalších příkladech. Podle potřeby ji samozřejmě rozšíříme, ale v zásadě budeme vycházet ze stejného vzoru.
Základem mikrokontroléru NXP LPC1115 je procesor ARM Cortex-M0.
Dokumentaci k procesoru najdete na stránkách společnosti ARM. Důkladný popis procesoru z pohledu programátora nabízí kniha Josepha Yiu The Definitive Guide to ARM® Cortex®-M0 and Cortex-M0+ Processors, kterou vřele doporučujeme k přečtení. Obsáhlý článek o procesorech ARM Cortex-M0/M0+, který napsal Pavel Tišnovský, vyšel na serveru Root.
Abyste měli jasnou představu o vlastnostech mikrokontroléru, měli byste si z webu NXP stáhnout manuály k LPC1115.
Resetem to začíná
Procesor Cortex-M0 po resetu nastaví ukazatel vrcholu zásobníku na hodnotu, kterou najde v paměti na adrese 0x00 a začne provádět instrukce od adresy, která je v paměti uložena na následující adrese 0x04. Proto jsme vytvořili tabulku vektorů přerušení a vyhradili jsme pro ni soubor vectors.c.
vectab_t vectors __attribute__ ((section(".vectors"))) = { &__stack_top, { reset_handler, /* EXC 1 */ nmi_handler, /* EXC 2 */ hardfault_handler, /* EXC 3 */ 0, /* EXC 4 */ 0, /* EXC 5 */ ...
První záznam v tabulce vektorů je adresa vrcholu zásobníku. Hodnota symbolu __stack_top je nadefinována v linkovacím skriptu a protože zásobník bobtná směrem k nižším adresám, je v našem příkladu nastavena na konec paměti SRAM. Aby se tabulka vektorů uložila do paměti od adresy 0x00, přiřadili jsme její definici atribut sekce s názvem .vectors a o uložení sekce na správnou adresu se postará linkovací skript.
Druhou položkou, která bude uložena na adrese 0x04, je adresa obsluhy resetu, čili adresa funkce reset_handler(). Funkci jsme zapsali do souboru reset.c
void reset_handler(void) { unsigned int *src; unsigned int *trg; /* copy initialized data from FLASH to RAM */ src = &__data_load; trg = &__data_begin; while (trg < &__data_end) { *trg++ = *src++; } /* clear unitialized data */ trg = &__bss_begin; while (trg < &__bss_end) { *trg++ = 0; } /* call low level hardware init */ lowinit(); /* call main() */ main(); /* main should never return */ while (1); }
První úkolem funkce reset_handler() je nastavit hodnoty inicializovaných globálních a statických proměnných, které jsou v sekci .data. To znamená překopírovat hodnoty uložené v paměti FLASH do paměti SRAM. Následně se nuluje obsah neinicializovaných globálních a statických proměnných, které jsou v sekci .bss. Hodnoty symbolů (adresy v paměti), které se dosazují do proměnných src a trg mají původ v linkovacím skriptu.
Další na řadě je základní nastavení mikrokontroléru, které jsme zapouzdřili do funkce lowinit() a konečně volání hlavní funkce main(), která obsahuje vlastní program.
Inicializace mikrokontroléru
Po resetu mikrokontroléru je vhodné co nejdříve nastavit jeho provozní parametry. V našem příkladu se o to stará funkce lowinit(), která je uložena ve stejnojmenném souboru lowinit.c. Funkce nedělá nic složitého, pouze volá dvě další funkce clk_init() a iopin_init(), jejichž účel se dá lehce odhadnout z názvu.
void lowinit(void) { clk_init(); iopin_init(); }
Funkce clk_init() se stará o nastavení registrů pro generování vnitřních hodinových pulsů mikrokontroléru pro procesor a periferie. Jako zdroj pulsů je použit externí 12 MHz krystal a PLL obvod mikrokontroléru a hlavní frekvence je nastavena na 48 MHz.
Druhá funkce iopin_init() připraví GPIO porty P3.4 a P3.5, na které máme připojené LEDky.
Podrobnosti o registrech a hodnotách najdete v manuálu LPC111x.
Jediná funkce main()
Tady toho asi moc k vysvětlování není. Funkce, kterou jsme uložili do souboru main.c, nedělá nic jiného, než v nekonečném cyklu střídavě rozsvítí a zhasne LEDky na portech P3.4 a P3.5 a chvilku počká ve zpoždovací smyčce.