| 3. Aufbau Praktikumsrechner & Betriebssoftware | Lösungsvorschläge zu den Praktischen Übungen |
Ein Rechensystem, dessen Arbeitsspeicher ausschließlich aus einem flüchtigen Schreib-/Lese-Speicher besteht, dessen Inhalt demnach beim Abschalten der Versorgungsspannung verloren geht, ist nur bedingt funktionsfähig: Da der Inhalt des Arbeitsspeichers beim Wiedereinschalten nicht definiert ist, ist der Prozessor nicht in der Lage, sinnvolle Operationen auszuführen. Es sind nicht einmal Grundfunktionen wie das Lesen der Tastatur möglich.
In der Frühzeit der Computer hat man sich damit beholfen, den Prozessor zunächst einmal - durch Sperren des Taktsignals - anzuhalten, um dann ein Programm unter Umgehung des Prozessors direkt in den Speicher zu schreiben.
Auch der in den Anfangsjahren unseres Praktikums eingesetzte Praktikumsrechner wurde nach diesem Prinzip betrieben: Zuerst wurde ein kurzes Hilfsprogramm eingegeben, indem man mittels Tastatur zunächst die Adresse auswählte und dann den zugehörigen Befehl in binärer Form in den Speicher schrieb. Dieses Programm führte dann automatisch die Inkrementierung der Adresse durch, und man konnte sich für die Eingabe des eigentlichen Programms auf die Eingabe des Befehls - natürlich immer noch in binärer Form - beschränken.
Stürzte das zu testende Programm dann ab, was, wie Sie mittlerweile wahrscheinlich wissen, nicht die Ausnahme, sondern die Regel ist, so war im allgemeinen auch das mühsam eingetippte Hilfsprogramm zerstört, und man mußte wieder völlig von vorn anfangen !
Wie Sie aus dieser kurzen Darstellung schon ersehen können, ist diese Methode der Programmierung nicht nur sehr fehleranfällig, sondern auch äußerst mühselig und zeitaufwendig. Daher ist die Forderung nach einem Programmsystem verständlich, das bestimmte Grundfunktionen des Rechners schon unmittelbar nach der Inbetriebnahme bereitstellt.
Bei diesem System muß es sich nicht unbedingt um ein Monitorprogramm oder ein komplettes Betriebssystem handeln. Insbesondere bei größeren Rechnersystemen, die auf jeden Fall mit externen Speichermedien wie Platten- oder Diskettenlaufwerken oder Bandmaschinen verbunden sind, existiert oft nur ein sogenannter Urlader (Bootstrap Loader), der den Rechner in den Stand setzt, ein Betriebssystem "von außen" in den Arbeitsspeicher zu laden und zu starten.
Bei einem kleineren System, wie dem von Ihnen benutzten Praktikumsrechner, der auch ohne angeschlossene Peripheriegeräte betriebsfähig sein soll, ist es dagegen günstiger, zumindest ein einfaches Betriebssystem in einem Festwertspeicher mitzuliefern, das dem Benutzer sofort nach dem Einschalten zur Verfügung steht und bei der Erstellung von Programmen, bei der Fehlerbeseitigung und der Benutzung der verschiedenen Komponenten des Rechners Hilfestellung gibt.
Anforderungen an ein Monitorprogramm
Wie oben bereits erwähnt, muß ein Rechner nach dem Einschalten in einen definierten Grundzustand überführt werden. Dazu gehören insbesondere das Setzen des Stackpointers und die Initialisierung der verschiedenen Rechnerkomponenten, insbesondere der Schnittstellen. Diese Aufgaben müssen von einem "eingebauten" Monitorprogramm übernommen werden.
Die Hauptaufgabe eines Monitors besteht aber in der Unterstützung des Programmierers bei der Eingabe und beim Austesten von Programmen. Der angebotene Komfort ist dabei vorwiegend von dem für den Monitor zur Verfügung stehenden Speicherplatz abhängig.
Da die Entwicklungssysteme auf der Basis von Einplatinencomputern noch vor einigen Jahren aufgrund der hohen Preise für Halbleiterspeicher mit sehr kleinen Monitorprogrammen auskommen mußten, war die Zahl der verfügbaren Funktionen auf ein absolut notwendiges Minimum beschränkt, das die Programmierung des Prozessors in Maschinensprache mit Hilfe einer Hexadezimaltastatur erlaubte.
Mit dem Preisverfall der Halbleiterbauelemente wurden jedoch immer umfangreichere und damit komfortablere Programme möglich, so daß ein Monitor mittlerweile durchaus über einen einfachen Assembler/Disassembler und eine ganze Reihe weiterer komplexer Funktionen verfügen kann.
Der im Praktikumsrechner benutzte Monitor ist in einem EPROM vom Typ 2764 untergebracht. Dieser Baustein wurde
schon im Abschnitt 3.1 kurz vorgestellt. Er besitzt eine Organisation von 8k·8 bit, also eine Kapazität von 8 kbyte. Davon sind zur Zeit knapp 4 kbyte vom Monitor belegt, die restlichen 4 kbyte sind für die schon in KE1 angesprochenen Hilfsroutinen reserviert.1)
Für ein reines Entwicklungssystem, auf dem hauptsächlich Programme für andere Rechner getestet und zum Laufen gebracht werden sollen, sind diese Routinen relativ uninteressant. Sollen die fertigen Programme aber auf demselben System ausgeführt werden, auf dem sie entwickelt wurden, ergibt sich durch eine solche Bibliothek eine bedeutende Einsparung an Speicherplatz, vor allem aber eine große Zeitersparnis bei der Programmierung. Bei geschickter Anwendung der Bibliotheksprogramme kann ein Anwenderprogramm dann zu einem großen Teil aus Unterprogrammsprüngen zu den Hilfsroutinen bestehen.
| Selbsttestaufgabe S3.2-1: |
|---|
| Überlegen Sie sich, warum die Routinen der Bibliothek nicht in einem Programm benutzt werden können, das auf einem anderen Rechner ausgeführt werden soll. |
Der Aufbau und die Funktionsweise eines Monitors sollen hier beispielhaft anhand des im Praktikumsrechner benutzten Monitors erklärt werden. Allerdings werden wir bei einigen Funktionen, die unserer Meinung nach schlecht gelöst wurden, auf andere bzw. bessere Methoden zurückgreifen.
Zum besseren Verständnis sehen Sie in Bild 3.2-1 die Adreßraumbelegung des Rechners.
| Bild 3.2-1: | Die Adreßraumbelegung des Praktikumsrechners |
Der gesamte adressierbare Bereich beträgt 64 kbyte. Davon werden 8 kbyte ($0000 - $1FFF) vom RAM belegt, weitere 8 kbyte vom EPROM ($E000 - $FFFF). Außerdem sind noch zwei Steckplätze für Speicherbausteine vorhanden, die aber in der Ihnen zugeschickten Grundausstattung des Praktikumsrechners nicht benutzt werden.
Das RAM enthält neben den Anwenderprogrammen die Systemvariablen, die in den ersten vier 256-byte-Seiten untergebracht sind, und den (System-)Stack. Da seine Größe statisch nicht feststeht, wird der Stackpointer zunächst auf das Ende des RAM-Bereichs gesetzt. Der Stack wächst dann rückwärts auf die Anwenderprogramme zu. Er benötigt in der Regel weniger als 200 byte 2), kann aber erheblich größer werden, falls ein Programm sehr stark verschachtelt ist oder die Parameter für Unterprogramme auf dem Stack übergeben werden.
Insbesondere Programmierfehler (z.B. das Vergessen, auf dem Stapel abgelegte Werte wieder herunterzunehmen) können dazu führen, daß der Stack in den Programmbereich "hineinwächst" und damit das Anwenderprogramm zerstört.
Das Flußdiagramm in Bild 3.2-2 zeigt in stark vereinfachter Form den allgemeinen Ablauf des Monitorprogramms.
| Bild 3.2-2: | Das Flußdiagramm des Monitors |
Nach dem Rücksetzen, ausgelöst durch die Taste C bzw. automatisch beim Einschalten des Rechners (Power on Reset), springt der Rechner in die Initialisierungsroutine. Diese Routine kann sehr vielfältige Aufgaben haben. Dazu gehören z.B. Selbsttests, Feststellung des RAM-Bereichs und der angeschlossenen Geräte oder Initialisierung der Schnittstellen.
Zwei Funktionen müssen aber auf jeden Fall ausgeführt werden: Der Stackpointer S muß gesetzt werden und die vom Monitor benötigten Variablen müssen ihre Startwerte erhalten. Dazu wird ein Bereich des ROM in den Systembereich des RAM kopiert, wo die Werte dann vom Monitor benutzt und bei Bedarf verändert werden können.
| Selbsttestaufgabe S3.2-2: |
|---|
| Der 6809-Prozessor sperrt alle Unterbrechungen, bis ein Wert in den Stackpointer S geschrieben wurde. Warum ? |
Zum Abschluß der Initialisierung wird eine Meldung ausgegeben, die anzeigt, daß der Rechner jetzt betriebsbereit ist, und eventuell noch einige Informationen über den Zustand des Systems gibt. Die Meldung Ihres Rechners wurde schon in KE1, Abschnitt 1.2.1, erklärt.
Der Kommandointerpreter ist das Kernstück des Monitors. Hier werden die Kommandos von der Tastatur eingelesen, ausgewertet und die entsprechenden Monitorroutinen aktiviert.
Das Assemblerlisting der Interpreterschleife in Bild 3.2-3 entspricht nicht der augenblicklichen Implementierung im Praktikumsrechner. Nach ihrem Muster soll die Interpreterschleife aber in einer der nächsten Versionen in den Monitor eingebaut werden. Durch die spezielle Codierung der Tasten (siehe Tabelle 1.2-2 in KE1) wird sowohl die Auswertungsroutine als auch die Sprungtabelle sehr kurz.
Die Interpreterschleife springt nach der Ausführung einer Funktion nicht automatisch wieder in die Tastaturabfrage (HALTKEY), und zwar deshalb, weil in den Monitorroutinen in der Regel nach weiteren Eingaben gefragt wird. Beim Erkennen einer für die laufende Routine ungültigen Eingabe wird die Routine beendet. Nur wenn der Kommandointerpreter im Akku B keinen gültigen Befehl findet (TESTB), wird die Tastatur erneut abgefragt.
Der Pseudobefehl FDB im Listing bedeutet "Form Double Byte". Es handelt sich also nicht um einen ausführbaren Befehl, sondern um die Anweisung an den Assembler, an dieser Stelle einen 16-bit-Wert einzutragen, in diesem Fall die Startadressen der Monitorroutinen.
Falls die Sprungtabelle sich im RAM-Bereich des Rechners befindet, ist der Austausch der Routinen durch den Benutzer möglich. Für die Tasten F1 - F3 gilt dies auch im vorliegenden Monitor (siehe auch KE1). Ein konkretes Beispiel dazu finden Sie in Unterabschnitt 3.2.5.
; Interpreterschleife
;
FALSCH: JSR CLRDISP ; Anzeige löschen
LDA #$D3 ; 7-Segmentcode für '?'
LDX #0 ; in der letzten Stelle
JSR SHOWA ; anzeigen
JSR HALTKEY ; auf einen Befehl warten
SCHLEIFE: TSTB ; Code in Akku B testen
BPL FALSCH ; keine Funktionstaste, zurück
ANDB #$7F ; Bit 7 löschen
LSLB ; 2 Bytes pro Eintrag
LDX #SPRUNGTB ; Basisadresse der Tabelle
JSR [B,X] ; indirekter Sprung zur Routine
BRA SCHLEIFE ; Endlos-Schleife
;
; In jeder Funktionsroutine: Fehlerbehandlung bei unzulässiger Funktionstaste
;
FEHLER: CLRB ; Tastencode löschen
RTS ; zur Interpreterschleife
;
; Sprungtabelle
;
SPRUNGTB: FDB #FEHLER ; + den Tasten '+' und '-' sind
FDB #FEHLER ; - keine Routinen zugeordnet
FDB #ADDRESS ; A |
FDB #DATA ; D |
FDB #REGIST ; R | ansonsten werden die
FDB #GO ; G |
FDB #SAVE ; S | jeweiligen Startadressen
FDB #LOAD ; L |
FDB #TRACE ; T | der Routinen in die
FDB #INSERT ; F1 |
FDB #DELETE ; F2 | Tabelle eingetragen
FDB #DUMP ; F3 |
|
| Praktische Übung P3.2-1: |
|---|
| Schreiben Sie eine Interpreterschleife, die Kommandos aus vier beliebigen Hexadezimalziffern (zwei Bytes) verwendet. Benutzen Sie zur Eingabe des Befehls die Routine SHOWADR. |
3.2.3 Aufbau der Hilfsroutinen
Beim Betrieb eines Rechners werden einige Standardfunktionen immer wieder benötigt. Um sie nicht jedesmal neu programmieren zu müssen und damit Arbeitszeit und Speicherplatz zu verschwenden, wird von vornherein eine Reihe dieser Funktionen als komplette Unterprogramme in den Monitor eingebaut.
Einen Teil der Hilfsroutinen haben Sie bereits in KE1, Abschnitt 1.2.2, kennengelernt. Dazu gehören die Ein-/Ausgaberoutinen und die Umwandlungsroutinen, z.B. vom Hexadezimal- in den Siebensegment-Code.
Daneben werden von einigen Monitorroutinen noch Unterprogramme zum Editieren der Parameter und zur Adreß- bzw. Dateneingabe gebraucht.
Als einfaches Beispiel ist in Bild 3.2-4 die Routine CLRDISP abgebildet, die die eingebaute 7-Segment-Anzeige löscht:
;CLeaRDISPlay: Siebensegmentanzeige löschen
;
CLRDISP: PSHS X,CC ; CPU Register retten
LDX #DISPL ; Display, rechte Stelle (S0)
CL1: CLR ,X+ ; löschen
CMPX #DISPL+16 ; bis DISPL+7 (S7)
BNE CL1 ;
PULS X,CC ; Register restaurieren
RTS ; Ende CLRDISP
;
DISPL EQU $F020 ; Startadresse der Anzeige, s. Abschnitt 3.1
|
Alle von der Routine benutzten Register werden zu Beginn auf den Stapel gerettet und vor der Beendigung wieder heruntergenommen, so daß der alte Zustand der CPU wiederhergestellt wird. Die Funktion der Routine ist einfach: Ein Register zeigt auf die erste Stelle der Anzeige. Dann wird in einer Schleife eine 0 in die entsprechende Adresse geschrieben und das Register inkrementiert, bis alle acht Stellen gelöscht sind. Durch den Pseudo-Opcode EQU wird der Variablen DISPL die Startadresse der Anzeige zugewiesen 1).
Obwohl die Hilfsroutinen nur Standardfunktionen realisieren, müssen sie nicht unbedingt trivial sein. Das läßt sich recht eindrucksvoll an der Routine SHOWADR nachweisen. Sie liest eine Adresse (zwei Bytes) zyklisch von der Tastatur in das Y-Register und stellt diese im Adreßfeld der Anzeige dar, bis eine Funktionstaste gedrückt wird. Deren Tastencode steht dann im Akku B zur Verfügung.
SHOWADR bedient sich mehrfach der Hilfsroutinen HALTKEY und SHOWD7SG, die ihrerseits weitere Routinen aufrufen. Bild 3.2-5 dient zur Veranschaulichung des Ablaufs: Bei der Ausführung von SHOWADR werden insgesamt acht weitere Routinen aufgerufen, die maximale Schachtelungstiefe ist 5.
| Bild 3.2-5: | Beispiel zur Unterprogramm-Schachtelung |
Aus dieser Sicht wird auch die Bedeutung der Zusicherung aus KE1, Abschnitt 1.2.2, klar, daß nur die Inhalte der explizit angegebenen Register verändert werden und alle anderen Daten unverändert bleiben. Anderenfalls wären die Auswirkungen eines solchen Funktionsaufrufs für den Programmierer kaum noch nachvollziehbar. Die einzige Möglichkeit, sich vor unliebsamen Überraschungen zu schützen, bestünde vor jedem Aufruf einer Hilfsroutine in der Rettung aller weiterhin benötigter Register auf den Stack und deren Restaurierung nach der Rückkehr.
| Praktische Übung P3.2-2: |
|---|
|
Abgesehen von der Hexadezimaltastatur gibt es auf dem Tastenfeld des Rechners vier Tasten, denen keine Routine zugeordnet ist: Die Funktionstaste C führt einen Systemstart durch und die Taste F4 gibt eine Interruptanforderung an den Prozessor. (Auf die Interruptbehandlung werden wir im Unterabschnitt 3.2.5 noch näher eingehen). Die Tasten '+' und '-' haben verschiedene Bedeutungen, auf die wir bei der Behandlung der einzelnen Monitorroutinen zurückkommen. Die übrigen Tasten rufen die verschiedenen Funktionen des Monitors auf, die bereits in KE1, Abschnitt 1.2.1, erklärt wurden und die sich grob in vier Klassen einteilen lassen:
Die Speicherbearbeitung wirft keine größeren Probleme auf. Es stehen geeignete Hilfsroutinen zur Verfügung, die den Großteil der auszuführenden Teilaufgaben übernehmen, so daß nur noch das Gerüst zusammengesetzt werden muß.
Die Tasten '+' und '-' haben bei diesen Routinen die Aufgabe, eingegebene Daten zu übernehmen und den Adressenzeiger zu inkrementieren bzw. dekrementieren.
Anhand der Routine ADDRESS demonstrieren wir Ihnen hier einmal die Implementierung einer Routine von der Problemstellung bis zum fertigen Programm mittels der Ihnen bekannten Hilfsroutinen.
Zunächst die Anforderungen:
Aus den Anforderungen wird das Flußdiagramm nach Bild 3.2-6 entwickelt:
| Bild 3.2-6: | Flußdiagramm der Routine ADDRESS |
Bild 3.2-7 zeigt die Routine in Assemblerschreibweise. Dabei ist bei der Benutzung der Routine SHOWADR zu beachten, daß das Y-Register im ersten Befehl gelöscht wird. Wenn Sie also nicht unbedingt eine neue Adresse eingeben möchten, springen Sie zum zweiten Befehl der Routine, was in diesem Fall ohne weiteres möglich ist, da SHOWADR keine Register auf den Stapel rettet. Da das Löschen des Y-Registers (LDY #0) vier Bytes beansprucht, muß eine neue Einsprungadresse berechnet werden.
Anmerkung: Sie können diese Routine anstelle der "Standard"-Routine benutzen, allerdings mit einigen Einschränkungen.
; Routine ADDRESS: Adresse editieren ; ; Ausgabe der Kennung und Holen der Adresse ; ADDRESS:PSHS A,X,Y,CC ; Register retten JSR CLRDISP ; Anzeige löschen LDD #$775E ; 7-Segmentcodes für "Ad" LDX #6 ; im Operationsfeld JSR SHOWD ; anzeigen LDY <ADR ; zu editierende Adresse holen ; zum Zeichen "<" siehe das ; Programmierhandbuch ; Adresse editieren ; W: LDB ,Y ; zugehöriges Datum in Akku B LDX #0 ; im Datenfeld JSR SHOWB7SG ; anzeigen LDX #2 ; Anzeige im Adreßfeld LDU SHOWADR+1 ; Startadresse aus Sprungtabelle JSR 4,U ; Aufruf ohne Löschen von Y ; ; Ergebnis von SHOWADR interpretieren ; CMPB #$81 ; letzte Taste BHI R ; >$81?, dann fertig BEQ $2 ; =$81(Taste '-')?, dann dekrementieren LEAY 2,Y ; Zeiger inkrementieren (um 2, da ; wieder dekrementiert wird) LEAY -1,Y ; Zeiger dekrementieren BRA W ; und erneut editieren ; ; falls Ergebnis nicht "+" oder "-", geordnetes Beenden des Programms ; R: STY <ADR ; neue Adresse ablegen PULS A,X,Y,CC ; Register restaurieren RTS ; zurück zum Monitor ADR EQU $.... ; 2 Speicherplätze für Adresse |
| Praktische Übung P3.2-3: |
|---|
|
Bei den Routinen INSERT und DELETE zum Einfügen und Entfernen von einzelnen Speicherstellen ergibt sich eine Schwierigkeit:
Die nachfolgenden Daten müssen im Speicher verschoben werden. Die Operation des Verschiebens selbst ist einfach, man muß sich aber darüber im Klaren sein, daß nicht der gesamte Speicherinhalt verschoben werden darf, da sonst auch der Stapelspeicher verschoben würde und der Rechner zwangsläufig abstürzt. Außerdem befinden sich oft mehrere Programme oder Programmteile gleichzeitig im Speicher, von denen nur eines geändert werden soll.
Die Frage ist also, welcher Speicherbereich verschoben wird. Wir haben uns dafür entschieden, jeweils 256 Bytes oberhalb der zu editierenden Adresse zu verschieben. In der Regel werden die von Ihnen geschriebenen Programme kürzer sein, und es besteht die Möglichkeit, zu anderen Programmen einen ausreichenden Zwischenraum zu lassen, so daß diese von der Verschiebung nicht betroffen sind.
Eine weitere Lösung des Problems besteht in der Festlegung eines Arbeitsbereichs, außerhalb dessen die Monitorroutinen nicht arbeiten. In diesen "geschützten" Bereich kann dann der Stack angelegt werden.
| Praktische Übung P3.2-4: |
|---|
| Schreiben Sie eine Routine für die INSERT-Funktion und testen Sie sie aus. |
B. Bearbeitung der Prozessor-Register
Mit der Taste R wird die Routine zur Darstellung und zum Editieren der Register des Mikroprozessors aktiviert. Dabei handelt es sich selbstverständlich nicht um die realen Register des Prozessors (,die zum Betrieb des Monitors benötigt werden und auch auf keinen Fall von außen verändert werden können), sondern um einen reservierten Teil des Systembereichs ($0000-$03FF), der die Werte enthält, die beim Start eines Anwenderprogramms in den Prozessor geladen werden und bei Unterbrechung des Programms die "geretteten" Registerinhalte wieder aufnimmt.
Die Routine besteht, wie aus dem Flußdiagramm im Bild 3.2-8 ersichtlich, aus zwei Hauptteilen: Auswahl eines Registers und Editieren des Inhalts. Sie funktioniert ganz ähnlich wie die DATA-Routine, beide unterscheiden sich hauptsächlich in der Funktion der Tasten '+' und '-'. Während in der DATA-Routine lediglich die Adresse inkrementiert bzw. dekrementiert wird, verwaltet die Hilfsroutine zur Parameterauswahl einen Zeiger auf eine Tabelle, der der eigentlichen Editierroutine den Zugriff auf die benötigten Parameter erlaubt. Das sind in diesem Fall die Kennung des Registers (wird im Adreßfeld angezeigt), die "Länge" des Registers (1 oder 2 Bytes), die für die Auswahl zwischen SHOWADR und SHOWDATA gebraucht wird, und ein Verweis auf die Zero-Page-Adresse, in der sich der Registerinhalt befindet.
| Bild 3.2-8: | Flußdiagramm zur Routine REGIST |
Da diese Routine ebenfalls nicht in der beschriebenen Form implementiert ist, haben wir das Listing einer universell nutzbaren Routine im Bild 3.2-9 angegeben, mit der es beispielsweise auch möglich wäre, die Registerinhalte der Peripheriebausteine zu editieren, und zwar allein durch die Ausgabe der entsprechenden Kennung im Operationsfeld und die Angabe der Basisadresse der benötigten Parametertabelle.
; Routine REGIST: Register editieren ; Kennung ausgeben und Startwerte setzen REGIST: PSHS A,X,Y,CC ; Register retten JSR CLRDISP ; Anzeige löschen LDD #$5079 ; 7-Segmentcodes für "rE" LDX #6 ; im Operationsfeld JSR SHOWD ; ausgeben |
LDY #REGPAR ; Basisadresse der Tabelle CLRA ; mit erstem Register beginnen JSR EDIT ; zur Editierroutine PULS A,X,Y,CC ; Register restaurieren RTS ; zurück in den Monitor ; Editieren EDIT: PSHS A,X,Y ; Register-Nummer und Basisadresse retten LDB #5 ; 5 Bytes MUL ; pro Eintrag LEAY B,Y ; zur Basisadresse addieren LDD ,Y++ ; 7-Segmentcode für Kennung LDX #0 ; im Datenfeld JSR SHOWD ; anzeigen LDX #2 ; Editieren im Adreßfeld TST ,Y+ ; 1- oder 2-byte-Daten PSHS Y ; Zeiger auf Parameter retten BEQ BYTE ; 0 für 1 Byte LDY [,Y] ; Datenwort editieren LDX SHOWADR+1 ; dazu Sprung nach SHOWADR ohne JSR 4,X ; Löschen von Y, s. Bild 3.2-7 TFR Y,X ; Datenwort nach X PULS Y ; Zeiger wiederherstellen STX [,Y] ; und Datum ablegen EDIT2: PULS A,X,Y ; Register restaurieren CMPB #$81 ; Taste '-' gedrückt ? BLS $1 ; >$81? (Tastencode für '-') RTS ; dann zurück ins Hauptprogramm BSR PARAM ; sonst Parameter ändern BRA EDIT ; und wieder editieren BYTE: LDY #0 ; nicht benutzte Anzeigestellen JSR SHOWYD ; löschen PULS Y ; Zeiger auf Datum restaurieren LDA [,Y] ; Datenbyte editieren LDX SHOWDATA+1 ; dazu Sprung nach SHOWDATA ohne JSR 2,X ; Löschen von A STA [,Y] ; und wieder ablegen BRA EDIT2 ; zurück nach EDIT ; Parameter ändern PARAM: PSHS CC ; Status retten BEQ MINUS ; falls letzte Taste = $81 INCA ; nächster Parameter CMPA -1,Y ; mit Tabellenende vergleichen BLE $1 ; noch nicht überschritten CLRA ; sonst zum Tabellenanfang BRA R ; fertig MINUS: DECA ; vorheriger Parameter BPL $2 ; ·0 ? dann o.k. LDA -1,Y ; sonst letzter Parameter R: PULS CC ; Status restaurieren RTS ; und zurück |
; ; Parametertabelle für Register ; TABLEN FCB #8 ; 9 Eintragungen von 0 - 8 REGPAR: FDB #$0077 ; 1. Register: Kennung " A" FCB #0 ; 0 = 1 Bytes FDB #$00F5 ; Datum in $F5 FDB #$007C ; 2. Register: Kennung " B" FCB #0 ; 1 Byte FDB #$00F6 ; Datum in $F6 FDB #$0076 ; 3. Register: Kennung " X" FCB #1 ; 1 = 2 Bytes FDB #$00F8 ; Datum in $F8/$F9 FDB $006E ; 4. Register: Kennung " Y" FCB 1 ; 2 Bytes FDB $00FA ; Datum in $FA FDB $003E ; 5. Register: Kennung " U" FCB 1 ; 2 Bytes FDB $00FC ; Datum in $FC FDB $006D ; 6. Register: Kennung " S" FCB 1 ; 2 Bytes FDB $00F2 ; Datum in $F2 FDB $7358 ; 7. Register: Kennung "PC" FCB 1 ; 2 Bytes FDB $00FE ; Datum in $FE FDB $5E73 ; 8. Register: Kennung "DP" FCB 0 ; 1 Byte FDB $00F7 ; Datum in $F7 FDB $5858 ; 9. Register: Kennung "CC" FCB 0 ; 1 Byte FDB $00F4 ; Datum in $F4 |
| Praktische Übung P3.2-5: |
|---|
| Assemblieren Sie die Routine REGIST und testen Sie sie. |
Der Monitor bietet mit den Routinen LOAD und SAVE die Möglichkeit, Programme und Daten über die V.24-Schnittstelle zu einem PC zu übertragen und dort auf der Festplatte oder einer Floppy Disk zu speichern 2). Diese Routinen gehören zu den aufwendigsten und umfangreichsten des Monitors. Wir wollen hier nur (eine mögliche Version für) die LOAD-Routine besprechen. (Die SAVE-Routine arbeitet analog dazu.)
Die Übertragung von Programmen und Daten erfolgt als ASCII-Zeichen im sogenannten Intel-Hex-Format, das im Bild 3.2-10 erklärt wird.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||
Die Übertragung in diesem Format ist zeilenweise orientiert. Jede Zeile beginnt mit dem Startzeichen ':' (ASCII-Code $3A). Danach folgt - wie alle anderen Daten in hexadezimaler Form - die Anzahl BA der in der Zeile übertragenen Datenbytes DBi. Es folgen zwei Bytes, die die Anfangsadresse ZAA (ZAAH,ZAAL) der übertragenen Datenzeile im Speicher angeben. Das nächste Byte ZK gibt durch ZK=$01 an, daß die letzte Zeile des Datenblockes übertragen wird. Alle vorherigen Zeilen werden durch ZK=$00 gekennzeichnet. Nach den Datenbytes DBi wird zum Abschluß jeder Zeile eine Prüfsumme PS übertragen, die als Zweierkomplement der 8-bit-Summe aller vorausgehenden Bytes der Zeile (außer der Kennung ':') berechnet wird.
| Selbsttestaufgabe S3.2-3: |
|---|
| Welchen Wert berechnet der Empfänger einer Übertragung im Intel-Hex-Format als Summe über alle Bytes einer Zeile (außer der Kennung ':'), in der kein Übertragungsfehler aufgetreten ist ? |
Bild 3.2-11 zeigt das Flußdiagramm der Routine Load. Nicht gezeigt ist die Initialisierung der V.24-Schnittstelle, die bereits nach dem Einschalten des Rechners (Power on Reset) oder nach jedem Betätigen der Funktionstaste C (Clear) durchgeführt wird 3). (Das Unterprogramm zum Empfang der übertragenen ASCII-Zeichen ist im Flußdiagramm 3.2-11 grau unterlegt dargestellt.)
Die empfangenen Daten werden im Programm LOAD vom ASCII-Code in die hexadezimale Form umgewandelt 4). Tritt dabei ein Fehler auf, wird die LOAD-Routine mit einer Fehlermeldung: 'FEhLEr hE' ('hE': Hexadezimal-Umwandlung) beendet. Eine Fehlermeldung: 'FEhLEr PS' ('PS': Prüfsumme) wird auch erzeugt, wenn die vom Empfänger neu berechnete Prüfsumme einen Übertragungsfehler anzeigt.
| Praktische Übung P3.2-6: |
|---|
| Disassemblieren Sie - mit Hilfe des Programms DS9 - die LOAD-Routine (ab Adresse $FE00). Analysieren Sie das Programm und fügen Sie ausführliche Kommentare bei. |
| Bild 3.2-11: | Flußdiagramm der Routine LOAD 5) |
D. Ausführung der Anwenderprogramme
Zur Kontrolle der auf dem Rechner laufenden Programme gehört nicht nur der Start eines Programms, sondern genauso seine kontrollierte Beendigung. Zu diesem Zweck müssen geeignete Maßnahmen ergriffen werden, um den Rechner beim Start eines Programms in einen definierten Ausgangszustand zu versetzen und den Zustand des Systems bei der Beendigung oder beim Abbruch des Programms zu speichern, um später Rückschlüsse auf dessen Verhalten ziehen zu können. Insbesondere reicht es nicht aus, ein Anwenderprogramm durch einen Sprung zu seiner Startadresse zu aktivieren.
Der normale Programmstart geht folgendermaßen vor sich: Nachdem die Startadresse eingegeben und bei Bedarf ein Unterbrechungspunkt gesetzt wurde, setzt die GO-Routine den Stackpointer S auf den vorgewählten Wert und führt einen Software-Interrupt SWI2 aus. Damit werden die Inhalte aller Register des Prozessors (außer dem Stackpointer S) auf den Stapel gelegt und - da sie im Benutzerprogramm nicht benötigt werden - Platz für die Registerinhalte geschaffen, die vom Anwenderprogramm gebraucht werden. Diese werden nun aus dem Systembereich in den Stapel kopiert. Mit dem Befehl RTI holt der Prozessor nun alle Register vom Stapel, einschließlich des PC-Registers, das dann die Startadresse des Programms enthält.
Die Trace-Funktion ist eine der interessantesten Routinen des Monitors, deshalb geben wir sie in Bild 3.2-12 vollständig wieder - wenn auch in einer anderen als der im Monitor implementierten Form. Sie startet das Programm prinzipiell auf die gleiche Art und Weise wie die GO-Routine. Der wesentliche Unterschied zwischen beiden ist die Vorbereitung zur Unterbrechung des Programms nach der Ausführung eines einzigen Befehls.
Dazu wird der Zähler #2 des Timer-Bausteins MC6840 6) so eingestellt, daß während der Abarbeitung des ersten Programmbefehls eine NMI-Anforderung an der CPU eintrifft. Diese unterbricht daraufhin das laufende Programm am Ende des momentan ausgeführten Befehls, rettet alle Register auf den Stapel und verzweigt in die NMI-Routine.
Dort wird die Beendigung des Programms fortgesetzt: Die Registerinhalte werden vom Stapel in den Systembereich zurückkopiert, der Stackpointer auf den Wert vor dem Eintreffen des NMI zurückgesetzt und die entsprechende Meldung auf dem Display ausgegeben. Damit ist die TRACE-Routine beendet und der Monitor bereit, neue Aufgaben durchzuführen, z.B. die Inhalte der Register anzuzeigen etc.
Mit der vollständigen Beschreibung der TRACE-Routine haben Sie schon eine Methode kennengelernt, aus dem Anwenderprogramm in den Monitor zurückzukehren. Die beiden anderen Rückkehrroutinen, SWI1 zur Bearbeitung eines Breakpoints und die Taste F4 zum Abbruch eines Programms von der Tastatur aus, funktionieren grundsätzlich genauso, wobei zu beachten ist, daß die Taste F4 eine maskierbare Unterbrechungsanforderung IRQ erzeugt, die im Gegensatz zum nicht maskierbaren Interrupt NMI der TRACE-Routine nur dann ausgeführt wird, wenn der Interrupt nicht durch das Setzen des I-Flags im Statusregister der CPU blockiert wurde 7).
; Routine TRACE: Einzelschrittausführung TI1CNT: EQU $F018 TI2CNT: EQU $F019 TI2MSB: EQU $F01C TI2LSB: EQU $F01D RECTAB: EQU $00F4 PCREG: EQU $00FE CCREG: EQU $00F4 TRACE: SWI3 ; Einen Programmbefehl ausführen ; Hier wird nach dem NMI fortgefahren LDA TI2CNT ; NMI abschalten durch Lesen von St.Reg. 2 LDA #$01 ; STA TI1CNT ; Timer Reset, sperren JSR Kopie ; Registersatz vom Stack in Zero-Page kop. RETRAC: JSR CLRDISP ; Anzeige löschen LDD #$3150 ; "Tr" LDX #6 ; im Operationsfeld JSR SHOWD ; anzeigen TLOOP: LDY #PCREG ; Startadresse nach Y LDB ,Y ; zugehöriges Datum LDX #0 ; im Datenfeld JSR SHOWB7SG ; anzeigen LDX #2 ; im Adreßfeld JSR SHOWADR ; Startadresse editieren CMPB #$80 ; zurück mit Taste '+' ? BNE +4 ; falls nein, weiter LEAY 1,Y ; Startadresse inkrementieren BRA TLOOP+4 ; erneut editieren CMPA #$81 ; zurück mit Taste '-' ? BNE +4 ; sonst zurück zur Hauptschleife LEAY -1,Y ; Startadresse dekrementieren BRA TLOOP+4 ; erneut editieren LDB ,Y ; Datum LDX #0 ; im Datenfeld JSR SHOWB7SG ; anzeigen STY #PCREG ; neue Startadresse ablegen LBRA MLOOP ; zurück zur Hauptschleife ; Start des Programms für TRACE SWI3: LDA <CCREG ; Statusregister ORA #$90 ; E- und I-Flag setzen STA <CCREG ; wieder ablegen ; Kopieren der Register vom Systembereich in den Stapel TFR S,Y ; Y ist Ziel für die Register LDX #REGTAB ; X ist die Quelle |
LDB #$C ; Länge der Tabelle LDA ,X+ ; Datum holen STA ,Y+ ; und auf dem Stapel ablegen DECB ; bis 12 Bytes kopiert, BNE -7 ; wiederholen ; Timer setzen LDA #$C3 ; Zähler #2, Interrupt zum NMI zulassen STA TI2CNT ; in Kontrollregister 2 CLRA ; 0 nach STA TI2MSB ; Zähler #2, MSB-Buffer LDA #$D ; 13 nach STA TI2LSB ; Zähler #2, LSB-Buffer CLRA ; Reset abschalten, STA TI1CNT ; Zähler #2 läuft los RTI ; Sprung in das Programm |
| Selbsttestaufgabe S3.2-4: |
|---|
| Vergleichen Sie den Programmabbruch bei Erreichen eines Breakpoints (SWI) mit dem bei der Einzelschrittausführung (NMI). Wo liegt der wesentliche Unterschied ? |
Die selbstprogrammierbaren Tastenfunktionen sind zwar mit den Routinen INSERT, DELETE und DUMP vorbelegt, können aber jederzeit auf eigene Routinen umgeleitet werden, indem die Startadresse in der Sprungtabelle des Kommandointerpreters gegen die Startadressen Ihrer eigenen Routinen ausgetauscht werden. Sie finden die entsprechenden Adressen bei der Beschreibung der Routinen in KE1.
Da der Kommandointerpreter, der die Routinen aktiviert, etwas anders arbeitet, als der in dieser Kurseinheit beschriebene, müssen Sie die Routinen mit dem Befehl JMP $E38F abschließen. Wie schon weiter vorn beschrieben, akzeptiert der Monitor einen eventuell schon im Akku B stehenden neuen Befehl nicht, der Rücksprung endet daher in der Tastaturabfrage des Monitors, und Sie müssen die gewünschte Funktionstaste noch einmal drücken.
Als Beispiel wollen wir die Taste F1 mit der Funktion FILLbelegen, die einen Speicherbereich zwischen zwei anzugebenden Bereichsgrenzen mit einem bestimmten Datum füllt. Wir benötigen dazu die Angaben über Bereichsanfang und Bereichsende und das Datum.
Für die Auswahl der Parameter verwenden wir praktischerweise die Routine, die auch zum Editieren der Register benutzt wird. Da sie gegenwärtig im Rechner nicht zur Verfügung steht, müssen wir sie selbst eintippen (Sie haben sie ja wahrscheinlich schon längst assembliert). Das Format der Tabelle entspricht ebenfalls exakt dem der vorgestellten Routine. Sie ist in Bild 3.2-13 dargestellt.
; Parametertabelle für FILL ; ORG $1800 ; Startadresse der Tabelle TABLEN: FCB 2 ; Anzahl der Einträge -1 FILPAR: FDB $7C77 ; Bereichsanfangs-Kennung "bA" FCB 1 ; 2-byte-Datum FDB #BA ; bei Adresse $1810 FDB $7C79 ; Bereichsende-Kennung "bE" FCB 1 ; 2-byte-Datum FDB #BE ; bei Adresse $1812 FDB $5E77 ; Datums-Kennung "dA" FCB 0 ; 1-Byte-Datum FDB #DA ; bei Adresse $1814 ; ORG $1810 ; Daten für die FILL-Routine BA: FDB $400 ; vorgegebener Bereichsanfang BE: FDB $4FF ; vorgegebenes Bereichsende DA: FCB $FF ; vorgegebenes Datum |
Es fehlt noch die eigentliche Routine. Zunächst entwickeln wir das Flußdiagramm nach Bild 3.2-14:
| Bild 3.2-14: | Das Flußdiagramm der FILL-Routine |
Damit gestaltet sich das Assemblerprogramm recht einfach. Dies ist im Bild 3.2-15 dargestellt.
; Routine FILL (Taste F1) ; ORG $1820 ; FILL: PSHS A,X,Y,CC ; Register retten JSR CLRDISP ; Anzeige löschen LDD #$7104 ; 7-Segmentcode für "FI" LDX #6 ; im Operationsfeld JSR SHOWD ; anzeigen LDY #FILPAR ; Y zeigt auf die Tabelle CLRA ; ersten Parameter JSR EDIT ; editieren CMPB #$89 ; zurück mit F1? BNE R ; sonst Ende LDX BA ; Bereichsanfang in X LDA DA ; Datum in A LOOP: STA ,X+ ; in den Speicher schreiben CMPX BE ; Bereichsende erreicht ? BNE LOOP ; falls nein, wiederholen STA ,X ; letztes Datum schreiben LDY #$7954 ; 7-Segment-Code für LDD #$5E79 ; "EndE" in Y und D LDX #2 ; im Adreßfeld JSR SHOWYD ; anzeigen R: PULS A,X,Y,CC ; Register restaurieren JMP $E38F ; zurück zum Monitor |
| Praktische Übung P3.2-7: |
|---|
| Assemblieren Sie das Programm und testen Sie es aus, indem Sie die Taste F1 neu belegen. |
Oft muß ein Rechner auf Ereignisse reagieren, die unabhängig vom laufenden Programm ("asynchron") auftreten. Grundsätzlich gibt es hierzu zwei Möglichkeiten, das Auftreten dieser Ereignisse zu erkennen. Die erste besteht in der regelmäßigen Abfrage der Statusregister der angeschlossenen Peripheriebausteine auf ein solches Ereignis. Der Nachteil dieser Methode liegt auf der Hand: Das laufende Programm muß regelmäßig in eine entsprechende Routine springen, die die Peripherie auf Unterbrechungsanforderungen testet.
Diese Art der Programmierung ist nicht nur umständlich, in vielen Fällen sogar unmöglich, es wird auch ein großer Teil der Rechenkapazität des Prozessors auf Abfragen verschwendet, die nicht zu einer Unterbrechung führen. Außerdem kann es unter Umständen relativ lange dauern, bis auf die Unterbrechungsanforderung reagiert wird.Aus diesem Grund besitzen Mikroprozessoren spezielle Eingangsleitungen, an die die Signale zur Unterbrechungsanforderung angelegt werden können. Der 6809-Prozessor stellt drei Leitungen für diese Signale zur Verfügung, die mit IRQ, FIRQ und NMI bezeichnet.
Die Arbeitsweise der Interrupts wurde im Einzelnen schon in KE1, Abschnitt 1.3.2, vorgestellt. Der Monitor benutzt zwei der drei Hardware-Interrupts, nämlich IRQ und NMI, und alle drei Software-Interrupts (SWI).
Zur Rückkehr aus einer Unterbrechung besitzt der Prozessor den speziellen Befehl RTI (ReTurn from Interrupt), der automatisch die zu Beginn der Unterbrechung gesicherten Register wieder vom Stapel holt. Er benutzt dazu das E-Bit des Statusregisters, das angibt, ob alle Register gerettet wurden (E = 1) oder nur die Register CC und PC.
Zurück zu den Hardwareunterbrechungen. Ein reales Computersystem kommt mit den drei zur Verfügung stehenden Interruptleitungen in der Regel bei weitem nicht aus. Selbst die Hardware des vergleichsweise kleinen Praktikumsrechners kann neun verschiedene Interrupts erzeugen, außerdem können extern weitere Geräte angeschlossen sein, die Unterbrechungsanforderungen an den Rechner schicken. Da jede dieser Anforderungen eine individuelle Behandlung verlangt, dem Prozessor aber nur drei Hardware-Interruptvektoren *) zur Verfügung stehen, ist es zunächst notwendig, die Herkunft eines Interrupts zu klären, um dann zu der speziellen Behandlungsroutine zu verzweigen. Diese Aufgabe wird von drei Auswertungsroutinen übernommen, die ebenfalls zum Monitorprogramm gehören und den drei Unterbrechungsleitungen zugeordnet sind. Die Startadressen der Auswertungsroutinen stehen in einem Stapel von µP-Vektoren, in dem auch der PC-Startwert und die SWI-Vektoren zu finden sind (s. KE1, Tabelle 1.3-1).
Am Beispiel der IRQ-Auswertungsroutine, deren Flußdiagramm Sie in Bild 3.2-16 finden, sehen Sie, wie das System funktioniert. Nacheinander werden die Peripheriebausteine daraufhin getestet, ob das Interrupt-Flag im Statusregister gesetzt ist und, falls ja, ob der Baustein den Interrupt auch an den Prozessor weitergeleitet hat. Das ist nötig, da es die Möglichkeit gibt, die Interrupt-Ausgangsleitung eines Peripheriebausteins abzuschalten,so daß ein im Statusregister angezeigter Interrupt gar nicht zum Prozessor weitergeleitet wird.
Die Abfrage wird fortgesetzt, bis die Quelle der Unterbrechung gefunden ist. Dann wird über eine Sprungtabelle, die sich im Systembereich befindet, die entsprechende Behandlungsroutine aufgerufen 1). Dadurch können Sie bei Bedarf eigene Routinen zur Bedienung der Unterbrechungen benutzen, indem Sie die Startadressen Ihrer eigenen Routinen in die Tabelle eintragen. Vom Monitor benutzt werden derzeit nur der IRQ über den Eingang CA1 von Port A des Parallel-Schnittstellenbausteins MC6821 2), der das Signal der BREAK-Taste weiterleitet, und der NMI von Zähler #2 von Baustein MC6840 für die TRACE-Funktion. Alle anderen Vektoren zeigen auf die Adresse $E643E und damit auf den Befehl RTI ($39), der unter dieser Adresse abgespeichert ist.
Für den Fall, daß mehrere Geräte an den Bus angeschlossen sind, die den gleichen Interrupt auslösen können, hat der Benutzer dafür Sorge zu tragen, daß der Vektor für eine Unterbrechung von außerhalb des Rechners auf eine weitere Auswertungsroutine zeigt, die nach oben dargestelltem Verfahren das auslösende Gerät ermittelt und die weitere Ausführung einleitet.
| Bild 3.2-16: | Flußdiagramm der Routine zur Auswertung des IRQs |
In der Interrupt-Vektorentabelle finden sich imübrigen auch die Startadressen der SWI-Routinen, die alle drei vom Monitor benutzt werden: SWI1 kennzeichnet einen Breakpoint im Anwenderprogramm, SWI2 führt den Programmstart und SWI3 die TRACE-Funktion durch. Um Ihnen die Möglichkeit zu geben, wenigstens SWI2 und SWI3 ebenfalls zu benutzen, werden auch diese auf ihren Ursprung hin untersucht: Durch die Abfrage der Rücksprungadresse auf dem Stapel wird festgestellt, ob der SWI aus dem RAM- oder ROM-Bereich kam. Ist sie kleiner als $8000, handelt es sich um einen vom Benutzer erzeugten SWI, anderenfalls wurde er vom Monitor ausgelöst.
Zum Abschluß wollen wir Ihnen noch ein typisches Beispiel für den Unterschied zwischen der Abarbeitung einer Routine mit wiederholter Abfrage eines Bausteins oder per Interrupt geben: Die Ausgabe einer Zeichenkette an die serielle Schnittstelle. Beide Flußdiagramme sind in Bild 3.2-17 und Bild 3.2-18 dargestellt. Während im ersten Fall der Prozessor während der gesamten Übertragungszeit mit der Ausgabe beschäftigt und damit für andere Aufgaben blockiert ist, wird er bei der zweiten Methode jeweils nur für wenige Mikrosekunden für die Ausgabe eines Zeichens beansprucht und ist während der restlichen Zeit für andere Aufgaben verfügbar 3).
| Bild 3.2-17: | Ausgabe mit zyklischer Abfrage des Statusregisters |
| Bild 3.2-18: | Ausgabe mit Interruptsteuerung |
| 3. Aufbau Praktikumsrechner & Betriebssoftware | Lösungsvorschläge zu den Praktischen Übungen |