Im vorherigen Kapitel haben wir den ersten Schritt zur Tastatur-Anbindung gemacht: Wir konnten die rohen 8-Byte-HID-Reports (Hex-Codes) vom Interrupt-Endpoint der Tastatur abfangen und auf dem Bildschirm anzeigen.
In diesem zweiten Teil vervollständigen wir den Tastatur-Treiber. Wir bauen das Keymapping (Übersetzung von Scancodes in Zeichen/ANSI-Sequenzen) über keymap_de.h ein, integrieren ein zustandsbehaftetes Modifier-Tracking (Shift, AltGr, Strg, CapsLock) und fügen in screen.c einen blinkenden Text-Cursor ein, um ein echtes Terminal-Erlebnis zu schaffen!
1. Die Übersetzungstabelle (`keymap_de.h`)
USB-Tastaturen senden keine ASCII-Zeichen, sondern standardisierte USB-HID-Scancodes (z. B. 0x04 für die Taste A). Um aus diesen Werten deutsche Sonderzeichen, Großbuchstaben oder Steuersequenzen zu erzeugen, nutzen wir die Übersetzungstabelle in keymap_de.h.
Die Tabelle bildet jeden physikalischen Scancode (0x00 bis 0x7F) auf fünf verschiedene Ausgabe-Zustände ab:
// Layout: { Normal, Shift, AltGr, AltGr+Shift, Ctrl }
{ 'a', 'A', KeyNone, KeyNone, KeyNone }, // Scancode 0x04
{ 'q', 'Q', C('\x40'), KeyNone, KeyNone }, // Scancode 0x14 -> AltGr + q = '@'
{ '<', '>', '|', KeyNone, KeyNone }, // Scancode 0x64 -> AltGr + < = '|'
{ KeyReturn, KeyReturn, KeyNone, KeyNone, KeyNone }, // Scancode 0x28
2. Der Zustandstracker & Handler in keyboard.c
Da USB-Tastaturen beim Drücken mehrerer Tasten immer den gesamten Zustand (Modifier-Byte + bis zu 6 Scancodes) senden, muss der Treiber Differenzen zwischen dem vorherigen und dem aktuellen Report ermitteln.
A. Tasten-Vergleich im ReportHandler
In CUSBKeyboardDevice_ReportHandler() vergleichen wir das neue 8-Byte-Paket mit m_LastReport:
- Modifier auswerten: Byte 0 enthält Bitmasken für
Shift,Ctrl,AltundGUI. Bit-Änderungen lösenKeyPressedoderKeyReleasedfür die Modifier-Codes (0x80+ Bit-Index) aus. - Key Release erkennen: Wir prüfen, welche Scancodes im alten Report vorhanden waren, im neuen jedoch fehlen.
- Key Press erkennen: Neue Scancodes im aktuellen Report lösen ein
KeyPressed-Event aus.
void CUSBKeyboardDevice_ReportHandler(CUSBHIDDevice* pHIDDevice, const u8* pReport, unsigned nReportSize)
{
CUSBKeyboardDevice* pThisDevice = (CUSBKeyboardDevice*)pHIDDevice->m_function.m_pOwner;
// 1. Modifier-Tasten (Byte 0) auswerten
for (unsigned i = 0; i < 8; i++)
{
unsigned nMask = 1 << i;
boolean bIsPressedNow = (pReport[0] & nMask) != 0;
boolean bWasPressedBefore = (pThisDevice->m_LastReport[0] & nMask) != 0;
if (bIsPressedNow && !bWasPressedBefore)
CKeyboardBehaviour_KeyPressed(&pThisDevice->m_Behaviour, 0x80 + i);
else if (!bIsPressedNow && bWasPressedBefore)
CKeyboardBehaviour_KeyReleased(&pThisDevice->m_Behaviour, 0x80 + i);
}
// 2. Losgelassene Tasten erkennen (im alten Report, aber nicht im neuen)
for (unsigned i = 2; i < USBKEYB_REPORT_SIZE; i++)
{
u8 ucKeyCode = pThisDevice->m_LastReport[i];
if (ucKeyCode != 0 && !CUSBKeyboardDevice_FindByte(pReport + 2, ucKeyCode, 6))
CKeyboardBehaviour_KeyReleased(&pThisDevice->m_Behaviour, ucKeyCode);
}
// 3. Neu gedrückte Tasten erkennen (im neuen Report, aber nicht im alten)
for (unsigned i = 2; i < USBKEYB_REPORT_SIZE; i++)
{
u8 ucKeyCode = pReport[i];
if (ucKeyCode != 0 && !CUSBKeyboardDevice_FindByte(pThisDevice->m_LastReport + 2, ucKeyCode, 6))
CKeyboardBehaviour_KeyPressed(&pThisDevice->m_Behaviour, ucKeyCode);
}
memcpy(pThisDevice->m_LastReport, pReport, USBKEYB_REPORT_SIZE);
}
Die Taste "
GUI" ist ein synonym für:
- Windows-Tastaturen: Es ist die Windows-Taste (mit dem Windows-Logo).
- Mac-Tastaturen: Es ist die Command-Taste (auch Befehlstaste genannt).
- Linux / Unix: Wird sie oft als Super-Taste oder Meta-Taste bezeichnet.
B. Die Übersetzungslogik (CKeyMap_Translate)
Die Funktion CKeyMap_Translate() ermittelt anhand der aktiven Modifier (z. B. KEY_LSHIFT_MASK oder KEY_ALTGR_MASK) die richtige Spalte in der Übersetzungstabelle keymap_de.h:
u16 CKeyMap_Translate(CKeyMap* pKeyMap, u8 nPhyCode, u8 nModifiers)
{
unsigned nTable = K_NORMTAB;
if (nModifiers & KEY_ALTGR_MASK)
{
nTable = (nModifiers & (KEY_LSHIFT_MASK | KEY_RSHIFT_MASK)) ? K_ALTSHIFTTAB : K_ALTTAB;
}
else if (nModifiers & (KEY_LSHIFT_MASK | KEY_RSHIFT_MASK))
{
nTable = K_SHIFTTAB;
}
u16 nLogCode = pKeyMap->m_KeyMap[nPhyCode][nTable];
// Sonderfunktion: CapsLock Umschaltung
if (nLogCode == KeyCapsLock)
{
pKeyMap->m_bCapsLock = !pKeyMap->m_bCapsLock;
return ActionSwitchCapsLock;
}
return nLogCode;
}
In CKeyMap_GetString() werden Sondertasten schlussendlich in lesbare Zeichen oder ANSI-Escape-Sequenzen umgewandelt (z. B. wird KeyUp zu \x1b[A für die Cursorsteuerung im Terminal).
3. Der Text-Cursor in screen.c
Um dem Benutzer visuelles Feedback zu geben, wo das nächste Zeichen getippt wird, fügen wir in screen.c eine Cursor()-Funktion ein:
void Cursor(void)
{
if (!CursorStatus)
{
DrawChar(' ', cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT);
CursorStatus = TRUE;
}
else
{
DrawChar('_', cursor_x * CHAR_WIDTH, cursor_y * CHAR_HEIGHT);
CursorStatus = FALSE;
}
}
Die Funktion toggelt das Zeichen an der aktuellen Textkoordinate (cursor_x, cursor_y) zwischen einem Leerzeichen ' ' und einem Unterstrich '_'.
4. Integration & Blinken in der main.c
Im Hauptprogramm führen wir nun alle Elemente zusammen:
A. Timer für das Cursor-Blinken
Über einen wiederkehrenden Kernel-Timer rufen wir alle 50 Ticks (ca. alle halbe Sekunde) die Cursor()-Funktion auf, sobald eine Tastatur erkannt wurde:
void TimerHandler(TKernelTimerHandle hTimer, void* pParam, void* pContext)
{
if (Kernel.m_pKeyboard != 0)
{
Cursor(); // Invertiert/Toggelt den Unterstrich an der aktuellen Textposition
}
Timer_StartKernelTimer(50, TimerHandler, NULL, NULL); // Timer neu aufsetzen
}
B. Registrierung des String-Handlers
In der PnP-Hot-Plug-Überwachung registrieren wir bei der Tastatur-Erkennung nicht mehr den RAW-Handler, sondern CUSBKeyboardDevice_RegisterKeyPressedHandler() mit unserem KeyPressedHandler:
// --- KEYBOARD DETECTION ---
if (Kernel.m_pKeyboard == 0)
{
CDevice* pKeyboard = DeviceNameService_GetDevice("ukbd1", FALSE);
if (pKeyboard)
{
SetFrontColor(green);
printf("Keyboard gefunden!\n");
SetFrontColor(white);
Kernel.m_pKeyboard = (CUSBKeyboardDevice *)pKeyboard->pOwner;
// Jetzt registrieren wir den String-Handler!
CUSBKeyboardDevice_RegisterKeyPressedHandler(Kernel.m_pKeyboard, KeyPressedHandler);
printf("-> Tastatur-Treiber aktiviert. Tippe etwas...\n");
}
}
C. Zeichen-Ausgabe (KeyPressedHandler)
Trifft ein übersetzter String vom Tastatur-Treiber ein, geben wir ihn direkt über printf() auf dem Bildschirm aus:
static void KeyPressedHandler(const char *pString)
{
if (pString != 0)
{
// Gibt normale Zeichen (z.B. "a", "A", "@") oder ANSI-Steuersequenzen aus
printf("%s", pString);
}
}
Zusammenfassung
Mit der Fertigstellung des zweiten Teils haben wir eine vollwertige Eingabeeinheit geschaffen:
- Tasteneingaben werden über den USB-Interrupt-Endpoint erfasst.
- Layout-Übersetzung (
keymap_de.h) wandelt HID-Scancodes korrekt in deutsche Zeichen inklusive Sonderzeichen (`@`, `€`, `ä`, `ö`, `ü`, `ß`) und Steuerzeichen um. - Sondertasten & Pfeiltasten erzeugen valide ANSI-Escape-Sequenzen, mit denen das Terminal gesteuert wird.
- Der blinkende Cursor signalisiert die aktuelle Eingabeposition im Terminal.
Den Sourcecode kannst du hier herunterladen: https://www.satyria.de/arm/sources/RPI4/C/USB6_Keyboard2.zip