Nachdem der grundlegende USB-Maus-Treiber und das Plug-and-Play-Subsystem stehen, verbinden wir nun alle Fäden im Hauptprogramm (main.c) und betrachten die genaue Definition sowie Steuerung des Hardware-Cursors in mouse.c.
1. Das Cursor-Bitmap in mouse.c
Ein zentraler Bestandteil des Grafiksystems ist das Aussehen des Mauszeigers. In mouse.c wird das Cursor-Symbol als statisches 2D-Array im ARGB8888-Format (32-Bit pro Pixel: Alpha, Rot, Grün, Blau) definiert:
// Pixel-Farbdefinitionen (ARGB8888 Format)
#define B 0 // Transparent (Hintergrund)
#define G 0xFF000000U // Deckendes Schwarz (Rahmen / Outline)
#define W 0xFFFFFFFFU // Deckendes Weiß (Füllung / Body)
#define S 0x80000000U // 50% Semi-transparentes Schwarz (Schatten)
#define CURSOR_WIDTH 16
#define CURSOR_HEIGHT 19
#define CURSOR_HOTSPOT_X 0
#define CURSOR_HOTSPOT_Y 0
static const u32 CursorSymbol[CURSOR_HEIGHT][CURSOR_WIDTH] =
{
{G,B,B,B,B,B,B,B,B,B,B,B,B,B,B,B},
{G,G,B,B,B,B,B,B,B,B,B,B,B,B,B,B},
{G,W,G,B,B,B,B,B,B,B,B,B,B,B,B,B},
{G,W,W,G,B,B,B,B,B,B,B,B,B,B,B,B},
{G,W,W,W,G,B,B,B,B,B,B,B,B,B,B,B},
...
{B,B,B,B,B,B,B,G,G,B,B,B,B,B,B,B}
};
Wichtige Eigenschaften des Cursors:
- Format & Transparenz: Durch das Alpha-Byte in ARGB8888 werden transparente Pixel (
B = 0) einfach vom Videocore-Grafikchip ignoriert, während schwarze Ränder (G) und weiße Innenflächen (W) scharf über dem Bildschirminhalt gezeichnet werden. - Dimensionsfestlegung: Die Ausmaße werden über
CURSOR_WIDTH(16 Pixel) undCURSOR_HEIGHT(19 Pixel) festgelegt. - Der Hotspot (
0, 0): Der Hotspot definiert den exakten Klickpunkt innerhalb des Bitmaps. Bei(0,0)liegt die Spitze des Pfeils genau auf dem oben-linken Pixel des Grafiksymbols.
2. Hardware-Cursor-Initialisierung via Mailbox (CMouseBehaviour_Setup)
Damit die GPU den Cursor kennt, wird bei der Initialisierung die Funktion CMouseBehaviour_Setup() ausgeführt. Sie baut eine Mailbox-Nachricht auf und schickt das Bitmap direkt an die GPU des Raspberry Pi (VideoCore):
boolean CMouseBehaviour_Setup(CMouseBehaviour* pMouseBehaviour, unsigned nScreenWidth, unsigned nScreenHeight)
{
// 1. Interne Zustände & Startposition (Bildschirmmitte) setzen
pMouseBehaviour->m_nScreenWidth = nScreenWidth;
pMouseBehaviour->m_nScreenHeight = nScreenHeight;
pMouseBehaviour->m_nPosX = nScreenWidth / 2;
pMouseBehaviour->m_nPosY = nScreenHeight / 2;
// 2. Mailbox-Nachricht im Coherent Memory aufbauen
CursorInfoMessage* msg = (CursorInfoMessage*)Memory_GetCoherentPage(COHERENT_SLOT_PROP_MAILBOX);
msg->header.size = sizeof(CursorInfoMessage);
msg->header.code = CODE_REQUEST;
msg->tagCursorInfo.header.id = Set_Cursor_Info; // Tag 0x00008010
msg->tagCursorInfo.nWidth = CURSOR_WIDTH;
msg->tagCursorInfo.nHeight = CURSOR_HEIGHT;
msg->tagCursorInfo.nPixelPointer = (u32)BUS_ADDRESS((uintptr)CursorSymbol);
msg->tagCursorInfo.nHotspotX = CURSOR_HOTSPOT_X;
msg->tagCursorInfo.nHotspotY = CURSOR_HOTSPOT_Y;
// 3. CPU-Cache für das Array flushen & Mailbox senden
CleanAndInvalidateDataCacheRange((u64)(uintptr)CursorSymbol, sizeof(CursorSymbol));
u32 nBusAddress = (u32)BUS_ADDRESS((uintptr)msg);
if (MailBox_WriteRead(MAILBOX_PROP_OUT, nBusAddress) != nBusAddress)
{
return FALSE;
}
return (msg->header.code == CODE_RESPONSE_SUCCESS);
}
Vorteil des Hardware-Cursors:
Die GPU überlagert das Cursor-Overlay direkt bei der Bildausgabe. Die CPU muss dadurch bei einer Mausbewegung nicht den Framebuffer neu zeichnen oder Pixel im Grafikspeicher sichern/wiederherstellen.
3. Effizientes Rendering in der Hauptschleife (CMouseBehaviour_UpdateCursor)
In der main.c wird in jedem Durchlauf der while(1)-Schleife CMouseDevice_UpdateCursor() aufgerufen. Damit die Mailbox-Schnittstelle zur GPU nicht unnötig überflutet wird, schützt sich die Funktion über ein Debouncing-Flag (m_bHasMoved):
void CMouseBehaviour_UpdateCursor(CMouseBehaviour* pMouseBehaviour)
{
// Aktualisierung NUR wenn sichtbar UND eine reale Bewegung stattfand
if (pMouseBehaviour->m_bCursorOn && pMouseBehaviour->m_bHasMoved)
{
pMouseBehaviour->m_bHasMoved = FALSE; // Flag zurücksetzen
// Position an die GPU senden
CMouseBehaviour_SetCursorState(pMouseBehaviour,
pMouseBehaviour->m_nPosX,
pMouseBehaviour->m_nPosY,
TRUE);
}
}
Die Variable m_bHasMoved wird ausschließlich dann auf TRUE gesetzt, wenn der USB-HID-Interrupt-Handler tatsächlich neue Relativdaten verarbeitet hat.
4. Integration in die main.c
Im Hauptprogramm laufen nun alle Subsysteme zusammen. Betrachten wir die drei wesentlichen Abschnitte in main.c:
A. PnP-Hot-Plug & Initialisierung
Sobald USBHostController_UpdatePlugAndPlay() die Verbindung einer neuen Maus meldet, wird das Gerät aus dem DeviceNameService als "mouse1" abgerufen und konfiguriert:
if (USBHostController_UpdatePlugAndPlay())
{
if (Kernel.m_pMouse == 0) // Nur wenn aktuell keine Maus aktiv ist
{
CDevice* pMouseDevice = DeviceNameService_GetDevice("mouse1", FALSE);
if (pMouseDevice != 0)
{
Kernel.m_pMouse = (CMouseDevice*)pMouseDevice->pOwner;
// 1. Hot-Unplug Handler registrieren
CDevice_RegisterRemovedHandler(pMouseDevice, MouseRemovedHandler, 0);
// 2. Hardware-Cursor vorbereiten & Event-Handler verknüpfen
CMouseDevice_Setup(Kernel.m_pMouse, SCREEN_X, SCREEN_Y);
CMouseDevice_RegisterEventHandler(Kernel.m_pMouse, MouseEventHandler);
// 3. Startposition in Bildschirmmitte setzen & sichtbar schalten
Kernel.m_nPosX = SCREEN_X / 2;
Kernel.m_nPosY = SCREEN_Y / 2;
CMouseDevice_SetCursor(Kernel.m_pMouse, Kernel.m_nPosX, Kernel.m_nPosY);
CMouseDevice_ShowCursor(Kernel.m_pMouse, TRUE);
}
}
}
B. Event-Callback (`MouseEventHandler`)
Sobald der USB-Treiber Daten empfängt, wird MouseEventHandler() asynchron aufgerufen:
static void MouseEventHandler(TMouseEvent Event, unsigned nButtons, unsigned nPosX, unsigned nPosY, int nWheelMove)
{
switch (Event)
{
case MouseEventMouseMove:
if (nButtons & (MOUSE_BUTTON_LEFT | MOUSE_BUTTON_RIGHT))
{
printf("Bewegung mit gedrückter Taste: X=%d, Y=%d\n", nPosX, nPosY);
}
Kernel.m_nPosX = nPosX;
Kernel.m_nPosY = nPosY;
break;
case MouseEventMouseDown:
if (nButtons & MOUSE_BUTTON_LEFT) printf("Linksklick bei: X=%d, Y=%d\n", nPosX, nPosY);
if (nButtons & MOUSE_BUTTON_RIGHT) printf("Rechtsklick bei: X=%d, Y=%d\n", nPosX, nPosY);
if (nButtons & MOUSE_BUTTON_MIDDLE) printf("Mittelklick bei: X=%d, Y=%d\n", nPosX, nPosY);
break;
case MouseEventMouseWheel:
printf("Mausrad bewegt: %d\n", nWheelMove);
break;
default:
break;
}
}
C. Sauberes Abfangen beim Ausstecken (MouseRemovedHandler)
Wird die Maus im laufenden Betrieb abgezogen, gibt das System die Ressourcen frei und setzt den Zeiger zurück:
static void MouseRemovedHandler(CDevice* pDevice, void* pContext)
{
printf("Maus wurde entfernt (Hot-Unplug)!\n");
// Setzt den Zeiger frei, damit das System beim erneuten Einstecken
// die neue Maus wieder als betriebsbereit akzeptiert.
Kernel.m_pMouse = 0;
}
Zusammenfassung
Mit der Einbindung des ARGB-Bitmaps, der Übergabe an die GPU per Mailbox und der Verknüpfung im Event-Loop der main.c ist unser Bare-Metal-Maustreiber komplett. Der Cursor bewegt sich ohne CPU-Overhead flüssig über den Bildschirm, Klicks und Scrollrad-Events werden verarbeitet und das System reagiert dynamisch auf das Ein- und Ausstecken der Hardware.
Den Sourcecode bekommst du hier: https://www.satyria.de/arm/sources/RPI4/C/USB5_Mouse3.zip