💻 C und Assembler mit Raspberry Pi

Cursor-Definition & Integration in das Hauptprogramm (RPI4, C)

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) und CURSOR_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