💻 C und Assembler mit Raspberry Pi

Praktische Anwendung – Ein Bare-Metal-Zeichenprogramm (RPI4, C)

Nachdem wir das Treiber-Fundament gelegt, den USB-HID-Stack verstanden und die Hot-Plug-Logik implementiert haben, wollen wir dem System Leben einhauchen. In diesem Kapitel nutzen wir die eingehenden USB-Maus-Events nicht mehr nur für Ausgaben im Text-Terminal, sondern bauen ein interaktives Bare-Metal-Zeichenprogramm!


1. Die Farbpalette & der Pinsel-Algorithmus

Damit flüssig auf den Bildschirm gezeichnet werden kann, definieren wir eine einfache Palette aus 8 ARGB-Farben (inklusive eines "Radierers") sowie eine Pinselgröße:

#define PAINT_BRUSH_SIZE 3  // Radius des Pinsels in Pixeln

static const u32 g_PaintColors[] = {
    0xFFFF0000, // 0: Rot
    0xFF00FF00, // 1: Grün
    0xFF0000FF, // 2: Blau
    0xFFFFFF00, // 3: Gelb
    0xFFFF00FF, // 4: Magenta
    0xFF00FFFF, // 5: Cyan
    0xFFFFFFFF, // 6: Weiß
    0xFF000000  // 7: Schwarz (Radierer)
};
#define PAINT_NUM_COLORS (sizeof(g_PaintColors) / sizeof(g_PaintColors[0]))

static u32 g_CurrentColorIndex = 0; // Startfarbe: Rot

Gefüllter Kreis-Pinsel (Paint_DrawBrush)

Ein einzelner Pixel wirkt bei hochauflösenden Bildschirmen oft zu dünn. Deshalb zeichnet die Funktion Paint_DrawBrush() an der aktuellen Mausposition einen kleinen gefüllten Kreis:

static void Paint_DrawBrush(u32 x, u32 y, u32 colorHex, u32 radius)
{
    // Farbkanäle aufspalten (ARGB8888 -> RGB)
    u8 r = (colorHex >> 16) & 0xFF;
    u8 g = (colorHex >> 8) & 0xFF;
    u8 b = colorHex & 0xFF;
    SetDrawColor(r, g, b);

    s32 sx = (s32)x;
    s32 sy = (s32)y;
    s32 sr = (s32)radius;

    for (s32 dy = -sr; dy <= sr; dy++) {
        for (s32 dx = -sr; dx <= sr; dx++) {
            // Kreisgleichung (Satz des Pythagoras): dx^2 + dy^2 <= r^2
            if ((dx * dx + dy * dy) <= (sr * sr)) {
                s32 px = sx + dx;
                s32 py = sy + dy;
                
                // Wichtig im Bare-Metal-Bereich: Vollständige Randprüfung!
                if (px >= 0 && px < (s32)SCREEN_X && 
                    py >= 0 && py < (s32)SCREEN_Y) {
                    DrawPixel((u32)px, (u32)py);
                }
            }
        }
    }
}

Kritischer Schutzmechanismus:

Da der Pinsel-Radius über die Mauskanten hinausragen kann, sind die Boundary-Checks (px >= 0 && px < SCREEN_X etc.) absolut überlebenswichtig! Ohne diesen Check würde das Schreiben außerhalb des Bildschirms Speicherbereiche des Kernels überschreiben und das System zum Absturz bringen.


2. Event-gesteuerte Interaktion (`MouseEventHandler`)

Das gesamte Zeichenprogramm wird über den asynchronen USB-Interrupt-Handler der Maus gesteuert. Jedes Mal, wenn die Maus bewegt wird, geklickt wird oder das Scrollrad gedreht wird, ruft der USB-Stack unseren Handler auf:

static void MouseEventHandler(TMouseEvent Event, unsigned nButtons, unsigned nPosX, unsigned nPosY, int nWheelMove)
{
    // 1. MAUSRAD: Farbe wechseln
    if (Event == MouseEventMouseWheel && nWheelMove != 0)
    {
        if (nWheelMove > 0) {
            g_CurrentColorIndex = (g_CurrentColorIndex + 1) % PAINT_NUM_COLORS;
        } else {
            g_CurrentColorIndex = (g_CurrentColorIndex + PAINT_NUM_COLORS - 1) % PAINT_NUM_COLORS;
        }
        
        printf("\r[Aktuelle Farbe: Index %u]", g_CurrentColorIndex);
    }

    // 2. RECHTE TASTE: Bildschirm löschen
    if (Event == MouseEventMouseDown && (nButtons & MOUSE_BUTTON_RIGHT))
    {
        ClearScreen();
    }

    // 3. LINKE TASTE: Zeichnen (beim Klick & beim Ziehen)
    if (Event == MouseEventMouseDown || Event == MouseEventMouseMove)
    {
        if (nButtons & MOUSE_BUTTON_LEFT)
        {
            u32 currentColor = g_PaintColors[g_CurrentColorIndex];
            Paint_DrawBrush(nPosX, nPosY, currentColor, PAINT_BRUSH_SIZE);
        }
    }
}

Bedienungsübersicht für den Benutzer:

  • Linksklick + Ziehen: Zeichnet mit der aktuell gewählten Farbe eine durchgehende Linie.
  • Mausrad (Scrollen): Blättert vorwärts/rückwärts durch die Farbpalette (Rot, Grün, Blau, Gelb, Magenta, Cyan, Weiß, Schwarz/Radierer).
  • Rechtsklick: Löscht die Zeichenfläche (ClearScreen).

3. Die Symbiose aus Hardware-Cursor und Framebuffer

Ein besonderes Highlight dieser Architektur ist das fehlerfreie Zusammenspiel zwischen dem Zeichenprogramm und der Anzeige:

  • Framebuffer (Software): Die Funktion DrawPixel() schreibt die Farbpunkte direkt in den Videospeicher des Bildschirms.
  • Hardware-Cursor (VideoCore GPU): Der Mauszeiger wird über die GPU-Mailbox als Hardware-Overlay gezeichnet.

Das bedeutet: Der Mauszeiger gleitet flüssig über die gezeichneten Linien, ohne beim Ziehen die eben gezeichneten Pixel zu radieren oder zu verdunkeln. Die GPU kümmert sich um die echten Überlagerungen, während die CPU sich rein auf die Logik des Malens konzentrieren kann.


Zusammenfassung

Mit weniger als 200 Zeilen C-Code haben wir ein voll funktionsfähiges, interaktives Grafikprogramm auf nackter Hardware (Bare-Metal) geschaffen. Es demonstriert eindrucksvoll das Zusammenspiel von USB-Protokollstapel, asynchronem Event-Polling, PnP-Verwaltung und GPU-Hardware-Accelerated Overlays!

Den Source bekommst du hier: https://www.satyria.de/arm/sources/RPI4/C/USB5_Mouse4_Draw.zip