Naar inhoud springen

Programmeren in TI-83+ Assembly/Controle/Springen en subroutines

Uit Wikibooks

Programmeren in TI-83+ Assembly

Voorblad / Inhoudsopgave

Assembly-basis

1 · 2 · 3 · 4 · 5 · T

Controle

1 · 2 · 3 · 4 · 5 · T

Geheugenbeheer

1 · 2 · 3 · 4 · 5 · T

Input en output

1 · 2 · 3 · 4 · 5 · T

Tekenen

1 · 2 · 3 · 4 · 5 · T

Registers en procedures

1 · 2 · 3 · 4 · 5 · T

Applications

1 · 2 · 3 · 4 · T

Gebruikersvariabelen

1 · T

Extra

1 · T

Speciaal

Het is mogelijk vanaf instructies naar andere plekken in je programma te springen en op die manier de volgorde van uitvoering van het programma te veranderen.

De Z80-processor heeft een instructie om naar een bepaald geheugenadres te "springen" en daar verder te gaan met de uitvoering van het programma (overigens hebben ook (bijna?) alle andere processoren dit, aangezien het een essentieel onderdeel is van programmeren in Assembly). Naar een geheugenadres springen kan met

    jp geheugenadres

jp staat voor JumP. Als de processor zo'n regel tegenkomt, gaat hij verder met het lezen van instructies bij het geheugenadres dat je hebt opgegeven. Let wel op dat de processor absoluut niet weet wat data is en wat code; voor hem zijn het alleen "nullen en enen". Als je dus naar een adres springt waar helemaal geen code staat maar data, dan gaat de processor de data interpreteren als code. Daar komen natuurlijk rare dingen uit.

Voorbeeld:

    jp $D000

Stel dat deze instructie in het geheugen op adres $9D94 staat. Als de processor de instructie op $9D94 dan leest, dan zal de volgende instructie die hij leest $D000 zijn. Hierna gaat hij gewoon weer verder met $D001, $D002 enzovoorts (tenzij hier weer sprong-instructies staan).

Het berekenen van adressen is vervelend, maar net als bij variabelen kan TASM dat voor je oplossen door een label te gebruiken. Hieronder zie je bijvoorbeeld een simpele oneindige lus in Assembly:

OneindigeLus:
    jp OneindigeLus

Net zoals bij variabelen weet de processor trouwens niets van labels; TASM vertaalt je label naar een echt geheugenadres.

Een andere manier om te springen is

    jr offset

jr staat voor Jump Relative. De naam zegt het al: de processor leest de offset in en telt dit op bij de huidige positie. De offset kan tussen -128 en 127 (inclusief) liggen. Een jr-instructie is één byte kleiner dan het jp-equivalent.

Het met de hand berekenen van offsets is nog veel vervelender dan het berekenen van (absolute) geheugenadressen, zoals bij jp, maar gelukkig begrijpt TASM wat je wilt doen als je gewoon een label achter jr zet. TASM berekent automatisch de offset en als dit niet mogelijk is (als de offset niet tussen -128 en 127 ligt, ofwel als het label te ver weg ligt), dan krijg je een foutmelding van TASM. Je kunt de bovenstaande oneindige lus dus ook schrijven met jr:

OneindigeLus2:
    jr OneindigeLus2

want het is duidelijk dat het label niet te ver weg ligt. Overigens komt 128 bytes overeen met grofweg 1 à 2 schermen aan code, dus je kunt bijna altijd jr gebruiken.

InformatieJe vraagt je misschien af waarom de assembler niet zelf kiest tussen jp en jr. De reden daarvoor is dat het ingewikkelder is dan het lijkt: jr kost iets meer tijd dan jp, dus als de snelheid van het uiteindelijke programma heel belangrijk is, is het beter om jp te laten staan.


call

[bewerken]

Er is nóg een manier van springen, en dat is om subroutines te maken. Het werkt als volgt:

    call Subroutine
    ; instructies-1
    ret

Subroutine:
    ; instructies-2
    ret

Eerst springt de processor naar Subroutine. Daar voert hij dus instructies-2 uit. Als hij ret tegenkomt, gaat hij weer terug naar waar hij vandaan kwam en voert hij instructies-1 uit. Bij de volgende ret eindigt het programma (dus terug naar de TI-OS).

Je kunt allerlei ingewikkelde structuren maken, zoals recursie en elkaar-aanroepende methoden. Echter, call gebruikt de stack zelf om de terugkeeradressen op te slaan. Net als bij het hele programma mag je dus de stack bij ret niet anders achterlaten dan je hem kreeg bij het begin van de subroutine. Zorg er bovendien voor dat de stack niet te vol wordt (maximaal ongeveer 100 elementen), wat bij recursie erg snel kan gebeuren. (Kun je nu zelf bedenken waarom je ret gebruikt om terug te keren naar het TI-OS en waarom het hele programma de stack niet anders mag achterlaten dan toen hij hem kreeg? Hint: welke instructie zou TI-OS gebruiken om je programma te starten?)

Opdracht

[bewerken]
Het resultaat

Maak een programma dat vier keer een tekst op het scherm schrijft. Gebruik daarvoor een subroutine met een bcall. Roep deze vier keer aan. Het resultaat moet er ongeveer uitzien als het plaatje hiernaast.

Klap uit voor het antwoord

Een mogelijk antwoord zie je hier. Vul de standaardcode zelf aan.

    call TekstSchrijven      ; TekstSchrijven aanroepen
    call TekstSchrijven      ; ... en nog een keer ...
    call TekstSchrijven
    call TekstSchrijven
    ret                      ; Terug naar de TI-OS.

TekstSchrijven:
    ; Let op, níét PenCol instellen, want dan zouden de "Hallo!"'s iedere keer op dezelfde plek komen!
    ld    hl, Tekst          ; Laad het label in hl.
    bcall(_PutS)             ; Zet de tekst, die staat vanaf hl (dus het label) op het scherm.
    bcall(_NewLine)          ; Zet de cursor op de volgende regel op kolom 0.
    ret                      ; Terug naar de hoofdmodule.

Tekst:                       ; Label Tekst
    .db "Hallo!", 0          ; Voeg aan het programma de tekenreeks "Hallo!" toe, gevolgd door een nul.


← Controle Springen en subroutines Optellen en aftrekken →
Informatie afkomstig van https://nl.wikibooks.org Wikibooks NL.
Wikibooks NL is onderdeel van de wikimediafoundation.