Assemblerkurs, Teil 4

Unser nun bereits vierter Ausflug in die Assemblerprogrammierung führt uns endlich in die wunderbare Welt der Grafik. Während wir zunächst den bequemen Weg über die BIOS-Routinen beschreiten, wollen wir uns anschließend durch den gefürchteten Hardwaredschungel schlagen und in kaum erschlossene Performance-Dimensionen vordringen.

Graf Ikmodus läßt bitten

Der Displaycontroller des Portfolio (HD61830 von Hitachi) verfügt neben dem wohlbekannten Textmodus auch über einen Grafikmodus, in dem sich alle 240x64 Pixel beliebig ein- oder ausschalten lassen. Zum Umschalten des Videomodus bedienen wir uns der Funktion 0 (ah=0) des BIOS-Interrupts 10h:

    mov ax,6    ; Grafikmodus
    int 10h
    ...
    mov ax,0    ; Textmodus
    int 10h

Die Unterfunktion 6 (al=6) aktiviert bei CGA-und VGA-Karten den Monochrom-Grafikmodus mit einer Auflösung von 640x200 Pixeln. Man kann sich vorstellen, daß der Bildschirm des Portfolio hiervon nur die linke obere Ecke darstellt. Manche andere Grafikmodi (z.B. al=0Ah) sind ebenfalls verwendbar, wobei aber beim Portfolio keine Unterschiede existieren. Zurück in den Textmodus gelangt man beispielsweise durch Aktivieren des Modus 0 ("BW40"). Auch hierzu gibt es gleichwertige Alternativen, wie z.B. den Modus 3 ("CO80").

Auf den Punkt gebracht

Wenn der Grafikmodus aktiviert wurde, stehen die Funktionen 0Ch und 0Dh von Int 10h zur Verfügung, um den Zustand einzelner Bildpunkte zu setzen bzw. auszulesen. Die gewünschten Koordinaten müssen sich in den Registern cx und dx befinden und beziehen sich auf die linke obere Bildschirmecke als Ursprung. Das Register al enthält die "Farbe" des Pixels, wobei (nur) der Wert 0 dem abgeschaltetem Zustand entspricht. Werte ab 128 bewirken beim Pixel-Setzen übrigens eine Invertierung.
Das folgende Beispiel aktiviert zunächst das Pixel bei (x=100;y=50), löscht es sofort wieder, und liest schließlich seine Farbe aus, was zu al=0 führen muß:

    mov cx,100    ; x = 0..239
    mov dx,50     ; y = 0..63
    mov ax,0C01h  ; Pixel setzen
    int 10h
    dec ax        ; wieder löschen
    int 10h       ; (ax=0C00h)
    mov ah,0Dh    ; Pixelfarbe
    int 10h       ; ermitteln

Ein photographisches Gedächtnis

Konventionelle PC-Grafikkarten besitzen traditionell einen Bildspeicher, der dem Prozessor im Adressraum z.B. ab B800:0 (je nach Karte und Modus) direkt zugänglich ist. Der LCD-Controller des Portfolio verfügt dagegen über ein separates 2 KByte großes RAM ohne direkte Zugriffsmöglichkeit durch den Prozessor. Um trotzdem die Kompatibilität mit Anwendungen zu gewährleisten, die direkt in den Bildspeicher schreiben, bietet der Portfolio bekanntlich in seinem System-Menü verschiedene Optionen für den Bildaufbau. 4 KByte des installierten Hauptspeichers (beim Standard-Pofo bleiben deshalb nur 124 KByte verfügbar) sind hierzu 16 mal hintereinander in den Speicherbereich zwischen B000:0 und C000:0 eingeblendet.
Je nach gewählter Bildaufbau-Option wird nun timergesteuert oder bei jedem Tastendruck der dortige "Phantombildspeicher" ins LCD übertragen. Die Größe von 4 KByte ergibt sich aus der Repräsentation des Textbildschirms durch 80x25x2=4000 Bytes (Je Zeichen: ASCII-Code und Attribut).
Der Textmodus einer herkömmlichen Grafikkarte wird auf diese Art recht brauchbar emuliert. Zwar unterhält das BIOS auch im Grafikmodus einen Bildspeicher bei B800:0, doch weicht dieser leider in seiner Organisation vom PC-Standard ab, so daß PC-Programme, die den Videospeicher zur Grafikdarstellung direkt ansprechen, nicht verwendbar sind. Da ein Byte des Videospeichers 8 Pixel repräsentiert, benötigt beim Portfolio jede Pixelzeile (240 Pixel) 30 Bytes. Alle 64 Pixelzeilen des Displays werden lückenlos hintereinander abgelegt, so daß der gesamte Grafikbildschirm in 1920 Bytes - der Länge einer PGF-Datei - untergebracht wird. Dieses vom BIOS verwaltete Abbild des Grafikbildschirms bietet im wesentlichen nur den Vorteil, daß der Zustand einzelner Pixel nicht nur gesetzt, sondern auch ausgelesen werden kann. Der Versuch, über entsprechende Befehle des LCD-Controllers an den eigentlichen Bildspeicher heranzukommen, scheitert nämlich (ich lasse mich aber gerne vom Gegenteil überzeugen).

Verkehrte Welt

Völlig unverständlich, weil ineffizient, ist die Art, auf die jeweils 8 Pixel vom BIOS zu einem Byte zusammengefaßt werden. Obwohl wie gesagt die Organisation des Bildspeichers inkompatibel zu allen PC-Grafikmodi ist, ordneten die Entwickler in Anlehnung zum PC dem linken Pixel einer Achtergruppe das höchstwertige Bit im Byte zu. Dagegen wäre nichts einzuwenden, wenn nicht der LCD-Controller genau die umgekehrte Bitreihenfolge verwenden würde. Darunter leidet bereits die Performance der "Putpixel"-Funktion (0Ch) von Int 10h. Regelrecht bei der Arbeit zusehen kann man aber der "Refresh"-Funktion (12h) von Int 61h, die den Grafikspeicher zum LCD überträgt. Die Bitmuster der Achtergruppen werden dazu auf bemerkenswert langsame Weise gespiegelt. Es sind deshalb diverse alternative Routinen bekannt, die mit Leichtigkeit eine Beschleunigung um den Faktor 10 oder mehr erzielen (siehe [1] und [2]). Im Grunde kann man aber auch sehr gut ganz auf diese Art der Refresh-Funktion verzichten, indem man einen eigenen Bildspeicher anlegt, der eine identische Kopie des LCD-RAMs enthält. Dieser Bildspeicher könnte sich durchaus bei B800:0 befinden, einfacher ist es jedoch, direkt im Datensegment 1920 Bytes zu reservieren. Abbildung 1 veranschaulicht hierzu die Organisation des LCD-Bildspeichers.
Abbildung 1

Wenn das komplette Bild am Stück berechnet werden kann und nicht wieder ausgelesen werden muß, kann man davon absehen, überhaupt ein Abbild der Grafik im Arbeitsspeicher zu erzeugen und stattdessen die Bilddaten direkt Byte für Byte dem LCD-Controller übergeben. Wenn die Bilddaten noch nicht komplett im Speicher vorbereitet wurden, kann zudem die ohnehin nötige Wartezeit (ca. 25 Taktzyklen beim Standard-Pofo, getunt entsprechend mehr, siehe auch [6]) zwischen den Zugriffen auf den LCD-Controller sinnvoll genutzt werden. Dieser schnellen Variante habe ich in dem Spiel "FoliDash" den Vorzug gegeben.

Wie sag ich's meinem Controller?

Die Kommunikation mit dem bereits mehrfach angesprochenen LCD-Controller des Portfolio erfolgt über nur zwei Portadressen:

    8010h = Datenregister
    8011h = Befehlsregister

Ein Kommando wird übergeben, indem zuerst ein gültiger Kommandocode in das Befehlsregister und anschließend das zugehörige Argument ins Datenregister geschrieben wird. Für unsere Zwecke sind folgende Kommandos des HD61830 von Interesse:

     8 = Bildspeicher-Startadresse Low festlegen
     9 = Bildspeicher-Startadresse High festlegen
    10 = Cursorposition Low festlegen
    11 = Cursorposition High festlegen
    12 = Byte(s) zum LCD übertragen
    14 = Bit setzen
    15 = Bit löschen

Die weiteren Erläuterungen und Programmbeispiele setzen voraus, daß der LCD-Controller wie beschrieben in den Grafikmodus versetzt wurde.
 
Die Kommandos 8 und 9 legen fest, ab welcher Adresse im 2048 Byte großen LCD-Video-RAM die 1920 dargestellten Bytes auszulesen sind. Das Bild kann um bis zu 4 Pixelzeilen ohne Überlappung nach oben verschoben werden, weswegen sich diese Technik zur Realisierung eines pixelgenaues vertikalen Scrollings anbietet.
Im folgenden Beispiel soll der Bildschirm um 1 Pixelzeile gescrollt werden. Obwohl die neue Startadresse nur 30 lautet, muß laut Datenblatt auch das höherwertige Byte, also 0, geschrieben werden.

    mov dx,8011h
    mov al,8      ; Set AdrLow
    out dx,al
    mov al,30     ; 1 Pixelzeile
    dec dx
    out dx,al
    inc dx
    mov al,9      ; Set AddHigh
    out dx,al
    mov al,0
    dec dx
    out dx,al

Analog zur Auswahl der Startadresse des Bildspeichers geschieht über die Kommandos 10 und 11 die Festlegung der aktuellen "Cursorposition". Gemeint ist die Adresse im Video-RAM, auf die sich die Kommandos 12, 14 und 15 beziehen. Es ist zweckmäßig, ein Unterprogramm zur Cursorpositionierung etwa in der folgenden Art zu definieren:

Set_LCD_Cursor:
    ; Eingabe: si=Adresse
    push ax
    push dx
    mov dx,8011h
    mov al,10     ; Set CursorLow
    out dx,al
    mov ax,si
    dec dx
    out dx,al
    inc dx
    mov al,11     ; Set CursorHigh
    out dx,al
    mov al,ah
    dec dx
    out dx,al
    pop dx
    pop ax
    ret

Soll ein kompletter Bildaufbau folgen, muß die Cursoradresse auf null gesetzt werden:

    xor si,si     ; si=0
    call Set_LCD_Cursor

Dagegen adressiert das nächste Beispiel eine Pixel-Achtergruppe nahe der Bildschirmmitte (x=15*8; y=32):

    mov si,15+32*30
    call Set_LCD_Cursor

Das Kommando 12 leitet die Übertragung von Bilddaten ein. Im Anschluß daran dürfen beliebig viele Bytes (z.B. 1920) nacheinander ins Datenregister geschrieben werden, wobei aber, wie erwähnt, zwischen den Portzugriffen eine gewisse Wartezeit einzuhalten ist. Man bedenke hierbei, daß ein einziger Prozessorbefehl mit Speicherzugriff bereits etwa 12 Takte benötigt (wie z.B. LODSB). Wer ganz sicher gehen will, kann das Busyflag des LCD-Controllers als Bit 7 von Port 8011h auslesen. Hier zur Demonstration eine Routine, die den Bildschirm mit einem vertikalen Linienmuster füllt und dieses dann ständig invertiert.

    mov ah,55h   ; Bitmuster
Effekt:
    xor si,si
    call Set_LCD_Cursor
    mov dx,8011h
    mov al,12    ; Daten an-
    out dx,al    ; kündigen
    mov cx,1920  ; Byte-Anzahl
L:  in al,dx
    test al,dh   ; Busy?
    jnz L
    dec dx
    mov al,ah    ; Byte
    out dx,al    ; schreiben
    inc dx
    loop L
    xor ah,255   ; invertieren
    jmp Effekt

Das Ergebnis - ein nervöses Flimmern - beweist, daß der byteweise Transfer von Bilddaten sehr effizient ist.
Trotzdem hat in manchen Fällen (Grafiken mit geringer durchschnittlicher "Schwärzung") das gezielte Setzen und Löschen einzelner Pixel seine Berechtigung. Unter Verwendung der Kommandos 14 und 15 des LCD-Controllers läßt sich relativ einfach eine "PutPixel"-Routine formulieren, die  in ihrer Anwendung der Funktion 0Ch von Int 10h entspricht. Die Ausführung von [7] verzichtet auf die Aktualisierung des Grafikspeichers bei B800:0 und die damit verbundene Invertierungsmöglichkeit (Farbe 128), erreicht dafür aber ungefähr die vierfache Geschwindigkeit des BIOS-Pendants. Man sollte sich allerdings darüber im Klaren sein, daß solcherart generierte Grafiken für Screen-Grabber-Programme unsichtbar bleiben.
Zur Funktionsweise der Kommandos 14 und 15 ist anzumerken, daß zunächst wieder die gewünschte Cursoradresse eingestellt werden muß, und zwar nach wie vor bytebezogen. Das zum Kommando gehörige Argument legt lediglich die Bit-Nummer innerhalb des spezifizierten Bytes fest und darf somit Werte von 0 bis 7 annehmen.
Sofern die Startadresse des Bildspeichers nicht verändert wurde (Scrolling!), aktiviert der nachfolgende Programmausschnitt das Pixel in der rechten unteren Bildschirmecke:

    mov si,1919
    call Set_LCD_Cursor
    mov dx,8011h
    mov al,14    ; 14=Set, 15=Clear
    out dx,al
    dec dx
    mov al,7     ; Bit-Nummer
    out dx,al
 
Vorschau

So viel zum Thema Grafik auf dem Portfolio. Weitere Informationen rund um den LCD-Controller finden sich in [3], [4] und [5]. Fragen und Anmerkungen bitte an .
Ganz im Sinne von Multimedia geht es im nächsten Teil weiter mit Wissenswertem über die Tonerzeugung beim Portfolio.

Referenzen:

[1]    Prozedur "grefresh" der Pascal-Unit auf Gunnar Thöles WWW-Seite
[2]    Newsletter von Paul Jolliffe, Ausgabe 4
[3]    Newsletter von Paul Jolliffe, Ausgabe 5
[4]    Newsletter von Paul Jolliffe, Ausgabe 9
[5]    Datenblatt zum HD61830: "H9CD0.PDF"
[6]    Programm LCD_TEST.COM auf meiner WWW-Seite (http://leute.server.de/peichl/pf.htm)
[7]    Assembler-Routine "PutPixel" auf meiner WWW-Seite



Zum 3. Kursteil
Zum Hauptmenü