BIOS-Level Programmierung - Die Tastatur |
Geschrieben von: StarShaper | |||||||||||||||||||||||||||||||||||||||||
Donnerstag, den 01. Juni 2006 um 10:30 Uhr | |||||||||||||||||||||||||||||||||||||||||
Seite 2 von 5
Tastatur Eingaben mit INT 16hEs 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. 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. 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 FlagsINT 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.
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 |
|||||||||||||||||||||||||||||||||||||||||
Zuletzt aktualisiert am Samstag, den 22. Mai 2010 um 17:08 Uhr |
AUSWAHLMENÜ | ||||||||
|