Die zwei grundlegendsten Befehle sind nun bekannt - etwas wirklich sinnvolles läßt sich damit
aber noch nicht machen. Um etwas Interaktivität in das eigene Programm zu bringen,
werden nun die bedingten Sprünge vorgestellt. Bedingte Sprünge führen in Abhängigkeit
bestimmter Zustände entweder einen Sprung zu einem angegebenen Label durch oder nicht. |
||||||||||||||||||
JB, JNB und JBC |
||||||||||||||||||
JB [Bit],[Sprungziel], JNB [Bit],[Sprungziel] und JBC [Bit],[Sprungziel] führen einen Sprung in Abhängigkeit von [Bit] nach [Sprungziel] aus. JB (Jump if bit is set) springt nach [Sprungziel], wenn [Bit] auf 1 gesetzt ist. JNB (Jump if bit is not set) macht genau das Gegenteil, es springt, wenn [Bit] gelöscht bzw. 0 ist. Die dritte Variante, JBC (Jump if bit is set and clear bit) verbindet das Verzweigen bei gesetztem [Bit] mit dem Löschen ebendieses Bits. Dieser Befehl kann eingesetzt werden, wenn es auf das letzte bisschen Rechnerleistung ankommt und beste Optimierung gefragt ist, da JBC um 1/3 schneller ist als eine Kombination aus JB und einem Löschbefehl, und 2/5 weniger Speicher benötigt als diese Befehlskombination.
Um die Anwendung bedingter Sprünge zu demonstrieren, wird das Beispiel eines kleinen Zahlenschlosses gewählt. An P1.0 bis P1.3 sind die Tasten 0 bis 3 angeschlossen. Nur wenn die Ziffernfolge '13' in der richtigen Reihenfolge eingegeben wird, soll ein an P3.0 angeschlossenes Relais anziehen und die Tür freigeben. Im Falle eines Einbruchsversuchs soll an P3.1 ein Alarmsignal ausgegeben werden, dass sich nicht abstellen lassen darf. Alle Tasten werden mit invertierten Pegeln betrieben, dabei bedeutet eine 0 eine gedrückte Taste. $NOMOD51 $INCLUDE (89C1051.MCU) mov P1,#0FFh ; Quell-Pins auf Input stellen mov P3,#11111100b ; Ausgabepins auf Anfangswert setzen wartenAufT1: jnb P1.0,alarm ; falsche Taste, Alarm auslösen jnb P1.1,wartenAufEndeT1; richtige Taste jnb P1.2,alarm ; falsche Taste, Alarm auslösen jnb P1.3,alarm ; falsche Taste, Alarm auslösen jmp wartenAufT1 ; keine Taste, zurück zum Anfang wartenAufEndeT1: ; warten, bis Anwender Taste losläßt jnb P1.1,wartenAufEndeT1 wartenAufT3: jnb P1.0,alarm ; falsche Taste, Alarm auslösen jnb P1.1,alarm ; falsche Taste, Alarm auslösen jnb P1.2,alarm ; falsche Taste, Alarm auslösen jnb P1.3,aktion ; richtige Taste jmp wartenAufT3 ; keine Taste, zurück zum Anfang aktion: ; Tür freigeben mov P3,#11111101n jmp ende alarm: ; Alarm auslösen mov P3,#11111110b ende: jmp ende ; Programm in Endlosschleife beenden END ; Dem Assembler das Programmende mitteilen
Ganz wichtig ist es, jedes Programm, das seine Aufgabe erledigt hat, in einer
Endlosschleife abzuschließen. Niemals darf ein Programm einfach
aus dem geschriebenen Code 'herauslaufen'!!! Im gelöschten Flash-Speicher,
also dem nicht mit einem Programm gefüllten Programmspeicher, sind alle Bytes auf
0FFh gesetzt. In Maschinensprache steht dies für mov R7,A. Und nachdem
der Controller also einige Male diesen Befehl ausgeführt hat, wird er
wieder am Anfang des Programmspeichers beginnen - und im Falle dieses
Beispiels den Alarm sofort wieder abstellen und die Tür verriegeln.
Das geht so schnell, dass man durchaus den Eindruck bekommt, das Programm
würde überhaupt nicht reagieren.
|
||||||||||||||||||
JC und JNC |
||||||||||||||||||
JC [Sprungziel] und JNC [Sprungziel] führen einen Sprung in Abhängigkeit vom Carry-Flag durch. JC (Jump if Carry is set) verzweigt, wenn bei der Ausführung des Befehls das C-Flag den Wert 1 enthält. JNC (Jump if Carry not set) macht genau das Gegenteil davon, es verzweigt wenn C=0 ist. Bei den Mathematikbefehlen werden einige Befehle auftauchen, die einen Teil des Ergebnisses wie z.B. das 'borgen' bei einer Subtraktion in das C-Flag schreiben. Im Zusammenhang mit diesen Mathe-Befehlen kommt JC und JNC eine große Bedeutung zu.
zurück zum Anfang |
||||||||||||||||||
JZ und JNZ |
||||||||||||||||||
JZ (Jump if A is zero) führt einen Sprung nach [Sprungziel] aus, wenn das ganze A-Register 0, also kein einziges Bit im A-Register gesetzt ist. JNZ (Jump if A is not zero) macht genau das Gegenteil, es springt, wenn A nicht 0 ist. Diese Befehle machen den meisten Sinn im Zusammenhang mit dem Testen auf bestimmte Bitkombinationen im Zusammenhang mit den logischen Operationen und Bitmanipulationen, die einem der nächsten Abschnitte beschrieben werden.
zurück zum Anfang |
||||||||||||||||||
CJNE |
||||||||||||||||||
Wer schon einmal mit 80x86-Assembler gearbeitet hat, wird sich wundern: CJNE ist ein Assemblerbefehl mit satten drei Argumenten - und diese haben es in sich! CJNE (Compare and Jump if not equal) vergleicht [Operator1] mit [Operator2] und verzweigt nach [Sprungziel], falls die beiden Operatoren nicht übereinstimmen. Diese komplexe Operation verbraucht dabei nur genausoviel Platz und Rechenzeit wie JB oder JNB. Daher ist CJNE im Allgemeinen sehr gut benutzbar und wird häufig eingesetzt. Folgende Kombinationen für die Operatoren sind möglich:
Die wenigen möglichen Kombinationen lassen leider einige Wünsche offen. Aber mit dem Wissen, dass bereits vermittelt wurde, lassen sich einige der Punkte geeignet formulieren: ; Ein Sprung-wenn-nicht-gleich cjne a,#162,weiter jmp sprungziel weiter: ; Register als zweiten Operator cjne a,3,sprungziel ; 3 ist die Speicheradresse von R3 ; ... sprungziel: zurück zum Anfang |
||||||||||||||||||
DJNZ |
||||||||||||||||||
Der DJNZ-Befehl (Decrement and jump if not zero) ist ein weiterer Befehl mit einer komplexen Funktion. Zuerst verringert er den Wert von [Operator]. Ist der Operator nach dem decrementieren nicht 0, so wird nach [Sprungziel] gesprungen. Damit ist dieser Befehl die erste Wahl, wenn es um Zählschleifen geht.
Eine Anwendung für DJNZ liegt in exakt berechneten Warteschleifen. Der 89C2051 läuft mit 24MHz. Ein Takt benötigt dabei 41,666 Nanosekunden, ein Maschinenzyklus aus je 12 Takten 0.5 Microsekunden. Um eine genau definierte Zeitspanne von beispielsweise 100 Microsekunden zu warten, müssen 200 Maschinenzyklen irgendwie verbraucht werden. Der DJNZ-Befehl benötigt zwei Maschinenzyklen, ein Mov-Befehl von einer Konstante nach A einen Maschinenzyklus. Folgender Code verzögert das Programm also um genau 100us: mov a,#0 ; 1 Maschinenzyklus mov a,#99 ; 1 Maschinenzyklus warteschleife: djnz Acc,warteschleife ; 2 Zyklen 99 mal verbrauchen Hinweis: Das Symbol Acc steht für die RAM-Adresse 0E0h, auf der der Inhalt des A-Registers vom Controller abgelegt wird. So ist es möglich, DJNZ auch mit dem A-Register zu benutzen, obwohl als möglicher Operator nur 'Byte im RAM' bzw 'Rn' erlaubt ist.
Solche hochpräzisen Timings sind eine Stärke von Assembler. In C beispielsweise könnte so eine
Schleife von einem guten Compiler einfach wegoptimiert werden, da sie nichts bewirkt - oder (was
leider weitaus wahrscheinlicher ist) durch irgendwelche
sinnfreien Befehle zusätzlich unnötig aufgebläht werden, da der Compiler
den Sinn eines solchen Konstrukts nicht semantisch erfassen kann.
|
||||||||||||||||||
JMP |
||||||||||||||||||
Der JMP-Befehl ist einer der wenigen Befehle, die von der basisrelativen Adressierung gebrauch machen. Er verzweigt an eine Adresse, aus der Summe von DPTR und A-Register ermittelt wird. Sinnvoll ist dieser Befehl, um Tabellen anzuspringen.
Auch dieses Beispiel zeigt eine der ganz großen Stärken von Assembler. Es ermöglicht eine hocheffiziente Verzweigung zu verschiedenen Programmteilen mit einer Zahl als Operator (in C oder Java als Switch-case Anweisung realisiert). Die gewünschte Adressnummer muss in A vorliegen und jeweils mit 2 multipliziert werden, da ein AJMP-Befehl genau zwei Byte lang ist. A=0 springt also zu adresse1, A=2 zu adresse2, A=4 zu adresse3 und so weiter. ; Sprung in Abhängigkeit von A mov DPTR,#sprungtabelle jmp @A+DPTR sprungtabelle: ajmp adresse1 ajmp adresse2 ajmp adresse3 ajmp adresse4 ajmp adresse5 adresse1: ; ... Code ... adresse2: ; ... und so weiter ... |