8.Vývojové prostriedky a vývoj aplikácie pre mikropočítače

 

            Táto kapitola je zameraná na vývoj aplikácie pre mikropočítače založené na jadre Intel 8051. V jednotlivých podklapitolách je čitateľ oboznámený s problematikou tvorby programu v asembleri a jazyku C s ukážkou možných programovacích techník a optimalizácií pre daný programovací jazyk. Pre názornú ukážku boli vytvorené praktické príklady ako v asembleri, tak aj v jazyku C za pomoci demo verzie  vývojového prostredia Keil a vývojovej dosky ADuC836EB, ADuC836_DIGITAL a ADuC836_ANALOG , ktoré boli vyvinuté na KEMT FEI TU.    

                V závere je stručný úvod do vývojového prostredia Keil, ako aj oboznámenie sa s vývojovými doskami ADuC836EB,  ADuC836_DIGITAL a ADuC836_ANALOG. 

8.1. Vývoj programu pre mikropočítače

Proces vývoja aplikácie pre mikropočítač pozostáva z určitých krokov realizovateľných pomocou príslušných softvérových prostriedkov a PC. Jednotlivé softvérové prostriedky slúžiace na realizáciu potrebných fáz vývoja programu pre mikropočítač sú v súčasnosti integrované do komplexných vývojových prostredí (Keil, IAR, Raisonance , Avocet , Wickenhäeuseriné ).

Vývojové prostredia realizujú základné funkcie nevyhnutné pri vývoji aplikácie, ako je editácia zdrojového kódu, preklad vo zvolenom programovacom jazyku, spojenie viacerých čiastkových zdrojových kódov do celku, prípadne pripojenie iných potrebných súborov nazývaných knižnice. Ďalšou dôležitou funkciou vývojových prostredí je overenie funkcie programu softvérovou simuláciou mikropočítača. Niektoré vývojové prostredia napríklad Keil umožňujú aj generovanie stimulov pre simulátor. Do simulácie je tak možné zahrnúť odozvu mikropočítača na externé signály. Pokročilejšou formou ladenia aplikácie je emulácia pomocou emulátora osadeného mikropočítačom pre ktorý vyvíjame program. Týmto spôsobom je možné priame overenie funkcie programu na mikropočítači. Emulátor je prepojený s vývojovým prostredím a umožňuje sledovať priebehy a hodnoty potrebných parametrov.

V nasledujúcich kapitolách poukážeme na jednotlivé kroky vývoja aplikácií, so zameraním sa na programovacie techniky v jazyku symbolických inštrukcií - asembleri a jazyku C. Názornou pomôckou ku kapitole sú niektoré príklady umiestnené v prílohe, vytvorené vo vývojovom prostredí Keil. V závere sú uvedené pokročilejšie techniky programovania.

 


 

8.2 Postup pri vývoji aplikácie

 

                Vývoj programu pre mikropočítač je možné rozdeliť do niekoľkých etáp. Cieľom je vytvorenie programu, ktorý realizuje pomocou mikropočítača požadovanú funkciu. Skôr ako sa pristúpi k samotnej tvorbe programu je nutná formulácia a analýza úlohy. Bez dôkladnej analýzy možností daného elektronického zapojenia a funkcie zariadenia je len veľmi obtiažne daný program realizovať.

Pri väčšine úloh je program prispôsobovaný pre konkrétne elektronické zapojenie, ktoré si vyžaduje daná aplikácia zariadenia. Preto, tvorba programu nasleduje až po návrhu elektronického zapojenia. V opačnom prípade, ak máme konkrétny algoritmus, prispôsobujeme k nemu čo najvhodnejší elektronický obvod, v našom prípade mikropočítač. Tento prípad nastáva v špeciálnejších úlohách. Vo všeobecnosti je možné postup vývoja programu rozdeliť do nasledujúcich etáp:

 

8.2.1  Softvérové prostriedky pre tvorbu programu

 

                Pri vývoji programu pre mikropočítač je výhodné využiť komplexné vývojové prostredie akým je napríklad vývojové prostredie Keil, ponúkajúc komfortný nástroj disponujúci potrebnými funkciami od editora kódu programu po overenie jeho správnosti formou simulácie. Proces tvorby programu je rozdelený do viacerých po sebe nasledujúcich krokov:

  1. editácia kódu súčasťou vývojového prostredia je editor umožňujúci vytvorenie zdrojového kódu programu v ASEMBLERI alebo C. Vo všeobecnosti je možné využiť ľubovoľný ASCII textový editor, ktorý do textu nepridáva žiadne riadiace a informačné znaky (napríklad Notepad pod Windows-om)

  2. preklad - po editácii kódu sa tento pomocou prekladača preloží do takzvaného relatívneho tvaru predstavujúceho časť programu alebo výsledný program pre mikropočítač. Počas prekladu je prekladačom zároveň otestovaný kód programu na syntaktické chyby. Výsledok prekladu je uložený do súboru protokol o preklade (zvyčajne súbor s príponou *.LST). Relatívny kód (zvyčajne súbor s príponou *.OBJ) ešte nie je vhodný pre umiestnenie do pamäte mikropočítača. Relatívny kód predstavuje tvar programu pri ktorom ešte nepoznáme presné umiestnenie jednotlivých inštrukcií daného programu v pamäti mikropočítača. V prípade kódu programu v jazyku C je tento kompilovaný kompilátorom obsiahnutým v prostredí Keil priamo do relatívneho tvaru (niektoré kompilátory vytvoria zo zdrojového kódu v jazyku C kód v asembleri a následne až tento je preložený do relatívneho tvaru)

  3. spojovanie programu – linkovanie - v prípade, že program je tvorený viacerými modulmi ako sú rôzne podprogramy a knižnice pripojené ku hlavnému programu (modulárna tvorba programu) sú tieto jednotlivé komponenty spojené do celku pomocou linkera. Výsledkom je absolútny program (zvyčajne súbor s príponou *.ABS) , ktorý predstavuje obraz programu ako má byť umiestnený v mikropočítači. Pri spojovaní je generovaný protokol o linkovaní (zvyčajne súbor s príponou *.A51). Absolútny program je možné ďalej využiť na overenie správnosti funkcie pomocou simulátora alebo obvodového emulátora (priamo hardverovo emuluje činnosť programu v mikropočítači)

  4. transformácia do výstupného tvaru po overení správnosti činnosti programu je tento transformovaný do súboru predstavujúceho konečný tvar programu aký sa uloží do pamäte mikropočítača. Rozoznávame niekoľko formátov (Intel HEX, Motorola S, binárny RAW formát) do ktorých je transformovaný výstupný súbor

 

Postup vývoja programu je znázornený aj na nasledujúcom obr. 3.1. Na overenie správnosti činnosti vytváraných programov je v rámci tejto práce využitá demo verzia vývojového prostredia Keil v spojení so simulačnou doskou ADuC836EB prípadne v spojení s ADuC836_ANALOG a ADuC836_DIGITAL, alebo iba simulátor obsiahnutý v danom vývojovom prostredí. Komunikáciu medzi vývojovou doskou a simulátorom je realizovaná pomocou programu – MONITOR, uloženého v externej EPROM pamäti mikropočítača umiestnenej na vývojovej doske ADuC836EB. Navyše existuje možnosť takzvanej „single-pin“ emulácie pomocou externého emulačného rozhrania [19]. Toto rozhranie však nie je na KEMT FEI TU dostupné.

 

 

 

                               

 

 

Obr. 8.1 Postup vývoja programu

 

 

8.2.2 Programovacie techniky

 

Podľa očakávanej zložitosti výsledného programu je programy možné rozdeliť do skupín.

 

 

Následne je možné určiť použitie niektorých z uvedených metód, prípadne je volená metóda podľa konkrétnej problematiky. 

 


8.3   Použitie asembleru

 

                Asembler je najnižším programovacím jazykom najbližším k strojovému kódu mikropočítača. Pri vývoji aplikácie pomocou asembleru je nutné poznať inštrukčnú sadu mikropočítača, čiže  súbor všetkých príkazov, ktoré mikropočítač pozná a taktiež direktívy (príkazy používané na riadenie prekladu programu, inicializáciu a rezerváciu pamäte a podobne). Ďalšou podmienkou je znalosť architektúry mikropočítača, keďže sa pracuje s príkazmi na najnižšej úrovni potrebujeme poznať bloky mikropočítača s ktorými používané príkazy manipulujú.

 

8.3.1     Základné konštrukčné prvky asembleru

 

                Medzi základné konštrukčné prvky asembleru patria:

 

MOV R0, #5                 ; použitím znaku # je indikované, že sa jedná o priamu

                                       ; číselnú hodnotu 5               

SLUCKA:     DJNZ R0, SLUCKA

 

do registra R0 je zapísaná hodnota 5, nasleduje návestie SLUCKA na ktoré sa vykoná skok za pomoci inštrukcie DJNZ, ktorá dekrementuje register R0 a vykonáva skok na dané návestie pokiaľ nie je obsah registra rovný nule

 

MOV      A, #100 ; do A je uložená  hodnota 100

 

prekladač akceptuje iba časť pre bodkočiarkou, text za ňou je komentár       

 

8.3.2     Konštrukcia programu v asembleri

 

                Program vytváraný v asembleri je možné rozčleniť do nasledujúcich častí:

 

Program ako celok je doplnený okrem príkazov, direktív a podobne aj o vhodný komentár slúžiaci na zvýšenie prehľadnosti programu ako celku. Praktický príklad programu ukazujúceho základnú konštrukciu programu v asembleri je príklad č.11  v sekcii príklady.  

 

8.3.3   Techniky optimalizácie programu v asembleri

 

                Jednou z možností optimalizácie programovania v asembleri je možnosť optimalizácie kódu na rýchlosť a na veľkosť.

 

V neposlednej rade je samozrejme vhodné zvážiť viacero možností realizácie danej časti algoritmu a vybrať to najvhodnejšie riešenie. Názorné príklady pre oba prípady spôsobov  optimalizácie sú uvedené v príklade č. 10 v sekcii príklady.

 

8.3.4       Vyhodnotenie kladov a záporov asembleru

 

                Asembler disponuje možnosťou optimálneho využitia prostriedkov mikropočítača, čiže programy sú rýchle a nenáročné na pamäť. Pri realizácii rozsiahlejších projektov je výhodné použiť modulárny systém návrhu programu, čiže rozsiahlejší program sa skladá z menších modulov. Pri realizácii programu pomocou asembleru je nutné poznať inštrukčnú sadu mikropočítača spolu s jeho vnútornou štruktúrou a stavbou, keďže pri asembleri pracujeme na najnižšej úrovni, najbližšej strojovému kódu mikropočítača.

                Nevýhodou je pri zložitých projektoch udržanie prehľadnosti kódu programu, zachovanie optimálnosti kódu, čiže rýchlosti a nízkej pamäťovej náročnosti. V tomto prípade je výhodou požitie niektorého z vyšších programovacích jazykov umožňujúcich zachovanie prehľadnej štruktúry programu a pri použití kvalitného prekladača aj dostatočnej rýchlosti a nízkej pamäťovej náročnosti programového kódu.

 


 

8.4        Použitie jazyka C – vyššieho programovacieho jazyka

 

                Táto kapitola slúži na priblíženie jazyka C ako vyššieho programovacieho jazyka poskytujúceho efektívnejšie možnosti pri tvorbe programov pre mikropočítače. V jednotlivých podkapitolách  sú uvedené postupne základné stavebné prvky a štruktúra programu v jazyku C. Taktiež sú spomenuté možnosti optimalizácie jazyka C vzhľadom k špecifickým vlastnostiam jazyka C použitého vo vývojovom prostredí Keil, ktoré patrí v súčasnosti medzi svetovú špičku pre mikropočítače založené na architektúre 8051. Súbežne sú využité pri vysvetľovaní vlastností jazyka C aj praktické príklady vytvorené vo vývojovom prostredí Keil určené ako pre simulátor, tak aj pre vývojovú dosku ADuC836EB.

Vzhľadom na to, že ohľadom jazyka C ako takého bolo vydaných mnoho publikácií a táto práca je orientovaná na uvedenie základných programovacích techník nebude sa jazyk C brať podrobne do hĺbky, bližšie sú rozobraté iba najdôležitejšie oblasti spolu s vysvetlením niektorých problémov na príkladoch.

Jazyk C pre mikropočítače je od klasického ANSI C v určitých smeroch odlišný. Dôvodom sú pomerne obmedzené systémové  zdroje mikropočítačov. Vzhľadom k týmto obmedzeniam jazyk C disponuje rozšíreniami umožňujúcimi eliminovať nedostatky vznikajúce na základe týchto obmedzení. Výsledkom je použitie rozšírení jazyka C pre mikropočítače týkajúcich sa hlavne:

8.4.1   Základné konštrukčné prvky jazyka C

 

                Pri tvorbe programu v jazyku C sú využité nasledovné prvky.

 

8.4.1.1      Premenné a dátové typy jazyka C

 

                Úlohou premenných je uloženie dát v pamäti.  Dáta sú použité pri realizácii výpočtov, vstupov, výstupov a pri riadení priebehu programu.

V závislosti od potreby použitia premennej určitej veľkosti je možné určiť jej typ, od ktorého závisí veľkosť pamäťového miesta, ktoré daná premenná obsadí a rovnako aj typ a rozsah čísla reprezentovaný danou premennou. V  tab. 8.1 sú uvedené používané typy premenných spolu s rozsahom čísla ktoré môžu reprezentovať.

 

 

                                            

                                                * - vývojové prostredie Keil nepodporuje tento číselný formát

 

Tab. 8.1: Typy premenných jazyka C

 

   

Typ premennej a tým aj veľkosť alokovanej pamäte je vhodné voliť podľa potrieb, čím je ušetrené pamäťové miesto.

Jednotlivé premenné môžu meniť svoj typ na základe určitého mechanizmu. Pri zmene menšieho typu na väčší dochádza ku zmene bez straty dát (napríklad char 8-bitové číslo na int 16-bitové číslo), pri zmene z väčšieho typy na menší sa dáta strácajú a platia nasledovné pravidlá:

 

                                                                                 

 

Obr. 8.2: Mechanizmus zmeny typu premenných

 

Napríklad číslo float 10,59 vyjadrené v pohyblivej rádovej čiarke s dĺžkou 4 bajty je konvertované na číslo 10, int  s rozsahom 2 bajty.

                U premennej okrem jej typu je rozlišovaný aj rozsah platnosti a doba platnosti. Pri rozsahu platnosti je určujúce v akej časti programu je premenná viditeľná, čo závisí od miesta v programe, kde je premenná deklarovaná. Pod pojmom životnosť sa rozumie časový interval počas výkonu programu, kedy existuje daná premenná. Vhodnou voľbou rozsahu platnosti a doby platnosti u premenných je určený iba ich nevyhnutný čas existencie, respektíve platnosti, čím sa efektívnejšie využíva často obmedzená pamäťová kapacita.

 

Príklad:

int a;

void func_interrupt(void) interrupt n{

      volatile int f;

      ...;

      }

void func_div(void){

                      char b

                      static int c

                      …..

}

void main(void){

      extern int d;

      register char e;

}

 

 

                Dátové typu v jazyku C pre mikropočítače sú identické ako v ANSI C,  preto je ich opis uvedený iba v krátkosti.

Jazyk C umožňuje organizáciu jednotlivých typov premenných do zložitejších dátových skupín obsahujúcich viacero premenných rovnakého alebo odlišného typu. Rozoznávame :

 

                Štruktúry, uniony a polia môžu byť sebe navzájom prvkami, čiže napríklad pole môže obsahovať prvky typu štruktúra, ktorej prvky sú uniony. Navyše existuje možnosť definovania si vlastného dátového typu.

 

Príklad:

struct osoba{

      unsigned char    vek;

      unsigned char    den_narodenia;

      unsigned char    mesiac_narodenia;

      unsigned int        rok_narodenia;

      }  

 

8.4.1.2    Operátory

Rozoznávame viacero typov operátorov.

 

8.4.1.3     Smerníky (pointers)

 

Smerník je premenná, ktorej hodnotou je adresa inej premennej ľubovoľného typu, čiže char, int, long, float, ale aj štruktúry, poľa alebo funkcie. V prípade jazyka C použitého vo vývojovom prostredí Keil závisí veľkosť smerníka od typu použitej premennej na ktorú smerník ukazuje.

Smerník je možné s výhodou použiť napríklad pri jednoduchom a efektívnom odovzdávaní parametrov medzi funkciami, keď sa odovzdáva iba adresa premennej namiesto jej celej hodnoty, ktorej veľkosť môže byť v porovnaní s veľkosťou smerníka omnoho väčšia.

Pri práci so smerníkmi sú použité štandardné aritmetické operácie, čiže adresa obsiahnutá v smerníku môže byť dekrementovaná, inkrementovaná a podobne.

Smerník je označovaný znakom ` * `, napríklad smerník na premennú x označujeme ako *x.

 

                int *x;                     // je smerník na premennú  typu int

                int   y;                    // definovanie premennej typu int

                x=&y;                      // priradenie adresy premennej y smerníku x

 

8.4.1.4    Príkazy na riadenie toku programu

 

               Spôsob zápisu príkazu v jazyku C je nasledovný:

            príkaz ;

čiže skladá sa z výrazu za ktorým nasleduje bodkočiarka. V prípade potreby je možné viacero príkazov združiť do bloku vymedzeného zloženými zátvorkami ako je to v nasledujúcej sekvencii:

 

                {

                príkaz1;

                príkaz2;

                 príkaz3;

                }

 

výsledkom tohto zápisu je že daný blok pôsobí ako jediný príkaz. V bloku môže byť vnorený iný blok obsahujúci taktiež vnorené bloky. Vytváranie takýchto blokov má zmysel napríklad pri použití v spojitosti s príkazmi na vytváranie cyklov a rozhodovacích príkazov. Pre tvorbu cyklov sú použité príkazy for, while a do-while a na tvorbu rozhodovacích štruktúr príkazy if-elseswitch-case. Na prerušenie cyklu slúži príkaz break a na znovuspustenie slučky v prípade while alebo do-while cyklu je použitý príkaz continue.

 

8.4.1.5     Direktívy

 

Direktíva je špeciálnym príkazom jazyka C. Slúži pri preklade programu na pripojenie externých súborov, pri optimalizáciách vykonávaných kompilátorom, pri podmienenom preklade a podobne. Direktíva je označovaná symbolom ` # ` umiestneným na začiatku výrazu. Príkladom je nasledujúca sekvencia obsahujúca direktívu include pomocou ktorej je k programu pripojený súbor s knižničnými funkciami použitými v programe a direktíva define slúži na priradenie symbolickej hodnoty číselnej hodnote, čím je možné sprehľadniť zdrojový kód.

 

#include <stdio.h>                             // pripojenie štandardnej systémovej knižnice stdio.h ku  zdrojovému kódu programu

                #define MAX 100                               // defícia symbolickej  konštanty MAX s hodnotou 100

   

            #include ”moje_definicie.h            /* pripojenie hlavičkového súboru definovaného programátorom ku zdrojovému kódu programu */

 

 

Direktíva #PRAGMA je riadiacou direktívou pomocou ktorej je indikované použitie niektorej z direktív ovplyvňujúcich kompiláciu programu. Príslušná direktíva (spolu s direktívou PRAGMA) je použitá iba raz na začiatku kódu programu, niektoré direktívy je možné použiť v kóde programu viackrát. Príkladom použitia direktívy PRAGMA je nasledujúci kód:

 

main(void){

                               funkcia_1( );

                               x = a – b;

#PRAGMA ASM                  /* označenie začiatku kódu v asembleri, ktorý je možné vložiť do zdrojového kódu v jazyku C */

JMP $                                    ; kód v asembleri

#PRAGMA ENDASM           // koniec kódu v asembleri

}  

Rôzne vývojové prostredia obsahujúce prekladačom jazyka C môžu obsahovať rôzne typy a počet direktív.

 

8.4.1.6   Knižničné funkcie

 

Sú súhrnom funkcií jazyka C podporovaných daným kompilátorom. Knižnice obsahujú štandardné funkcie pre určité operácie, čím sa zvyšuje prenositeľnosť kódov vytvorených vo vývojových prostrediach obsahujúcich vlastný kompilátor jazyka C.

Ako pri direktívach, tak aj pri knižničných funkciách môže nastať odlišnosť v obsiahnutých funkciách oproti ANSI C. Niektoré nemusia byť vôbec podporované, respektíve sú doplnené o nové, špeciálne určené pre ten daný mikropočítač využívajúc jeho špecifické možnosti. Napríklad knižnica stdio.h obsahuje základné funkcie pre vstup a výstup (ktoré sú obvykle zviazané so sériovou linkou), alebo math.h obsahuje matematické funkcie (sin, cos, tg a iné).

 

8.4.2     Konštrukcia programu v jazyku C

 

              Základom konštrukcie programu  v jazyku C je funkcia tvorená príkazmi, alebo inými funkciami uzavretými do bloku. Program je tvorený jednoznačne určenými blokmi, súborom funkcií s ktorých jedna musí byť označená ako main (hlavná funkcia od ktorej sa začína výkon programu). K hlavnému programu je pri použití vývojovej dosky ADuC836EB nevyhnutné pripojenie STARTUP súboru. Spúšťa sa bezprostredne po resete a slúži na vykonanie počiatočných inicializácii mikropočítača skôr ako sa riadenie systému odovzdá main funkcii. 

Funkcia je vytváraná pomocou udania svojho názvu za ktorým nasleduje v okrúhlych zátvorkách súbor vstupných parametrov a následne v zložených  zátvorkách definícia funkcie zložená z príkazov, alebo iných funkcií. Názov funkcie nesmie byť zhodný so žiadnym z rezervovaných výrazov. Príkladom je nasledovná sekvencia:

 

                typ názov_funkcie( vstupné premenné ){

            príkaz1;

                            príkaz2;

                        }

 

Funkcie v programe realizujú potrebné úlohy, spracovávajú dáta, premenné a navzájom medzi sebou komunikujú. V prípade hlavnej funkcie main sú v tejto funkcii umiestnené okrem príkazov aj volania iných funkcií. Konštrukcia programu je:

   

                void main(void){                                // označenie hlavnej funkcie

                                 príkaz1;

                            for(…){…;

                                   príkaz2;

                                   }

                    funkcia_1( );                                // volanie funkcie

               funkcia_2(a,b);                               // volanie funkcie s odovzdaním parametrov a, b

                }

 

V jazyku C sú známe dva typy funkcií, funkcia vracajúca hodnotu a funkcia nevracajúca hodnotu. Funkcia označená ako void nevracia, inej funkcii po svojom ukončení žiadnu návratovú hodnotu. V prípade, že funkcia vracia napríklad hodnotu typu int (označenie typu návratovej hodnoty je pred názvom funkcie) a vstupné hodnoty sú dve premenné typu int, zápis je nasledovný:

 

            int  funkcia_porovnaj(int x, int y){

                  if(x>y) return x;

                                             return y;        

                            }

 

Funkcia porovnáva vstupné premenné x, y a vracia väčšiu z nich príkazom return. Príkladom ukážky odovzdávania si parametrov medzi funkciami je príklad č. 3 v sekcii príklady.

 

8.4.3    Techniky optimalizácie programu v jazyku C pre architektúru Intel 8051

 

               Táto podkapitola je venovaná technikám vylepšenia efektivity programu v jazyku C pre prekladač už spomenutého vývojového prostredia Keil. Pod pojmom zvyšovanie efektivity je  myslené zmenšovanie veľkosti kódu a zvyšovanie jeho rýchlosti. V ďalšej časti sú uvedené spôsoby akými je možné optimalizáciu kódu v jazyku C vykonať.

 

8.4.3.1    Optimalizácia premenných

 

8.4.3.1.1   Voľba vhodného dátového typu premennej  

               Vhodnou voľbou dátového typu je možné lepšie využitie značne obmedzeného pamäťového priestoru ktorým mikropočítač disponuje. Napríklad na vyjadrenie príznakov indikujúcich nejaký stav (zapnuté = 1, vypnuté = 0 a podobne) operácií vykonávaných mikropočítačom je zbytočné využiť celú 8-bitovú pamäťovú bunku pre každý z príznakov zvlášť, čiže je využitá bitová premenná v bitovo adresovateľnej časti pamäte mikropočítača.

Vo všeobecnosti v závislosti od rozsahu premennej s ktorou sa pracuje je pre ňu volený vhodný dátový typ (bit, char, int, long a podobne), čiže najmenší postačujúci. Taktiež je snahou podľa možností použitie unsigned -  bezznamienkového typu, ktorý je priamo podporovaný mikropočítačom, čo je dané jeho inštrukčnou sadou a architektúrou.  Jednotlivé dátové typy sú uvedené v tab. 8.2.

 

                                       

                                        * - dátové typy bit, sbit a sfr  nie sú definované štandardom ANSI C 

 

Tab. 8.2: Dátové typy použité pre 8051

 

8.4.3.1.2   Používanie bezznamienkových premenných

            V prípade realizácie logických operácií (AND, OR, XOR, bitový posun a podobne) môže dôjsť k problémom pri použití znamienkových premenných vzhľadom na využitie MSB (Most Significant Bit) ako znamienkového, preto je nutné v rámci možností používané premenné definovať ako bezznamienkové použitím označenia unsigned.

  

   Pred optimalizáciou:

 

int a (int input) {

    int bitfield;
    bitfield = status | 0x8000;
    /* touto operáciu nastavíme znamienkový
 bit premennej bitfield označenej ako int */
     return (bitfield);

}

 

 

    Po optimalizácii:

 

int a (void) {

    unsigned int bitfield;
    bitfield = status | 0x8000;
    /* zadaním typu unsigned premennej int bitfield zabránime vytvoreniu 
záporného čísla, pretože unsigned int nadobúda iba kladné hodnoty  */
     return (bitfield);

}

 

     

8.4.3.1.3   Konverzia premenných

V prípade porovnávania premenných, ak je to možné, volia sa pre premenné rovnaké dátové typy. Kompilátor v prípade rozdielnych dátových typov premenných doplňuje kód programu o sekvenciu realizujúcu konvertovanie jedného z odlišných typov premennej na rovnaký dátový typ.

 

   Pred optimalizáciou:

 

int a (int var1, char var2) {
      /* kompilátor musí realizovať konverziu typu 
premennej var2 na int  */
    if (var1 > var2) return (1)
    else return (0);
} 

 

 

   Po optimalizácii:

 

int a (int var1, int var2) {
    /* použitím rovnakého typu premenných nie je nutná 
konverzia typov */
    if (var1 > var2) return (1)
    else return (0);
} 

 

 

 
 
 
 
 
 
 

 

8.4.3.1.4   Rozsah platnosti premenných a ich umiestnenie

            Premenné, ktoré sú často menené v tele slučiek je vhodné označiť ako register. Kompilátor tak indikuje možnosť vykonať optimalizáciu s tou danou premennou. Ako sa optimalizácia vykoná, závisí od konkrétneho kompilátora. Takto označené premenné sú umiestnené do registrov mikropočítača s minimálnou dobou prístupu.

 Ak sú premenné využívané iba v tele niektorej z funkcií je vhodné ich definovať ako lokálne, čiže obmedziť ich dobu platnosti. Premenné sú tak používané iba počas nevyhnutnej doby výkonu funkcie a nezaberajú trvalo pamäťové miesto počas celého behu programu. Po opustení funkcie je nimi zabraté pamäťové miesto opäť k dispozícii. 

 

   Pred optimalizáciou:

 
    int i;
    int checksum;
....
int a (void) {
    for (i=1; i<500; i++) {
        checksum = checksum ^ getChar();
        }
    return (checksum);
   } 

 

 

   Po optimalizácii:

 

int a (void) {
    int i;
    register int checksum;
    for (i=1; i<500; i++) {
       checksum = checksum ^ getChar();
        }
    return (checksum);
   } 

 

 
 
 
 
 
 
 
 
 
 
 

 

8.4.3.1.5   Globálne premenné

            Globálne premenné pokiaľ je to možné, je vhodné označiť ako static. Takto označená premenná si zachováva svoju hodnotu aj po opustení funkcie. Ďalším efektom je obmedzenie viditeľnosti premennej iba na daný programový modul, iné moduly k nej nemajú prístup, čím dochádza k takzvanému zapuzdreniu, ochrane premennej pred prístupom zvonku. Kompilátor je navyše informovaný o rozsahu platnosti premennej a môže previesť optimalizáciu s následným vhodným umiestnením premennej do pamäte.

 

8.4.3.1.6   Definovanie konštánt

            Pri použití konštánt je vhodné definovať ich ako makro, ktoré sa použije v kóde programu. Na nasledujúcom príklade je využité definovanie konštánt, ktoré sú v druhom prípade po optimalizácii spracované preprocesorom ešte počas kompilácie kódu a nie počas behu programu umiestnené v pamäti RAM ako je to v prvom prípade a navyše sa zvyšuje čitateľnosť kódu. 

 

Pred optimalizáciou:

 

void a (void) {
    unsigned int waittimebase = 50;
    unsigned int waittimeextra = 10;
    /*nasledujúci kód sa vygeneruje ako súčet dvoch 
premenných priradený premennej delay  */
    delay(waittimebase + waittimeextra);
} 

 

 

   Po optimalizácii:  

 

#define WAITTIME_BASE 50
#define WAITTIME_EXTRA 50
void  a (void) {
    /* hodnota delay je vypočítaná ešte počas kompilácie programu, v prípade,
 že boli použité konštanty definované ako makro  */
       delay(WAITTIME_BASE +     WAITTIME_EXTRA);
} 

 

 

 
 
 
 
 
 
 
 
 
 

8.4.3.1.7   Uloženie dát do dočasnej premennej

            Pre premenné na ktoré sa odvoláva pomocou ich smerníka je vhodné uložiť do lokálnej - dočasnej premennej. Princíp je zrejmý z nasledujúceho príkladu:

 

Pred optimalizáciou:

 

int a (struct time_s *time) {
    if (time->hour >= 12)
       return (time->hour - 12);
    else
       return (time->hour);
}

 

 

Po optimalizácii:

 

int a (struct time_s *time) {
    unsigned int hour;
    hour = time->hour;
    if (hour >= 12)
        return (hour - 12);
    else
        return (hour);
}

 

 

 

 

 

 

 

 

 

 

 

Pred optimalizáciou takto zapísaný kód realizoval dvakrát výpočet adresy premennej a jej načítanie z vypočítaného pamäťového miesta. Po optimalizácii, uložením premennej do dočasnej lokálnej premennej je nutné adresu vypočítať iba raz a premennú umiestniť do danej lokálnej premennej.

 

8.4.3.1.8   Použitie štruktúr

            Pri použití štruktúr je vhodné umiestniť jej prvky zostupne od najväčšieho po najmenší. Premenné sú tak optimálne umiestnené v pamäti a zaberajú iba nevyhnutné miesto na ich uloženie. Príklad je nasledovný:

 

   Pred optimalizáciou:

 

struct time_s {
    char hour;
    char minute;
    char sec;
    long x;
    float y;
    char month;
    int year; 
};

 

 

   Po optimalizácii:

 

struct time_s {
    long x;
    float y     ;
    int year;
    char hour;
    char minute;
    char sec;
    char month;
};

 

 
 
 
 
 
 
 
 
 
 
 
 

8.4.3.1.9   Voľba typu pamäťovej oblasti

            Daný prekladač disponuje možnosťou voľby typu pamäťovej oblasti pre jednotlivé premenné. Premenné je možné podľa potrieb umiestniť do danej pamäťovej oblasti (externá RAM, interná RAM a podobne). Jednotlivé typy pamäťových oblastí sú uvedené v tab. 8.3.

 

                                                     

   

Tab. 8.3: Typy pamäťových oblastí

 

Grafické znázornenie jednotlivých pamäťových oblastí je na obr. 8.3.

 

   

                                           

 

Obr. 8.3: Zobrazenie pamäťových oblastí

 

Ku každému typy pamäťovej oblasti je prístup realizovaný iným mechanizmom. Spôsob prístupu realizuje kompilátor automaticky. Vhodnou voľbou umiestnenia premennej do niektorej z pamäťových oblastí, dosiahneme rozdielne doby prístupu k dátam vzhľadom na rozdielne doby prístupu k rôznym typom pamäťových oblastí. Príkladom je umiestnenie často používaných premenný do internej RAM pamäte disponujúcou najkratšou dobou prístupu.

Bezprostredne s voľbou typu pamäťovej oblasti súvisí voľba pamäťového modelu pre použitie typu pamäte pre dané premenné. Odpadá tak nutnosť deklarácie typu pamäte pre danú premennú, uvedením pamäťového modelu sú premenné automaticky presunuté do určitej pamäťovej oblasti. Rozoznávame pamäťové modely small, compactlarge.

8.4.3.1.10    Smerníky

            Oproti ANSI C sú definované dva typy smerníkov:

Pre vyhodnotenie  rozdielu medzi použitím všeobecného a špecifikovaného smerníka slúži nasledujúca tab. 8.4 porovnávajúca podobné sekvencie programového kódu pri použití rôznych typov smerníkov.

 

                                   

 

  Tab. 8.4: Porovnanie typov smerníkov

 

Je dôležité si uvedomiť kedy úloha vyžaduje použitie daného typu smerníka. Vhodnou voľbou typu smerníka je možné zvýšenie efektivity programového kódu.

 

8.4.3.2   Funkcie, odovzdávanie parametrov medzi funkciami a funkcie prerušenia

            U funkcií vracajúcich hodnotu je možnosť odovzdávania parametrov rôznymi mechanizmami. Odovzdávanie parametrov cez zásobník je obmedzené vzhľadom na veľkosť internej RAM do ktorej je zásobník alokovaný a navyše takýto spôsob je neefektívny a pomalý. Tento spôsob je štandardný pre ANSI C. Kompilátor vývojového prostredia Keil preto každému parametru funkcie priradí fixné miesto v pamäti do ktorého sú pri volaní funkcie uložené potrebné parametre. Do zásobníka je uložená iba návratová adresa.

            Na zvýšenie efektivity je možné využitie odovzdávania parametrov medzi funkciami cez registre. Tento režim je aktivovaný použitím direktívy REGPARMS pred deklaráciou funkcie. Príklad je nasledovný:

 

            #PRAGMA REGPARMS

            extern int funkcia_x(int, char);

 

     Príslušnou direktívou je aktivované odovzdávanie parametrov cez registre pre externú funkciu funkcia_x vracajúcu premennú typu int. Jednotlivé možnosti ako sú uložené parametre funkcie sú znázornené v tab. 8.5 (Keil C51).

 

   

                               

 

Tab. 8.5: Umiestnenie parametrov funkcie

 

Spôsob odovzdávania parametrov cez registre je zrejmý aj z nasledujúceho príkladu:

 

                        funkcia_1( int a, int b, int *c) {                             /*  parameter a je odovzdávaný cez R6 a R7, */

            .... ;                                         /* parameter b cez R4 a R5 a smerník cez registre  R1 až R3 */                   

                                                    }

 

V prípade, že je funkcii odovzdávaných viac parametrov ako je možné odovzdať cez registre jednej banky, zvyšné parametre sú odovzdávané cez vyhradené miesto v pamäti. Príklad je nasledovný:

 

        funkcia_2 ( float x, float y ) {                            /* parameter x je odovzdaný cez registre R4 až  R7 a parameter y cez miesto vyhradené   v pamäti */

                                                                 … ;

                                                                 }

 

              Pri funkcii vracajúcej hodnotu je návratová hodnota stále umiestnená do registrov mikropočítača. Spôsob umiestnenia je znázornený v tab. 8.6 (Keil C51).

 

                                            

 

Tab. 8.6: Umiestnenie návratovej hodnoty funkcie

 

Spôsob vracania hodnoty funkcie je zrejmý z nasledujúceho príkladu funkcie vracajúcej súčet dvoch kladných 8-bitových čísel. Výsledok súčtu (súčet dvoch 8-bitových čísel je 16-bitové číslo) je umiestnený do registrov R6 a R7.

 

            int funkcia_scitania ( unsigned char a, unsigned char b) {

                                        return ( a + b);

                                        }

 

Ďalšou z možností zvýšenia efektivity kódu je využitie absolútneho adresovania pre registre R0 až R7 aktivované direktívou AREGS. Použitím tohto módu je umožnená priama manipuláciu s registrami (R0 až R7) pomocou inštrukcií PUSH a POP. Príkladom je nasledujúca sekvencia:

 

                extern char func ( );

                    char k;

 

               #PRAGMA NOAREGS

                noaregfunc ( ) {

                k = func ( ) + func ( ) ;

                                }

            #PRAGMA AREGS

              aregfunc ( ) {

                               k = func ( ) + func ( ) ;

                               }

 

Predchádzajúcej sekvencii kódu v jazyky C zodpovedá kód v asembleri:

; začiatok noaregfunc

LCALL func

MOV       A,R7

PUSH     ACC

LCALL func

POP       ACC

ADD        A, R7

MOV       k, A

RET

; koniec noaregfunc

 

;začiatok aregfunc

LCALL func

PUSH     AR7

LCALL   func

POP       ACC

ADD        A, R7

MOV       k, A

RET

; koniec aregfunc

 

Je zrejmé, že pri aregfunc je namiesto MOV A,R7PUSH ACC použité iba PUSH AR7. Dochádza ku skráteniu kódu o jednu inštrukciu, čo v globálnom merítku môže mať značný vplyv na rýchlosť výkonu programu v prípade, že daná sekvencia kódu je použitá v často volanom podprograme a podobne.

V prekladači jazyka C prostredia Keil je všeobecná deklarácia funkcie nasledovná:

 

[typ návrat. hodnoty] názov funkcie ([argumenty])                [{small, compact, large}]

                                                                                                              [reentrant] [interrupt n] [using n]

 

-         typ návratovej hodnoty – typ hodnoty vrátenej funkciou v prípade, že vracia hodnotu, ak nie je určený, kompilátorom je nastavený na int

-         názov  funkcie – jedinečný názov identifikujúci funkciu

-         argumenty – zoznam vstupných premenných danej funkcie

-         small, compact, large – voľba pamäťového modelu pre umiestnenie premenných funkcie

-         reentrant – označenie funkcie, ktorú je možné volať rekurzívne alebo viackrát súčastne napríklad v hlavnom programe aj v podprograme prerušenia

-         interrupt n – takto označená funkcia je funkciou  prerušenia n, funkcia je volaná pri vyvolaní príslušného prerušenia

-         using n – umožňuje špecifikovať registrovú banku pre danú funkciu

 

Príkladom je nasledujúca funkcia func_INT0 (obsluhujúca prerušenie od externého zdroja),

 

            char func_INT0 ( char a, char b) small interrupt 0 using 3{

                          return ( a-b);

                          }

 

vracajúca hodnotu char, ktorá je rozdielom premenných a a b. Funkcia používa pamäťový model small, čiže všetky premenné sú v internej RAM pamäti, čo zabezpečí rýchly výkon funkcie. Funkcia je spustená pri prerušení od externého zdroja INT0 mikropočítača, navyše využíva banku registrov 3, takže pri volaní tejto funkcie nie je nutné uložiť obsah banky registrov 0. Banka 0 registrov, je štandardne využitá pri všetkých ostatných funkciách ktoré neslúžia na obsluhu prerušení. V tab. 8.7 sú uvedené prerušenia mikropočítača ADuC836. V prípade modifikácii sú prerušenia od ďalších zdrojov umiestnené vždy s odstupom 8 bajtov.

 

 

                                                           

 

Tab. 8.7: Prerušenia mikropočítača ADuC836

 

Funkciu označenú ako reentrant môžu súčastne zdieľať viaceré procesy. Po spustení reentrant funkcie je možné jej výkon prerušiť iným procesom, ktorý spustí túto funkciu nanovo. To je možné opakovať viackrát po sebe, čiže reentrant funkciu je možné simultánne volať dvoma a viacerými procesmi naraz. Príklad takejto funkcie je nasledovný:

 

            int reentrant_func ( char x, char y ) reentrant {

                            return ( x * y );

                            }

 

Funkcia násobí dve 8-bitové čísla x a y a vracia ich 16-bitový súčin.

Vlastnosť simultánneho volania funkcie viacerými procesmi má význam v spojitosti s RTOS (Real Time Operating System) – operačným systémom reálneho času zabezpečujúcim simultánny beh viacerých aplikácii na mikropočítači. Príklad zdieľania reentrant funkcie viacerými procesmi je nasledovný:

 

            int reentrant_func ( char x, char y ) reentrant             /* definovanie reentrantnej funkcie */

            return ( x * y );     

            }

            int func_1 (int ) interrupt n {                                          

                                           … ;

                                           t=reentrant_func ( a, b );

                                           … ;

                            }

            int func_2 (int) interrupt n+2 {

                                           … ;

                                           w=reentrant_func ( c, d );

                                           … ;

                                           }

            void main (void){

                                           …;

                                           while(1){

                                                       …;

                                                         z=reentrant_func (e, f );

                                                      …;

                                           }

            }             

 

Reentrantná funkcia reentrant_func je volaná z viacerých miest programu. Môže nastať prípad že počas jej volania v main funkcii, je vyvolané prerušenie od zdroja n a bezprostredne aj od zdroja n+2. Dôjde tak k jej viacnásobnému spusteniu a prerušeniu viacerými procesmi súčastne. Pre kompilátor to znamená zabezpečiť rezerváciu pamäťového miesta v dátovej pamäti pre vytvorenie reentrantného zásobníka do ktorého sú ukladanú dáta a parametre reentrantnej funkcie pri jej viacnásobnom spustení. Funkcia, ktorá nie je reentrantná má pridelený statický segment pamäte a pri svojom ďalšom spustený premaže parametre a dáta v ňom uložené pri svojom predchádzajúcom spustení.

 

8.4.3.2              Iné optimalizácie funkcie

 

8.4.3.2.1   Použitie statických funkcií

            Funkcie používané iba vo vnútri programového bloku definujeme ako static. Takto označená funkcia je kompilátorom optimalizovaná. Dosiahneme vylepšenie rýchlosti kódu vzhľadom na, že kompilátor má možnosť voľby inštrukcie volania podprogramu, ktoré sú v prípade architektúry 8051 dve a to 2-bajtová ACALL a 3-bajtová LCALL.

 

Príklad:

            static int func_b ( char x, char y) {

                            return ( x * y );

                             }

   

8.4.3.2.2   Použitie definovania argumentov funkcií ako const

            Pomocou označenia argumentu funkcie ako const je indikované kompilátoru, že argument sa počas výkonu programu nemení a kompilátor má možnosť vykonať jeho optimalizáciu. Taktiež pri nedovolenej zmene argumentu dochádza k chybovému hláseniu.

 

Príklad:

            int func_a ( const char x ) {

            ... ;

            }

   

8.4.3.2.3   Odstránenie všeobecného kódu z rozhodovacích štruktúr

            Pri použití rozhodovacích štruktúr (if-else, switch-case) je vhodné odstrániť z rozhodovacích ciest všetok všeobecný kód, čím sa skráti čas výkonu kódu. Princíp je uvedený v nasledujúcom príklade:

 

 

   Pred optimalizáciou:

 

int a (int data) {
    int key;
    if (data < 100) {
        data = data + 100;
        key = c (data);
        data = data % key;
    }
    else {
        key = c (data);
        data = data % key;
    }
    return (data);
}

 

 

   Po optimalizácii:

 int a (int data) {
    int key;
    if (data < 100) {
        data = data + 100;
    }
    key = c (data);
    data = data % key;
    return (data);
}

 


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Z rozhodovacích ciest bol odstránený prebytočný kód, ktorý je umiestnený na miesto, kde to má zmysel. 

8.4.3.2.4   Použitie dekrementovania namiesto inkrementovania

             V iteračných slučkách, ktoré sa vykonávajú určitý počet krát je vhodnejšie namiesto operácie inkrementovania

 od nuly po určitú hodnotu použiť dekrementovanie do nuly. Dôvodom je existencia inštrukcie v prípade mikropočítača 

8051 priamo podporujúca túto operáciu a to DJNZ (Decrement and Jump If not Zero – dekrementuj a skoč ak nie je nula), 

takže realizácia tejto operácie je jednoduchšia oproti realizácii inkrementovania, čo prospieva k urýchleniu a zjednodušeniu 

výsledného kódu.

Príklad:

            func_delay ( unsigned int delay ) {

                            unsigned int i ;

                            for ( i = delay ; i > 0 ; i -- ) {

            … ;

             }

            }

 

8.4.3.2.5   Umiestnenie nepotrebných výrazov mimo slučku

            Do tela slučky sú umiestnené výlučne výrazy súvisiace s danou slučkou. Použitím nepotrebných výrazov v slučke dochádza ku zbytočnému predĺženiu doby výkonu slučky. Postup je uvedený v nasledujúcom príklade.

 

 

     Pred optimalizáciou:

 

int a (int data1, int data2) {
    unsigned int x;
    int temp = 100000;
    for (x = 10; x>=0; x--) {
        temp = temp / (data1 * data2);
    }
    return (temp);
}

 

 

 

   Po optimalizácii:
 
 
int a (int data1, int data2) {
    unsigned int x;
    int temp = 100000;
    int temp2;
    temp2 = data1 * data2;
    for (x = 10; x>=0; x--) {
        temp = temp / temp2;
    }
    return (temp);
}

 

 

 

 

 

 

 

 

 

 

 

 

 

8.4.3.3      Optimalizácie vykonávané kompilátorom

 

            Optimalizáciu kódu je možné realizovať na základe vhodného použitia buď typu premenných, pamäťového modelu a podobne. Navyše kompilátor jazyka C použitý vo vývojovom prostredí Keil vykonáva počas kompilácie programu určité všeobecné optimalizácie za účelom zmenšiť a urýchliť kód na maximálnu možnú mieru. Príkladom je prekrývanie pamäťového miesta pre parametre dvoch funkcií, ktoré nie sú použité v rovnaký čas, čím dosiahneme lepšie využitie pamäťového priestoru. Kompiláciu je možné do určitej miery ovládať použitím príslušných direktív (Keil C51 strana 21).

Základné optimalizácie, ktoré vykonáva kompilátor sú:

8.4.3.4      STARTUP súbor

            Niektoré optimalizácie je možné zabezpečiť pomocou nastavení v súbore "Startup.A51". Tento súbor sa využíva aj na počiatočnú inicializáciu mikropočítača pri ladení aplikácie na vývojovej doske. Súbor startup je spustený ešte pred spustením ladeného programu a až následne sa riadenie systému odovzdá main funkcii ladeného programu. Typickými operáciami sú inicializácia hardverového zásobníka, inicializácia zásobníka a ukazateľa zásobníka niektorého s pamäťových modelov (small, compact, large), nulovanie obsahu internej/externej/stránkovanej externej dátovej pamäte, inicializácie súvisiace s použitím stránkovania pamäte prípadne iné nastavenia potrebné v závislosti od konkrétneho použitého mikropočítača. Príkladom je súbor startup.a51, ktorý je použitý v príkladoch určených pre ukážku použitia vývojovej dosky ADuC836EB. V súbore je obsiahnuté napríklad nastavenie taktovacej frekvencie mikropočítača a konfigurácia sériovej linky potrebná pre realizáciu komunikácie vývojovej dosky a prostredia Keil. Za týmito nastaveniami napríklad nasleduje inicializácia veľkostí a umiestnenia určitých typov pamätí.

 

8.4.4   Vyhodnotenie kladov a záporov jazyka C

 

            Programovací jazyk C prináša nové možnosti do tvorby aplikácie pre mikropočítačové systémy. Odpadá dokonalá znalosť inštrukčnej sady mikropočítača. Dobrá znalosť architektúry nie je nevyhnutnou, no aj pri použití jazyka C umožňuje lepšie zvládnutie systémových prostriedkov mikropočítača.

Pri tvorbe zložitejších projektov prehľadnosť kódu v jazyku C umožňuje ľahšiu orientáciu v programe, pri prípadnom hľadaní chýb alebo pri potrebe pozmeniť časť kódu. Knižničné funkcie v závislosti od použitého vývojového prostredia s prekladačom jazyka C sú optimálne vytvorené pre konkrétnu aplikáciu a aj menej skúsený programátor dokáže vytvoriť relatívne dobrý program.

Nevýhodou použitia jazyka C je v závislosti od použitého prekladača veľkosť výsledného kódu a efektivita kódu oproti kódu vytvoreného pomocou asembleru. Moderné prekladače však disponujú vlastnosťami, ktoré umožňujú pri rozsiahlejších projektoch vytvoriť kód blízky efektivite kódu vytvoreného priamo v asembleri.    

 


 

8.5     Pokročilejšie programovacie techniky

 

            Optimalizácie realizovaného programu realizuje použitý kompilátor jazyka C alebo programátor použitím vhodného programovacieho štýlu a zásad pri programovaní v jazyku C. Existujú však aj iné možnosti zvýšenia efektivity kódu, ktoré sú následne uvedené.

 

8.5.1    Použitie kombinácie programovacieho jazyka C s asemblerom

 

            V predchádzajúcich kapitolách boli spomenuté výhody aj nevýhody asembleru a jazyka C. Alternatívnym riešením je spojenie výhod oboch jazykov do jedného celku. Princíp je založený na vytvorení nosnej štruktúry programu v jazyku C kvôli jeho prehľadnosti a jednoduchosti. Kritické miesta v programe akými sú napríklad často volané podprogramy vytvoríme v asembleri, ktorý sa vyznačuje optimálnosťou a rýchlosťou kódu.

Kompilátor jazyka C vývojového prostredia Keil disponuje možnosťou vytvorenia štruktúry kombinujúcej kód v jazyku C s kódom v JSI. Názorná ukážka možnej konštrukcie programu s využitím jazyka C a asebleru je uvedená v príklade č. 7 v sekcii príklady.

 

8.5.2  Využitie RTOS

 

            RTOS (Real Time Operating System) – operačný systém reálneho času je jednou z progresívnych možností tvorby aplikácie pre mikropočítače. RTOS má principiálne podobnú funkciu ako je známa vo sfére operačných systémov pre PC (Windows, Linux), čiže sprostredkováva využitie systémových zdrojov rôznym aplikáciám a umožňuje vytvorenie samostatných aplikácii na danom mikropočítačovom systéme.

Využitie RTOS v oblasti mikropočítačov znamená zjednodušenie vývoja aplikácii pre mikropočítače.

Princíp použitia RTOS je vysvetlený na nasledujúcom príklade:

Nech aplikácia s mikropočítačom realizuje meranie napätia teplotného senzora pomocou ADC prevodníka, prepočet napätia na teplotu, výpis teploty na LCD displej, obsluhu klávesnice a komunikáciu s PC pomocou sériovej linky. Mikropočítač odmeria napätie, spracuje ho a vypíše na displej vypočítanú teplotu, pomocou klávesnice je možné riadiť prepínanie merania z viacerých teplotných senzorov a podobne. Tieto uvedené funkcie je možné realizovať ako komplexný program vykonávajúci všetky potrebné operácie. Nutnosťou je jeho vhodné odladenie kvôli správnemu vykonávaniu jednotlivých potrebných operácií ako je odovzdávanie dát z ADC, ich spracovanie a vyslanie na LCD displej. V prípade zložitejšej aplikácie ako uvedenej v príklade vhodné zladenie jednotlivých funkcií nie je  jednoduchou záležitosťou.

Použitím RTOS odpadá nutnosť realizácie tejto úlohy ako jediného komplexného programu. Každú z uvedených úloh (meranie pomocou ADC, zobrazovanie dát na LCD a ostatné) je možné rozdeliť do samostatných programových modulov vykonávajúcich vyhradenú špecifickú úlohu, pričom dané moduly spracovávajú iba vlastné dáta. Napríklad v prípade merania ADC prevodníkom to znamená, že tento má vyhradené určité pamäťové miesto, do ktorého je ukladaná nameraná hodnota. Medzi týmito programovými modulmi je nutná výmena dát. Túto funkciu zabezpečuje práve RTOS, ktorý okrem iného riadi spravovanie každého programového modulu (nazývaného aj úloha – task) a jeho zdrojov ako je prideľovanie pamäte, priorita úlohy a odovzdávanie dát.

RTOS obsahuje plánovač – scheduler, ktorý úlohám podľa potreby prideľuje určitý systémový čas mikropočítača a tak každá z úloh beží samostatne. V prípade že sa hlási o pridelenie systémového času úloha s vyššou prioritou, momentálne vykonávaná úloha je prerušená. Po skončení úlohy s vyššou prioritou pokračuje prerušená úloha vo svojej činnosti. Prostriedkami RTOS môžu úlohy navzájom komunikovať, odovzdávať si dáta a ak je to potrebné navzájom sa synchronizovať.

 

 

8.5.2.1    Výhody RTOS

 

            Pri použití RTOS nie je vytváraný komplexný program riadiaci celú aplikáciu, ale iba programové moduly realizujúce konkrétnu úlohu. Dané programové moduly sú následne pripojené k RTOS, ktorý riadi ich súčinnosť. Z použitia RTOS plynú nasledujúce výhody:

8.5.2.2              Kľúčové pojmy u RTOS

 

o        správa systémových zdrojov – príkladom je rozdelenie systémového času mikropočítača jednotlivým úlohám, správa pamäte, semaforov a fronty

o        riadenie prenosu dát medzi úlohami – na základe fronty správ a semaforov

o        prevzatie riadenia pamäte

o        zastavenie aktívnej úlohy – v prípade nutnosti spustenia inej úlohy s vyššou prioritou

o        umožnenie prerušenia činnosti pomocou systému prerušení mikropočítača – prerušenie sa nesmie stratiť, čiže musí byť obslúžené, ináč by funkcia RTOS zlyhala

 

Na obr. 8.4 sú znázornené možné prechody medzi jednotlivými stavmi v ktorých sa úloha môže nachádzať.

 

                                         

 

Obr. 8.4: Možné prechody medzi stavmi úlohy

 

Základom behu úloh je multitasking, čiže z hľadiska používateľa je zdanie akoby paralelného behu viacerých programov, každého zvlášť na vlastnom mikropočítači.

            Multitasking je realizovateľný viacerými metódami:                 

o        roundrobin – ak pomocou plánovača sú úlohám s rovnakou prioritou prideľované rovnaké časové úseky na výkon danej úlohy

o        kooperatívny multitasking – bežiaca úloha odovzdáva riadenie systému iným úlohám s rovnakou prioritou

o        preemptívny multitasking – riadenie úloh je závislé plne na systéme, prideľovanie systémových prostriedkov úlohám plne riadi RTOS

 

Na záver je uvedený príklad demonštrujúci využitie RTOS RTX51.

 

#include <rtx51.h>                            // pripojenie knižnice s definovanými funkciami

                                                                              // RTOS RTX51

#include <stdio.h>

 

#define P1_0_BLINKER_TASK       1             // symbolickému výrazu priradíme hodnotu 1

#define P1_1_BLINKER_TASK       2             // symbolickému výrazu priradíme hodnotu 2

#define STARTUP_TASK                 0             // symbolickému výrazu priradíme hodnotu 0

 

sbit P1_0 = 0x90;                                             

sbit P1_1 = 0x91;

//************************************************************************

void task0 (void) _task_ P1_0_BLINKER_TASK _priority_ 0

{

while (1)                                                              // úloha task0 tvorená nekonečnou sľučkou

  {

  P1_0 = 1;                                                           // nastavenie výtupu P1.0 na log.1

  os_wait (K_TMO, 100, NULL);                      // volanie funkcie na vytvorenie oneskorenia

  P1_0 = 0;                                                           // nastavenie výtupu P1.0 na log.1

  os_wait (K_TMO, 100, NULL);                      // volanie funkcie na vytvorenie oneskorenia

  }

}

//************************************************************************

void task1 (void) _task_ P1_1_BLINKER_TASK _priority_ 0

{

while (1)                                                              // úloha task1 tvorená nekonečnou sľučkou

  {                                                                          

  P1_1 = 1;                                                           // nastavenie výtupu P1.1 na log.1

  os_wait (K_TMO, 75, NULL);                        // volanie funkcie na vytvorenie oneskorenia

  P1_1 = 0;                                                           // nastavenie výtupu P1.1 na log.1

  os_wait (K_TMO, 75, NULL);                        // volanie funkcie na vytvorenie oneskorenia

  }

}

//************************************************************************

void task2 (void) _task_ STARTUP_TASK

{

os_set_slice (1000);                                        // nastavenie frekvencie hodinového oscilátora

 

os_create_task (P1_0_BLINKER_TASK);   // vytvorenie ulohy task0

os_create_task (P1_1_BLINKER_TASK);   // vytvorenie ulohy task1

 

os_delete_task (os_running_task_id ());   // zrušenie momentálne bežiacej úlohy pomocou

                                                               // volania funkcie os_delete_task, identifikačné číslo // bežiacej funkcie sa zistí funkciou      

                                                               // os_runnig_task_id

 

}

//************************************************************************

void main (void)

{

os_start_system (STARTUP_TASK);           // volanie funkcie, ktora spusti system, ako prva je

                                                                              // spustena uloha task2

}

 

 

Ako prvá je spustená funkcia main, ktorá však v tomto prípade nemá ten charakter ako pri tvorbe klasického programu. Vo funkcii main je volanie funkcie aktivujúcej systém a spustenie úlohy task2 v ktorej sú definície ako nastavenie hodinových impulzov oscilátora, vytvorenie úloh pomocou volania funkcií RTOS os_create_task s parametrom poradového čísla úlohy. Volaním funkcie os_delete_task  s parametrom volaním funkcie os_running_task_id () (slúži na zistenie čísla momentálne bežiacej úlohy) je zrušená aktuálne bežiaca úloha.

Aktivované sú dve úlohy task0task1, ktoré realizujú rozsvietenie a zhasnutie LED diód pripojených na P1.1 a P1.0 vývodoch. Medzi zhasnutím a rozsvietením je oneskorenie vytvorené funkciou os_wait s príslušnými parametrami.