Artikel
Ingo Eickmann
Screen Splitting
Bildschirmteilung bei EGA- und VGA-Adaptern
EGA- und VGA-Adapter erlauben, gleichzeitig zwei Bildschirmseiten im Videospeicher abzulegen. Dies setzt eine zum Kathodenstrahl des Monitors synchronisierte Programmierung voraus. Assembler-, Turbo-Pascal- und MS-C-Freunde finden hier konkrete Programmbeispiele.
Die Aufteilung des sichtbaren Bildschirms auf zwei unterschiedliche
Bereiche des Videospeichers ist nicht neu. Diese Möglichkeit
besteht seit der Markteinführung der
EGA,
doch bisher hat sich
kaum jemand ernsthaft damit beschäftigt. Uns sind lediglich zwei
Programme – und zwar
EGA-/VGA-Demoprogramme
– bekannt, in denen diese Eigenschaft benutzt wird. Die geringe Bedeutung
des Screen-Splittings ist wohl darauf zurückzuführen, daß
erst wenige Programmierer hiervon Kenntnis haben. Anwendungsmöglichkeiten
für eine hardwaremäßige Aufteilung des sichtbaren
Bildschirms gibt es ja genug (siehe auch
[1]). Die beiden
Schlüsselregister für das Screen Splitting sind das
Startadreßregister (Index 0Ch und 0Dh) und das Line Compare
Register (Index 18h) des Cathode Ray Tube Controllers, abgekürzt
CRTC.
Das Startadreßregister enthält den Offset des
Bytes, das in der linken oberen Ecke des Bildschirms dargestellt
wird. Beispielsweise beginnt der Speicherbereich der ersten
Bildschirmseite im Videomodus 3 (80×25 Color) bei B800:0000h, der
Inhalt des Startadreßregisters ist 0000h. Wird der Inhalt der
zweiten Bildschirmseite, der bei B800:1000h beginnt, dargestellt, muß
das Startadreßregister auf 1000h gesetzt werden. Das
Startadreßregister ist aufgeteilt auf den Index 0Ch des CRTC
für das MSB
und auf den Index 0Dh für das LSB. Die
CRTC-Register werden programmiert, indem man den Index in Port 3D4h –
oder in den monochromen Modi – in Port 3B4h schreibt und den neuen
Registerinhalt in Port 3D5h beziehungsweise 3B5h. Bei
EGA-Karten kann
der adressierte Wert in Port 3D5h/3B5h nur geschrieben werden, bei
VGA-Adaptern
sind die Register auch lesbar.
Der CRTC
enthält einen internen Adreßzähler, der
während des Bildaufbaus automatisch mitgezählt wird und
immer auf den gerade dargestellten Speicherinhalt zeigt. Gleichzeitig
wird die Nummer der vom Kathodenstrahl gezeichneten Rasterzeile mitgezählt.
CRTC-Register
Stimmt
die Anzahl der gezeichneten Rasterzeilen mit dem Wert im
Line-Compare-Register
(CRTC
Index 18h) überein, so wird für
die nächste darzustellende Rasterzeile der interne Adreßzähler
auf 0000h zurückgesetzt. Von nun ab wird im verbleibenden
Bildschirmbereich der Anfang des Videobuffers dargestellt. Bei
gesplittetem Bildschirm wird im unteren Teil also immer der Anfang
des Videobuffers gezeigt.
Dies verdeutlicht Bild 1.
Das Screen Splitting arbeitet
völlig unabhängig vom Inhalt des Videospeichers,
hier sind keine Modifikationen notwendig. Es ist
ebenfalls unabhängig vom Videomodus: Sowohl Text- als auch
Grafikbildschirme können geteilt werden. Lediglich die Wahl der
Startadresse für den oberen Bildschirmteil muß abhängig
vom Videomodus und dem, was man darstellen will, gebildet werden. Im
unteren Teil ist jedoch immer der Anfang des Videospeichers zu sehen.
Die Voreinstellung für den
Line-Compare-Vorgang ist die
bezüglich der Registergröße maximal mögliche:
Bei der EGA 1FFh,
bei der VGA 3FFh.
Über Index 18h des CRTC lassen sich lediglich die untersten 8
Bit
– Bit 0 bis 7 – des
Line-Compare-Registers adressieren. Das Bit 8 ist in das
Overflow-Register (CRTC Index 7) als Bit 4 eingebaut worden. Bei der
VGA mußte auch noch Platz für Bit 9 geschaffen werden, es
ist als Bit 6 an das Max-Scan-Register (CRTC Index 9) angehängt
worden. Das schaut sehr zusammengeflickt aus, war aber aus
Kompatibilitätsgründen leider nicht anders möglich.
Die Werte, mit denen man das Line-Compare-Register belegt, sollten
sehr sorgfältig gewählt werden. Grundsätzlich sind
alle Werte von 0 bis zum Start des Vertical Retrace zulässig.
Der maximale Wert (1FFh oder 3FFh) sollte verwendet werden, um das
Splitting aufzuheben. Rasterzeilennummern
zwischen dem Vertical
Retrace und den Vertical Total Lines
(CRTC Index 6) sind zu
vermeiden. In den 200-Zeilen-Modi der VGA sind nur gerade Werte für
das Line-Compare-Register sinnvoll, da jede Rasterzeile dupliziert
wird. Darüber hinaus gibt es eine Anomalie, die man im
Zweifelsfall selbst ausprobieren sollte: Bei einigen EGA-Karten
beginnt der Controller in den Grafikmodi erst bei der doppelten Zahl
von Rasterzeilen zu splitten.
- Bild 1. Der obere Bildschirmteil beginnt bei der Speicheradresse im Startadreßregister. Nachdem so viele Rasterzeilen dargestellt sind, wie im Line-Compare-Register vereinbart wurden, beginnt die Ausgabe vom Anfang des Videospeichers
Kathodenstrahlsynchronisation
Das Listing in Bild 2 enthält die Programmierung der beiden CRTC-Register als Makro. Die Synchronisation der Lese- und Schreibvorgänge ist wichtig, um mit dem videokarteninternen Zugriff auf diese Register nicht zu kollidieren. Das Startadreßregister wird während der Vertical-Retrace-Phase, der vertikalen Rückführung des Elektronenstrahls, in den internen Adreßzähler übertragen. Daher wird für die Programmierung dieses Registers auf die nächste fallende Flanke des Vertical-Retrace-Bits im Input-Status-Register 1 gewartet. Das Line-Compare-Register wird dagegen während des Bildschirmaufbaus nach jeder Rasterzeile zum Vergleich gelesen, es kann nur während der vertikalen Rückführungsphase des Kathodenstrahls neu geschrieben werden. Im Makro Split_Screen wird daher auf die darauffolgende steigende Flanke des Vertical-Retrace-Signals gewartet, bevor das Line-Compare-Register seinen neuen Wert erhält. Das Listing in Bild 2 ist für den Betrieb einer VGA-Karte ausgelegt. Dies gilt auch für Bild 3. Anhand der Kommentare lassen sich die wenigen Änderungen für die Benutzung einer EGA-Farbkarte leicht durchführen. Die Schnappschuß-Funktion, die im Juni [1] für zwei Videokarten im PC vorgestellt wurde, ist in Bild 3 für das Screen Splitting neu geschrieben worden. So kommen auch die Benutzer eines EGA- oder VGA-Adapters in den Genuß, Momentaufnahmen von ihrem Bildschirm machen zu können. Die aktive Bildschirmseite 1 wird hierzu mit der Tastenkombination ALT S auf die 2. Bildschirmseite kopiert. Sie kann mit ALT U vor der 1. Bildschirmseite hoch und mit ALT D runter gefahren werden. Das Schnappschuß-Programm ist nur für den meistbenutzten Textmodus 3 (80×25 Color) ausgelegt. Wenn der Bereich und die Größe des Bildschirmbuffers vom Videomodus abhängig gemacht werden, läßt sich das Programm an weitere Text- und Grafikmodi anpassen. Zu beachten ist allerdings, daß man im gesamten Bereich des virtuellen Bildschirms nicht das Ende des Bildschirmspeichers oder das Ende des 64-KByte-Segments überschreitet. Anhänger von Turbo-Pascal mögen ihren Blick auf Bild 4 lenken. Dort ist Split_Screen als Prozedur in einer Unit deklariert. MS-C-Benutzer können die gleiche Deklaration verwenden, lediglich der Segmentname code ist durch _TEXT zu ersetzen und die Parameterübergabe der zwei Integer-Werte an das gewünschte Speichermodell anzupassen. Erster Parameter ist jeweils die neue Startadresse für die Bildschirmausgabe, zweiter Parameter ist die maximale Rasterzeile des oberen Bildschirmbereichs.
Literatur
- [1] Eickmann, I.: Zwei Videokarten im PC.
mc 6/89, S. 99. - [2] Wilton, R.: The programmer's guide to PC and PS/2 video Systems.
Microsoft Press, 1987.
;------------------------------------------------------------------------
; Split Screen - Macros release 1.0
; Copyright (c) Ingo Eickmann, 1989 06/14/89
;------------------------------------------------------------------------
IRP com,<push,pop> ; Macros zum push-en und pop-en von
REG_&com macro paras ; Registern
IRP n,<paras>
&com &n
endm
endm
endm
Vertical_retrace macro n ; Vertical Retrace (Bit 3) im Input
local Loco ; Status Register 1 abfragen und
Loco: in al,dx ; auf 0 bzw. 1 warten
test al,00001000b
j&n Loco
endm
Split_Screen macro Start,Line ; Teilung des Bildschirms:
local LC1,LC2 ; Start: Bildschirmspeicheroffset
mov ax,40h ; für oberen Bildschirmteil
mov es,ax ; Line: letzte Scan Line für oberen Teil
mov dx,es:[63h] ; dx: CRTC address port (3B4h oder 3D4h)
add dx,6 ; dx: CRT Status port (3BAh oder 3DAh)
Vertical_retrace z ; Warten, bis Vertical Retrace aktiv
Vertical_retrace nz ; Marten, bis Vert. Retrace nicht aktiv
cli
sub dl,6
mov ax,Start ; Startadresse für oberen Bildteil
shr ax,1
mov bl,al
mov al,0Ch
out dx,ax ; MSB in CRTC - Register 0Ch
inc al
mov ah,bl
out dx,ax ; LSB in CRTC - Register 0Dh
sti
Vertical_retrace z ; Warten, bis Vertical Retrace aktiv
cli
mov ax,Line ; letzte Scan Line für oberen Bildteil
mov bh,ah
mov bl,ah
and bx,0201h ; Bit 8 und 9 trennen und aufbereiten
mov cl,4
shl bx,cl
shl bh,1
mov ah,18h
xchg ah,al
out dx,ax ; Bits 7-0 ins Line Compare Register
mov al,7 ; schreiben
out dx,al ; Overflow - Register adressieren
inc dx
in al,dx ; ! EGA (350 Zeilen-Mode): mov al,1Fh !
; ! EGA (200 Zeilen-Mode): mov al,11h !
and al,11101111b ; Bit 8 des Line Compare Wertes in
or al,bl ; Bit 4 des Overflow - Registers
out dx,al ; schreiben
dec dx
mov al,9 ; ! entfällt für EGA: !
out dx,al ; ! !
inc dx ; ! Bit 9 des Line Compare Wertes !
in al,dx ; ! in Bit 6 des Max Scan Line !
and al,10111111b ; ! Registers schreiben !
or al,bh ; ! !
out dx,ax ; ! bis hier entfällt es für die EGA !
sti
endm
;------------------------------------------------------------------------
; Split_Screen - SnapShot release 1.0
; Copyright (c) Ingo Eickmann, 1989 06/14/89
;------------------------------------------------------------------------
; Assemblieren mit MASM 5.x :
; MASM split; I. Eickmann
; LINK split; Im Leuchterbruch 8
; EXE2BIN split.exe split.com 5000 Köln 80
; Getestet unter MS-DOS 3.3
;------------------------------------------------------------------------
PAGE 65,80
TITLE Split_Screen
ALT_U equ 1600h ; Scan-Code von <ALT-U>
ALT_D equ 2000h ; Scan-Code von <ALT-D>
ALT_S equ 1F00h ; Scan-Code von <ALT-S>
Snap_Buffer equ 1000h ; Offset der 2. Bildschirmseite
max_Lines equ 400 ; für VGA-Textmode, EGA-Textmode: 350
if1
include split.mac
endif
;------------------------------------------------------------------------
KBDSEG segment at 40h ; Einträge im System-Segment
org 1Ah
HEAD dw ? ; Vektor auf ältesten Eintrag im Buffer
TAIL dw ? ; Vektor auf freien Platz im Buffer
Buffer dw 16 dup(?) ; Tastatur-Buffer
org 80h
Buffer_Start dw ? ; erste Speicherstelle des Buffers
Buffer_End dw ? ; letzte Speicherstelle + 1
KBDSEG ends
;------------------------------------------------------------------------
code segment ; Segment des Programms
assume cs:code,ds:code,es:KBDSEG
org 100h ; Startadresse für .com-File
start: jmp Install
;------------------------------------------------------------------------
oldint09h dd 0 ; Vektor auf alte Interruptroutine 09h
oldint1Ch dd 0 ; Vektor auf alte Interruptroutine 1Ch
Line_Compare dw -1 ; Wert für Line_Compare_Register
Add_Word dw 0
;------------------------------------------------------------------------
INT09h proc near ; Erweiterung für Tastaturinterrupt
pushf ; retten aller Register und Flags
REG_PUSH <ax,bx,cx,dx,di,si,ds,es>
pushf ; Aufruf der alten ISR 09h
call dword ptr cs:[oldint09h]
cli
push cs ; Vorbelegung der Segmentregister
pop ds
mov ax,KBDSEG
mov es,ax
cmp byte ptr es:[49h],3 ; Text-Mode 80x25 Color?
jne Raus
mov si,[Tail] ; Vektor auf letzten Eintrag im
cmp si,[Head] ; Tastatur-Buffer
jz Raus ; Scan-Code vorhanden? - Nein => Raus
cmp si,[Buffer_Start]
jg ok2
mov si,[Buffer_End]
ok2: sub si,2 ; Scan-Code entnehmen
mov ax,es:[si] ; und in ax speichern
cmp ax,ALT_U ; Ist Scan-Code ALT-U?
jne ok3
sub Add_word,1 ; JA: nach oben beschleunigen
jmp Ende
ok3: cmp ax,ALT_D ; Ist Scan-Code ALT-D?
jne ok4
add Add_word,1 ; JA: nach unten beschleunigen
jmp Ende
ok4: cmp ax,ALT_S ; Ist Scan-Code ALT-S?
jne Raus
REG_PUSH <si,ds,es> ; JA: Register speichern
mov ax,0B800h ; Video-Segment laden
mov ds,ax
mov es,ax
mov si,0 ; Offset der 1. Bildschirmseite
mov di,Snap_Buffer ; Offset des Zwischenspeichers
mov cx,1000h shr 1 ; Zähler auf 1000h Bytes
cld
repnz movsw ; Bildschirmseite kopieren
REG_POP <es,ds,si> ; Register wiederherstellen
Ende: mov [Tail],si ; Vektor auf nächsten Eintrag zurück-
Raus: REG_POP <es,ds,si,di,dx,cx,bx,ax> ; schreiben
popf ; alle Register wiederherstellen
iret ; Rücksprung aus der ISR
INT09h endp
;------------------------------------------------------------------------
INT1Ch proc near ; Erweiterung des Zeitgeber-Interrupts
sti
pushf ; Register retten
REG_PUSH <ax,bx,cx,dx,ds,es,cs>
pop ds ; Segmentregister vorbelegen
mov ax,Add_word
or ax,ax ; ist Verschiebung notwendig?
jnz pt0
jmp no_mov ; NEIN: => Raus
pt0: add ax,Line_Compare ; Neuen Wert für Line_Compare berechnen
cmp ax,-1 ; oberste Zeile erreicht?
jg pt1
mov Add_word,0 ; JA: Verschiebung zurücksetzen
mov ax,-1
pt1: cmp ax,max_Lines ; unterste Zeile erreicht?
jl pt2
mov Add_word,0 ; JA: Verschiebung zurücksetzen
mov ax,max_Lines
pt2: mov cx,Snap_Buffer ; Offset für Start_Address_Register
push ax
cmp ax,-1 ; oberste Zeile?
jg pt3
mov ax,3FFh ; JA: default für Line_Compare
mov cx,0 ; Offset für 1. Bildschirmseite
pt3: mov Line_Compare,ax
Split_Screen cx,Line_Compare ; s. Listing der Macros (Bild 2)
pop ax
mov Line_Compare,ax ; aktuellen Line_Compare speichern
no_mov: REG_POP <es,ds,dx,cx,bx,ax> ; Register wiederherstellen
popf
jmp dword ptr cs:[oldint1Ch] ; Sprung in die alte ISR
INT1Ch endp
;------------------------------------------------------------------------
Install: push cs ; ds-Segment vorbelegen
pop ds
IRP n,<09h,1Ch> ; Macro für Interrupts 09h und 1Ch
mov ax,35&n
int 21h ; alte ISR-Vektoren retten
mov word ptr oldint&n,bx
mov word ptr oldint&n+2,es
mov ax,25&n ; neue ISR-Vektoren setzen
mov dx,offset Int&n
int 21h
endm
mov dx,offset InstMSG ; Meldung ausgeben
mov ah,9
int 21h
mov ax,3100h ; resident beenden
mov dx,offset Install
mov cl,4
shr dx,cl
inc dx
int 21h
InstMSG db '╔═══════════════════════════════════════'
db '═══════════════════════════════════════╗'
db '║ Split_Screen - SnapShot '
db ' release 1.0 ║'
db '║ Copyright (c) I.Eickmann, 1989 '
db ' 06/14/89 ║'
db '║ Press <ALT-U> to accelerate up, <ALT-D'
db '> to acc. down, <ALT-S> for SnapShot ║'
db '╚═══════════════════════════════════════'
db '═══════════════════════════════════════╝$'
code ends
end start
;------------------------------------------------------------------------
; Split Screen für Turbo Pascal 4.0/5.x release 1.0
; Copyright (c) Ingo Eickmann, 1989 06/14/89
;------------------------------------------------------------------------
code segment word public 'CODE' ; MS-C: Segmentname code in _TEXT ändern
assume cs:code ; MS-C: Segmentname code in _TEXT ändern
if1
include split.mac
endif
public _Split_Screen
_Split_Screen proc far
Lines equ [bp+6] ; die Parameter liegen in umgekehrter
Start equ [bp+8] ; Reihenfolge auf dem Stack
push bp
mov bp,sp
push es
Split_Screen Start,Lines ; Einbindung des Macros
pop es
pop bp
ret 4 ; 2 Integer-Parameter belegten 4 Bytes
; zusätzlich auf dem Stack
_Split_Screen endp
code ends ; MS-C: Segmentname code in _TEXT ändern
end
(* Unit - Deklaration für Turbo Pascal 4.0/5.x, I. Eickmann 1989 *)
unit split;
{$F+}
interface
procedure _Split_Screen(Start,Line : Integer);
implementation
{$L split_tp.obj}
procedure _Split_Screen; external;
end.
aus mc 01/90 – Seite 38-40