PS/2-Maus Tutorial

Aus Lowlevel
Wechseln zu:Navigation, Suche
PS/2-Maus
Schwierigkeit:

Stern gold.gifStern gold.gifStern weiß.gifStern weiß.gifStern weiß.gif

Benötigtes Vorwissen: IRQ, PIC, KBC
Sprache: C


Die Maus ist neben der Tastatur eines der wichtigsten Eingabemittel. Über den Keyboard Controller kann das Betriebssystem mit ihr kommunizieren, der hierfür vorgeschriebene Ablauf ist im sogenannten PS/2-Protokoll beschrieben.

Dieses Tutorial geht nicht auf alle Aspekte der PS/2-Maus ein, sondern liefert nur einen Leitfaden zur Programmierung. Für Details siehe PS/2-Maus.

Der Keyboard-Controller

Der Keyboard Controller wurde entwickelt, um zwei Eingabegeräte zu steuern. Das erste ist die Tastatur, das zweite in der Regel die Maus. Die Tastatur sendet einen IRQ1, wenn sie Daten für die CPU hat, also wenn der Benutzer eine Taste gedrückt hat. Die Maus verwendet dafür den IRQ12. Die Maus selbst und das Senden des IRQ12 müssen aber erst durch den Keyboard Controller aktiviert werden. Der Keyboard Controller ist über die Ports 0x60 und 0x64 ansprechbar. Der Port 0x60 dient dabei als Datenpuffer, der Port 0x64 als Status- und Befehlsregister:

  • Lesen von 0x60 liefert den Inhalt des Output Puffers.
  • Schreiben nach 0x60 schreibt Daten in den Input Puffer.
  • Lesen von 0x64 liefert das Statusbyte des Keyboard Controllers.
  • Schreiben nach 0x64 sendet einen Befehl an den Keyboard Controller.

Das Statusregister

Das Statusregister kann jederzeit vom Port 0x64 gelesen werden. Hier sind die ersten beiden Bits wichtig:

Bit gelöscht (0) gesetzt (1)
0 Es befinden sich keine Daten im Output Puffer. Es befinden sich Daten im Output Puffer, das Lesen vom Port 0x60 ist somit erlaubt.
1 Der Input Puffer ist leer. Es dürfen Befehle über die Ports 0x60 und 0x64 gesendet werden. Es befinden sich noch Daten im Input Puffer. Der Keyboard Controller nimmt momentan keine Befehle an.

Die anderen Bits sind erst einmal irrelevant.

Verwendung des Daten Puffers

Einige der weiter unten aufgeführten Befehle erwarten zusätzliche Daten. Diese werden über den Input Puffer (Port 0x60) übergeben. Das Schreiben in den Input Puffer ist aber nur erlaubt, wenn Bit 1 im Statusregister gelöscht ist. Die folgende Funktion wartet darauf, dass das Bit 1 gelöscht ist und sendet dann die Daten:

<c> void kbc_write_inbuf(uint8_t data) {

 uint32_t to = 255;
 while ( inb(0x64) & 0x02)
 {
   if (! (to--) )
   {
     kprintf(“mouse timeout!”);
     return;
   }
 }
 outb(0x60, data);

} </c>

Andere Befehle des Keyboard Controllers liefern Daten zurück. Diese werden dann aus dem Output Puffer (Port 0x60) gelesen. Hier muss darauf gewartet werden, dass das Bit 0 im Statusregister gesetzt ist:

<c> uint8_t kbc_read_outbuf() {

 uint32_t to = 255;
 while ( ! (inb(0x64) & 0x01) )
 {
   if (! (to--) )
   {
     kprintf(“mouse timeout!”);
     return;
   }
 }
 return inb(0x60);

} </c>

Befehle des Keyboard Controllers

Befehle an den Keyboard Controller werden zum Port 0x64 geschrieben. Auch hier muss vor dem Senden darauf gewartet werden, dass das Bit 1 des Statusregisters gelöscht ist:

<c> void kbc_send_cmd (uint8_t cmd) {

 uint32_t to = 255;
 uint8_t kbc_status = inb(KBC_STATUS);
 while ( inb(0x64) & 0x02)
 {
   if (! (to--) )
   {
      kprintf(“mouse timeout!”);
      return;	
   }
   sleep(10);
 }
 outb (0x64, cmd);

} </c>

Der Keyboard Controller kennt einige Befehle. Hier eine Auswahl der Befehle, die man zur Ansteuerung der Maus benötigt:

Wert Beschreibung
0xA8 Mause aktivieren
0xA7 Maus deaktivieren
0x20 Command Byte lesen
0x60 Command Byte schreiben
0xD4 sende den nächsten Befehl an die Maus anstatt zur Tastatur

Maus aktivieren (0xA8) und deaktiveren (0xA7)

Der Keyboard Controller kann zwei Eingabegeräte steuern. Standardmäßig ist aber nur das erste, nämlich die Tastatur, aktiviert. Mit diesem Befehl wird auch die das zweite Eingabegerät, nämlich die Maus aktiviert. Das kann natürlich auch wieder rückgängig gemacht werden.

Command Byte lesen (0x20)

Das Command Byte befindet sich im RAM des Keyboard Controllers. Es enthält ein für uns wichtiges Bit, nämlich Bit 1. Ist dieses gesetzt, dann sendet die Maus immer einen IRQ12, wenn es neue Daten für uns hat (also wenn der Benutzer die Maus bewegt hat). Zum Lesen des Command Bytes muss man zuerst den Befehl 0x20 an den Keyboard Controller senden, woraufhin dieser das Byte im Output Puffer (0x60) platziert.

Command Byte schreiben (0x60)

Zum Schreiben muss man zuerst den Befehl 0x60 an den Keyboard Controller senden und danach das Command Byte im Input Puffer platzieren.

Befehl an die Maus senden (0xD4)

Der Keyboard Controller steuert wie bereits erwähnt zwei Eingabegeräte. Wenn man Daten im Inputpuffer platziert interpretiert der Keyboard Controller diese als Befehle an die Tastatur und leitet sie entsprechend weiter. Wenn man aber nun Befehle an die Maus senden möchte, dann muss man dieses vorher mit dem Befehl 0xD4 signalisieren:

<c> void mouse_send_cmd(uint8_t cmd) {

 kbc_send_cmd(KBC_CMD_MOUSE);
 kbc_write_inbuf(cmd);

} </c>

Auch wenn ein Mausbefehl noch Daten erwartet, dann muss das Senden von Daten vorher mit dem Befehl 0xD4 signalisiert werden. Im PS/2-Protokoll sind u.a. folgende Befehle definiert, die die Maus „kennt“:

Befehl Beschreibung
0xF4 Teile der Maus mit, dass sie Daten an die CPU senden soll
0xF5 Teile der Maus mit, dass sie keine Daten an die CPU senden soll
0xF6 Setze Mauseinstellungen auf Standarteinstellungen zurück

Auf all diese Befehle antwortet die Maus, indem sie den Wert 0xFA (ACK – Acknowledge genannt) im Output Puffer platziert:

<c> uint8_t mouse_read_data() {

 return kbc_read_outbuf();

} </c>

Initialisierung der Maus

Folgende Funktion aktiviert die Maus so, dass sie immer einen IRQ12 auslöst, wenn der Benutzer die Maus bewegt hat:

<c>

  1. define ACK 0xFA

void mouse_install() {

 uint8_t command_byte;
 //leere den Daten Puffer
 while ( inb(0x64) & 0x01)
 {
   inb(0x60);
 }
 //aktiviere die Maus beim Keyboard Controller
 kbc_send_cmd(0xA8);
 //Bit 1 im Command Byte setzen
 kbc_send_cmd(0x20);
 cb = kbc_read_outbuf();
 cb |= 0x02;
 kbc_send_cmd(0x60);
 kbc_write_inbuf(cb);
 //Standards setzen
 mouse_send_cmd(0xF6)
 if( mouse_read_data() != ACK)
 {
   kprintf(“mouse error!”);
   return;
 }
 //das Senden von Daten aktivieren
 mous_send_cmd(0xF4);
 if( mouse_read_data() != ACK)
 {
   kprintf(“mouse error!”);
   return;
 }
 return;

} </c>

Der IRQ12

Datenpakete

Die Maus sendet nun immer mehrere IRQ 12, wenn sie bewegt oder geklickt wurde. Im einfachsten Falle sendet sie drei IRQs, bei denen mit jedem IRQ nacheinander folgende Datenbytes im Output Puffer liegen:

Byte # Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
1 Y Overflow X Overflow Y Sign X Sign Immer 1 Mittlerer Button gedrückt Rechter Button gedrückt Linker Button gedrückt
2 Bewegung in X Richtung
3 Bewegung in Y Richtung

Beim Drücken der Maustasten sendet die Maus also drei Bytes, bei dem im ersten jeweils Bit 0, Bit 1 und Bit 2 entsprechend den Tasten gesetzt / gelöscht sind. Wenn die Maus dann zusätzlich noch bewegt wird, dann bekommen Byte 2 und Byte 3 sowie die Sign Bits im Byte 1 noch eine Bedeutung:

  • Wenn die Maus horizontal nach rechts bewegt wird, dann ist das Sign Bit gelöscht, Byte 2 gibt die Weite der Bewegung an.
  • Wenn die Maus horizontal nach links bewegt wird, dann ist das Sign Bit gesetzt, Byte 2 ist das Zweierkomplement der Weite der Bewegung.
  • Wenn die Maus vertikal nach oben bewegt wird, dann ist das Sign Bit gelöscht, Byte 3 gibt die Weite der Bewegung an.
  • Wenn die Maus horizontal nach unten bewegt wird, dann ist das Sign Bit gesetzt, Byte 3 ist das Zweierkomplement der Weite der Bewegung.

Wenn im Byte 1 das X Overflow und/oder das Y Overflow Bit gesetzt ist, dann sollte das Paket verworfen werden. Das Bit 3 in Byte 1 ist immer gesetzt. Es kann dazu genutzt werden, um sicherzustellen, dass man bei der Reihenfolge der Daten nicht durcheinander kommt.

IRQ 12 Handler

Der Handler für den IRQ 12 wird dreimal aufgerufen, damit man alle drei Bytes der Maus hat. Im Handler muss man sich natürlich merken, bei welchem der Bytes man gerade ist. Der Handler kann also z.B. so aussehen:

<c> uint8_t cycle=1; uint8_t flags; int x_mov, y_mov;

void irq_12_handler() {

 uint32_t val;
 switch (cycle)
 {
   case 1:
     flags = mouse_read_data();
     if (! (flags & 0x08) )	//teste, ob Bit 3 wirklich gesetzt ist
     {
       cycle = 1;		//wenn nicht, dann sind wir mit der Reihenfolge der Bytes durcheinander gekommen!
     }
     else
     {
       cycle = 2;
     }
     break;
   case 2:
     val = mouse_read_data();
     val &= (uint32_t )0xFF;		//es dürfen wirklich nur die ersten 8 Bits belegt sein!
     if (flags & 0x10)
       x_mov = (val | 0xFFFFFF00);
     else
       x_mov = val;
     cycle = 3;
     break;
   case 3:
     val = inb(0x60);
     val &= (uint32_t )0xFF;
     if (flags & 0x20)
       y_mov = - (val | 0xFFFFFF00);
     else
       y_mov = - (val);

//auf overflow testen

     if ((flags & 0x40) || (flags & 0x80))
       ;
     else
     {
       update_mouse(flags, x_mov, y_mov);	//oder was auch immer du damit machen möchtest..
     }
     cycle = 1;
     break;
 }

} </c>

Die Rechnerei in case 2 und case 3 dient nur dafür, das Zweierkomplement von neun Bits auf 32 zu erweitern. Diese Rechnung funktioniert (ich werde hier nicht erklären wie) aber nur wenn:

  • x_mov und y_mov wirklich Integerwerte (32 Bit!) sind
  • die linke obere Ecke die kleinsten Koordinaten und die untere rechte die höchsten Koordinaten hat

Vergiss nicht, das EOI Signal an den PIC zu senden!

Weitere Maus Features

Scrollrad und ähnliches

//TODO

Nicht lineare Bewegung

//TODO

Siehe auch