Produkty Novinky Články Návody Kontakty

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.