Im Magazin „CT" wurde im Heft 6 1988 der folgende Artikel abgedruckt.
Eine Grafik-Bibliothek ohne GSX.

Die Grafik rollt

Grafiktreiber für Joyce unter CP/M Plus

Frank-Christian Baum
Zu den wenigen Computern, für die CP/M in der Version 3.0 als Betriebssystem (mit)geliefert wird, gehört neben dem CPC 6128 auch der Joyce von Schneider. Sauber 'BDOS-programmierte' Software ist auf beiden Rechnern lauffähig. Bei direkten Bildschirmzugriffen hört die Einigkeit allerdings auf. Was würde sich für eine Demonstration der Joyceschen Bildschirm-RAM-Organisation besser eignen als ein Grafiktreiber, der dazu noch mehr kann als der zum GSX-System mitgelieferte?

Zwar preist die Firma Schneider die Joyce-Modelle PCW 8256 und 8512 als Textsysteme an, daß sie aber auch zu anderen Zwecken benutzt werden können, belegt allein schon die Tatsache, daß beim Kauf gleich das Betriebssystem CP/M Plus mitgeliefert wird. Zum Umfang des Systems gehört neben vielen nützlichen und weniger nützlichen Programmen auch die grafische Systemerweiterung GSX, die schon in früheren Ausgaben von c't eingehend erläutert wurde [4]4.

Der Vorteil von GSX besteht darin, daß man seine Grafiken auf den unterschiedlichsten Geräten wie Drucker, Bildschirm oder Plotter ausgeben lassen kann, ohne daß man sich weiter um die gerätespezifischen Koordinatensysteme zu kümmern braucht. Programmiert wird in einem imaginären Koordinatensystem, die Anpassung an die real existierenden Koordinatensysteme übernimmt die Treibersoftware der einzelnen Geräte.

Beim Joyce gehören vier dieser Treiber zur Grundausstattung, wobei allerdings der Bildschirmtreiber sehr mager ausfällt. So bietet GSX unter anderem die Möglichkeit, Flächen mit verschiedenen Mustern zu füllen, Text in unterschiedlichen Richtungen zu drucken und Teile des Bildschirms abzuspeichern. Realisiert wurden diese Fähigkeiten aber nur bei den beiden Druckertreibern. Der vierte, ein Plotter-Treiber, konnte mangels Plotter nicht getestet werden.

Teile und schalte

Will man also die grafischen Möglichkeiten des Joyce voll ausnutzen, bleibt nichts anderes übrig, als einen neuen Treiber zu kaufen oder einen eigenen zu schneidern.

Entscheidet man sich für letzteres, sollte man sich zunächst einmal die Speicheraufteilung unter CP/M Plus näher ansehen. Sie ähnelt sehr stark der des CPC 6128 oder mehr noch dem erweiterten CPC 6512 (siehe [6,7]).

Die Speicherorganisation der ersten 128 KByte beim Joyce unter CP/M Plus

Die 256 beziehungsweise 512 KByte sind in Blöcke zu je 16 KByte unterteilt, und da die Z80-CPU nur 64 KByte direkt adressieren kann, sind diese wieder zu Bänken zusammengeschaltet.

Bank 0 enthält neben Teilen des Betriebssystems auch das Bildschirm-RAM. Die TPA, in der alle Programme ablaufen, ist in Bank 1 untergebracht, und in Bank 2 sind weitere Teile des Systems verstaut. Die restlichen Blöcke 9 bis 15 (PCW 8256) beziehungsweise 9 bis 31 (PCW 8512) werden als RAM-Disk genutzt.

In den oberen 16 KByte des Speichers ist bei allen Bänken der gleiche Block eingeblendet. Dieser Teil heißt Common-RAM. Hier sind sozusagen unter dem Dach Teile des BDOS sowie ab FC00h die BIOS-Sprungleiste untergebracht. Diese Sprungleiste besteht aus Sprungbefehlen der Form 'JMP adresse' zu den einzelnen BIOS-Funktionen; so steht ab FC00h der Sprungbefehl zur BIOS-Funktion 0 (Kaltstart), ab FC03h der Sprungbefehl zur Funktion 1 (Warmstart) und so fort...

Gewöhnlich sind in CP/M-Plus-Systemen 29 BIOS-Funktionen implementiert, Funktion 30 (USERF) ist für den Benutzer reserviert. Amstrad hat sie denn auch benutzt. Hier wird diese Funktion zum Aufruf weiterer BIOS-Routinen benutzt, die das erweiterte BIOS, kurz auch XBIOS genannt, bilden. Wir haben sie schon einmal beim CPC 6128 beschrieben [7].

Durch die vom XBIOS zur Verfügung gestellten Routinen ist es dem Programmierer zum Beispiel möglich, die serielle Schnittstelle zu initialisieren, die Tastatur neu zu belegen und, was für das hier vorgestellte Programm wichtig ist, den Bildschirmspeicher zugänglich zu machen. Der Aufruf der XBIOS-Routinen erfolgt mittels CALL USERF, wobei die Einsprungadresse für USERF bei FC5Ah liegt. Hinter dem Aufruf CALL USERF erwartet die Routine USERF zwei Bytes, die die gewünschte XBIOS-Funktion spezifizieren.

Verlangen die Funktionen Parameter, so werden diese in den Registern der CPU übergeben oder in einer Parametertabelle im Common-Bereich abgelegt. Eine detaillierte Beschreibung der XBIOS-Funktionen kann man in [1] nachlesen.

Versucht man, mittels Bank-Switching direkt in den Bildschirmspeicher hinein zu schreiben, so erzeugt der Joyce meist ein recht ansprechendes Muster auf seiner Mattscheibe und entschwindet dann in den siebenten Computerhimmel. Zurückholen läßt er sich nur durch einen Reset oder, falls besonders empfindsame Teile des RAMs getroffen wurden, durch Betätigung des Ein/Aus-Schalters.

Abhilfe schafft hier die XBIOS-Funktion SCR_RUN_ROUTINE, die alle für den direkten Zugriff auf den Bildschirmspeicher nötigen RAM-Bereiche zusammenschaltet und ein im Common-Block abgelegtes Unterprogramm ausführt. Die Adresse des Unterprogramms wird vor dem Aufruf der XBIOS-Routine im BC-Register abgelegt, alle anderen CPU-Register können zur Parameterübergabe an das Unterprogramm benutzt werden. Somit braucht man sich bei direktem Zugriff auf den Bildschirmspeicher über diese XBIOS-Routine nicht mehr um das lästige Bank-Switching zu kümmern.

Rollerball im RAM

Der Bildschirm des Joyce hat eine Auflösung von 720 x 256 Pixel, wobei die linke obere Ecke die Koordinaten 0/0 und die rechte untere Ecke die Koordinaten 255/719 besitzt. Der Bildschirmspeicher selbst liegt zwar immer fest im Bereich ab 4000h, jedoch ist die Adresse einer Pixelzeile variabel. Die Adressen der 256 Pixelreihen werden von einem besonderen Speicherbereich verwaltet, dem Roller-RAM, welches in Bank 0 ab Adresse B600h zu finden ist.

Zum Auslesen des Bildschirmspeichers sucht sich der Video-Controller des Joyce zunächst die Adressen der Pixelreihen im Roller-RAM, ehe er zur eigentlichen Tat schreitet. Auf diese Art ist zwar ein sehr schnelles Scrolling möglich, da ja im Speicher nur die Adressen der Pixelreihen vertauscht werden müssen und nicht die Pixelreihen selbst, aber leider ist diese Organisation des Bildschirms nicht gerade dazu prädestiniert, auf einzelne Pixel direkt zuzugreifen.

Zudem kommt erschwerend hinzu, daß im Speicher nicht die einzelnen Bytes einer Pixelreihe aufeinander folgen, sondern jeweils die 8 Bytes eines Zeichens einer Textzeile, das heißt, nach den 8 Bytes des ersten Zeichens einer Textzeile folgen die 8 Bytes des zweiten Zeichens einer Textzeile, des dritten... Außerdem muß man die Adressen aus dem Roller-RAM erst mit 2 multiplizieren, damit sie in den Bildschirmspeicher zeigen.

Pixel-Schneider

Doch nun zum Programm. Der Kern des Programms, der den direkten Zugriff auf den Bildschirmspeicher in Bank 0 ermöglicht und selbst im Common-Block stehen muß, wurde in Z80-Assembler geschrieben, da er sich in Pascal oder anderen Hochsprachen nur schwer oder gar nicht realisieren läßt. Dieser Programmteil (PIXEL.MAC) wird von Turbo-Pascal aus als externe Funktion angesprochen und erwartet als Parameter die x- und y-Koordinate des Pixels sowie die Speicheradresse, in der der aktuelle Modus (Pixel setzen, Pixel testen oder Pixel löschen) vermerkt ist. Um nun das entsprechende Pixel auch im Bildschirmspeicher zu finden, werden von der externen Funktion die angegebenen Bildschirmkoordinaten (0..719, 0..255) in Textkoordinaten (0..89, 0..31) umgerechnet.

Zunächst wird die Textzeile, in der sich das betreffende Pixel befindet, berechnet:

tzeile = y DIV 8

Die Adresse im Roller-RAM ergibt sich aus

rolladr = $B600 + tzeile * 16

Die Textspalte des Pixels berechnet sich nach

tspalte = x DIV 8
tspalte = tspalte * 8 + y MOD 8

Schließlich muß noch eine entsprechende Bitmaske erstellt werden:

bit = x MOD 8
maske = 128 Shr bit

Die reale Bildschirmadresse errechnet sich aus

scradr = (rolladr) * 2 + tspalte

Nachdem diese Berechnungen ausgeführt sind, prüft die Funktion noch, welcher Modus gewählt wurde. Hier bedeutet eine '0' Pixel löschen, eine '1' Pixel setzen, bei anderen Werten wird getestet. Je nachdem, ob das betreffende Bit gesetzt war oder nicht, wird beim Testen TRUE oder FALSE zurückgegeben, sonst ein nicht definierter Wert.

Aufmerksame Beobachter werden beim Betrachten des Listings schon gemerkt haben, daß dort nicht nur die Routinen zur Pixelmanipulation untergebracht sind, sondern zusätzlich eine Prozedur zum Laden einer Bildschirmzeile in einen Puffer im Common-Block sowie eine Prozedur, die den Pufferinhalt in den Bildschirmspeicher schiebt. Mit diesen beiden kurzen Routinen ist es möglich, den Inhalt des Bildschirms auf Diskette zu speichern und wieder zu laden.

Da beide Prozeduren als Parameter die gewünschte Textzeile verlangen, muß man nicht immer den ganzen Bildschirm laden oder speichern, sondern kann sich auch auf bestimmte Teile beschränken. Vorsicht! Das Speichern des ganzen Bildschirms belegt auf Diskette immerhin 23 KByte.

Rein ins RAM

Dazu benötigt man einen Z80-Assembler, der verschiebbaren Code erzeugen kann. Nach dem Assemblieren ist der Objektcode nur noch zu linken, wobei man darauf achten sollte, daß die Ladeadresse (im Beispiel wurde F000h gewählt) im Common-Bereich liegt und sich nicht mit dem Zeilenpuffer, der ab Adresse C000h liegt, überschneidet. Die Adressen der Speicher- und der Lade-Routine muß man sich dann aus der Symbol-Liste des Assemblers besorgen und bei den External-Prozeduren LoadLine und SaveLine im Pascal-Programm eintragen. Bei einer Ladeadresse von F000h würde sich als Adresse für die Laderoutine F099h und für die Speicheroutine F077h ergeben.

M80-Benutzer können entweder mit .PHASE arbeiten oder beim Linken mit der entsprechenden Option die Anfangsadresse bestimmen ([LF000h] beim LINK.COM). Versieht man die Labels der Lade- und Speicher-Routine mit einem Doppel-Doppelpunkt, dann gibt LINK.COM diese mit Adresse beim Linken auf den Bildschirm aus.

Am besten wird dann das vom Linker erzeugte File PIXEL.COM in PIXEL.EXT umbenannt, damit auch nach einer Woche Computerenthaltsamkeit kein peinliches Mißverständnis aufkommt.

Der Rest des Bildschirmtreibers wurde in Turbo-Pascal geschrieben. Das Listing zu GRAPHIC.INC beinhaltet die Pascal-Grundroutinen zum Setzen, Löschen, Testen eines Punktes sowie zum Speichern und Laden einzelner Bildschirmzeilen auf und von Diskette.

Der Treiber kann individuell erweitert und ergänzt werden, zum Beispiel könnte man noch Linien-, Kreis- oder Füllprozeduren aufnehmen. Geeignet sind hierzu auch einige Routinen aus der Hercules-Grafik-Toolbox, die in c't 8/87 und 9/87 vorgestellt wurde. Als Beispiel wurde hier die Draw-Prozedur aus der Toolbox implementiert.

Eine Druckerausgabe zu installieren ist nicht weiter schwierig. Die einzelnen Pixelzeilen legt die Laderoutine ja im Zeilenpuffer ab. Von dort kann man die Pixel entweder pur oder ein bißchen auf Papierformat umgerechnet auf den Drucker ausgeben.

In eigene Programme läßt sich der Treiber einfach als Include-File einbauen. Hierbei ist nur darauf zu achten, daß zu Beginn des Programms die Prozedur GraphInit aufgerufen wird, die die externe Funktion unter dem Namen PIXEL.EXT erst mal auf dem aktuellen Laufwerk sucht und in den Speicher lädt. Zudem muß beim Erzeugen eines COM-Files die Endadresse im Turbo-Options-Menü kleiner als F000h eingestellt werden, da es sonst zu unliebsamen Überraschungen kommen kann.

Als Demo ist noch ein kleines Programm abgedruckt, das in sechs Minuten ein Apfelmännchen auf den Bildschirm zaubert, auf Diskette speichert und dann nach Löschen des Bildschirms wieder in selbigen lädt. Wem übrigens das Laden eines Bildes von Diskette zu langsam geht, der sollte es mal mit der RAM-Disk versuchen ...
(bb)

Literatur
[1]JOYCE Sonderheft Nr. 1, DMV-Verlag, Eschwege 1987
[2]Jürgen Hückstädt: CP/M-Plus Anwender-Handbuch CPC 6128/Joyce, Markt & Technik, Haar bei München 1986
[3]Holger Schmidt: Virtuoser Grafikhelfer, Teil 1 und 2, c't 8 und 9/87
[4]Martin Kotulla: GSX ohne Geheimnisse, c't 12/86 bis 3/87
[5]Schröer/Herr: Aus David wird Goliath, c't 10/87, Seite 156 ff.
[6]Schröer/Herr: Byte-Hirte, c't 11/87, Seite 146 ff.
[7]Holger Merk: CPC ruft Laufwerk B, c't 5/87, Seite 156 ff.

Der Assembler-Kern des Grafiktreibers ist kein ablauffähiges COM-Programm, sondern wird in das Turbo-Pascal-Programm eingebunden. Ein Pascal-Programm führt die Grafikberechnungen durch, während der externe Assembler-Teil die Pixel in das Bildschirm-RAM befördert. Apfelmännchen reißen nun wirklich niemand mehr vom Stuhl. Dieses Demoprogramm soll nur zeigen, wie der Grafiktreiber eingesetzt wird.

Eingescanned von Werner Cirsovius
Dezember 2002
© Heise Verlag