BIOS-Level Programmierung Drucken
Benutzerbewertung: / 47
SchwachPerfekt 
Geschrieben von: StarShaper   
Donnerstag, den 01. Juni 2006 um 10:30 Uhr
bios-chip

Inhalt

In diesem Tutorial werden wir die unterste Ebene der Systemprogrammierung kennenlernen, die BIOS-Level Programmierung. Selbstverständlich existiert auf dieser Ebene nur eine Sprache mit der es möglich ist alles aus der Hardware rauszukitzeln was in ihr steckt - die Assemblersprache! Sie erfahren in diesem Tutorial unter Anderem wie man direkt mit Hardware Interrupts arbeitet, die Maus direkt beeinflußt und Pixel über die Grafikkarte mithilfe der BIOS Services auf den Bildschirm zeichnet. Um zu verstehen wie die Operationen vom BIOS bewerkstelligt werden, ist zunächst eine kurze Einführung in das Basic Input/Output System notwendig.

Das BIOS

Das BIOS ist die Basis-Software des Computers und kann im entferntesten Sinn, analog zum menschlichen Gehirn, als das Stammhirn des Rechners angesehen werden. Das BIOS regelt in der Boot-Up Phase alle niederen Funktionen des PCs. Um Zugriff auf das BIOS zu erlangen ist es erforderlich den Prozessor in den so genannten Real Mode zu schalten. Der Real Mode ist ein Operationsmodus der 80286 und x86-kompatiblen CPUs. Im Real Mode steht ein 1 MB großer segmentierter Speicher zur Verfügung. In diesem Modus findet keine Abbildung von virtuellen in physikalischen Adressraum statt. Das Programm das die Speicheradressen verwendet interpretiert diese als physikalischen Speicher und greift auf diesen über einen 20-Bit breiten Adressraum zu der in einem Bereich zwischen 0 und FFFFF liegt. Darüberhinaus ist im Real Mode kein Speicherschutz und auch keine Multitasking-Unterstützung vorhanden. Die Software hat jedoch vollständigen Zugriff auf die BIOS Routinen und peripheren Geräte.

Der Real Mode wird von neuen Prozessoren im Sinne der Abwärtskompatibilität unterstützt. Das heißt jeder moderne x86-Prozessor startet im Real Mode und wird später manuell vom Betriebssystem in den Protected Mode geschalten. Unter allen modernen Windows Betriebssystemen erledigt das der NT Loader (Abkürzung: ntldr) in der Startphase. Erst danach kann auf die vollständigen 32-Bit des Speichers zugegriffen werden. Nachdem der NT Loader einige Page Tables generiert hat um einen 16 MB großen Speicher mit aktiven Paging betreiben zu können, aktiviert er das Paging und Windows kann normal geladen werden.

Bei den in diesem Tutorial vorgestellten Quellcodes handelt es sich um 16-Bit Applikationen. Da nur im Real Mode ein direkter Zugriff auf die Hardware möglich ist, neue Windows Betriebssysteme der NT-Serie den Prozessor jedoch beim Start in den Protected Mode schalten und das System vollständig abschirmen, ist es ggf. erforderlich die erstellten Programme beim Start separat im DOS-Modus auszuführen. Sie können diese aber auch unter Windows NT laufen lassen. Zu diesem Zweck stellen Windows Betriebssysteme eine virtuelle DOS Maschine, auch WOW genannt, zur Verfügung. Diese läuft unter dem Namen ntvdm.exe im Taskmanager. Dabei handelt es sich um ein Win16 Subsystem das es 16-Bit Anwendungen ermöglicht so ausgeführt zu werden als ob diese direkt in einer normalen DOS Umgebung laufen würden.

NTVDM nutzt dazu einen speziellen Modus der x86-Prozessoren namens Virtual-8086 Mode. Der Modus erlaubt es Anwendungen, die für den Real Mode konzipiert wurden, in einer kontrollierten Umgebung zu laufen. Ihr Programm wird also von der virtuellen Maschine emuliert während Windows NT allein darüber entscheidet wie und ob das Programm auf die Hardware zugreifen darf. Da die Programme unter einer Multitasking Umgebung laufen, können die 16-Bit Anwendungen Windows NT auch nicht zum Absturz bringen. Windows NT verhindert in jedem Fall das NTVDM Instruktionen ausführt die direkt den Speicher oder die Hardware manipulieren können!

Neben der Bedingung das unsere Programme im Real Mode laufen müssen, gibt es noch einige weitere Punkte zu berücksichtigen um mit Assemblersprache auf der BIOS Ebene programmieren zu können. So benötigen wir einen entsprechenden Assemblierer und 16-Bit Linker. In diesem Tutorial greifen wir auf den Microsoft Macro Assembler 6.x zurück. Microsoft bietet den MASM 6.x nicht mehr als Retail Produkt an. Bei Bedarf lässt sich dieser jedoch separat beim Support bestellen. Sie müssen sich darum jedoch nicht kümmern. Der MASM 6.x ist samt Update Patch und 16-Bit Linker im Anhang dieses Tutorials enthalten.

Windows Vista und Windows 7

Bitte beachten Sie das einige der hier vorgestellten 16-Bit DOS-basierten Programme seit Windows Vista nicht mehr lauffähig sind. Das betrifft nur die Programme, die im Fullscreen Mode operieren. Alle anderen Programme werden von der virtuellen DOS-Maschine unter Windows Vista und Windows 7 ordnungsgemäß ausgeführt. Nähere Details zu dem Problem entnehmen Sie der Beschreibung unter "Some 16-bit DOS-based Programs and the Command Prompt will not run in full-screen mode in Windows Vista and in Windows 7". Falls Sie Windows Vista oder Windows 7 nutzen, müssen sie diese Programme unter DOS ausführen oder die Videotreiber von Windows XP installieren.

Einführung in den Microsoft Macro Assembler

Der Microsoft Macro Assembler, auch MASM genannt, ist ein Assembler für die x86 Mikroprozessor Familie. Er wurde ursprünglich von Microsoft für die Entwicklung auf dem MS-DOS Betriebssystem entwickelt und war für einige Zeit der bekannteste Assembler für dieses Betriebssystem. Er unterstützte eine breite Anzahl an Makro Einrichtungen und strukturierten Programmier-Idiomen, inklusive High-Level Konstruktionen für Schleifen, Prozeduren Aufrufen und Alternationen. Aufgrunddessen kann der MASM auch als ein Beispiel für einen High-Level Assembler angesehen werden. Den späteren Versionen des MASM wurden kontinuierlich zusätzliche Komponenten hinzugefügt und entsprechend auf die neuen Windows Betriebssysteme angepasst. Der MASM ist eines von wenigen Microsoft Tools für die es keine getrennten 16-Bit und 32-Bit Versionen gab.

Anfang der 1990er erschienen erstmals alternative Assembler. Darunter der populäre NASM und der Borland Turbo Assembler. Trotz der aufkommenden Konkurrenz war der MASM auch bis spät in die 90er der gängigste Assembler auf dem Markt. Insbesondere die Tatsache das Microsoft den kommerziellen Verkauf einstellte und begann den MASM kostenlos als Teil des DDK zu publizieren verhalf dem Assembler zu seiner anhaltenden Popularität. Aber auch der neu erschienene MASM32, mit dem es nun auch möglich war richtige Windows Applikationen zu entwickeln, sowie die weit verbreiteten Tutorials trugen dazu bei.

Heute ist der MASM32 immer noch der populärste Assembler, trotz anhaltender Konkurrenz von neuen Produkten wie dem aktuellen NASM, FASM, GoAsm, oder HLA. Es existieren viele laufende Software Projekte die den MASM unterstützen, darunter IDE's (z.B. RadASM), Debugger (z.B. OllyDbg), und Disassembler (z.B. IDAPro). Das MASM32 Projekt, zu finden auf movsd.com, hat zahlreiche Dokumentationen, Tutorials, Bibliotheken und Quellcodes für die MASM32 Entwickler zusammengestellt. Der Microsoft Macro Assembler wird durch eine große Community unterstützt, darunter zahlreiche Webseiten und Support Foren wie dem MASM Forum. Trotz seines Alters bleibt der MASM nach wie vor einer der am besten unterstützten Assembler in der Geschichte der Computerprogrammierung.

Obwohl der MASM kein kommerzielles Produkt mehr ist, setzt Microsoft den Support weiterhin fort. Ein Grund dafür besteht darin das im Hause Microsoft und weltweit immer noch eine große Menge an Assembler Quellcode existiert. Für die Microsoft MASM 6.x Produktlinie wurden seit der Standard Edition 6.11 weitere Updates publiziert. Microsoft hat das Angebot des MASM 6.11 eingestellt und bietet diesen nurmehr auf direkte Anfrage beim Support an. Die neuen Versionen des MASM werden seither direkt mit den Visual C++ Versionen mitgeliefert. Beispielsweise enthält die Visual C++ 2002 Distribution den MASM 7.0, Visual C++ 2003 die Version 7.1 und die aktuelle Version Visual C++ 2005 die Version 8.0 des MASM. Diese unterstützt neben der 32-Bit Entwicklung auch die 64-Bit Architektur.

Die Versionsunterschiede der letzten Microsoft Macro Assembler bestehen hauptsächlich in Bugfixes, dem Hinzufügen von neuen Instruktionen für neue Prozessoren und der Verbesserung der 64-Bit Unterstützung. Neue radikale Änderungen sind für die Zukunft nicht mehr zu erwarten.

Microsoft Macro Assembler installieren

Falls Sie im Besitz einer neuen Visual C++ Edition sind, oder sogar eine Visual Studio Entwicklungsumgebung Ihr Eigen nennen, können Sie den mitgelieferten MASM verwenden. Sie finden diesen im Visual C++ Verzeichnis unter VC/bin und dem Namen ml.exe. Um die Quellcodes direkt mithilfe Ihrer Visual Studio IDE assemblieren zu können, müssen Sie bedenken das der ML 8.x standardmäßig mit der Option /coff assembliert und nicht mit /omf. Das bedeutet es werden Dateien im Common Object File Format (COFF) generiert. Dies ist für die Entwicklung von 32-Bit Assembler Applikationen die Standardeinstellung. Bei 16-Bit Anwendungen muss allerdings das Object Module File Format (OMF) verwendet werden! Um Dateien im alten OMF generieren zu können benötigen Sie zudem einen 16-Bit Linker. Dieser ist Bestandteil des im Anhang enthaltenen MASM 6.x.

Ich möchte anmerken das ich in diesem Tutorial ausschließlich in der Kommandozeile, ohne den Balast einer IDE arbeiten werde und auf den MASM in der Version 6.14 zurückgreife. Wir arbeiten schließlich nur eine Ebene über der Hardware und sollten uns dementsprechend auch vom überflüssigen Schnickschnack die eine IDE mit sich bringt verabschieden. Nachdem Sie das Paket im Anhang heruntergeladen haben, können Sie mit der Installation des MASM 6.11 beginnen. Starten Sie dazu die Setup.exe und installieren Sie den Microsoft Macro Assembler.

masm6

Installieren Sie den Assembler in dem angegebenen Pfad unter C:\MASM611\. Nachdem Sie den Assembler erfolgreich installiert haben, können Sie diesen auf die aktuelle Version updaten. Dazu befindet sich die Datei ml614.exe - ML614.exe 6.14 patch available - im Paket. Verschieben sie das Programm in das Verzeichnis des MASM 6.11 und patchen Sie den MASM auf eine aktuelle Version. Anschließend benötigen Sie noch den 16-Bit Linker. Führen Sie dazu das Programm lnk563.exe aus. Es wurden drei Dateien entpackt. Kopieren Sie diese ebenfalls in das MASM Verzeichnis. Damit haben Sie die Installation erfolgreich abgeschlossen.

Arbeiten mit dem MASM

Wie bereits erwähnt, existiert seit geraumer Zeit auch ein 64-Bit Microsoft Macro Assembler. Der Appendix 64 (ml64.exe) weist darauf hin. Die Kommandozeilen Referenz für den ML und den ML64 ist auf den offiziellen MSDN Seiten unter Microsoft Macro Assembler Reference zu finden. Die Parameter sind wie folgt spezifiziert:

ML [[options]] filename [[ [[options]]  filename]]
ML64 [[options]] filename [[ [[options]]  filename]]
...
[[/link linkoptions]]

Die Assemblierung gestaltet sich denkbar einfach. Im Normalfall reicht es aus den Dateinamen mit dem enthaltenen Quellcode zu übergeben. Der Assembler sucht stets im aktuellen Verzeichnis nach verwiesenen Dateien. Statische Bibliotheken werden anhand der Erweiterung *.lib erkannt und entsprechend mit der generierten Objektdatei zu einem ausführbaren Programm gelinkt. Eine Zusammenfassung der Kommandozeilen Syntax können Sie sich auch über den Befehl /? anzeigen lassen.

mlconsole

Das BIOS

Das Basic Input Output System, kurz BIOS genannt, ist bei PCs der x86 Prozessor Architektur die Basis-Software, die der Computer direkt nach dem Einschalten lädt und ausführt. Sie wird dazu in einem nichtflüchtigen Speicher abgelegt und steht dem Steuerwerk der CPU so direkt zur Verfügung. Die primäre Funktion des BIOS besteht darin die Maschine vorzubereiten, so dass andere Softwareprogramme, wie beispielsweise die Firmware des CD/DVD-Laufwerks oder der Festplatten geladen und ausgeführt werden kann. Dieser Prozess wird als Boot-Up bezeichnet. Das BIOS steuert die grundlegenden Mechanismen beim Start jedes Rechners. Nach der Boot-Up Phase wird die Kontrolle an das Betriebssystem übergeben.

Das BIOS befindet sich auf einem PROM, EPROM oder auch Flash Speicher genannt. Nach dem Start entpackt es sich in den RAM und wird anschließend vom Prozessor ausgeführt. In modernen Rechnern wird ein zusätzliches Setup Programm aus dem flüchtigen CMOS geladen. Dieses enthält benutzerspezifische Einstellungen die im BIOS Konfigurationsmenü getätigt wurden. Dazu gehört neben der Reihenfolge in der die Laufwerke geladen werden, auch die Zeit und das Datum.

Als die ersten IBM-PCs erschienen, wollten viele Programmierer wissen wie man direkt mit der Computer Hardware arbeitet. Relativ schnell erschienen die ersten Publikationen die sich mit den Eigenschaften und geheimen Internas des IBM-PCs befassten. Darunter das populäre Inside the IBM-PC von Peter Norton. Wenig später entschied sich die Firma IBM den Assembler Quellcode des IBM PC/XT BIOS frei zu veröffentlichen. Spieleentwickler optimierten ihren Code mithilfe des Wissens das sie über das BIOS gewonnen hatten. Bekannte Spiele die davon profitierten waren die ersten Ego-Shooter Quake und Doom. In diesem Tutorial werden nun auch Sie die unterste Ebene der Systemprogrammierung kennenlernen. Sie werden unterhalb einer verwalteten Schicht, wie dem .NET Framework arbeiten, unterhalb des Betriebssystems und sogar noch unterhalb von MS-DOS selbst. Sie werden erfahren was passiert wenn eine Taste auf der Tatstatur gedrückt wird, wie der Tastatur Puffer ausgelesen wird, wie Farben dargestellt werden, wie Grafiken auf den Bildschirm gezeichnet werden und wie die Maus Bewegungen registriert. Bevor wir uns jedoch der praktischen Seite der Programmierung widmen werfen wir zunächst einen Blick auf den BIOS Datenbereich.

Der BIOS Datenbereich

Aufgrund der Tatsache das das BIOS sich auf nicht flüchtigen Speicherchips befindet, ist es nicht weiter verwunderlich das Informationen in einem Datenbereich (engl. Data Area) gespeichert werden müssen. Der BIOS ROM Datenbereich beinhaltet im Gegensatz zu den fest kodierten Instruktionen, allerdings nur einen kleinen Teil der Systemdaten, die vom BIOS Code benötigt werden. Außerhalb des eigenen Datenbereichs existiert ein weiterer Speicherbereich der vom BIOS verwaltet wird. Dieser kleine Speicherbereich hält Informationen über die Konfiguration des Computers bereit. Er speichert mitunter die Einstellungen die während des BIOS Setup getätigt werden.

Während Ihr Computer unter der Kontrolle des BIOS steht, finden die wichtigsten Speicheroperationen des BIOS in speziellen Bereichen statt die vom BIOS auf dem PC angelegt werden. Sobald das BIOS den Computer initialisiert sammelt es Informationen über die Ausrüstung und Eigenschaften des Computers und platziert die Ergebnisse an bestimmten Orten in Ihrem Systemspeicher. Zu den gesammelten Daten gehören Informationen über die Anzahl und Art der installierten Ports, der Typ des Display-Adapters usw. Neben den genannten Daten werden auch Flags abgespeichert, Basisadressen der I/O Adapter, Tastaturzeichen und diverse Modi. Alle diese Daten werden an ganz bestimmten Stellen im Arbeitsspeicher hinterlegt, so dass Programme gezielt darauf zugreifen können um so an Informationen über die auf dem System verfügbaren Features zu gelangen. Beispielsweise sucht das BIOS nach seriellen Ports an spezifizierten Adressen. Alle diese Daten bilden einen vom BIOS im Arbeitsspeicher angelegten Informationsblock, genannt der BIOS Datenbereich.

Der Datenbereich ist fixiert und liegt über der Interrupt Vektor Tabelle. Er umfasst 256 Bytes, angefangen bei der absoluten Startadresse 00400h und endet mit der Adresse 004FFh (0040:0000 - 0040:00FF). Die nachfolgende Tabelle 1 gibt einen Überblick über die Art der gespeicherten Daten. Die Zellenbelegung kann je nach Hersteller geringfügig abweichen.

Table 1: BIOS Data Area
Offset - Segment 0040h Datentyp Beschreibung
0000 WORD Port-Adresse COM1
0002 WORD Port-Adresse COM2
0004 WORD Port-Adresse COM3
0006 WORD Port-Adresse COM4
0008 WORD Port-Adresse LPT1
000A WORD Port-Adresse LPT2
000C WORD Port-Adresse LPT3
000E WORD Port-Adresse LPT4
0010 WORD Liste der installierten Hardware
0012 BYTE Diagnose Flags der Initialisierung
0013 WORD Speichergröße in Kbytes
0015 WORD Speicher im I/O Kanal
0017 BYTE Tastatur Status Flags
0018 BYTE Zusätzliche Tastatur Status Flags
0019 BYTE Interner Tastatur Speicher (ASCII-Code bei Eingabe über Alt+Ziffern)
001A WORD Zeiger auf das Offset des nächsten Zeichens im Tastaturpuffer
001C WORD Zeiger auf das Offset des letzten Zeichens im Tastaturpuffer
001E 32 BYTE Tastaturpuffer, pro Zeichen zwei Bytes
003E BYTE Floppy Rekalibrierung
003F BYTE Floppy Motor Status
0040 BYTE Floppy Motor-Aus Zähler
0041 BYTE Floppy Status der letzten Operation
0042 7 BYTE Floppy Controller Status Bytes
0049 BYTE Aktueller Video-Modus
004A WORD Anzahl der Zeichen pro Zeile (Spalten)
004C WORD Größe der aktuellen Bildschirmseite in Bytes
004E WORD Adresse des Bildschirmspeichers relativ zum Video-RAM
0050 16 BYTE Cursorposition (8x 2 Byte für 8 Seiten)
0060 WORD End- und Startzeile des Cursors im Zeichenraster
(0-7 Farbe bzw. 0-14 Monochrom)
0062 BYTE Aktuelle Bildschirmseite
0063 WORD Port-Adresse des Video-Controllers
0065 BYTE Videomodus-Register der Grafikkarte
0066 BYTE Palettenregister (CGA)
0067 DWORD Reset-Restart-Adresse
006C WORD Zähler der Systemuhr; dieses Doppelwort wird bei jedem Timer-Aufruf
(ca. 18mal pro Sekunde) erhöht; alle 24 Stunden wird es auf Null gesetzt
0070 BYTE Zeitgeber-Überlauf; wird alle 24 Stunden erhöht
0071 BYTE CTRL-Break-Status
0072 WORD Reset-Status:
Der Wert gibt an, wie sich das BIOS nach einem Reset verhält.
0000h: Kaltstart
1234h: Warmstart
4321h: Warmstart, Speicherinhalt bleibt erhalten
5678h: System anhalten
9abch: Diagnoseprogramm
abcdh: Warteschleife aufrufen
0074 BYTE Status der letzten Festplatten-Operation (nur AT)
0075 BYTE Anzahl der Festplatten (nur AT)
0076 BYTE Festplatten-Kontrollbyte (nur AT)
0077 BYTE Port-Adresse Festplatte (nur AT)
0078 BYTE LPT1 Timeout Wert
0079 BYTE LPT2 Timeout Wert
007A BYTE LPT3 Timeout Wert
007B BYTE LPT4 Timeout Wert / nicht verwendet
007C BYTE COM1 Timeout Wert
007D BYTE COM2 Timeout Wert
007E BYTE COM3 Timeout Wert
007F BYTE COM4 Timeout Wert
0080 WORD Zeiger auf Anfang Tastaturpuffer (nur AT)
0082 WORD Zeiger auf Ende Tastaturpuffer (nur AT)
0084 BYTE Zahl der Videozeilen - 1 (nur EGA/VGA)
0085 WORD Punktzeilen pro Zeichen (nur EGA/VGA)
0087 4 BYTE EGA-/VGA-Status-Bereich (herstellerabhängig)
008B BYTE Floppy Daten und Schritt-Rate (nur PS/2)
008C BYTE Festplatten Controller-Status (nur PS/2)
008D BYTE Festplatten Fehler-Status (nur PS/2)
008E BYTE Festplatten Interrupt-Control (nur PS/2)
008F BYTE Diskettencontroller Information
0090 BYTE Medium in Laufwerk 0 (nur PS/2)
0091 BYTE Medium in Laufwerk 1 (nur PS/2)
0092 BYTE Medienstatus Diskettenlaufwerk 0 bei Start der Operation
0093 BYTE Medienstatus Diskettenlaufwerk 1 bei Start der Operation
0094 BYTE Augenblickliche Spur in Laufwerk 0 (nur PS/2)
0095 BYTE Augenblickliche Spur in Laufwerk 1 (nur PS/2)
0096 BYTE Tastenfeldstatus 3 (nur AT)
0097 BYTE Status Tastenfeld-LED (nur AT)
0098 DWORD Zeiger auf Wait-Flag (nur AT)
009C DWORD Zeit-Zähler (nur AT)
00A0 BYTE Wait-Status
00A1 95 BYTE Reserviert (herstellerabhängig belegt)
0100 BYTE HardCopy-Status

Zusätzlich ergänzt der erweiterte BIOS Datenbereich den für den Basis BIOS Datenbereich zur Verfügung stehenden Speicherbereich. Dieser zusätzliche Speicher belegt ein weiteres Kilobyte des Arbeitsspeichers. Programme lokalisieren die Adresse des Speichers mit dem Aufruf einer Basis BIOS Funktion, genannt Interrupt 15h. Obwohl die Ingenieure zahlreiche Ideen hatten, wie sie diesen zusätzlichen Speicherbereich funktionell verwenden wollten, existiert derzeit nur eine aktive Funktion in diesem erweiterten Speicher und belegt lediglich weitere 30 Byte. Das Betriebssystem Windows verwaltet seine eigenen Datentabellen und ist nicht auf den normalen oder den erweiterten BIOS Datenbereich angewiesen.

Wie schon ewähnt wurde, kontrolliert das BIOS zusätzlich eine andere Art des Speichers, den CMOS in dem die Setup Einstellungen gespeichert sind. Die BIOS Setup-Routinen sind hauptsächlich mit der Verwaltung dieses flüchtigen Speichers beschäftigt. Der CMOS wird durch einen Akku mit Strom versorgt, so dass die Daten auch bei einem längerfristigen Stromausfall erhalten bleiben. Wird die Batterie entfernt findet ein BIOS Reset statt und alle benutzerspezifischen Daten gehen verloren.


Tastatur Eingaben mit INT 16h

Es existieren in Assembler verschiedene Methoden um mit der Tastatur I/O-Operationen durchzuführen. Die Assemblersprache bietet dem Programmierer eine enorme Flexibilität und Kontrolle im Umgang mit der Tastatur. Da wir uns in diesem Tutorial auf der BIOS Ebene bewegen, werden wir auch gleich die Funktionen nutzen die der Computerhersteller bereits ab Werk zur Verfügung stellt. Konkret gesagt, wir verwenden den BIOS Tastatur Handler Interrupt 16h. Dieser liefert uns eine Taste aus dem Tastaturpuffer. INT 16h eignet sich hervorragend dazu die erweiterten Tastaturtasten, wie die Cursortasten oder Bild ↑ und Bild ↓ Tasten zu lesen. Jede dieser erweiterten Tasten erzeugen einen 8-Bit Scan Code der auf allen IBM-kompatiblen Computern gleich ist.

Tatsächlich generieren alle Tasten einen Scan Code! Jedoch ist der ASCII Code universell und daher wird den seitens der ASCII Zeichen generierten Scan Codes keine besondere Aufmerksamkeit geschenkt. Sobald aber eine erweiterte Taste gedrückt wird, ist sein ASCII Code entweder E0h oder 00h bei den Funktions Tasten (F1, F2, ...).

Der Tastatureingabe folgt ein Ereignispfad, der beim Tastatur Controller Chip beginnt und damit endet dass das Zeichen in einem 30 Byte großen Array, genannt Tastatur-typeahead-Puffer, gespeichert wird. Bis zu 15 Tastatureingaben können im Puffer gleichzeitig gehalten werden. Ein Zeichen belegt jeweils 2 Byte. Wie aus Tabelle 1 hervorgeht beginnt der Puffer an der Adresse 0040:001E und endet bei der Adresse 0040:003E. 4 Byte werden für den Head- und Tail-Zeiger benötigt die jeweils angeben an welcher Stelle die nächste Taste gelesen bzw. gespeichert wird. Die aktuelle Head-Adresse wird an der Position 0040:001A gespeichert, während die Tail-Adresse an der Adresse 0040:001C zu finden ist. Der Tastaturpuffer wird auch als zirkulierender Puffer (Ringpuffer) bezeichnet, weil die Start- und Endpunkte sich konstant drehen. In der Boot-Up Phase des Rechners zeigen Head und Tail auf diesselbe Adresse. Sobald eine Taste gedrückt wird, rückt der Tail-Zeiger um 2 Byte nach vorne. Wird ein Zeichen gelesen, wird der Head-Zeiger um 2 Byte weitergerückt. Das folgende Abbild zeigt den prinzipiellen Ablauf wenn eine Taste auf der Tastatur gedrückt wird.

keyboard-handler

Nachdem die Taste betätigt wurde sendet der Controller Chip einen 8-Bit großen numerischen Scan Code an den Input Port der Tastatur. Der Input Port wurde für die Behandlung von Interrupts konstruiert. Nachdem ein Interrupt ausgelöst wurde reagiert die CPU umgehend und führt die INT 9h Service Routine aus. Diese empfängt den Tastatur Scan Code vom Input Port und sucht nach dem zugehörigen ASCII Code. Anschließend werden beide Werte im typeahead-Puffer gespeichert und stehen absofort allen laufenden Programmen zur Verfügung.

Unsere Eingaben werden wir mit INT 16h aus dem Tastatur-typeahead-Puffer lesen. Dabei werden der ASCII-Code als auch der Scan Code in einem Schritt gelesen. Dies ist besonders dann von Vorteil wenn erweiterte Tasten empfangen werden müssen, da diese eben keine ASCII-Codes besitzen. Auf MS-DOS Ebene und darüber können auch Funktionen aufgerufen werden die INT 21h verwenden. Im Gegensatz zu INT 16h muss hier aber im Falle einer erweiterten Taste ein zweiter Aufruf erfolgen um den Scan Code zu empfangen. Parallel zu dem bereits genannten Vorteil, besitzt INT 16h noch weitere zusätzliche Eigenschaften. So lässt sich mit INT 16h auch der Status der Tastatur Flags empfangen. Darüberhinaus ist es möglich die Typematic Rate zu setzen. Mit der Typematic Rate wird der Wert eingestellt, wann die Tastenfunktion nach dem Drücken einsetzt. Die verfügbaren Werte variieren zwischen den BIOS Herstellern geringfügig. Beim AMI BIOS sind die Werte 250, 500 (Standard) oder 1.000 Millisekunden einstellbar. Beim Award BIOS sind es 250, 500, 750, 1000 Millisekunden. Nachfolgend sehen Sie ein kleines Programm das eine einfache Zeicheneingabe mittels INT 16h realisiert und gleichzeitig die Typematic Rate der Tastatur auf 1000ms setzt:

TITLE INT 16h Examples    (typekey.asm)
 
COMMENT ! Dieses Programm gibt einen zuvor mit INT 16h eingegebenen String
          wieder aus und setzt die Typematic Rate auf 1000ms.
          Last update: 25/05/06 !
 
.model small, stdcall       ; 1 Code und 1 Data Segment
.stack 200h
.386
 
.data
    strsize  WORD 8
    enterstr BYTE "Enter some Characters: $"
    message  BYTE 0dh, 0ah, "You entered: $"
    quit     BYTE 0dh, 0ah, "Press ESC to quit!$"
    target   BYTE SIZEOF strsize DUP(0)
 
.code
main PROC
    mov ax, @data           ; Initialisiere das DS Segment Register
    mov ds, ax
 
    mov dx, 0
    call clrScreen          ; Lösche den Bildschirm
    mov ah, 9               ; Schreibe $-terminierten String auf Standardausgabe
    mov dx, OFFSET enterstr ; Adresse des Zeichen Arrays
    int 21h                 ; Rufe den Interrupt auf
 
    mov ax, 0305h           ; Setze die Typematic Rate
    mov bh, 3               ; Verzögerung (Repeat Delay) 3 = 1000ms
    mov bl, 0Fh             ; Repeat Rate auf 0 setzen (0Fh = schnellste)
 
    mov si, 0               ; 16-Bit Index Register
    mov cx, strsize         ; Schleifenzähler
 
L1: mov ah, 10h              ; Tastatur Input
    int 16h                 ; Verwende den BIOS Interrupt Handler
    mov target[si], al      ; Speicher das Zeichen im target Array
    inc si                  ; Nexter Index im String
    mov  ah, 2               ; Einzelnes Zeichen auf Standardausgabe
    mov dl, al
    int  21h
    loopd L1                ; L1 Schleife (Register CX fungiert als Zähler)
 
    mov ah, 9
    mov dx, OFFSET message  ; Ausgabe von message
    int 21h
 
    mov ah, 40h             ; Schreibe in File/Device
    mov bx, 1               ; Ausgabe Handle (Konsole = 1)
    mov cx, si              ; Anzahl der zu schreibenden Zeichen
    mov dx, OFFSET target
    int 21h
 
    mov ah, 9
    mov dx, OFFSET quit     ; Ausgabe von quit
    int 21h
 
L2: mov ah, 10h
    int 16h
    cmp al, 1Bh              ; ESC Taste gedrückt?
    jne L2                  ; Nein, springe an L2
 
    mov ah, 4Ch             ; Beende den Prozess (analog zu ExitProcess, 4C00h)
    mov al, 0               ; Gebe einen Return Code zurück
    int 21h                 ; Rufe den Interrupt auf
main ENDP
 
;------------------------------------------------------
; clrScreen
; Bereinigt den Bildschirm (video page 0) und lokalisiert
; den Cursor auf Reihe 0 und Spalte 0.
;-------------------------------------------------------
clrScreen PROC
    pusha
    mov ax, 0600h         ; Scrolle das Fenster hoch
    mov cx, 0             ; Obere linke Ecke (0,0)
    mov dx, 184Fh        ; Untere rechte Ecke (24,79)
    mov bh, 7            ; Normale Attribute
    int 10h             ; Rufe BIOS
    mov ah, 2            ; Lokalisiere Cursor auf 0,0
    mov bh, 0            ; Video Page 0
    mov dx, 0             ; Reihe 0, Zeile 0
    int 10h
    popa
    ret
clrScreen ENDP
 
END main

Das obige Beispiel nutzt die INT 16h Funktion 10h um die nächste verfügbare Taste aus dem typeahead Puffer zu entfernen. Falls sich keine Taste im Puffer befindet, wartet der Tastatur Handler solange bis eine Taste eingegeben wurde. Der Scan Code wird im AH (High) Register abgespeichert, während der ASCII Code im AL (Low) Register zu finden ist.

Oft möchte man wissen wie der Inhalt der Register aussieht nachdem eine Taste betätigt wurde. Welche Scan und ASCII Codes werden von einer normalen Taste generiert? Wie sieht es bei einer erweiterten Taste aus? Was passiert mit den Status-Flags? Das folgende Programm beantwortet diese Fragen indem es nach jedem Tastendruck den Inhalt der Register ausgibt.

TITLE Keyboard ShowRegisters           (showkeybd.asm)
 
COMMENT ! Dieses Programm zeigt den Inhalt der Register
          nach jedem Tastendruck an.
          Last update: 25/05/06 !
 
.model small, stdcall   ; Code < 64k ;  Data < 64k
.stack 200h
.386
 
DumpRegs PROTO          ; Prototyp für die Funktion DumpRegs
 
.code
main PROC
    mov ax, @data
    mov ds, ax
 
L1: mov ah, 10h
    int 16h
    call DumpRegs       ; Funktion ist in der Win16.lib definiert (Ausgabe
                        ; aller Registerinhalte samt CPU Flags)
    cmp al, 1Bh          ; Escape gedrückt?
    jne L1              ; Nein, gehe zurück zu L1
 
    mov ah, 4Ch         ; Beende den Prozess
    mov al, 0
    int 21h
main ENDP
 
END main

Da sowohl das AH als auch das AL Register Teil des größeren 16-Bit AX Registers sind, welches widerum Teil des noch größeren 32-Bit EAX ist, können diese entsprechend mit 8-Bit adressiert werden. Sehen Sie sich dazu einmal den Inhalt des EAX Registers nach Betätigung der Taste 'R' an. Die Taste generiert den ASCII Code 52 und den Scan Code 13 in hexadezimaler Notation.

eax-register

Wie schon erwähnt wurde besitzen die erweiterten Tasten keinen ASCII Code, weshalb sie anhand des Scan Codes identifiziert werden müssen. Die Funktionstasten generieren deshalb allesamt den gleichen ASCII Code 00h, während Tasten wie z.B. die ↓ Bild ab Taste, den ASCII Code E0h generieren. Sie können testweise mit dem Programm experementieren und sich alle Tastencodes genauer ansehen.

Tastaturpuffer und Tastatur Flags

INT 16h ist ein Tastatur Handler der eine ganze Reihe weiterer nützlicher Funktionen zur Verfügung stellt. Wir haben bereits einige dieser Funktionen kennengelernt. Dazu gehörte 03h um die Typematic Rate der Tastatur zu setzen und 10h um auf Tastenbetätigungen zu warten. INT 16h bietet aber noch weitere Funktionen die nützlich sein können. So ermöglicht es die Funktion 03h manuell Tasten in den Tastaturpuffer zu schieben. Zu diesem Zweck kann ein beliebiger Scan Code in das CL Register und ein entsprechender ASCII Code in das CH Register kopiert werden. Mit der Funktion namens 11h ist der Programmierer anschließend auch in der Lage den Tastaturpuffer zu überprüfen. Die Funktion schaut nach wartenden Tasten im Puffer. Auf diese Weise kann man eine Funktion innerhalb einer Schleife nutzen um gezielt weitere Programmaufgaben erledigen zu lassen. Falls sich eine Taste im Tastaturpuffer befindet, ist das Zero Flag (ZF) nicht gesetzt. Andernfalls ist sein Inhalt 1. Die anstehende Taste kann nach einer erfolgreichen Überprüfung aus den entsprechenden Registern (AH & AL) ausgelesen werden.

Auch die Tastatur Flags offerieren Informationen die sehr nützlich sind. Beispielsweise zeigt das sechste Bit im Tastatur Flag an das die Caps Lock Taste geschaltet ist. Wenn Sie mit Word oder einer anderen Software zur Textverarbeitung arbeiten, muss die Anwendung den Flag Status kennen um darauf reagieren zu können. Dazu überprüft das Programm kontinuierlich das Status Flag auf Änderungen. Das Status Flag verrät dem Programm aber noch einige weitere Dinge. Zum Beispiel ob eine Taste momentan gedrückt wird oder nicht. Tabelle 2 zeigt die wesentlichen Status Flags der Tastatur.

Table 2: Keyboard Flags
Bit Beschreibung
0 Rechte Shift Taste ist gedrückt
1 Linke Shift Taste ist gedrückt
2 Strg Taste ist ebenfalls gedrückt
3 Alt Taste ist ebenfalls gedrückt
4 Scroll (Rollen) Lock Taste geschaltet
5 Num Lock Taste geschaltet
6 Caps Lock Taste geschaltet
7 Einfügen Taste aktiv
8 Linke Strg Taste ist gedrückt
9 Linke Alt Taste ist gedrückt
10 Rechte Strg Taste ist gedrückt
11 Rechte Alt Taste ist gedrückt
12 Scroll Taste ist gedrückt
13 Num Lock Taste ist gedrückt
14 Caps Lock Taste ist gedrückt
15 SysReq Taste ist gedrückt

Viele Programme benutzen häufig Schleifen um bestimmte Operationen immer wieder nacheinander auszuführen. Spiele wiederholen beispielsweise ständig Animationen und geben Sequenzen auf den Bildschirm aus. Oft ist es so, dass das Spiel Tasteneingaben ignoriert und nur auf bestimmte Eingaben möglichst sofort reagieren soll. Der Benutzer füllt jedoch den Tastaturpuffer möglicherweise mit unnützen Tastencodes auf. Wie kann das Programm dann auf neue Tasten umgehend reagieren?

Wir können mit den bisher gewonnen Informationen dieses Problem lösen. Dazu benötigen wir ledeglich die Funktionen 11h und 10h. Das folgende Programm zeigt ununterbrochen einen laufenden Punkt auf dem Bildschirm an. Sie können versuchen den Tastaturpuffer mit beliebigen Daten aufzufüllen. Sobald Sie jedoch die Abbruchtaste betätigen reagiert das Programm umgehend darauf und beendet die Ausführung.

TITLE Tastaturpuffer kontinuierlich leeren       (clearkeybd.asm)
 
COMMENT ! Dieses Programm zeigt wie man den Tastaturpuffer leeren
          kann, während man auf die Eingabe einer bestimmten Taste
          wartet. Dazu wird in einer Schleife ein Punkt
          ausgegeben und überprüft ob eine Taste gedrückt wurde.
          Falls ja, wird diese aus dem Puffer wieder entfernt.
          Handelte es sich um die Abbruchtaste, bricht das Programm ab.
          Last update: 25/05/06 !
 
.model small, stdcall
.stack 200h
.386
 
NewLine  MACRO                        ; Zeilenumbruch Makro
   mov  dl,0dh
   mov  ah,02
   int  21h
   mov  dl,0ah
   mov  ah,02
   int  21h
   ENDM
 
ClearKeyboard PROTO, scanCode:BYTE
ESC_key = 1                           ; Scan Code
 
.code
main PROC
L1: mov ah, 2                         ; Zeigt einen Punkt an
    mov dl, '.'
    int 21h
    mov eax, 300                      ; Verzögerung auf 300ms stellen
    call Delay                        ; Prozedur Delay aufrufen
 
    INVOKE ClearKeyboard, ESC_key     ; Überprüfe auf Escape Taste
    jnz L1                            ; Wiederhole die Schleife falls ZF = 0
 
quit:
    call clrScreen
    mov ah, 4Ch
    mov al, 0
    int 21h
main ENDP
 
;---------------------------------------------------
; ClearKeyboard
; Leert den Puffer während es auf die Eingabe einer
; bestimmten Taste wartet.
; Empfängt: Tastaur Scan Code
; Rückgabewert: Zero flag wird gesetzt falls der
; ASCII Code gefunden wurde. Ansonsten wird das
; ZF geleert.
;---------------------------------------------------
ClearKeyboard PROC, scanCode:BYTE
    push ax
 
L1: mov ah,11h                        ; Überprüfe den Tastaturpuffer
    int 16h                           ; Wurde eine Taste gedrückt?
    jz  noKey                         ; Nein, dann beende den PROC (ZF = 0)
    mov ah, 10h                       ; Ja, entferne ihn aus dem Puffer
    int 16h
    cmp ah, scanCode                  ; War es die Abbruchtaste?
    je  quit                          ; Ja, dann beende den PROC (ZF = 1)
    jmp L1                            ; Nein, überprüfe den Puffer nochmal
 
noKey:                                ; Keine Taste gedrückt
    or al, 1                          ; Bereinige das Zero Flag mittels x ODER 1
quit:
    pop ax
    ret
ClearKeyboard ENDP
 
;------------------------------------------------------
; clrScreen
; Bereinigt den Bildschirm (video page 0) und lokalisiert
; den Cursor auf Reihe 0 und Spalte 0.
;-------------------------------------------------------
clrScreen PROC
    pusha
    mov ax, 0600h                     ; Scrolle das Fenster hoch
    mov cx, 0                         ; Obere linke Ecke (0,0)
    mov dx, 184Fh                    ; Untere rechte Ecke (24,79)
    mov bh, 7                        ; Normale Attribute
    int 10h                         ; Rufe BIOS
    mov ah, 2                        ; Lokalisiere Cursor auf 0,0
    mov bh, 0                        ; Video Page 0
    mov dx, 0                         ; Reihe 0, Zeile 0
    int 10h
    popa
    ret
clrScreen ENDP
 
;-----------------------------------------------------------
; Delay
; Generiere eine n-Millisekunden Verzögerung.
; Empfängt: EAX = Millisekunden
; Rückgabewert: -
; Anmerkung: Diese Prozedur nutzt direkt die Hardware Ports! Eventuell
; funktioniert es nur unter Windows 95, 98, ME bzw. direkt im DOS-Modus.
; Unter Windows NT (2000, XP, ...) kann es sein das die NTVDM die
; Ausführung nicht gestattet bzw. das Programm nicht ordnungsgemäß abläuft.
; Quelle: "The 80x86 IBM PC & Compatible Computers" by
; Mazidi and Mazidi, page 343. Prentice-Hall, 1995.
;-----------------------------------------------------------
Delay PROC
 
MsToMicro = 1000000                   ; Konvertiere in Millisekunden
ClockFrequency = 15085                ; Mikrosekunden pro Takt
 
.code
    pushad
 
    mov  ebx, MsToMicro               ; Konvertiere Millisekunden in Mikrosekunden
    mul  ebx
 
    mov  ebx, ClockFrequency          ; Teile durch die Taktfrequenz von 15.085 micsek,
                                      ; um den Counter für Port 61h zu generieren.
    div  ebx                          ; eax = counter
    mov  ecx,eax
 
L1:                                   ; Beginne mit der Überprüfung von Port 61h. Halte
                                      ; Ausschau nach Änderungen am vierten Bit zwischen
                                      ; 1 und 0 alle 15.085 Mikrosekunden.
    in  al, 61h                       ; Lese Port 61h
    and al, 10h                       ; Bereinige alle Bits außer Bit 4
    cmp al, ah                        ; Hat es sich verändert?
    je  L1                            ; Nein, springe zurück
    mov ah, al                        ; Ja, speichere den Status
    dec ecx
    cmp ecx, 0                        ; Sind wir fertig?
    ja  L1
 
quit:
    popad
    ret
Delay ENDP
 
END main

Video Programmierung mit INT 10h

Vielleicht kennen Sie noch die alten MS-DOS Spiele aus den frühen 90er Jahren? Die Grafikchips waren in ihrer Leistung und ihrem Funktionsumfang noch relativ bescheiden. Technologien heutiger High-End Grafikprozessoren die mit den neuesten Vertex- und Pixel-Shadern arbeiten, parallel mehrere Rendering-Pipelines zur Verfügung stellen und Transistoren jenseits der 130 Millionen Marke auf einem einzelnen Chip beherbergen standen noch nicht zur Verfügung. So ist es aus heutiger Sicht nicht weiter verwunderlich das die damaligen Spiele im Gegensatz zu aktuellen Grafik-Knüllern etwas antiquitiert wirken. Neue APIs wie Direct3D, DirectDraw und OpenGL stellen dem Programmierer von heute eine riesige Anzahl an Werkzeugen zur Grafikprogrammierung zur Auswahl. Hersteller moderner GPUs versuchen ständig die Spezifikationen neuer API-Versionen zu erfüllen und zu übertreffen. Doch es war ein langer und steiniger Weg bis die ersten brauchbaren APIs den Grafikprogrammieren auf dem Massenmarkt zur Verfügung standen.

Als mit Windows 95 erstmals der Nachfolger von MS-DOS Ende des Jahres 1995 erschien, kam ein Problem auf das von Microsoft gelöst werden musste. Während DOS noch den direkten Zugriff auf die Videokarte, die Tastatur und Maus sowie das Soundsystem und andere Komponenten des Systems gewährte, baute Windows 95 auf dem Protected Mode auf, in dem kein direkter bzw. nur ein eingeschränkter Zugriff (signierte Treiber) auf das System gestattet wird. Microsoft benötigte eine Methode den Programmierern eine Schnittstelle zur Verfügung zu stellen ohne das Model des "Geschützten Modus" zu unterwandern. Das war die Geburtsstunde von DirectX. Im Laufe der Zeit entwickelte sich DirectX zu einer Sammlung von APIs für alle Bereiche der Spieleprogrammierung inklusive exzellenter Sound Komponenten (DirectX Sound) und vorgefertigten Installationsroutinen (DirectSetup). Microsoft verwendete aber in seinen ersten Betriebssystemen der NT-Serie die damals noch weitaus ausgreiftere OpenGL API die de facto als Industriestandard galt. Als Ende der 90er die Hardware Komponenten immer günstiger wurden und sich auch der Otto Normalverbraucher leistungsstarke Hardware kaufen konnte, wurde die Entwicklung von DirectX rasant beschleunigt und ist dafür verantwortlich das DirectX heute eine professionelle und leistungsstarke API-Sammlung zur Spieleprogrammierung ist. Mit dem Auftauchen von Windows Vista am Horizont wird diese Entwicklung fortgesetzt und die neue Windows Graphics Foundation, auch DirectX 10 genannt, bedeutet einen weiteren Quantensprung in der Programmierung.

Alle neuen Schnittstellen haben eines gemeinsam: Sie stellen eine weitere Abstraktionsschicht dar und trennen den Programmierer von der Hardware und den zugrundeliegenden Technologien ab. Das ist der Preis für eine standardisierte und plattformunabhängige API. Ein Preis der von Vielen zugunsten einer höheren Produktivität gerne gezahlt wird. Wir hingegen bewegen uns knapp über der Hardwareebene und wollen in diesem Tutorial erfahren wie die Video Programmierung mithilfe der BIOS-Dienste funktioniert. Dazu sehen wir uns zunächst einmal die möglichen Level des Zugriffs an. Wenn ein Programm beispielsweise Zeichen im Textmodus auf dem Bildschirm darstellen möchte kann es effektiv zwischen drei verschiedenen Zugriffsebenen wählen.

  • MS-DOS-Level Zugriff: Jeder Computer der MS-DOS ausführt oder emuliert kann INT 21h nutzen um Ausgaben durchzuführen. I/Os können schnell zu anderen Geräten umgeleitet werden. Die Ausgabe ist ziemlich langsam und die Textfarbe kann nicht beeinflußt werden.
  • Direct Video Zugriff: Zeichen werden direkt in den Video RAM kopiert, so dass die Ausführung nahezu sofort geschieht. Ausgaben können nicht umgeleitet werden. Auf allen Windows Systemen der NT-Serie kann dieser Zugriff nur im Vollbild Modus verwendet werden.
  • BIOS-Level Zugriff: Zeichen werden ausgegeben indem die Funktion INT 10h verwendet wird. Ausgaben können nicht umgeleitet werden und bei großen Auflösungen kann es zu geringfügigen Verzögerungen kommen.

Je nach nachdem was das Programm leisten soll, muss sich der Entwickler entscheiden welche der drei Zugriffsmethoden er auswählt. Die Methoden bauen in Schichten aufeinander auf. Während die MS-DOS Interrupts auf BIOS-Level Routinen zurückgreifen, verwenden die BIOS Routinen widerum den direkten Video Zugriff um die Ausgabe zu erzeugen.

Text-Modus

Es existieren zwei verschiedene Arten von Video-Modi, der Text- und der Grafik-Modus. Im Text-Modus wird der Bildschirm von oben bis unten in Reihen und Spalten aufgeteilt. Je nachdem welche Schriften/Fonts gerade ausgewählt sind wird die Zeilenhöhe bestimmt. Die Fonts befinden sich in einem Speicher und können in neuen BIOS Versionen auch geändert werden. Dadurch lassen sich individuelle Fonts für den Text-Modus gestalten. Der Video Speicher für den Text-Modus ist in multiple separate Bildschirmseiten (engl. Video Text Pages) aufgeteilt. Programme können eine Seite präsentieren und gleichzeitig Text zu anderen versteckten Seiten schreiben. Die Standard Video Seite ist die Seite 0. Jedem Zeichen wird ein Byte mit Attributwerten zugeteilt in dem die Hintergrundfarbe und die Farbe des Zeichens definiert ist. Die Zeichen sind exakt auf dem Bildschirm positioniert und können auch zum Blinken gebracht werden. Der Video Controller invertiert zu diesem Zweck die Vorder- und Hintergrundfarbe abwechselnd in einer festgelegten Taktrate. Das Blinking kann mithilfe einer BIOS Funktion an- und abgeschaltet werden.

Alle Pixel auf jedem gängigen Röhren-Monitor und auch auf allen modernen LCD-Monitoren werden mithilfe eines additiven Farbmodells erzeugt. Innerhalb eines so genannten RGB-Farbraums entstehen auf diese Weise sichtbare Farben indem jeweils der Anteil einer Grundfarbe gelöscht bzw. aktiviert wird. Die Grundfarben sind Rot, Grün und Blau. Um also die Farbe eines Pixels festzulegen ist die Kontrolle der drei Grundfarben erforderlich. Dazu steht ein 4-Bit großer Werteraum zur Verfügung. Die ersten drei Bits entscheiden darüber welche Grundfarben im Pixel leuchten sollen und welche nicht. Das vierte Bit gibt die Intensität der Leuchtkraft des gesamten Pixels an. Beispielsweise wird die Farbe Schwarz intern als 0000 dargestellt, die Farbe Weiß hingegen mit 1111 und die Farbe Rot mit 0100. Ein helles strahlendes Rot erhält man indem man zusätzlich das vierte Bit auf 1 setzt. Das folgende Abbild zeigt den Aufbau eines Attribut Bytes bestehend aus 8 Bit. Das linke Nibble ist für die Hintergrundfarbe verantwortlich und das rechte Nibble für die Vordergrundfarbe. Falls das Blinking aktiviert ist wird das Intensitätsbit des Hintergrundes als Identifikationsbit verwendet. In diesem Fall ist es nicht möglich eine strahlende Farbe für den Hintergrund zu verwenden. Unter Windows ist das Blinking auf den Vollbildmodus beschränkt.

rgb-mode

Um in Assemblersprache ein entsprechendes Attribut Byte aus zwei Farben zu erzeugen ist ledeglich ein Shift sowie eine Oder Operation erforderlich. Der x86-Befehlssatz stellt hierfür den Befehl SHL (Shift left) zur Verfügung. Das nachfolgende Beispiel zeigt im Ansatz wie ein blauer Hintergrund mit gelben Zeichen erzeugt werden kann.

blue = 1
yellow = 1110
mov bh, (blue SHL 4) OR yellow      ; Erzeugt 00011110

Im nächsten Abschnitt werden wir uns die wesentlichen Funktionen ansehen die INT 10h dem Programmierer zur Verfügung stellt. Tabelle 3 beschreibt diese Funktionen kurz.

Table 3: INT 10h Functions
Funktionsnummer Beschreibung
0 Durch Aufruf dieser Funktion wird ein Videomodus ausgewäht und initialisiert.
1 Setzt die Cursor Linie und kontrolliert die Gestalt und die Größe des Cursors.
2 Der Cursor, der die Bildschirmposition für die Zeichenausgabe über eine der BIOS- oder DOS-Funktionen zur Zeichenausgabe bestimmt, wird durch Aufruf dieser Funktion versetzt.
3 Die Position des Textcursors in einer bestimmten Bildschirmseite und die Start- und Endzeile des blinkenden Bildschirmcursors werden ausgelesen.
6 Scrollt ein Fenster auf der aktuellen Bildschirmseite (Video Page) aufwärts und ersetzt alle passierten Linien durch Leerzeichen.
7 Scrollt ein Fenster auf der aktuellen Bildschirmseite abwärts und ersetzt alle passierten Linien durch Leerzeichen.
8 Liest das Zeichen und sein Attribut Byte an der aktuellen Cursor Position aus.
9 Ein Zeichen mit einer bestimmten Farbe (Attribut) wird an die aktuelle Cursorposition (in einer vorgegebenen Bildschirmseite) geschrieben.
0A Ein Zeichen wird an die aktuelle Cursorposition (in einer vorgegebenen Bildschirmseite) geschrieben.
0C Ein Grafikpixel wird im Grafik-Modus auf den Bildschirm geschrieben.
0D Liest die Farbe eines einzelnen Grafikpixels an einer bestimmten Position.
0E Ein Zeichen wird an die aktuelle Cursorposition in der aktuellen Bildschirmseite geschrieben, wobei die Farbe des alten Zeichens an dieser Bildschirmposition beibehalten wird.
0F Gibt Information über den Video-Modus.
10 Schaltet das Blinking und die Intensität.
1E Schreibt einen String auf den Bildschirm im Teletype-Modus.

Die Funktion 0 von INT 10h setzt den Video-Modus. Bevor ein neuer Video-Modus gesetzt wird sollte der alte Modus in einer Variable zwischengespeichert werden. Auf diese Weise lässt sich kurz vor dem Programmende der alte Zustand wieder herstellen. Das nachfolgende Beispiel zeigt wie das prinzipiell aussehen könnte.

mov ah, 0Fh        ; Aufruf von 0Fh
int 10h
mov vmode, al      ; Speichert die Nummer des Modus in der Variable vmode
mov columns, ah    ; Speichert die Anzahl der Spalten (Zeichen oder Pixel)
mov page, bh       ; Speichert die aktive Bildschirmseite (Video Page)
 
mov ah, 0          ; Aufruf von 00h
mov al, 3          ; Setzt den Video Modus 3 (farbiger Text)
int 10h

Die Funktionen 1-3 dienen hauptsächlich diversen Manipulationen des Cursors. Eine relativ einfache Möglichkeit den Cursor zu verstecken ist zum Beispiel einen ungültigen bzw. zu großen Wert einzugeben. Die beiden Prozeduren HideCursor und ShowCursor veranschaulichen dies.

HideCursor PROC
    mov ah, 3         ; Empfängt die Cursor Größe
    int 10h
    or ch, 30h        ; Setzt die obere Reihe auf einen ungültigen Wert
    mov ah, 1         ; Setzt die Cursor Größe
    int 10h
    ret
HideCursor ENDP
 
 
ShowCursor PROC
    mov ah, 1         ; Setzt die Cursor Größe
    mov cx, 0607h     ; Standardgröße
    int 10h
    ret
ShowCursor ENDP

Die Funktion 6 scrollt ein rechteckiges Fenster entlang. Ein Fenster kann zuvor entsprechend definiert werden. Die Größe des Fensters auf dem Bildschirm ist gegeben durch zwei 2D-Koordinaten. Die Punkte bestimmen wo jeweils die gegenüberliegenden Eckpunkte liegen. Diese Eckpunkte spannen das Fenster auf. Die Register CH und CL definieren die Reihe und Spalte des linken oberen Eckpunktes. Der untere rechte Eckpunkt wird durch die Register DH und DL definiert.

screen-window

Wird entlang eines Fensters hinab oder hinauf gescrollt kann das Fenster auf diese Weise vollständig bereinigt werden. Das folgende Beispiel zeigt ein vollständiges Assembler Programm das ein Fenster mitsamt Text erzeugt. Sobald die Taste 'd' gedrückt wird, löscht die Funktion 06h den Text des erstellten Fensters.

TITLE Write and delete Color Text Window    (wrianddelctw.asm)
 
COMMENT ! Dieses Programm demonstriert wie sich ein Fenster
          generieren lässt und sich Zeichen in diesem
          Fenster löschen lassen. Dazu wird der farbige
          Text-Modus verwendet.
          Last update: 25/05/06 !
 
.model small, stdcall
.stack 200h
.386
 
.data
    message BYTE "Welcome to another CodePlanet Tutorial!", 0
    press BYTE "(Press 'd' to delete this message)", 0
 
.code
main PROC
    mov ax, @data
    mov ds, ax
 
    mov ax, 0600h       ; Scrollt ein Fenster entlang
    mov bh, 00011110b  ; Setzt alle Attribute der Zellen auf blaue Hintergrundfarbe
                        ; und gelbe Vordergrundfarbe
    mov cx, 050Ah       ; Linker oberer Eckpunkt
    mov dx, 0A42h       ; Rechter unterer Eckpunkt
    int 10h
 
    mov ah, 2           ; Positioniert den Cursor innerhalb der Fensters
    mov dx, 0714h       ; Reihe 7, Spalte 20
    mov bh, 0           ; Video Page 0
    int 10h
 
    mov dx, OFFSET message    ; Schreibt einen Text auf den Bildschirm
    call WriteString
 
    mov ah, 2
    mov dx, 0814h
    mov bh, 0
    int 10h
 
    mov dx, OFFSET press
    call WriteString
 
    mov ah, 10h
    int 16h
    cmp al, 64h          ; Wurde die Taste 'd' gedrückt?
    jne Exit
 
Del:
    mov ah, 6           ; Löscht die Zeichen in dem aufgespannten Fenster
    mov al, 0
    mov ch, 5
    mov cl, 0Ah
    mov dh, 0Ah
    mov dl, 42h
    mov bh, 00011110b
    int 10h
 
    mov ah, 10h         ; Warte auf einen Tastendruck
    int 16h
 
Exit:
    mov ah, 4Ch
    mov al, 0
    int 21h
main ENDP
 
;---------------------------------------------------------
; Str_length
; Gibt die Länge eines einen Null-terminierten Strings zurück.
; Empfängt: pString - Zeiger auf den String
; Rückgabewert: AX = Stringlänge
;---------------------------------------------------------
Str_length PROC USES di, pString:PTR BYTE
 
    mov di, pString
    mov ax, 0                ; Zeichen Zähler
L1:
    cmp byte ptr[di], 0      ; Ende des Strings?
    je  L2                   ; Ja, springe zu quit
    inc di                   ; Nein, gehe zum nächsten Zeichen
    inc ax                   ; Addiere 1 zum Zähler
    jmp L1
L2: ret
Str_length ENDP
 
;--------------------------------------------------------
; WriteString
; Schreibt einen Null-terminierten String auf die Ausgabe
; Empfängt: DS:DX zeigt auf den String
; Rückgabewert: -
;--------------------------------------------------------
WriteString PROC
 
    pusha
    INVOKE Str_length, dx   ; AX = string length
    mov  cx, ax             ; CX = number of bytes
    mov  ah, 40h            ; write to file or device
    mov  bx, 1              ; standard output handle
    int  21h                ; call MS-DOS
    popa
    ret
WriteString ENDP
 
END main

Vielleicht kennen Sie Programme die den Text der auf dem Bildschirm präsentiert wird, über das Soundsystem akustisch wiedergeben. Eine mögliche Variante einen Teil dieser Funktionalität zu realisieren ist die INT 10h Funktion 08h zu benutzen. Die Funktion liest das Zeichen an der aktuellen Cursor Position aus. Mithilfe der beiden nachfolgenden Funktionen kann ein Zeichen samt Attribut auch geschrieben werden. Die Funktion 09h unterscheidet sich von 0Ah nur in der Spezifikation des Attribut Bytes. Dieses ist bei 0Ah nicht anzugeben. Wird ein Zeichen geschrieben, so muss in den meisten Fällen anschließend manuell der Cursor um die Anzahl der geschriebenen Zeichen nach vorne verschoben werden.

.data
    char BYTE 0
    attribute BYTE 0
 
mov ah, 8             ; Aufruf von 08h. Die Funktion liest ein Zeichen
                      ; und das zugehörige Attribut Byte aus.
mov bh, 0             ; Video Page 0
int 10h
mov char, al          ; Speichert das Zeichen in der Variable char
mov attribute, ah
 
 
mov ah, 9             ; Die Funktion 08h schreibt ein Zeichen samt Attribut
                      ; an die aktuelle Cursor Position.
mov al, 'A'
mov bh, 0
mov bl, 0F1h          ; Attribut blau auf weiß
mov cx, 1             ; Gibt an wie oft das Zeichen 'A' wiederholt wird
int 10h

Wie bereits erwähnt wurde ist es im Text-Modus möglich die Zeichen zum Blinken zu bringen wenn das Blinking aktiviert wurde. Mit der Unterfunktion 03h lässt sich das Blinking sowie die Intensität kontrollieren indem direkt das vierte Bit manipuliert wird. Der Quellcode textblinking.asm veranschaulicht die Anwendung der Funktion.

TITLE Color Text Blinking              (textblinking.asm)
 
COMMENT ! Dieses Programm gibt einen blinkenden Text in
          verschiedenen Farben aus. Die Option Blinking steht
          unter Windows nur im Vollbildmodus zur Verfügung!
          Last update: 25/05/06 !
 
.model small, stdcall
.stack 200h
.386
 
NewLine  MACRO                 ; Zeilenumbruch
    mov  dl, 0dh
    mov  ah, 02
    int  21h
    mov  dl, 0ah
    mov  ah, 02
    int  21h
    ENDM
 
.data
    ATTRIB_HB = 10000000b
    string BYTE "I'm Blinking!"
    color  BYTE 1
 
.code
main PROC
    mov  ax, @data
    mov  ds, ax
 
    call clrScreen             ; Leere den Bildschirm
    call EnableBlinking        ; Aktiviere das Blinking
    mov  cx, SIZEOF string
    mov  si, OFFSET string
 
L1: push cx                    ; Speichere den Schleifenzähler
    mov  ah, 9                 ; Schreibe das Zeichen/Attribut
    mov  al, [si]              ; Zeichen das angezeigt werden soll
    mov  bh, 0                 ; Video Page 0
    mov  bl, color             ; Attribut
    or   bl, ATTRIB_HB         ; Setze das Blinking/Intensitäts Bit
    mov  cx, 1                 ; Zeige es einmal an
    int  10h
    mov  cx, 1                 ; Verschiebe den Cursor nach
    call AdvanceCursor         ; Nächste Bildschirm Spalte
    inc  color                 ; Nächste Farbe
    inc  si                    ; Nächstes Zeichen
    pop  cx                    ; Stelle den Schleifenzähler wieder her!
    loop L1
 
    NewLine                    ; Makro
    mov ah, 4Ch
    mov al, 0
    int 21h
main ENDP
 
;--------------------------------------------------
; EnableBlinking
; Aktiviert das Blinking indem das höchste Bit
; im Attribut Byte auf 1 gesetzt wird.
; Funktioniert unter Windows nur im Vollbildmodus.
;--------------------------------------------------
EnableBlinking PROC
  push ax
  push bx
  mov ax, 1003h
  mov bl, 1                  ; Blink-Modus (Intensität = 0, Blinking = 1)
  int 10h
  pop bx
  pop ax
  ret
EnableBlinking ENDP
 
;--------------------------------------------------
; AdvanceCursor
; Verschiebt den Cursor n Spalten nach rechts.
; Empfängt: CX = Anzahl der Spalten
; Rückgabewert: -
;--------------------------------------------------
AdvanceCursor PROC
  pusha
L1:
  push cx                    ; Schleifenzähler mit n initialisiert
  mov  ah, 3                 ; Empfange die aktuelle Cursor Position - 03h
  mov  bh, 0                 ; Video Page 0 (ins DH, DL)
  int  10h                   ; Der Aufruf verändert das CX Register!
  inc  dl                    ; Inkrementiere die Spalten
  mov  ah, 2                 ; Setze die Cursor Position
  int  10h
  pop  cx                    ; Stelle den Schleifenzähler wieder her!
  loop L1                    ; Nächste Spalte
 
  popa
  ret
AdvanceCursor ENDP
 
;------------------------------------------------------
; clrScreen
; Bereinigt den Bildschirm (video page 0) und lokalisiert
; den Cursor auf Reihe 0 und Spalte 0.
;-------------------------------------------------------
clrScreen PROC
    pusha
    mov ax, 0600h                     ; Scrolle das Fenster hoch
    mov cx, 0                         ; Obere linke Ecke (0,0)
    mov dx, 184Fh                    ; Untere rechte Ecke (24,79)
    mov bh, 7                        ; Normale Attribute
    int 10h                         ; Rufe BIOS
    mov ah, 2                        ; Lokalisiere Cursor auf 0,0
    mov bh, 0                        ; Video Page 0
    mov dx, 0                         ; Reihe 0, Zeile 0
    int 10h
    popa
    ret
clrScreen ENDP
 
END main

Unter Windows NT muss das Programm im Vollbildmodus ausgeführt werden. Öffnen Sie dazu die Eigenschaften des Programms und wählen Sie die Registerkarte Bildschirm. Setzen Sie einen Haken im Kontrollkästchen Vollbild. Falls dies nicht zum Erfolg führt, können Sie die Kommandozeile separat öffnen und über Alt+Enter in den Vollbildmodus schalten. Anschließend sollten Sie das Programm ohne Probleme aufrufen können.


Grafik-Modus

Neben den verschiedenen Text-Modi existieren auch einige Grafik-Modi (Graphics Mode). Mithilfe der INT 10h Funktion 0Ch lassen sich Pixel und Linien sehr einfach auf den Bildschirm zeichnen. Bevor Sie jedoch Pixel auf den Bildschirm zeichnen können, müssen Sie den Video Adapter in einen standardisierten Grafik-Modus bringen. Einen Überblick über die vorhandenen Video-Modi finden Sie in der Tabelle 4. Der Modus lässt sich genau wie im Abschnitt "Text-Modus" erklärt über die Funktion INT 10h 00h setzen.

Table 4: Video Modes
Modus Typ Karte Auflösung Farben
00 Text CGA, EGA, VGA 40 x 25 1
01 Text CGA, EGA, VGA 40 x 25 16
02 Text CGA, EGA, VGA 80 x 25 2
03 Text CGA, EGA, VGA 80 x 25 16
04 Grafik CGA, EGA, VGA 320 x 200 4
05 Grafik CGA, EGA, VGA 320 x 300 1
06 Grafik CGA, EGA, VGA 640 x 200 2
07 Text MDA, Hercules, EGA an MDA-Monitor 80 x 25 Monochrom
0D Grafik EGA, VGA 320 x 200 16
0E Grafik EGA, VGA 640 x 200 16
0F Grafik EGA an MDA-Monitor 640 x 350 Monochrom
10 Grafik EGA, VGA 640 x 350 16
11 Grafik VGA 640 x 480 2
12 Grafik VGA 640 x 480 16
13 Grafik VGA 320 x 200 256
14 Text VGA 132 x 25 16
6A Grafik VGA 800 x 600 16
weitere hardwareabhängig

Die Funktion 0Ch schreibt nicht direkt in den Video RAM, so dass es beim Zeichnen vieler Pixel zu geringfügigen Verzögerungen kommen kann. Die Verzögerung hängt proportional mit der Anzahl der Zeichenaufrufe für einen Pixel zusammen. Neben der Möglichkeit Pixel zu zeichnen ist es auch möglich ein Pixel vom Bildschirm zu lesen. Der prinzipielle Code für das Schreiben und Lesen eines Pixels kann folgendermaßen aussehen.

;--------------------------------------------------
; INT 10h Funktion 0Ch
; Schreibt einen Pixel auf den Bildschirm.
;--------------------------------------------------
mov ah, 0Ch
mov al, pixelValue         ; Der Pixelwert
mov bh, videoPage          ; Die Bildschirmseite
mov cx, xcoord             ; X-Koordinate
mov dx, ycoord             ; Y-Koordinate
int 10h
 
;--------------------------------------------------
; INT 10h Funktion 0Dh
; Liest einen Pixel vom Bildschirm. Die Funktion
; gibt das Pixel im AL-Register zurück.
;--------------------------------------------------
mov ah, 0Dh
mov bh, 0                 ; Die Bildschirmseite
mov cx, xcoord
mov dx, ycoord
int 10h

Bevor wir uns einer deutlich schnelleren Variante Pixel auf den Bildschirm zu zeichnen zuwenden, werden wir das bisher errungene Wissen dazu benutzen ein einfaches Schachbrett auf den Bildschirm zu zeichnen. Das folgende Programm zeichnet zunächst das einfache Gitternetz und füllt danach die weißen Felder auf. Dies dient ledeglich Demonstrationszwecken und ist für das Endresultat nur bedingt erforderlich.

TITLE Chess field        (chessfield.asm)
 
COMMENT ! Dieses Programm schaltet in den Grafik-Modus 6Ah (800 x 600)
          und zeichnet zunächst ein Gitternetz aus Schachfeldern.
          Anschließend füllt das Programm abwechselnd die
          entsprechenden Felder weiß auf, so dass am Ende ein
          Schachbrett auf dem Bildschirm zu sehen ist.
          Last update: 25/05/06 !
 
.model small, stdcall
.stack 200h
.386
 
;------------------------------------------------------
; Video-Modi Konstanten
;------------------------------------------------------
Mode_06 = 6         ; 640x200,  2 colors
Mode_0D = 0Dh       ; 320 x 200, 16 colors
Mode_0E = 0Eh       ; 640 x 200, 16 colors
Mode_0F = 0Fh       ; 640 x 350,  2 colors
Mode_10 = 10h       ; 640 x 350, 16 colors
Mode_11 = 11h       ; 640 x 480,  2 colors
Mode_12 = 12h       ; 640 x 480, 16 colors
Mode_13 = 13h       ; 320 x 200, 256 colors
Mode_6A = 6Ah       ; 800 x 600, 16 colors
 
white = 1111b
 
.data
    saveMode BYTE ?         ; Speichert den Video-Modus
    fRows WORD 20           ; Speichert die Reihe der horizontalen Linie
    fCols WORD 120          ; Speichert die Spalte der vertikalen Linie
    tmp WORD 0              ; Temporäre Variable
    innerCounter Word 0     ; Innerer Schleifenzähler
    outerCounter WORD 1     ; Äußerer Schleifenzähler
    fields BYTE 0           ; Anzahl der Felder pro Spalte
    fieldsCols WORD 0       ; Anzahl der Spalten
 
.code
main PROC
    mov ax, @data
    mov ds, ax
 
    mov  ah, 0Fh            ; Empfange den aktuell eingestellten Video-Modus
    int  10h
    mov  saveMode, al       ; Speichere diesen Video-Modus
 
    mov  ah, 0              ; Setze den Video-Modus 
    mov  al, Mode_6A        ; Nimm den Grafik-Modus mit 800 x 600 und 16 Farben
    int  10h
 
 
Rows:                       ; Zeichne die horizontalen Gitterlinien
    mov  cx, 120            ; X-Koordinate der Starlinie
    mov  dx, fRows          ; Y-Koordinate der Starlinie
    mov  ax, 560            ; Länge der Linie
    mov  bl, white          ; Farbe der Linie
    call DrawHorizLine      ; Zeichne die horizontale Linie
    add  fRows, 70          ; Nächste Linie
    cmp  fRows, 580         ; Sind wir fertig?  
    jbe  Rows               ; Falls fRows <= 580 springe zurück                           
 
Cols:
    mov  cx, fCols          ; Zeichne die vertikalen Gitterlinien
    mov  dx, 20
    mov  ax, 560
    mov  bl, white
    call DrawVerticalLine   ; Zeichne die vertikale Linie
    add  fCols, 70
    cmp  fCols, 680
    jbe  Cols               ; Falls fCols <= 680 springe zurück
 
    mov tmp, 20             ; Setze die temporäre Variable auf x = 20
    add fieldsCols, 120     ; Setze die Variable auf y = 120
                            ; Oberer linker Eckpunkt P(120|20)
 
L1:                         ; Fülle nun die weißen Felder aus indem jeweils
                            ; 70 horizontale Linien gezeichnet werden
    mov  cx, fieldsCols
    mov  dx, tmp
    mov  ax, 70
    mov  bl, white
    call DrawHorizLine
    inc innerCounter        ; Inkrementiere den inneren Schleifenzähler
    add tmp, 1
    cmp innerCounter, 70
    jb L1
    add fields, 1           ; Inkrementiere die Anzahl der weißen Felder in der Spalte
    cmp fields, 4           ; Haben wir 4 Felder gezeichnet?
    jae NextCol
    mov innerCounter, 0     ; Nein, inkrementiere die innere Schleife
    add tmp, 70             ; Addiere 70 dazu und überspringe damit das schwarze Feld
    jmp L1
 
NextCol:
    cmp outerCounter, 8     ; Haben wir die achte Spalte erreicht?
    jae Finished            ; Falls outerCounter >= 8 springe
    inc outerCounter
    mov fields, 0
    mov innerCounter, 0
    add fieldsCols, 70
    mov dx, 0
    mov ax, outerCounter
    mov cx, 2               ; Teile durch 2. Dies überprüft ob die Spalte gerade
                            ; oder ungerade ist.
    div cx                  ; 16-Bit unsigned Division
    cmp dx, 0               ; Checke ob der Rest (Remainder) in DX gleich 0
                            ; (Modulo Division) ist
    je EVENx                ; Springe an EVENx, falls gleich
    jmp ODDx                ; Andernfalls an ODDx
 
EVENx:
    mov tmp, 90
    jmp L1
 
ODDx:
    mov tmp, 20
    jmp L1
 
Finished:
    mov  ah, 10h            ; Warte auf einen Tastendruck
    int  16h
 
    mov  ah, 0              ; Stelle den anfangs gespeicherten Video-Modus wieder her
    mov  al, saveMode
    int  10h
 
    mov ax, 4C00h   ; Beende das Programm samt Exit Code
    int 21h         ; Gebe die Kontrolle wieder an DOS zurück
main endp
 
;------------------------------------------------------
; DrawHorizLine
; Zeichnet eine horizontale Linie beginnend an der
; Position X,Y mit einem übergebenen Längenwert und Farbe.
; Empfängt: CX = X-Koordinate, DX = Y-Koordinate,
;           AX = Länge, und BL = Farbe
; Rückgabewert: -
;------------------------------------------------------
DrawHorizLine PROC
.data
currX WORD ?
 
.code
    pusha
    mov  currX, cx          ; Speichert die X-Koordinate
    mov  cx, ax             ; Schleifenzähler
 
DHL1:
    push cx                 ; Speichert den Schleifenzähler
    mov  al, bl             ; Farbe
    mov  ah, 0Ch            ; Zeichne Pixel
    mov  bh, 0              ; Video Page
    mov  cx, currX          ; Empfabge die X-Koordinate
    int  10h
    inc  currX              ; Gehe 1 Pixel nach rechts
    pop  cx                 ; Stelle den Schleifenzähler wieder her
    Loop DHL1
 
    popa
    ret
DrawHorizLine ENDP
 
;------------------------------------------------------
; DrawVerticalLine
; Zeichnet eine vertikale Linie beginnend an der
; Position X,Y mit einem übergebenen Längenwert und Farbe.
; Empfängt: CX = X-Koordinate, DX = Y-Koordinate,
;           AX = Länge, und BL = Farbe
; Rückgabewert: -
;------------------------------------------------------
DrawVerticalLine PROC
.data
currY WORD ?
 
.code
    pusha
    mov  currY, dx          ; Speichert die Y-Koordinate
    mov  currX, cx
    mov  cx, ax             ; Schleifenzähler
 
DVL1:
    push cx                 ; Speichert den Schleifenzähler
    mov  al, bl             ; Farbe
    mov  ah, 0Ch            ; Zeichne Pixel
    mov  bh, 0
    mov  cx, currX          ; Setze die X-Koordinate
    mov  dx, currY          ; Setze die Y-Koordinate
    int  10h                ; Zeichne Pixel
    inc  currY              ; Gehe 1 Pixel nach unten
    pop  cx                 ; Stelle den Schleifenzähler wieder her
    Loop DVL1
 
    popa
    ret
DrawVerticalLine ENDP
END main

Vergessen Sie nicht das Programm unter Windows im Vollbildmodus auszuführen!

Memory-Mapped I/O

Das Zeichnen mittels Interrupt 10h ist bei vielen Pixeln oft eine träge Angelegenheit. Der Grund liegt darin das sehr oft sehr viele Codezeilen ausgeführt werden müssen. Dies verlangsamt das Programm ganz erheblich. Eine wesentlich effizientere und oft verwendete Variante ist eine Technik namens memory-mapped graphics, die alle Daten direkt im VRAM ablegt bevor sie auf dem Bildschirm gezeichnet werden.

Der Video-Modus 13h ist der bevorzugte Modus sobald man direkt mit dem Speicher arbeitet. Der Modus besitzt eine Auflösung von 320 x 200 und kann 256 Farben darstellen. Die Bildschirm-Pixel werden als zweidimensionale Arrays von Bytes dargestellt, indem jeder Pixel ein eigenes Byte belegt. Das Array beginnt mit dem Pixel in der oberen linken Ecke des Bildschirms und erstreckt sich bis zum Pixel der oberen rechten Ecke. Im Video-Modus 13h sind das exakt 320 Bytes. Das nächste Byte bildet das Pixel in der zweiten Zeile ab und so weiter. Ein Byte kann einschließlich der 0 insgesamt 256 verschiedene Ganzzahlenwerte darstellen. Damit lassen sich die 256 verschiedene Farben repräsentieren. Alle Pixel und Farbwerte können zum Video Adapter transferiert werden indem die OUT Instruktion verwendet wird. Dazu wird eine 16-Bit Port Adresse an DX zugewiesen und der Wert der an den Port gesendet wird, wird in AL abgelegt. Die Video Farbpalette befindet sich an der Port Adresse 3C8h. Die Farben werden mithilfe einer RGB Farbpalette dargestellt. Es können insgesamt 262144 verschiedene Farben ausgegeben werden. Zeitgleich können natürlich nur 256 Farben angezeigt werden, jedoch kann die Ausgabe zur Laufzeit geändert werden.

Im nächsten Programm werden wir sehen wie sich Animationen programmieren lassen. Das Programm FireScreen bildet ein flackerndes Feuer auf dem Bildschirm ab. Falls Sie Probleme haben den folgenden, etwas komplexeren Quellcode nachzuvollziehen, können Sie sich parallel dazu die anderen Beispiele zum Memory-Mapping im Anhang dieses Tutorials ansehen. Diese sind teilweise einfacher strukturiert.

TITLE Fire Screen          (firescreen.asm)
 
COMMENT ! Dieses Programm nutzt memory-mapped graphics
          um mithilfe einer RGB Farbpalette ein
          Feuereffekt auf dem Bildschirm darzustellen.
          Last update: 25/05/06 !
 
.model small, stdcall
.stack 200h
.386
 
.data
    saveMode BYTE ?                                ; Gespeicherter Video Modus
    urlmessage BYTE "http://www.codeplanet.eu/$"   ; Wird am Ende angezeigt
    xVal = 80                                      ; Bildschirmweite in Pixeln
    yVal = 112                                     ; Bildschirmhöhe in Pixeln + 2
    seed WORD ?                                    ; Zufallszahlen
 
    ; RGB Farbpalette von 0-63 (maximum), die Palette repräsentiert das Feuer
    palette BYTE  0,  0,  0,  0,  0,  6,  0,  0,  6,  0,  0,  7,  0,  0,  8,  0,  0,  8
            BYTE  0,  0,  9,  0,  0, 10,  2,  0, 10,  4,  0,  9,  6,  0,  9,  8,  0,  8
            BYTE 10,  0,  7, 12,  0,  7, 14,  0,  6, 16,  0,  5, 18,  0,  5, 20,  0,  4
            BYTE 22,  0,  4, 24,  0,  3, 26,  0,  2, 28,  0,  2, 30,  0,  1, 32,  0,  0
            BYTE 32,  0,  0, 33,  0,  0, 34,  0,  0, 35,  0,  0, 36,  0,  0, 36,  0,  0
            BYTE 37,  0,  0, 38,  0,  0, 39,  0,  0, 40,  0,  0, 40,  0,  0, 41,  0,  0
            BYTE 42,  0,  0, 43,  0,  0, 44,  0,  0, 45,  0,  0, 46,  1,  0, 47,  1,  0
            BYTE 48,  2,  0, 49,  2,  0, 50,  3,  0, 51,  3,  0, 52,  4,  0, 53,  4,  0
            BYTE 54,  5,  0, 55,  5,  0, 56,  6,  0, 57,  6,  0, 58,  7,  0, 59,  7,  0
            BYTE 60,  8,  0, 61,  8,  0, 63,  9,  0, 63,  9,  0, 63, 10,  0, 63, 10,  0
            BYTE 63, 11,  0, 63, 11,  0, 63, 12,  0, 63, 12,  0, 63, 13,  0, 63, 13,  0
            BYTE 63, 14,  0, 63, 14,  0, 63, 15,  0, 63, 15,  0, 63, 16,  0, 63, 16,  0
            BYTE 63, 17,  0, 63, 17,  0, 63, 18,  0, 63, 18,  0, 63, 19,  0, 63, 19,  0
            BYTE 63, 20,  0, 63, 20,  0, 63, 21,  0, 63, 21,  0, 63, 22,  0, 63, 22,  0
            BYTE 63, 23,  0, 63, 24,  0, 63, 24,  0, 63, 25,  0, 63, 25,  0, 63, 26,  0
            BYTE 63, 26,  0, 63, 27,  0, 63, 27,  0, 63, 28,  0, 63, 28,  0, 63, 29,  0
            BYTE 63, 29,  0, 63, 30,  0, 63, 30,  0, 63, 31,  0, 63, 31,  0, 63, 32,  0
            BYTE 63, 32,  0, 63, 33,  0, 63, 33,  0, 63, 34,  0, 63, 34,  0, 63, 35,  0
            BYTE 63, 35,  0, 63, 36,  0, 63, 36,  0, 63, 37,  0, 63, 38,  0, 63, 38,  0
            BYTE 63, 39,  0, 63, 39,  0, 63, 40,  0, 63, 40,  0, 63, 41,  0, 63, 41,  0
            BYTE 63, 42,  0, 63, 42,  0, 63, 43,  0, 63, 43,  0, 63, 44,  0, 63, 44,  0
            BYTE 63, 45,  0, 63, 45,  0, 63, 46,  0, 63, 46,  0, 63, 47,  0, 63, 47,  0
            BYTE 63, 48,  0, 63, 48,  0, 63, 49,  0, 63, 49,  0, 63, 50,  0, 63, 50,  0
            BYTE 63, 51,  0, 63, 52,  0, 63, 52,  0, 63, 52,  0, 63, 52,  0, 63, 52,  0
            BYTE 63, 53,  0, 63, 53,  0, 63, 53,  0, 63, 53,  0, 63, 54,  0, 63, 54,  0
            BYTE 63, 54,  0, 63, 54,  0, 63, 54,  0, 63, 55,  0, 63, 55,  0, 63, 55,  0
            BYTE 63, 55,  0, 63, 56,  0, 63, 56,  0, 63, 56,  0, 63, 56,  0, 63, 57,  0
            BYTE 63, 57,  0, 63, 57,  0, 63, 57,  0, 63, 57,  0, 63, 58,  0, 63, 58,  0
            BYTE 63, 58,  0, 63, 58,  0, 63, 59,  0, 63, 59,  0, 63, 59,  0, 63, 59,  0
            BYTE 63, 60,  0, 63, 60,  0, 63, 60,  0, 63, 60,  0, 63, 60,  0, 63, 61,  0
            BYTE 63, 61,  0, 63, 61,  0, 63, 61,  0, 63, 62,  0, 63, 62,  0, 63, 62,  0
            BYTE 63, 62,  0, 63, 63,  0, 63, 63,  1, 63, 63,  2, 63, 63,  3, 63, 63,  4
            BYTE 63, 63,  5, 63, 63,  6, 63, 63,  7, 63, 63,  8, 63, 63,  9, 63, 63, 10
            BYTE 63, 63, 10, 63, 63, 11, 63, 63, 12, 63, 63, 13, 63, 63, 14, 63, 63, 15
            BYTE 63, 63, 16, 63, 63, 17, 63, 63, 18, 63, 63, 19, 63, 63, 20, 63, 63, 21
            BYTE 63, 63, 21, 63, 63, 22, 63, 63, 23, 63, 63, 24, 63, 63, 25, 63, 63, 26
            BYTE 63, 63, 27, 63, 63, 28, 63, 63, 29, 63, 63, 30, 63, 63, 31, 63, 63, 31
            BYTE 63, 63, 32, 63, 63, 33, 63, 63, 34, 63, 63, 35, 63, 63, 36, 63, 63, 37
            BYTE 63, 63, 38, 63, 63, 39, 63, 63, 40, 63, 63, 41, 63, 63, 42, 63, 63, 42
            BYTE 63, 63, 43, 63, 63, 44, 63, 63, 45, 63, 63, 46, 63, 63, 47, 63, 63, 48
            BYTE 63, 63, 49, 63, 63, 50, 63, 63, 51, 63, 63, 52, 63, 63, 52, 63, 63, 53
            BYTE 63, 63, 54, 63, 63, 55, 63, 63, 56, 63, 63, 57, 63, 63, 58, 63, 63, 59
            BYTE 63, 63, 60, 63, 63, 61, 63, 63, 62, 63, 63, 63
 
    screen BYTE xVal * yVal dup (?)                ; Virt. Bildschirm (nach der palette!)
 
.code
main PROC
    mov ax, @data
    mov ds, ax
 
    call SetUpScreen
    mov seed, 7895h
    mov si, OFFSET screen
    mov cx, LENGTHOF screen
    xor ax, ax
    rep stosb                  ; Leere den Bildschirm.
 
ML1:                           ; Das nächste Bit gibt entweder 0 oder 255 entlang der
    mov si, OFFSET screen      ; untersten Reihe des virtuellen Bildschrims aus.
    add si, LENGTHOF screen
    sub si, xVal               ; SI=OFFSET(screen)+LENGTHOF(screen)-xVal. Letzte Reihe
    mov cx, xVal               ; Wiederhole die komplette letzte Reihe
    xor dx, dx
 
L1:
    call Randomize             ; Erzeuge Zufallswerte
    mov ds:[si], dl
    inc si
    dec cx
    jnz L1
 
    mov cx, LENGTHOF screen    ; Mildere die Werte im virtuellen Array ab,
    sub cx, xVal               ; so dass ein sanfter Feuereffekt entsteht
    mov si, OFFSET screen
    add si, xVal
 
L3:
    xor ax, ax
    mov al, ds:[si]
    add al, ds:[si+1]
    adc ah, 0
    add al, ds:[si-1]
    adc ah, 0
    add al, ds:[si+xVal]
    adc ah, 0
    shr ax, 2
    jz ZERO                    ; Am Ende?
    dec ax
 
ZERO:
    mov ds:[si-xVal], al       ; AL=((pos)+(pos+1)+(pos-1)+(pos+80))/4 - 1
    inc si
    dec cx
    jnz L3
    mov dx, 3DAh               ; VGA-Ausgabe
 
L4: in  al, dx
    and al, 8h
    jnz L4
 
L5: in  al, dx
    and al, 8h
    jz  L5
 
    mov cx, LENGTHOF screen
    shr cx, 1
    mov si, OFFSET screen
    xor di, di
    rep movsw
 
    mov ah, 11h                ; Überprüfe den Tastaturpuffer
    int 16h                    ; Wurde eine Taste gedrückt (ZF)?
    jz ML1                     ; Nein, mache weiter
 
    call RestoreVideoMode      ; Warte auf Tastendruck, stelle VM wieder her
    mov ah, 9                  ; Ausgabe eines $-terminierter Strings
    mov dx, OFFSET urlmessage  ; DS:DX = Segment/Offset des Strings
    int 21h
 
    mov ah, 10h                ; Warte auf Tastendruck
    int 16h
 
    mov ax, 4C00h              ; Beende das Programm samt Exit Code
    int 21h                    ; Gebe die Kontrolle wieder an DOS zurück
main ENDP
 
 
;----------------------------------------------------------
; Randomize
; Diese Prozedur erzeugt Zufallswerte für den bewegenden
; Feuereffekt.
;----------------------------------------------------------
Randomize PROC NEAR
    mov ax, seed               ; Initialisiere
    mov dx, 8405h
    mul dx                     ; DX:AX = AX * DX
    inc ax
    mov seed, ax
    ret
Randomize ENDP
 
;----------------------------------------------------------
; SetUpScreen
; Diese Prozedur bereitet den Bildschrim vor. Mithilfe der Prozedur
; SetVideoMode wird der Video-Modus umgeschaltet. Die Werte
; an Port 3C8h gesendet, zeigen welche Video Palette geändert wird.
;----------------------------------------------------------
SetUpScreen PROC NEAR
    call SetVideoMode          ; Schalte in den Mode 13h, 320 x 200, 256 Farben
    cli                        ; Leere die Interrupt Flags (Clear Interrupt Flag)
    cld
    mov dx, 3C4h
    mov ax, 604h
    out dx, ax
    mov ax, 0F02h
    out dx, ax
    xor ax, ax
    mov cx, 32767
    rep stosw                  ; Leere den Bildschirm
    mov dx, 3D4h
    mov ax, 14h                ; Deaktiviere DWORD-Modus
    out dx, ax
    mov ax, 0E317h             ; Aktiviere Byte-Modus
    out dx, ax
    out dx, ax
    mov ax, 00409h             ; Zellenhöhe
    out dx, ax
 
    mov si, OFFSET palette
    mov dx, 3C8h               ; Video Paletten Port
    mov al, 0                  ; Setze den Palettenindex
    out dx, al                 ; Beginne bei der Farbe null
    inc dx
    mov cx, 768
 
PLOOP:
    outsb                      ; Stringausgabe seitens ES:(E)DI auf Port zeigend
    dec cx
    jnz PLOOP
    ret
SetUpScreen ENDP
 
;----------------------------------------------------------
; SetVideoMode
; Diese Prozedur speichert den aktuellen Video-Modus, schaltet
; in einen neuen Modus und zeigt ES auf das Video Segment.
;----------------------------------------------------------
SetVideoMode PROC
    mov ah, 0Fh                ; Empfange den aktuellen current Video-Modus
    int 10h
    mov saveMode, al           ; Speichere den Modus
 
    mov ah, 0                  ; Setze den Video-Modus
    mov al, 13h                ; auf Modus 13h
    int 10h
 
    push 0A000h                ; Video Segment Adresse
    pop es                     ; ES = A000h (Video Segment).
 
    ret
SetVideoMode ENDP
 
;----------------------------------------------------------
; RestoreVideoMode
; Diese Prozedur wartet auf die Betätigung einer Taste und
; stellt den Video Modus wieder her.
;----------------------------------------------------------
RestoreVideoMode PROC
    mov ah, 10h                ; Warte auf Tastendruck
    int 16h
    mov ah, 0                  ; Resete den Video-Modus
    mov al, saveMode           ; auf den gespeicherten Video-Modus.
    int 10h
    ret
RestoreVideoMode ENDP
 
END main

Programmieren mit der Maus

Zum Ende dieses Tutorials werden wir einen abschließenden Blick auf die Maus werfen. Die Maus übermittelt ihre Bewegung über horizontale und vertikale Längenangaben an den Rechner. Eine Länge entspricht dem 1/200 Teil eines Inch (amerikanische Längeneinheit) und wird als Mickey bezeichnet. Die BIOS Dienste stellen für den Umgang mit der Maus den Interrupt 33h zur Verfügung. Darin enthalten sind verschiedene Funktionen, unter anderem zur Geschwindigkeitsmessung, Positionsangabe und Abfrage des letzten Tastendrucks. Der Mauszeiger selbst lässt sich mithilfe der INT 33h Funktionen auch verbergen und wieder anzeigen. Dazu stehen die Funktionen 01h und 02h zur Verfügung.

mov ax, 1          ; Zeige den Mauszeiger
int 33h
 
mov ax, 2          ; Verberge den Mauszeiger
int 33h

Die Funktion 00h gibt Informationen über den Mausstatus zurück. Zudem wird beim Aufruf der Funktion eine vollständiges Reset der Maus durchgeführt. Der Mauszeiger wird auf dem Bildschirm zentriert, alle Einstellungen werden auf die Werkseinstellungen zurückgesetzt und der Zeiger wird unsichtbar. Der folgende Code demonstriert den Vorgang.

mov ax, 0                ; Aufruf der Funktion 00h (Maus Reset und Statusabfrage)
int 33h
cmp ax, 0                ; Falls die Maus nicht verfügbar ist, ist das
je MouseNotAvailable     ; AX-Register 0. Andernfalls befindet sich dort
                         ; der Wert FFFFh und im BX die Anzahl der Maustasten.

Um die Position der Maus zu empfangen und zu setzen kann man die beiden Funktionen 03h und 04h verwenden. Mithilfe der Funktion 03h lässt sich auch überprüfen ob eine Taste gedrückt wird.

mov ax, 3                    ; Empfange die Position
int 33h
mov bx, dx                   ; Speichere die Y-Koordinate
cmp cx, 222                  ; Überprüfe ob die X-Koordinate des
je TrueData                  ; Mauszeigers auf 222 liegt.
 
mov ax, 3                    ; LT (Bit 0), RT (Bit 1), MT (Bit 2)
int 33h
test bx, 1                   ; Teste das erste Bit auf 0  mittels logischen
jne Left_Button_Down         ; AND (Flags werden beinflußt)
test bx, 2
jne Right_Button_Down
test bx, 4                   ; Dezimal=4, Binär=100 (Bit 2)
jne Middle_Button_Down
 
mov ax, 4                    ; Positioniere die Maus manuell
mov cx, 250                  ; X-Koordinate
mov dx, 100                  ; Y-Koordinate
int 33h

INT 33h stellt noch einige weitere Funktionen bereit. Eine vollständige Übersicht finden Sie auf der Seite Datasource unter DOS-Interrupt 33h (Mausfunktionen). Dem Dateianhang dieses Tutorials ist selbstverständlich ein ausführliches Beispiel zu INT 33h beigefügt.

Schluss

Die BIOS Ebene bietet eine große Kontrolle und Flexibilität über die I/O-Geräte des Computers. Sie können direkt mit der Hardware arbeiten und diese direkt nach Ihren eigenen Wünschen und Vorstellungen manipulieren. Darüberhinaus gewährt diese hardwarenahe Systemprogrammierung einen tiefen Einblick in die zugrundeliegende Technik die jeden Computer zu dem macht was er ist, ein multifunktionales Werkzeug.

Zuletzt aktualisiert am Samstag, den 22. Mai 2010 um 17:08 Uhr
 
AUSWAHLMENÜ