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_Xetc.) 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