The following article was printed in issue 2/88 of the magazine „PC AMSTRAD INTERNATIONAL".
Another project displaying grapics on the JOYCE.

Grafik - ohne GSX?

SCREENY macht's möglich ...

Wer kennt ihn nicht, den Ärger mit der GSX-Grafikerweiterung? Wir jedenfalls haben uns von Anfang an darüber geärgert, daß GSX zwar hervorragend den Drucker, aber nur unzulänglich den Bildschirm unterstützt. Ein besonderer Dorn im Auge war uns dabei die fehlende Möglichkeit, einmal gezeichnete Linien später wieder löschen zu können. Der einzige Weg bestand lange Zeit darin, den ganzen Bildschirm zu löschen. Aber das konnte auf Dauer keine Lösung sein.

Und dann erschien das JOYCE-Sonderheft Nr. 1 mit einem Artikel über das XBIOS (Extended BASIC Input Output System). Dort war genau nachzulesen, wo der Bildschirmspeicher liegt, wie er aufgebaut ist und vor allem, wie man an ihn herankommt. Das ist nämlich gar nicht so einfach.

Grundlagen

Die Z80 CPU kann nur 64 KByte direkt adressieren, der JOYCE hat aber 256 KByte RAM. Damit der gesamte Speicherbereich genutzt werden kann, wird er in vier Bänke zu je 64 KByte aufgeteilt. Für die CPU ist immer nur eine Bank existent. Die anderen Bänke können nur über das sogenannte Bankswitching erreicht werden. Dabei wird einfach eine Bank ausgeblendet, und eine andere erscheint an ihrer Stelle. BASIC liegt in Bank 1, der Bildschirmspeicher befindet sich aber in Bank 0. Darum ist er auch normalerweise von BASIC aus nicht zu erreichen. Anders sieht es für ein Maschinenspracheprogramm im Common-Bereich aus (der Common-Bereich wird beim Bankswitching nicht verändert). Von dort kann die XBIOS-Routine #35 (Screen-Run-Routine) aufgerufen werden, die BASIC aus- und den Bildschirmspeicher einblendet. Gleichzeitig sind der sogenannte Roller-RAM und der Character-RAM erreichbar. Der Artikel im JOYCE Sonderheft Nr. 1 beschrieb den Aufbau von Bildschirmspeicher und Roller-RAM aber nur sehr ungenau. Klar war von Anfang an eigentlich nur, daß der Bildschirmspeicher bitmapped aufgebaut ist. D.h., daß jedem Pixel auf dem Bildschirm ein Bit im Speicher entspricht. Außerdem sollten im Roller-RAM die Adressen der einzelnen Pixelreihen stehen. Das ist eine der Besonderheiten des JOYCE. Die Daten im Bildschirmspeicher stellen zwar ein Abbild des Bildes auf dem Schirm dar, aber wo sie auf dem Schirm erscheinen, wird vom Roller-RAM bestimmt. Das hat den Vorteil, daß beim Scrollen nicht der gesamte Bildschirmspeicher umgeschrieben werden muß. Geändert werden lediglich die Adressen der Pixelreihen im Roller-RAM. Das Roller-RAM gibt beim Bildschirmaufbau also an, wo die Daten für die einzelnen Pixelreihen im Speicher stehen. Wie das alles aber nun genau organisiert ist, das war vorerst noch äußerst unklar. Mit folgender kleinen Routine haben wir uns dann daran gemacht, die Sache genauer zu untersuchen:
ORG #F000 BEGIN LD
BC,ADRESS
CALL #FC5A
DW #00E9
RET ADRESS LD HL,QUELLE
LD DE,#F100
LD BC,#0200
LDIR
RET

Die Routine lädt ab einer mit QUELLE definierten Stelle 512 Byte von Bank 0 in den Common-Bereich. Dort können die Werte dann auch von BASIC verarbeitet werden. Zunächst haben wir versucht, die Anfangsadresse des Bildschirmspeichers zu finden. Dazu haben wir von BASIC aus den Bildschirm gelöscht, verschiedene Werte für QUELLE in den Speicher gepoked und uns dann das Resultat im Common-Bereich angesehen. Bald war die Stelle gefunden, an der über einen großen Bereich nur noch Nullen folgten. Der Bereich begann bei #5930. Jetzt ging es darum, den genauen Aufbau einer Pixelreihe herauszufinden. Unsere Überlegung war, daß nach dem Einschalten und bevor der Bildschirm gescrollt wird, der Bildschirmspeicher noch "in Ordnung" ist, d.h. in den ersten Bytes auch das erste Zeichen auf dem Schirm codiert ist. Wir gingen dann wie oben vor, nur daß wir nach dem Löschen genau ein Zeichen in die linke obere Ecke des Bildschirms schrieben und daß wir das Programm gleich nach dem Booten gestartet haben. Es stellte sich dann heraus, daß nicht etwa (wie es die Beschreibung des Roller-RAMs vermuten ließ) nacheinander von den ersten 90 Zeichen jeweils die oberste Reihe im Speicher stand, sondern, daß die ersten acht Byte genau für das erste Zeichen standen. Im Bildschirmspeicher stand also eine Kopie des Zeichens aus dem Character-RAM. Jetzt wollten wir wissen, wie denn dann aber das Roller-RAM organisiert ist. Die Startadresse des Roller-RAMs ist #B600, jedenfalls behauptete das der Artikel. Also mußten wir mal wieder CP/M booten, BASIC laden und uns dann mit unserem Programm den Bereich ab #B600 ansehen. Auf den ersten Blick sah es so aus, als ob wir einer Ente aufgesessen wären. Jeweils zwei Byte als Adresse gewertet, ergaben Zahlen wie #2C98. Aber dort lag der Bildschirmspeicher doch gar nicht. Allerdings lagen alle Werte in einem festen Bereich, und das war dann doch ein Zeichen dafür, daß es sich tatsächlich um den Roller-RAM handelte. Des Rätsels Lösung ergab sich schließlich aus den Differenzen. Die erste Zwei-Byte-Adresse sollte ja auf den Beginn der ersten Pixelreine zeigen, die neunte mußte logischerweise auf den Beginn der ersten Pixelreihe der zweiten Bildschirmzeile zeigen. Da in einer Zeile 90 Zeichen stehen und ein Zeichen von acht Byte dargestellt wird, ergibt sich eine Differenz von 720 Byte. Die Differenz zwischen der neunten und der ersten Zwei-Byte-Adresse betrug aber tatsächlich 360 Byte, genau die Hälfte. Die Adressen im Roller-RAM müssen also erst verdoppelt werden, wenn man die entsprechende Stelle im Bildschirmspeicher finden will. Und schon ist man reingefallen. Es gibt da nämlich noch ein kleines Problem (und das übergeht Matthias Uphoff in seinem Artikel in Heft 6 dieses Jahres). Zur Erinnerung: Im Bildschirmspeicher liegen die acht Byte, die ein Zeichen darstellen, direkt hintereinander. Nach dem Booten steht das Byte für die oberste Pixelreihe des ersten Zeichens auf dem Schirm in #5930, das für die zweite Reihe in #5931 usw. Diese Adressen beziehen sich aber nicht nur auf das erste Zeichen, sondern sind die Anfangsadressen der gesamten ersten bzw. zweiten Pixelreihe. Die sollten aber auch im Roller-RAM stehen, allerdings durch zwei geteilt. Und da liegt der Hase im Pfeffer. Teilen Sie mal #5931 durch 2! Das Ergebnis läßt sich nicht als Zwei-Byte-Zahl darstellen. Tatsächlich stehen im Roller-RAM aufeinanderfolgende Adressen, also #2C98, #2C99, ..., #2C9F für die ersten acht Pixelreihen. Der erste Wert verdoppelt ergibt genau die gewünschte Adresse, beim zweiten ist das Ergebnis um 1 zu groß, beim dritten um 2 usw. Das ist aber so regelmäßig, das sich daraus leicht eine Formel ableiten läßt. Wenn die oberste Pixelreihe die Nummer Null erhält, gilt folgende Formel:
Adresse im Bildschirmspeicher =
Adresse im Roller-RAM * 2
- Zeilennummer Modulo 8

Und jetzt stimmt es wirklich, daß im Roller-RAM die Anfangsadressen der Pixelzeilen stehen. Auf dieser Grundlage entstanden die Maschinencode-Routinen im Programm "Screeny". Wer aufmerksam das Quellcode-Listing liest, wird die Umsetzung der Formel erkennen. Die Maschinencode-Routinen sind so gestaltet, daß sie problemlos in andere Assemblerprogramme eingebunden werden können.

Das Programm "Screeny"

ermöglicht pixelweise Bildschirmgrafik ohne GSX.
Es beinhaltet, neben zwei Demos dafür, insgesamt vier Routinen, die kurz erläutert werden sollen. Die Laderoutine ab Zeile 50000 lädt die Programmteile
"SETCHAR" (ab Zeile 50030),
"SETPOINT" (ab Zeile 51000),
"DRAW" (ab Zeile 52000) und
"POINT2" (ab Zeile 53000).
Ab Zeile 54000 werden die Variablen der Startadressen festgelegt. Mit der Routine "SETCHAR" kann man ASCII-Zeichen an jede beliebige Bildschirmposition setzen.
"SETCHAR" hat folgende Syntax: CALL setchar (zeile%, spalte%, zeichennummer%)
Die Argumente in der Klammer müssen Integer-Variablen sein, damit sie von der Maschinensprachroutine richtig definiert werden. Der Bildschirm besteht ja bekanntlich aus 256*720 Pixeln. Sie werden jeweils zeilen- und spaltenweise angesprochen. Für "zeile%" kann 0-248, für "spalte%" 0-712 als Variable eingesetzt werden. Diese Begrenzung ergibt sich daraus, daß jedes Zeichen aus 8*8 Pixeln besteht. Für "zeichennummer%" wird der jeweilige ASCII-Code-Wert eingesetzt.
Mit der Routine "SETPOINT" kann man einzelne Pixel ansprechen durch: CALL setpoint (zeile%, spalte%, modus%)
Hier liegen die Variablen für "zeile%" zwischen 0 und 255, für "spalte%" zwischen 0 und 719. Außerdem gibt es drei Modi:
0: Löschen
1: Setzen
2: Austauschen
"SETCHAR" und "SETPOINT" können alleinstehend als Unterprogramm verwendet werden. "POINT2" und "DRAW" gehören zusammen und benutzen "SETPOINT" als Unterprogramm. Zum Zeichnen einer Linie werden so alle drei Teile benötigt.
Mit "POINT2" wird zuerst der Zielpunkt festgelegt. Falls mehrere Linien den gleichen Zielpunkt haben, muß dieser nicht vor jedem Aufruf neu festgelegt werden, sondern kann für alle Linien verwendet werden. "POINT2" hat die Syntax: CALL point2 (zeile%, spalte%)
Die Variablen gehen zeilenweise wieder von 0-255 und spaltenweise von 0-719.
"DRAW" zeichnet eine Linie im jeweiligen Modus (0, 1 oder 2) von dem angesprochenen Punkt zu dem mit "POINT2" bestimmten Zielpunkt. Die Syntax: CALL draw (zeile%, spalte %, modus%) (Variablen wie gehabt!)
"POINT2" muß außer bei der erwähnten Mehrfachbenutzung immer vor "DRAW" aufgerufen werden.
Zur Demonstration für die Grafikmöglichkeiten beinhaltet das Programm "Screeny" eine Analog/Digital-Uhr (Demo 1) sowie eine "Apfelmännchen"-Grafik (Demo 2), bei der das Software-Experiment aus Heft 7/86 für den JOYCE umgesetzt wurde.
Bild 2: Die Uhr-Demo zeigt die SCREENY-Spezialitäten anschaulich...
Demo 2: "Apfelmännchen"-Grafik
Durch das Drücken irgendeiner Taste kommt man jeweils ins Auswahlmenü zurück! Hier nur noch einige Bemerkungen zur Demo 2: In den Zeilen 2080-2120 werden die ASCII-Zeichen 0-3 umdefiniert, um eine optimale Bildschirmauflösung zu erzielen. Es werden zwar jeweils 8*8 Pixel gezeichnet, davon werden aber nur 1*3 Pixel zur Farbabstufung angesprochen, die restlichen werden überzeichnet.
0 : ...(ganz leer)
1 : .X.
2 : X.X
3 : XXX(ganz voll)
Die Erstellung der Grafik kann je nach Iterationstiefe (Zeile 2200) sehr lange dauern. Die Spiralmustergrafik (siehe PC 2/87 S. 113) dauert bei Iterationstiefe 200 ca. 2 Tage!
(U. Baumgarten/J. Steingräber)
Hinweise zum Abtippen des Programms:
Die links neben den Zeilennummern befindlichen Zahlen in spitzen Klammern nicht mit abtippen, es handelt sich hierbei um Prüfsummen für den Checksummer aus Heft 2/87. Die geschweiften und eckigen Klammern und den senkrechten Strich im Lisling durch die jeweiligen Umlaute ersetzen.

Nach dem Abtippen mit SAVE"SCREENY.BAS" abspeichern! Start des Programms: RUN"SCREENY"

LISTING >SCREENY <, REMARK = >'<.
[BASIC source]

LISTING >ROUTINEN<, REMARK = >;<.
[The individual routines for the M80 assembler: SETCHAR, SETPOINT, DRAW, POINT2 and additionally character definition (line 2080 in the BASIC program) ]

Scanned by Werner Cirsovius
January 2005
© DMV-Verlag