communityWir suchen ständig neue Tutorials und Artikel! Habt ihr selbst schonmal einen Artikel verfasst und seid bereit dieses Wissen mit der Community zu teilen? Oder würdet ihr gerne einmal über ein Thema schreiben das euch besonders auf dem Herzen liegt? Dann habt ihr nun die Gelegenheit eure Arbeit zu veröffentlichen und den Ruhm dafür zu ernten. Schreibt uns einfach eine Nachricht mit dem Betreff „Community Articles“ und helft mit das Angebot an guten Artikeln zu vergrößern. Als Autor werdet ihr für den internen Bereich freigeschaltet und könnt dort eurer literarischen Ader freien Lauf lassen.

BIOS-Level Programmierung - Die Tastatur Drucken E-Mail
Benutzerbewertung: / 52
SchwachPerfekt 
Geschrieben von: StarShaper   
Donnerstag, den 01. Juni 2006 um 10:30 Uhr
Beitragsseiten
BIOS-Level Programmierung
Die Tastatur
Video Programmierung
Grafik-Modi
Mausprogrammierung
Alle Seiten

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


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