UNIVERZITA TOMÁŠE BATI VE ZLÍNĚ FAKULTA APLIKOVANÉ INFORMATIKY
PROGRAMOVÁNÍ MIKROPOČÍTAČŮ CVIČENÍ 3 Podprogramy a práce se zásobníkem
Jan Dolinay Petr Dostálek Zlín 2013
Tento studijní materiál vznikl za finanční podpory Evropského sociálního fondu (ESF) a rozpočtu České republiky v rámci řešení projektu: CZ.1.07/2.2.00/15.0463, MODERNIZACE VÝUKOVÝCH
MATERIÁLŮ A DIDAKTICKÝCH METOD
Cvičení 3 – Podprogramy a práce se zásobníkem
Cvičení 3 – Podprogramy a práce se zásobníkem
STRUČNÝ OBSAH CVIČENÍ:
Vysvětlení principu podprogramů a jejich použití, souvislost se zásobníkem Řešený proklad – podprogram pro určení, zda je číslo sudé Řešený příklad – počítání sudých čísel v poli Úkoly k procvičení
VSTUPNÍ ZNALOSTI: Toto cvičení předpokládá znalosti získané na předchozím cvičení.
CÍL:
V tomto cvičení se naučíme pracovat se zásobníkem a vytvářet podprogramy, což nám usnadní tvorbu složitějších programů v budoucnu. Díky využití podprogramů budeme moci vytvářet knihovny znovupoužitelných programových modulů.
Cvičení se vztahuje k těmto otázkám
Funkce a ovládání zásobníkové paměti, instrukce pro práci se zásobníkem u mikropočítače HC08 Makroinstrukce a podprogramy, princip, použití, příklady
MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
2
Cvičení 3 – Podprogramy a práce se zásobníkem
1. Řešené příklady Program 1 – Podprogram pro určení, zda je číslo sudé Úkol: Vytvořte podprogram JeSude, který určí, jestli je zadané číslo sudé. Vstupním parametrem bude číslo v registru A, výsledkem je v registru A číslo 1, jestliže bylo zadané číslo sudé, 0 jestliže nebylo sudé. Podprogram využijte v programu, který určí kolik z čísel c1 a c2 je sudých - tedy zda žádné, jedno nebo obě. Poznámka: Návratovou hodnotu podprogramu JeSude jsme úmyslně zvolili tak, že jestliže je předané číslo sudé, vrací podprogram 1, protože jednička má v kombinační logice význam „pravda“ (true). Jestliže není číslo sudé vrací nulu – což je hodnota pro „nepravdu“, (false). Můžeme tedy při použití podprogramu jako pomůcku uvažovat, že se podprogramu „JeSude“ ptáme, zda zadané číslo je sudé. Jestliže ano, dostaneme odpověď 1, pravda.
Řešení Zda je číslo sudé určíme tak, že číslo vydělíme dvěma a jestliže je zbytek po celočíselném dělení nulový, číslo je sudé (je dělitelné dvěma beze zbytku). Algoritmus pro podprogram JeSude lze tedy slovně zapsat takto: Vydělíme předané číslo dvěma Porovnáme zbytek s nulou Jestliže je zbytek roven nule, do registru A vložíme 1 – viz zadání úkolu, 1 = je sudé – a ukončíme podprogram. V opačném případě vložíme do registru A nulu a ukončíme podprogram. Vývojový diagram a odpovídající kód je na následujícím obrázku.
MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
3
Cvičení 3 – Podprogramy a práce se zásobníkem
Obr. 1 Vývojový diagram a kód podprogramu JeSude.
Hlavní program má určit počet sudých čísel pomocí podprogramu, který jsme už napsali. Pro jednoduchost pouze ze dvou čísel, tj. z čísel c1 a c2. V hlavním programu proto budeme používat počitadlo, které se zvětší o jedna tehdy, jestliže při testu některého z čísel zjistíme, že je sudé. V programu se nejprve vynuluje toto počitadlo sudých čísel. Poté se testuje, jestli je c1 sudé číslo. Pokud ano, inkrementujeme počítadlo sudých čísel. Následuje test c2 a opět, jestliže je toto číslo sudé, inkrementuje se počitadlo sudých čísel. Jestliže tedy není ani c1 ani c2 sudé, projde program „rovně dolů“ vývojovým diagramem a v počitadle sudých zůstane nula. Bodově můžeme hlavní program popsat takto: Vynuluje se počitadlo sudých čísel Do registru A vloží číslo c1 (argument podprogramu JeSude) Provede skok do podprogramu JeSude Porovná obsah registru A (návratovou hodnotu podprogramu) s 1. Jestliže je návratová hodnota rovna 1 (což znamená že c1 je sudé), inkrementuje se počitadlo sudých čísel. Do registru A se nahraje c2. Provede se skok do podprogramu JeSude atd. Postup pro c2 je stejný jako pro c1. Vývojový diagram a odpovídající kód je na následujícím obrázku. MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
4
Cvičení 3 – Podprogramy a práce se zásobníkem
5
Obr. 2 Vývojový diagram a kód hlavního programu pro testování podprogramu JeSude.
Potřebné instrukce LDHX – nahraje 2-bajtové číslo z paměti do registru H:X CMP – porovná obsah A s paměťovou buňkou: (A) – (M) BEQ – skočí na zadané návěští pokud číslo v akumulátoru A je rovno číslu v paměťové buňce (M). BNE – opak BEQ, skočí pokud není rovno. INC – inkrementuje obsah zadané paměťové buňky: M (M) + 1 PSHH – uloží registr H na zásobník PULA – načte do reg. A hodnotu ze zásobníku BRA – skočí vždy (bez podmínky) na zadané návěští JSR – skok do podprogramu RTS – návrat z podprogramu Vysvětlení programu Nejprve se zabývejme podprogramem JeSude. Srovnáme-li vývojový diagram v levé části obrázku 3.4 se slovním popisem algoritmu výše, měla by být zřejmá souvislost. V pravé části obrázku jsou jednotlivé bloky vývojového diagramu přepsány do assembleru pro procesor HC08. Základem podprogramu je instrukce pro dělení - DIV, která vydělí 16-bitové číslo uložené v registrech H:A (tedy horní bajt čísla v registru H a dolní v registru A) číslem uloženým v registru X. Výsledek po celočíselném dělení pak vloží do registru A, a zbytek po MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
Cvičení 3 – Podprogramy a práce se zásobníkem
dělení do registru H. (jedná se o celočíselné dělení. Procesor HC08 neumí pracovat s reálnými čísly). Např. při dělení čísla 5 číslem 2, tedy: 5 / 2 bude v registru A výsledek = 2 a v registru H bude zbytek 1. (5/2 = 2, zbytek 1). Instrukce DIV je v instrukční sadě popsána takto: DIV: A <- (H:A) / (X), H <- zbytek Podívejme se nyní na kód programu odpovídající jednotlivým blokům vývojového diagramu na obr. 2. Podprogram JeSude dostane v registru A číslo, které má testovat. Jestliže je toto číslo sudé, (ANO), má podle zadání umístit do registru A číslo 1. Jestliže číslo není sudé, má do registru A umístit číslo 0. Blok porovnání je nejsložitější, protože provádí dělení. Již víme, že instrukce pro dělení DIV dělí číslo v registru H:A číslem v registru X. Výsledek dělení bude v registru A, zbytek v reg. H. Náš podprogram ovšem dostává jako vstupní parametr pouze registr A tj. spodní byte čísla, které pro dělení použije instrukce DIV. Horní byte, registr H musí být proto nulový (tak aby se dělilo skutečně jen číslo v registru A, a nikoliv i případná hodnota v registru H. Proto před dělením vynulujeme instrukcí CLRH obsah registru H. Poté do registru X nahrajeme číslo 2, tj. číslo kterým budeme dělit. V reg. A už máme číslo, které chceme testovat, takže můžeme provést dělení instrukcí DIV. Po vykonání instrukce DIV potřebujeme otestovat jestli je zbytek po dělení roven nule. K tomu použijeme instrukci pro porovnání – CMP. Ta ovšem porovnává registr A, a paměťovou buňku. Přitom ale zbytek po dělení je v registru H (viz popis instrukce DIV výše). Proto musíme zbytek přesunout z registru H do registru A. Uděláme to s pomocí zásobníku: obsah registru H uložíme na zásobník instrukcí PSHH a uloženou hodnotou pak naplníme registr A pomocí instrukce PULA. Pak už můžeme obsah reg. A porovnat instrukcí CMP s nulou. Skok BNE se v našem případě provede tehdy, jestliže číslo v reg. A není rovno nule (tedy jestliže zbytek po dělení nebyl nula), tj. jestliže je číslo liché. V případě, že se skok provede (číslo je liché) se proto v následujícím kódu do registru A nahrává nula – návratová hodnota. Jestliže se skok neprovede, nahraje se do registru A číslo 1 (návratová hodnota pro případ sudého čísla). Poté se instrukcí BRA provede skok na konec podprogramu. Skok BRA je zde nutný, jinak by program pokračoval následující instrukcí, což je nahrání nuly do registru A. Takový podprogram by pak vracel vždy nulu bez ohledu na vstupní parametr. Hlavní program provede nejprve nulování počitadla sudých čísel a to instrukcí CLR (clear). Proměnná je pojmenovaná nSude.
MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
6
Cvičení 3 – Podprogramy a práce se zásobníkem
Test zda je číslo sudé provádíme pomocí podprogramu JeSude. Tento podprogram má jako vstupní parametr číslo, které chceme testovat v registru A. Proto před skokem do něj nahráváme číslo c1 do registru A (instrukcí LDA). Poté zavoláme podprogram instrukcí JSR JeSude. Podprogram vrátí v reg. A číslo 1 jestliže zadané číslo bylo sudé. Porovnáme toto vrácené číslo (návratovou hodnotu) s číslem 1 pomocí instrukce CMP. Jestliže je vrácené číslo rovno 1, provede se skok instrukcí BEQ (BEQ = skoč jestliže je rovno) na návěští Pricti1, kde se inkrementuje počitadlo nSude. Jestliže se skok BEQ neprovede, provede se nepodmíněný skok BRA, který přeskočí inkrementaci a pokračuje další částí programu, kde se testuje číslo c2. Postup testování c2 je stejný jako u c1. Na konci hlavního programu bude v proměnné nSude číslo 0, 1 nebo 2 podle toho zda není sudé žádné z obou čísel, jedno z nich nebo obě. Kód programu Na následujícím výpisu je uveden kompletní zdrojový kód programu. Pozor na umístění na správné místo v kostře programu, kterou vygeneroval CodeWarrior, tj. data programu (proměnné) do Data section a kód programu do Code section. Podprogram JeSude umístěte na úplný konec zdrojového souboru. ; variable/data section MY_ZEROPAGE: SECTION SHORT c1 DS.B 1 c2 DS.B 1 nSude DS.B 1
; code section MyCode: SECTION main: LDHX #__SEG_END_SSTACK ; initialize the stack pointer TXS CLI ; enable interrupts mainLoop:
NOP MOV MOV
#8,c1 #3,c2
CLR LDA JSR CMP BEQ BRA
; inicializace promennych
nSude ; nuluj pocet sudych cisel c1 ; priprava parametru pro podprogram JeSude JeSude ; volani podprogramu JeSude #1 ; vratil podprogram jednicku? (je cislo sude?) Pricti1 ; pokud je sude, inkrementuj pocitadlo Skok1 ; pokud ne, preskoc inkrementaci a testuj ;dalsi cislo
Pricti1 INC
nSude
Skok1
LDA JSR
; nSude = nSude + 1 c2 JeSude
; totez pro druhe cislo
MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
7
Cvičení 3 – Podprogramy a práce se zásobníkem CMP BEQ BRA
#1 Pricti2 Skok2
Pricti2 INC
nSude
Skok2
NOP BRA
; konec programu mainLoop
;-----------------------------------------------------; Podprogram JeSude ; VSTUP: reg.A - cislo, ktere su bude testovat ; VYSTUP: reg.A: 1 = cislo je sude, 0 = cislo je liche ;-----------------------------------------------------JeSude CLRH ; vynulujem registr H LDX #2 ; budeme delit 2 DIV PSHH ; uloz zbytek na zasobnik (chceme dostat H>A) PULA ; nactu do A hodnotu ze zasobniku CMP #0 ; je zbytek po deleni 0? BNE Liche ; neni-li, cislo je liche LDA #1 ; v opacnem pripade je sude BRA Konec Liche CLRA ; liche cislo, v A vracime 0 Konec RTS
Testování programu Program přeložíme a spustíme. Nejčastější chyby: Zkopírován a vložen ze vzorového programu celý jeho obsah i s kódem, který už v kostře aplikace byl. Některá návěští se pak v programu vyskytují dvakrát. Není vložen kód z druhého (zeleného) pole tj. definice proměnných. Není vložen kód podprogramu Návěští (tj. jméno podprogramu) je odsazeno od levého okraje řádku. Vyzkoušíme program odkrokovat. Se zadanými čísly c1 = 8 a c2 = 3 by měl být výsledek v proměnné nSude roven 1 (jedno sudé číslo). Vyzkoušejte nyní také tlačítko simulátoru Step over (krok přes). To, na rozdíl od dosud používaného tlačítka Single Step, neprovede skok dovnitř podprogramu, ale provede celý podprogram v jediném kroku - jakoby to byla jediná instrukce. To je výhodné pokud už máme podprogram odladěn, víme tedy že funguje a nemusíme jej při každém použití krokovat.
MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
8
Cvičení 3 – Podprogramy a práce se zásobníkem
Sami vyzkoušejte v simulátoru následující: Výsledek pro různé hodnoty c1 a c2 Krokování dovnitř podprogramu tlačítkem Single Step. Krokování „přes“ podprogram tlačítkem Step Over. Obsah registru A po návratu z podprogramu JeSude jestliže předané číslo bylo/nebylo sudé. Pro sudé číslo bude v reg A číslo 1, pro liché č. 0. Program 2 – Využití podprogramu v cyklu Úkol: Podprogram JeSude z minulého příkladu použijte v programu, který určí počet sudých čísel v poli data o šesti prvcích. Počet sudých čísel uložte do proměnné sud. Řešení Princip: v cyklu projdeme prvky pole a pro každý zavoláme podprogram JeSude pro určení zda je daný prvek sudé číslo. Pokud jste už zapomněli základní principy práce s polem, podívejte se nejprve do předchozí kapitoly na program pro nulování pole. Slovně bychom mohli činnost programu pro určení počtu sudých čísel v poli popsat takto: Vynuluje se počitadlo sudých čísel Podprogramu JeSude se předá první prvek pole Otestuje se návratová hodnota podprogramu JeSude. Jestliže bylo předané číslo sudé, inkrementuje se počitadlo Posuneme se na další prvek pole Testujeme, jestli už jsme prověřili všechny prvky pole. Jestliže ne, opakujeme volání JeSude s aktuálním prvkem pole a opět inkrementujeme počitadlo jestliže tento prvek je sudé číslo Tento popis je ovšem příliš „lidský“, neurčitý. Přesnější popis s ohledem na princip programování v assembleru je zachycen na vývojovém diagramu na následujícím obrázku.
MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
9
Cvičení 3 – Podprogramy a práce se zásobníkem
10
Obr. 3 – Vývojový diagram programu pro počítání sudých čísel v poli
Nejprve nastavujeme do registru H:X (index registr) adresu pole – viz minulá lekce o cyklech a polích. Také nulujeme počitadlo cyklů, které slouží k tomu, abychom cyklus ukončili po zpracování všech prvků pole. Blok „zpracuj aktuální prvek“ má zde význam: „zjisti jestli je aktuální prvek sudé číslo a pokud ano, inkrementuj počitadlo sudých čísel“. Posun na další prvek se provede inkrementací obsahu index registru (instrukce AIX). Poté se testuje, zda už je zpracováno všech šest prvků pole. Jestliže ne, vrací se program na začátek cyklu a zpracovává další prvek. Možná jste si všimli, že vývojový diagram je stejný jako u příkladu na nulování pole v minulé kapitole. Diagram je totiž obecný pro prakticky libovolnou operaci s prvky pole. Blok „Zpracuj aktuální prvek pole“ totiž v sobě skrývá celou akci, která se má s prvkem pole vykonat, ať už je to nulování nebo zjištění zda je sudý a inkrementace počitadla. Ostatní bloky tedy vlastně představují režii, pomocné operace, nezbytné pro práci s polem. V kódu se nahrává aktuální prvek pole do registru A pomocí instrukce LDA ,X, tedy používáme indexové adresování. V index registru (H:X) je adresa začátku pole. Připomeňme si, že podprogram JeSude vrací 1, jestliže jsme mu v registru A (kam vždy nahráváme aktuální prvek pole) předali sudé číslo. V tom případě se zde neprovede skok BNE, protože to je skok jestliže „není rovno“. Tedy jestliže je číslo sudé, skok se neprovede a inkrementuje se proměnná sud pomocí instrukce INC sud. Jestliže je číslo liché, skok se provede a tím se inkrementace přeskočí. MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
Cvičení 3 – Podprogramy a práce se zásobníkem
11
Následuje inkrementace index registru instrukcí AIX #1 čímž se adresa aktuálního prvku pole v registru H:X zvětší o jedničku a tudíž při opakování cyklu už instrukce LDA ,X nahraje do registru A další prvek pole. Také se inkrementuje počitadlo cyklů v proměnné citac. Obsah této proměnné se následně nahraje do registru A k porovnání s počtem prvků pole (s číslem 6). Jestliže je čítač menší než 6, provede se skok BLO (skoč jestliže je menší) na začátek cyklu a kód se opakuje pro další prvek pole. Na následujícím výpisu je kód podprogramu JeSude. Všimněme si červeně orámovaných změn oproti minulé verzi tohoto podprogramu. Jedná se o uložení obsahu registrů H a X na začátku programu na zásobník a jejich obnovení na konci, před instrukcí RTS. Proč se tyto registry musejí v podprogramu ukládat?
Obr. 4 – Úprava podprogramu JeSude
Sledujte kód hlavního programu na předchozím obrázku. Do registru H:X nahráváme adresu začátku pole a z této adresy pak vždy v průběhu cyklu nahráváme aktuální prvek pole instrukcí LDA ,X. Spoléháme tedy na to, že registr H:X obsahuje vždy adresu aktuálního prvku pole. Tuto adresu také zvětšujeme uvnitř cyklu instrukcí AIX. Jestliže by ale podprogram JeSude registr H:X neuložil, při jeho zavolání by byla adresa aktuálního prvku pole ztracena – protože podprogram JeSude registr H:X využívá (v H je zbytek po dělení, do X nahrává dělitele atd.). Je tedy třeba aby podprogram vrátil registr H:X se stejným obsahem, s jakým jej dostal. K tomu stačí na začátku uložit registry H a X na zásobník a na konci podprogramu je ze zásobníku obnovit, tak jak je to provedeno na výpisu na obr. 4. MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
Cvičení 3 – Podprogramy a práce se zásobníkem
; code section MyCode: SECTION main: LDHX pointer TXS CLI mainLoop: NOP MOV MOV MOV MOV MOV MOV CLR CLR LDHX opakuj LDA dany adresou v H:X JSR CMP BNE inkrementaci sud INC preskoc AIX pole INC LDA CMP BLO skoc na opakuj BRA
#__SEG_END_SSTACK
; initialize the stack
; enable interrupts
#3,data #1,data+1 #5,data+2 #8,data+3 #2,data+4 #3,data+5
; inicializace dat v poli
sud citac #data ,x
; adresa pole data do H:X ; nacti do A prvek pole
JeSude #1 preskoc
; je cislo sude? ; pokud ne, preskoc
sud #1
; prejdi na dalsi prvek
citac citac #pocet opakuj
; citac = citac + 1 ; jsme na konci pole? ; pokud je citac<pocet,
mainLoop Obr. 5 – Zdrojový kód hlavního programu
Testování programu Po překladu program vyzkoušejte a prohlédněte si v simulátoru následující: Výsledek v proměnné sud po proběhnutí programu Hodnotu v registru A vždy po nahrání aktuálního prvku a pak po návratu z podprogramu (použijte tlačítko Step over). Obsah registru A po návratu z podprogramu JeSude jestliže předané číslo bylo/nebylo sudé. Pro sudé číslo bude v reg A číslo 1, pro liché č. 0. Krokování dovnitř podprogramu tlačítkem Single Step a uložení a obnovení obsahu H:X. MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
12
Cvičení 3 – Podprogramy a práce se zásobníkem
Poznámka: Pole jako parametr podprogramu Pokud bychom chtěli podprogram, který by zpracoval sám celé pole a vrátil rovnou např. počet sudých nebo lichých čísel v něm, narazíme na otázku jak podprogramu předat pole. Samozřejmě by bylo zbytečné předávat podprogramu celé pole např. jeho zkopírováním na zásobník. Raději předáme jen adresu začátku pole. Nejjednodušší je předat ji přímo v registru H:X tj. v index registru. Podprogram pak pracuje s tímto registrem obvyklým způsobem pro indexové adresování. Aby byl podprogram univerzální, použitelný pro pole různých rozměrů (ať už ve stejném programu nebo v nějakém jiném), musíme mu také předat počet prvků pole.
Obr. 6 – princip předání pole jako parametru do podprogramu
Příklady k procvičení 1. Vytvořte podprogram, který sečte dvě čísla a výsledek vrátí v registru A. Vstupní parametry dostane v registru A a X. 2. Vytvořte podprogram, který spočítá lichá čísla v předaném poli. Adresa pole se předá v registru H:X, počet prvků v registru A. Podprogram vrátí počet lichých čísel v reg. A. Pak napište program, který pomocí zadaného podprogramu určí počet lichých čísel v poli data s pěti prvky. Pole můžete inicializovat na libovolné hodnoty. 3. Pomocí stejného podprogramu se pokuste spočítat počet bajtů v paměti mikropočítače, které obsahují lichá čísla v paměti od adresy $182C v rozsahu 10 bajtů. Poznámka: od této adresy začíná v paměti kód našeho programu)
Doplňující zdroje [1]
Freescale: Firemní dokumentace pro mikropočítače HCS08, dostupné online: http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=S08GB&nodeId= 01624684491437EDD5 MODERNIZACE VÝUKOVÝCH MATERIÁLŮ A DIDAKTICKÝCH METOD CZ.1.07/2.2.00/15.0463,
13