Meine erste Nachricht an Claude: „Ich habe den RPi von meinem CNC-Router leider neu aufsetzen müssen … kannst du das System neu einrichten?“ Die SD-Karte war defekt, der Pi lief mit einem frischen Raspberry Pi OS – und sonst nichts.
Am Ende liefen CNCjs, die Referenzfahrt, der Antaster und der Kiosk wieder. Die reine Arbeitszeit waren ein paar Stunden. Allein hätte ich für dieselbe Strecke deutlich länger gebraucht – vor allem für die Fehlersuche. Dieser Artikel ist kein Tutorial zum Nachbauen, sondern ein ehrlicher Bericht: was funktioniert hat, wo es geklemmt hat und wo die KI falsch lag.
Das Setup
| Komponente | Was verbaut ist |
|---|---|
| Steuerung | Arduino Uno mit CNC-Shield, Grbl 1.1h (selbst kompiliert) |
| Verbindung | USB über CH340 zum Pi |
| Host | Raspberry Pi 3B |
| Oberfläche | CNCjs 1.11.5 als systemd-Dienst, Port 8000 |
| Display | erst Waveshare 480×800 mit resistivem SPI-Touch, später 7-Zoll-HDMI mit USB-Touch |
| Kiosk | Chromium unter labwc (Wayland), Autologin |
| Netzteile | umgebautes ATX-Netzteil für die Motoren, Relais für die Spindel – beide vom Pi geschaltet |
| Kamera | Pi-Kameramodul v1 (ov5647) als MJPEG-Stream |
Erst sichern, was nicht auf dem Pi liegt
Der erste Schritt war keine Installation, sondern eine Sicherung. Die eigentlichen Maschinenparameter – Schritte pro Millimeter, Verfahrwege, Referenzfahrt, Nullpunkte G54 bis G56 – liegen nicht auf dem Pi, sondern im EEPROM des Arduino. Die Neuinstallation hatte sie nicht gelöscht. Ein defekter Arduino oder ein versehentliches $RST=* würde es tun. Also zuerst alles auslesen und weglegen:
# In der Grbl-Konsole (z. B. CNCjs) – Ausgabe komplett in eine Datei kopieren
$$ # alle Einstellungen ($0 … $132)
$# # Nullpunkte G54–G59, G28/G30, Werkzeuglängen-Offset
$I # Version und Kompilier-Optionen, z. B. [OPT:,15,128]Diese Sicherung hat sich später bezahlt gemacht: Nach einem Kurzschluss am Shield (dazu unten mehr) ließ sich in einer Minute prüfen, dass alle 34 Parameter unverändert waren.
Das zweite Fundstück waren zwei alte Backups von 2023 – aus der Zeit, als auf dem Pi noch Klipper/Moonraker bzw. OctoPrint lief. Darin stand, was sonst niemand mehr wusste: welcher GPIO welches Netzteil schaltet und wo der Not-Aus hängt.
# moonraker.conf (2023)
[power Antriebe]
pin: !gpio26
[power Spindel]
pin: gpio20
# OctoPrint config.yaml (2023), gekürzt
psucontrol: { onoffGPIOPin: '26', invertonoffGPIOPin: true }
physicalbutton: { name: Emergency STOP, gpio: '16', buttonMode: Normally Closed }Wie die Zusammenarbeit lief
Claude Code lief auf meinem Server und war per SSH-Schlüssel auf dem Pi. Ich stand an der Maschine: Kabel umstecken, Taster drücken, aufs Display tippen, Fotos vom Shield machen. Claude installierte, schrieb Skripte, las Kernel-Logs und Grbl-Statuszeilen – und, ganz wichtig, konnte selbst nachsehen, was auf dem Display passiert.
Genau diese Werkzeuge haben am meisten Zeit gespart, weil sie Rückfragen an mich überflüssig machten:
grimmit gesetztemWAYLAND_DISPLAYliefert ein Bildschirmfoto des Kiosks – ohne dass ich etwas abfotografieren muss.- Chromium mit
--remote-debugging-port=9222zeigt JS-Fehler und den DOM-Zustand der Seite. - Ein kleiner Touch-Mitschreiber protokolliert jede Berührung in eine Logdatei, egal wann ich tippe.
Eine Lektion für beide Seiten: Die Anleitung kommt vor der Messung. Zwei leere Messfenster gingen allein darauf zurück, dass ein Messskript schon lief, während ich noch las, was ich tun sollte.
Falle 1: Der Touch trifft daneben
Am Waveshare-Panel kommt das Bild über HDMI, der Touch aber über den Pin-Header per SPI (XPT2046, Treiber ads7846). Welche Chip-Select-Leitung und welcher Interrupt-Pin, stand in keiner Anleitung, die zum Panel passte. /proc/interrupts verriet es: 1118 Auslösungen auf pinctrl-bcm2835 25 Edge ads7846 – also CS1 und PENIRQ auf GPIO 25.
Danach lief der Touch, aber die Ecken lagen über Kreuz. Aus zwei gezeichneten Strichen leitete Claude ab: X und Y spiegeln (invx, invy). Die Werte standen in der config.txt – und kamen nie an. Im Gerätebaum fehlten sie, obwohl xmin aus derselben Zeile angekommen war.
Die Overlay-Zeile war 127 Zeichen lang. Die Firmware des Pi schnitt sie ab Zeichen 98 ab und verwarf den Rest – ohne Fehlermeldung, ohne Logeintrag. Abhilfe: Parameter auf Folgezeilen mit dtparam= verteilen, sie wirken auf das Overlay darüber.
Als die Spiegelung endlich ankam, wurde es nicht besser, sondern anders falsch. Erst eine Kalibrierseite mit fünf Fadenkreuzen klärte das Bild: Die Mitte traf auf wenige Pixel genau, die Ecken waren über Kreuz vertauscht. Das ist die Signatur einer 180-Grad-Drehung zu viel. Der Compositor (wlroots in labwc) dreht die Touch-Eingabe mit der Bildschirmdrehung transform 270 bereits selbst mit – die eigene Spiegelung im Treiber war doppelt.
Die Grenzwerte kamen am Ende aus einer Geradenanpassung über alle fünf Punkte, nicht aus Einzelstrichen (bei denen man die Ränder naturgemäß nie ganz erreicht):
# /boot/firmware/config.txt – kein invx, kein invy, kein swapxy
dtoverlay=ads7846,cs=1,penirq=25,penirq_pull=2,speed=50000,pmax=255
dtparam=xohms=150,xmin=142,xmax=3944,ymin=199,ymax=3901Ergebnis: maximal 5 px Abweichung waagerecht und 7 px senkrecht, vorher 695 px waagerecht und 354 px senkrecht. Später habe ich das Panel trotzdem gegen ein 7-Zoll-HDMI-Display mit USB-Touch getauscht – auf einem resistiven Touch ist eine CNC-Oberfläche kein Vergnügen. Wichtig dabei: Overlay auskommentieren. Solange es lud, meldeten sich zwei Touchscreens, und der verwaiste alte lieferte Zufallswerte. Auf einer Oberfläche, die eine Fräse verfahren kann, will man das nicht.
Falle 2: Das Motornetzteil lässt sich nicht schalten
Die Referenzfahrt scheiterte, weil die Schrittmotoren keinen Strom hatten. Das Netzteil der Antriebe ist ein umgebautes ATX-Netzteil, eingeschaltet über PS_ON – und diese Leitung liegt im Netzteil auf 5 V. Sie hing jahrelang direkt an GPIO26 und hat den Pin irgendwann zerstört.
Gefunden haben wir das ohne einen einzigen geschalteten Ausgang: Alle GPIOs als Eingang, einmal mit Pull-up, einmal mit Pull-down. Freie Pins folgen, beschaltete nicht. Die Lösung ist ein PC817-Optokoppler an GPIO19. Die ganze Geschichte mit Schaltung und Skript steht im eigenen Artikel ATX-Netzteil per Raspberry Pi schalten: PS_ON mit PC817.
Bevor die Backups auf dem Tisch lagen, war GPIO16 der beste Kandidat fürs Netzteil, und Claude wollte ihn als Ausgang auf Masse legen. An GPIO16 hing aber der geschlossene Not-Aus-Kontakt gegen 3,3 V – ein Kurzschluss über den Ausgangstreiber. Verhindert hat es eine Rückfrage vor dem Treiben. Seitdem gilt: Erst passiv messen, dann schalten.
Falle 3: Der Z-Antaster löst nicht aus
Der Antaster ist simpel: ein Metallplättchen an Grbls Probe-Eingang A5, eine Krokodilklemme am Fräser auf Masse. Gesucht haben wir trotzdem Stunden – nach einer Beschriftung „A5“ oder „Probe“, die es auf dem Shield nicht gibt. Dort heißt der Pin SCL, weil A5 am Uno zugleich die I²C-Taktleitung ist. Erkannt hat Claude das erst auf einem Foto des Shields.
Auf dem Weg dahin passierte zweierlei:
- Kurzschluss. Die Masseader landete einen Stift daneben auf 5 V. Der Kernel meldete über 1300-mal
over-current change, dazuUndervoltage detected, der CH340 verschwand, die Motoren klackten. Abziehen, durchatmen, Parameter gegen die Sicherung prüfen: alle 34 identisch. - Falsch herum beschaltet. 10 kΩ nach Masse und 5 V über den Taster – klingt vernünftig, funktioniert aber nicht. Grbl schaltet den internen Pull-up (20–50 kΩ) trotzdem ein. Zusammen mit 10 kΩ ergibt das 0,83 bis 1,67 V Ruhepegel, genau um die Schaltschwelle. Der Eingang zeigte dauerhaft „berührt“.
Die funktionierende Beschaltung ist die einfachste:
# Beschaltung (Grbl $6=0: Ruhe = High über internen Pull-up, Masse = berührt)
# Plättchen --[1 kOhm]-- SCL (= A5) Krokodilklemme -- GND
# Prüfung ganz ohne Bewegung: Statusbericht abfragen
? # Ruhe: <Idle|WPos:...|F:0> (kein Pn)
? # berührt: <Idle|WPos:...|Pn:P|...>Der Zustand des Probe-Eingangs steht nur im periodischen Statusbericht. CNCjs blendet diese Berichte in der Konsole aus – dass beim Berühren „nichts passiert“, ist also normal. Claude hat deshalb ein kleines Überwachungsskript geschrieben, das die Statuszeilen mitliest und jede Pn:-Flanke protokolliert. Gegenprobe mit einem Endschalter (Pn:Y) zeigte, dass das Skript selbst funktioniert.
Am Ende sechs saubere Flanken innerhalb einer halben Minute: zwei per Brücke direkt am Shield, vier durch Berühren des Plättchens. Danach habe ich das Antast-Makro in CNCjs angelegt. Wichtig: Es ist noch nicht im Fräsbetrieb getestet. Belegt sind nur die sauberen Pn:P-Flanken; eine echte Antastfahrt ([PRB:…:1]) habe ich noch nicht gemacht:
; Z-Antasten, Plättchen 1,50 mm dick – NOCH NICHT im Fräsbetrieb getestet
G91 ; relativ
G38.2 Z-20 F50 ; langsam nach unten bis Kontakt
G10 L20 P1 Z1.50 ; aktuelle Position = Plättchendicke in G54
G0 Z5 ; freifahren
G90 ; zurück auf absolutFalle 4: Der Kiosk bleibt weiß
Dreimal zeigte das Display nur Weiß, und dreimal war es etwas anderes:
- Token und Geduld. Die Pendant-Oberfläche TinyWeb braucht einen Zugriffstoken, sonst antwortet
/api/commandsmit403und die Seite bleibt weiß. Ein fest eingetragener Token läuft nach 30 Tagen ab. Lösung: eine kleine Zwischenseite holt bei jedem Start überPOST /api/signineinen frischen Token und leitet weiter. Danach blieb es trotzdem weiß – TinyWeb braucht auf dem Pi 3 über eine Minute zum Laden. Die Bildschirmfotos fielen genau in dieses Fenster. - Auflösung. Der Skalierer im 7-Zoll-Display meldet per EDID 1920×1080. Damit ist der Pi 3 bei der vollen CNCjs-Oberfläche überfordert. Lösung: Modus in kanshi festschreiben.
- Startzeitpunkt. CNCjs öffnet seinen Port, bevor es wirklich bereit ist. Der Autostart wartete nur auf den Port, Chromium lud zu früh – und lädt eine weiße Seite nie von selbst nach.
# ~/.config/kanshi/config
profile {
output HDMI-A-1 mode 1024x768
}
# ~/.config/labwc/autostart (Auszug): warten, bis CNCjs wirklich antwortet
until curl -sf -X POST http://localhost:8000/api/signin | grep -q '"token"'; do
sleep 2
done
sleep 10
chromium --kiosk --app=http://localhost:8000/ \
--password-store=basic --remote-debugging-port=9222 &Als Sicherheitsnetz läuft zusätzlich ein kleiner Wächter. Er fragt alle 30 Sekunden über die DevTools-Schnittstelle ab, wie viele sichtbare Zeichen auf der Seite stehen – gefüllt sind es rund 1300, leer 0. Ist die Seite zweimal hintereinander leer, lädt er sie neu und pausiert danach zwei Minuten.
Getestet haben wir das nicht nur auf dem Papier: Seiteninhalt absichtlich gelöscht, und das Protokoll meldete 16:22:47 Seite wirkt leer (0 Zeichen), 30 Sekunden später lade die Seite neu. Die Oberfläche war zurück, inklusive Verbindung zur Steuerung.
Die Prüfreihenfolge fürs nächste Mal: erst wlr-randr (welcher Modus?), dann Bildschirmfoto mit grim, dann nur den Browser neu starten. Nie cncjs.service neu starten, nur um den Kiosk zu reparieren – das trennt die Verbindung zur Steuerung und setzt den Arduino zurück.
Wo die KI danebenlag
Claude hat beim Neuaufbau viel richtig gemacht. Aber ein Bericht ohne die Fehler wäre geschönt. Die wichtigsten:
- „TinyWeb ist inkompatibel“ – voreilig aus einem weißen Bildschirmfoto geschlossen. Der DOM war längst gefüllt, die Seite brauchte nur länger als das Foto.
- „invx und invy sind nötig“ – aus zwei Strichen abgeleitet. Erst die 5-Punkt-Messung zeigte die doppelte Drehung.
- „GPIO16 als Ausgang auf Masse“ – der Beinahe-Kurzschluss am Not-Aus.
- „GPIO26 ist tot“ – zu früh gesagt, als noch die Ader steckte. Gestimmt hat es am Ende, belegt war es erst mit abgezogenem Kabel.
- Reihen am Pin-Header vertauscht. Die geraden Pins (z. B. 36, 38, 40) sind die äußere Reihe. Claude hatte es umgekehrt beschrieben, und ich landete beim Umstecken auf dem falschen Pin. Besser ist eine Ansage ohne Zählen: „der Stift direkt gegenüber, gleiche Höhe“.
- Eigener Messfehler: Ein zweiter, parallel gestarteter Überwachungslauf überschrieb die Protokolldatei des ersten.
Was die Fehler entschärft hat, war jedes Mal dasselbe: Rückfragen vor riskanten Schritten, Messungen statt Annahmen und Tests, bei denen der Fehler absichtlich provoziert wird. Die KI ist schnell im Vermuten – der Wert entsteht erst, wenn sie ihre Vermutung selbst widerlegen kann.
Was ich künftig anders mache
- Die Maschine ist jetzt dokumentiert. Belegung, Dienste, Skripte und Fallstricke liegen in meinem Notizsystem – nicht mehr nur auf der SD-Karte.
- Grbl-Parameter regelmäßig sichern. Sie sind die einzige Konfiguration, die sich nicht aus Backups rekonstruieren lässt.
- Steckverbindungen. Einzelne Dupont-Buchsen haben sich beim Neuaufbau dreimal gelöst. Alle Steuerleitungen bekommen ein gemeinsames Steckergehäuse mit Zugentlastung.
- Stromversorgung. Der Pi 3 ist knapp versorgt, und der Arduino hängt mit zwei Relaismodulen an dessen USB. Beides bekommt eine eigene, kräftigere Versorgung – dann hätte der Kurzschluss am Shield den Pi gar nicht erst erreicht.
- CNCjs hat ab Werk keine Anmeldung. Mit
allowRemoteAccesskann jeder im LAN die Fräse verfahren. Das gehört ins eigene VLAN oder hinter eine Anmeldung.
Und die SD-Karte selbst: Bei der nächsten Maschine, die einen Pi bekommt, nehme ich einen Pi 5 mit NVMe statt einer Karte, die irgendwann stirbt.
Anzeige · Affiliate-Link – kaufst du darüber, erhalte ich ggf. eine Provision. Für dich ändert sich am Preis nichts.
Hardware: Raspberry Pi 3B, Arduino Uno mit CNC-Shield, Grbl 1.1h, CNCjs 1.11.5, Chromium unter labwc, Waveshare-Panel 480×800 (XPT2046 über SPI), später 7-Zoll-HDMI-Display mit USB-Touch. Werkzeug: Claude Code per SSH vom Homeserver.
Alle Zahlen stammen aus den Logs und Messungen beim Neuaufbau. Das Antast-Makro ist davon ausgenommen (siehe oben, noch ungetestet). Die Zahlen gelten für diese Maschine, nicht allgemein.
Die Fräse läuft wieder, und sie ist besser dokumentiert als je zuvor. Was CNCjs nicht kann, ist CAM: Für einfache Kreise, Nuten und Fenster habe ich mir inzwischen einen kleinen G-Code-Erzeuger mit Weboberfläche auf den Pi gebaut. Der kommt in einem eigenen Artikel.
Anzeige · Affiliate-Link – kaufst du darüber, erhalte ich ggf. eine Provision. Für dich ändert sich am Preis nichts.