Der Corona-Lockdown bedeutet mehr Zeit zuhause und im Home-Office. Was also machen mit der vielen Zeit? Ich habe eine alte Idee ausgegraben, der Selbstbau eines XY-Plotters, also einer Maschine, die einen Stift über Papier führt und so Zeichnungen produziert.
Natürlich kann jeder Laser- oder Inkjet-Drucker beliebige Bilder mit hoher Qualität ausdrucken. Es reizte mich aber, Zeichnungen mit einem geführten Stift maschinell zu erstellen. Die Resultate sind Unikate, die von den Eigenschaften von Stift und Papier abhängen. Und mir gefiel die selbst gestellte Aufgabe als eine spannende Herausforderung.
Dieses Projekt begann im ersten Corona-Lockdown im März 2020 und hat bis heute einen sehr schön funktionierenden Prototyp und allerlei interessante Zeichnungen ergeben. Eine Galerie findet sich am Ende dieses Beitrags.
Inhalt
- Das Konzept
- Die Mechanik
- Die Elektronik
- Die Software
- Protokollarisches
- Zeichenprogramme
- Galerie
- Ressourcen
Das Konzept
Die Aufgabe eines Plotters ist es, einen Stift zu führen, so dass er Spuren auf dem Papier hinterlässt. Ein Bild kommt aber erst im Zusammenspiel mit einem PC zustande, der die passenden Zeichenanweisungen erzeugt und an den Plotter sendet. Dazu stellt der Plotter Funktionen zum Zeichnen von Linien, Rechtecke, Kreise, Ellipsen, Kreisbögen und mehr bereit, beherrscht sogar einen einfachen Zeichensatz für Texte. Es gibt eine (stetig wachsende) Befehlstabelle (siehe hier). Die Verbindung zwischen PC and Plotter kann über die serielle USB-Schnittstelle oder über das WLAN erfolgen.
Die Zeichnungen können auf dem PC mit beliebiger Software erstellt werden. Ich verwende hauptsächlich Python-Skripts, die ansprechende Grafiken produzieren. Dabei beschäftige ich mich auch mit der Umwandlung von Fotos in Vektor-Zeichnungen im SVG-Format, die dann vom Plotter zu Papier gebracht werden – ein weites Experimentierfeld.
Die Mechanik
Das Design ist schnell beschrieben. Es gibt eine X- und eine Y-Achse, die mit Schrittmotoren betrieben werden. Die Mechanik basiert auf dem von 3D-Druckern bekannten Verfahren. Die Achsen werden aus Linearwellen (polierter Rundstahl mit 8 mm Durchmesser) aufgebaut, auf denen Schlitten mit Linearlagern laufen. Die horizontale X-Achse ist doppelt ausgeführt und bewegt zwei gegenüberliegende Schlitten, die die vertikale Y-Achse tragen. Auf der Y-Achse bewegt sich ein Schlitten mit dem Stifthalter. Die Positionierung erfolgt über Zahnriemen.
Der Zeichenstift ist an einer Wippe befestigt, die mit einem Modellbau-Servo gehoben oder gesenkt wird. Der Anpressdruck auf dem Papier wird mit einer Feder angepasst. Der Stifthalter kann beliebige Stifte aufnehmen, allerdings nur einen Stift zur Zeit. Mehrere Farben benötigen also einen manuellen Stiftwechsel. Das Papier wird mit Magneten fixiert, so dass es schnell gewechselt werden kann. Die bei professionellen Plottern verbreitete Fixierung mit Unterdruck erschien mir für den Selbstbau zu aufwendig.
Überall kommen Linear- und Kugellager zum Einsatz, so dass die Mechanik weitgehend spielfrei funktioniert. Die benötigten Komponenten sind bei den üblichen Online-Händlern zu akzeptablen Preisen verfügbar.
Die benötigten Konstruktionsteile kommen aus dem 3D-Drucker. Zur Konstruktion verwende ich durchgängig FreeCad und zur Herstellung der Teile meinen bewährten Dremel 3D40.
Die Auswahl eines geeignetes Stifts ist nicht ganz unproblematisch. Beim Zeichnen können durchaus lange Strecken zusammenkommen, so dass sich der Stift schnell abnutzt. Ich habe gute Erfahrungen mit Tintenrollern oder Gelrollern gemacht.
Die Elektronik
Im Zentrum der Maschine werkelt ein ESP32. Er treibt die Schrittmotoren XA, XB und Y mit Hilfe der üblichen A4988 Treiber-Module, die als Breakout-Board verfügbar sind. Ich verwende den 8tel-Schritt-Modus (MS1 und MS2 liegen auf +3.3V). Die Enable-Leitungen aller 3 Treiber sind zusammengefasst. Der Prozessor kann damit alle Motoren ein- oder ausschalten. Jeder Motor-Treiber hat einen Step- und einen Dir-Eingang, die unabhängig voneinander angesteuert werden. Für jede Achse gibt es einen Endtaster, der nach dem Einschalten angefahren wird (“Referenzfahrt”), um eine definierte 0-Position zu finden.
Weiterhin gibt es ein alphanumerisches LCD-Display, das über das I2C-Interface betrieben wird. Das Display hat 4 Zeilen mit jeweils 20 Zeichen und gibt Status-Informationen aus.
Das System beinhaltet 3 Taster, mit denen der Anwender den Ablauf beeinflussen kann:
- Taster 0 (“Stop”, grün) hält die Ausführung an bzw. setzt sie fort.
- Taster 1 (“Standby”, blau) schaltet den Motostrom ab. Sobald der Plotter irgendein Zeichenkommando bekommt, werden die Motoren wieder eingeschaltet.
- Taster 2 (“Pen Up/Down”, gelb) hebt oder senkt den Stift. Das ist hilfreich zum Einsetzen eines Stifts.
Die Schaltung arbeitet mit 3 verschiedenen Spannungen: Sie wird von einem externen Netzteil mit 12V versorgt. Die Spannung dient direkt zum Treiben der Motoren (Vmot). Ein Spannungsregler auf der Platine erzeugt daraus 5V (Vdd) für die Stromversorgung von ESP32, LCD-Display und Stift-Servo. Der ESP32 trägt auf seinem Board einen 3.3V Spannungsregler (Vcc). Diese Spannung wird außerdem für die Versorgung der Logik der Schrittmotortreiber verwendet
Die Schaltung ist im Laufe der Zeit auf einer Lochrasterplatine im Prototyp-Stil “gewachsen”. Das Resultat ist nicht schön, funktioniert aber zuverlässig. Bei Gelegenheit kann man dafür eine Platine anfertigen.
Ursprünglich war der Plotter als horizontaler “Flachbett-Plotter” konzipiert. Aus Platzgründen in meinem Home-Office habe ich ihn dann aber auf ein Gestell gesetzt, so dass er mit etwa 60 Grad Neigung im Bücherregal steht. Leider reicht es jetzt nicht mehr ganz für das A3-Papierformat.
Die Software
Wie so oft bei diesen Projekten benötigt die Software den größten Arbeitsaufwand. Die “Firmware” für den ESP32 habe ich mit der Arduino-IDE entwickelt. Sie übernimmt die Steuerung der Motoren und des Servos für den Stift und kann eine Reihe von Grafik-Primitiven ausführen. Eingaben kommen über die serielle Schnittstelle oder das WLAN und landen in einem internen Puffer, der sukzessive abgearbeitet wird.
Die Schrittmotoren werden über einen Timer-Interrupt gesteuert. Der Interrupt beherrscht den Bresenham-Algorithmus, um zwei beliebige Punkte mit einer geraden Linie zu verbinden, was die Grundlage für alle Zeichenfunktionen bildet. Die Motoren werden mit einer Rampe auf Geschwindigkeit gebracht und vor dem Ziel wieder abgebremst. So kann der Plotter den Schlitten relativ schnell bewegen, ohne Schritte zu verlieren. Das klappt tatsächlich ausgesprochen gut. Die Logik unterscheidet zwischen Bewegungen mit gehobenem (schnell) und gesenktem Stift (einstellbare Geschwindigkeit, normalerweise langsamer). Weitere Anforderungen an das Timing müssen beachten werden. Zum Beispiel wird nach dem Befehl pen down
für einen kurzen Moment gewartet, bis der Stift auf dem Papier angekommen ist.
Der Eingabepuffer nimmt mit 70 kB den größten Teil des verfügbaren RAM-Speichers auf dem ESP32 in Anspruch. Die Steuerung der Motoren und das Abarbeiten des Puffers geschieht vollständig im Hintergrund. Die Schnittstellen werden auch während des Plot-Betriebs bedient. Das sendende Programm muss allerdings abfragen, ob genügend Platz für weitere Anweisungen im Puffer vorhanden ist. Dazu dient das Kommando F
, das den aktuell verfügbaren Speicherplatz zurück gibt.
Für die WLAN-Verbindung verwende ich die Socket-Kommunikation, die an anderer Stelle beschrieben wurde. Bei der ersten Inbetriebnahme muss der Anwender das Netzwerk über die serielle Schnittstelle auswählen und das zugehörige Passwort eingeben. Die Einstellung einer statischen Adresse ist möglich. Die Daten werden im Flash-Speicher abgelegt, so dass sich der Plotter beim nächsten Einschalten direkt und ohne weitere Eingaben mit dem Netzwerk verbindet.
Die Software ist ein umfangreiches Projekt und hat in der aktuellen Version sicherlich noch einen experimentellen Charakter, läuft aber stabil und produziert schon sehr ansehnliche Bilder.
Protokollarisches
Wie bringt man den Plotter dazu, etwas zu zeichnen? Dafür verwende ich ein einfaches Protokoll. Die Syntax ist angelehnt an die Scalable Vector Graphics-Sprache. Jede Anweisung besteht aus einem Buchstaben, der die Art des Befehls kennzeichnet, gefolgt von Parametern, z.B. Koordinaten. Die Parameter werden durch Komma getrennt. Leerzeichen werden ignoriert. Eine Anweisung wird durch ein Semikolon oder das Zeilenende abgeschlossen.
Der Plotter verwendet ein X/Y-Koordinatensystem. Der Nullpunkt ist unten links und wird nach dem Einschalten angesteuert. Ein Schritt entspricht 0,025 mm, was eine recht hohe Auflösung für diese Anwendung ist. Auch mit sehr feinen Stiften lassen sich keine Treppenstufen erkennen. Zur Zeit gibt es 37 Befehle. Hier ist ein Ausschnitt der Tabelle.
Ein einfaches Beispiel: Die hier gezeigte Sequenz zeichnet ein Smiley, bestehend aus Kreisen, Ellipsen, Kreisbögen und geraden Linien, die ein Dreieck bilden. Die Anweisung Z
(Zeile 4) schließt den Polygon-Zug, wobei der Stift an die letzte durch ein M
angesteuerte Position zurückkehrt.
1 2 3 4 5 |
M 6000,2000; C 800 M 5700,2200; E 200,80; M 5700,2200; C 60 M 6300,2200; E 200,80; M 6300,2200; C 60 M 6000,2100; L 6200,1800; L 5800,1800; Z M 6000,2050; C 600,120,240 |
Es gibt auch Anweisungen zum Zeichnen von Text. Der Plotter hat einen eingebauten Zeichensatz, der allerdings noch recht eingeschränkt ist und im Wesentlichen die unteren 80 Zeichen der ASCII-Tabelle enthält.
Zeichenprogramme
Der Plotter ist ein Werkzeug, das Anweisungen empfängt und den Stift bedient. Eine ansprechende Grafik besteht aber aus sehr vielen Anweisungen und kommt in der Regel von einem Programm auf dem PC. Ich verwende verschiedene Python Programme, die mit unterschiedlichen Algorithmen Grafiken produzieren oder Fotos in Zeichnungen umsetzen.
Zum Testen des Plotters und der Verbindung eignet sich eine kleine Python-Anwendung, die manuelle Eingaben per Tastatur entgegennimmt und über die WebSocket-Schnittstelle an den Plotter sendet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# websocket_test.py - gets input from the keyboard and sends it to the plotter # SLW 03/21 import time import websocket plotter_ip = "192.168.1.70:90" #------------------------------------------ def send_msg(msg): """ Sends a message to websocket server. Returns the response """ try: ws.send(msg) result = ws.recv() except OSError as err: print(err) result = "" return result #------------------------------------------ ws = websocket.WebSocket() try: ws.connect("ws://" + plotter_ip, timeout=3) print("Connected to WebSocket server, IP", ip) connection = True except OSError as err: connection = False print(err) if connection: while True: instr = input("> ") if len(instr) == 0: break print(send_msg(instr)) ws.close() print("Connection closed") |
Das Skript verbindet sich mit dem WebSocket-Server auf dem Plotter und öffnet eine interaktive Session, in der man die einzelnen Grafik-Befehle ausprobieren kann.
Die Python-Programme, mit denen ich meine Grafiken erstelle, produzieren eine Liste von Zeichenanweisungen, die als Zwischenschritt erst einmal in einer Datei landen. Bei mir heißt diese Datei meistens plot_file.plt
. Diese Datei wird dann mit einem Python-Skript zum Plotter gesendet. Dabei muss das Skript regelmäßig den verfügbaren Pufferplatz auf dem Plotter abfragen und darf die Daten nur bei ausreichend Platz senden. Ein sehr einfaches Programm für diese Aufgabe ist hier gezeigt. Dieses Skript hält den Transfer an, wenn der freie Pufferplatz unter 1 kB fällt und startet den Transfer, wenn er über 10 kB ansteigt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# plot_file.py - reads an input file and sends it to the plotter # SLW 03/21 import time import websocket # Global parameter --------------------------------------- ip = "192.168.1.70:90" filename = "plot_file" extension = ".plt" #--------------------------------------------------------- def send_msg(msg): try: ws.send(msg) result = ws.recv() except websocket._exceptions.WebSocketTimeoutException: print("timeout occured") result = None return result #---------------------------------------------------------- def get_buffer_size(): result = send_msg("F") if result: buffer_size = int(result.split(':')[1]) return buffer_size else: return 0 #---------------------------------------------------------- # open communication channel ws = websocket.WebSocket() ws.connect("ws://" + ip, timeout=5) print("Connected to WebSocket server on IP", ip) # read input file if filename.find(extension) < 0: filename += extension try: with open(filename, "r") as f: lines = f.readlines() print(len(lines), "lines read") except IOError: print("File '" + filename + "' not available or accessible") lines = None # send data to plotter if lines: buffer_okay = False for n, l in enumerate(lines): while True: buf = get_buffer_size() if buf > 10000: buffer_okay = True elif buf < 1000: buffer_okay = False if buffer_okay: send_msg(l) if n % 20 == 0: print(" - {:d} lines sent".format(n)) break; else: time.sleep(1) # finish plot and close connection send_msg("H; P") ws.close() print("Connection closed") |
Zum Abschluss ein kurzes Python-Programm, das eine einfache Zeichnung erstellt und in der Datei plot_file.plt
ablegt. Die Computer-Grafiken der 80er Jahre feiern hier ein Revival.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# nested_check.py - a program plotting px, py = 1000, 3000 length, steps = 4000, 40 x, y = [px, px, px+length, px+length], [py, py+length, py+length, py] f = open("plot_file.plt", "w") for i in range(steps): s = "M {:d}, {:d}; ".format(x[0], y[0]) for n in range(1, 4): s += "L {:d}, {:d}; ".format(x[n], y[n]) s += "L {:d}, {:d}\n".format(x[0], y[0]) f.write(s) y[0] += length // steps x[1] += length // steps y[2] -= length // steps x[3] -= length // steps f.close() |
Die Python-Unterstützung des Plotters ist im Moment noch rudimentär und wird im Laufe der Zeit weiter entwickelt. Auch die Erstellung von Grafiken mit Python-Skripts ist ein weites Feld. Zu gegebener Zeit werden weitere Beispiele folge.
Fazit
Spielereien mit Computer-Grafik haben mich schon immer fasziniert. Der Bau eines Plotters ist ein spannendes Projekt. Die mit dem Stift gezeichneten Ergebnisse habe einen eigenen Reiz.
Der Eigenbau eines solchen Gerätes wird durch die Verfügbarkeit von 3D-Druck und leistungsfähiger Hardware-Komponenten erst möglich. Es ist erstaunlich, welche Präzision sich in der heimischen Werkstatt erreichen lässt. Der ESP32 hat mich mit seiner Leistungsfähigkeit wieder einmal beeindruckt. Die Verwaltung der Motoren mit schnellen Interrupt-Folgen parallel zur Bedienung des Eingabepuffers, LCD, Stift-Servo und Netzwerk sind eine anspruchsvolle Aufgabe, die den ESP32 aber noch lange nicht an seine Grenzen bringt.
Galerie
Ressourcen
- Tabelle der Plotter-Commands als PDF
- Link zur Firmware und Python-Skripts auf GitHub: smlaage/XY-Plotter (github.com)
Hallo, sind ja alles schöne Grafiken. Gibt es ein Programm dafür womit auch ein Nicht-Programmierer klar kommt ?. Möchte sowas gerne auf einem Stiftplotter zeichnen. m. f. G. Jürgen
Hallo Jürgen, schön, dass es dir gefällt. Das Thema von diesem Artikel war die Mechanik und Software vom Plotter.
Einige der Beispiel-Grafiken habe ich mit einem Python-Programm berechnet, das drei drehende Scheiben verbunden mit einer Stift-Halterung simuliert. Das Programm habe ich für meine eigenen Experimente gemacht. Es ist ein bisschen “quick & dirty”, sicherlich nicht selbst erklärend und hat kein grafisches Frontend. Sorry.
Viele Grüsse,
Stephan
Hallo,
schöne Bilder mit dem Plotter.
Wollte auch mit demESP32 ne Wetteruhr basteln.
Ich frage mich nur beim I2C Display welches auf 5Volt läuft, wie das mit den I2C Anschlüssen vom ESP32 welche ja nicht mehr als 3,3 Volt abkönnen sollen, funktioniert. In der Schaltung werden die Pins ja mit 2,2K auf 5 Volt gezogen?
Hallo, stimmt, guter Punkt. Danke für den Hinweis. Tatsächlich ist das Schaltbild an der Stelle falsch. Die Pullup-Widerstände gehen auf Vcc mit 3.3V. Ich habe das korrigiert.
Aber streng genommen hast du natürlich Recht: Hier sollte eigentlich ein Level-Shifter für die Anpassung der I2C-Signale von 5 auf 3.3V sorgen. Eigentlich deswegen, weil ich mit der gezeigten Verschaltung noch nie Probleme hatte, auch bei anderen Projekten. Ich habe diese I2C-Displays immer wieder im Einsatz, an ESP32 und auch am Raspberry Pico. Die Controller haben dabei noch nie Schaden genommen.
Viele Grüsse, Stephan
Das heißt ich kann die beiden koppeln, ohne das praktisch etwas kaputt geht?
Gruß aus Berlin
Hallo Joachim, ja, meine Erfahrung ist, dass SDA und SCL direkt miteinander verbunden werden können.
Wenn Pullup-Widerstände notwendig sind, dann auf jeden Fall nach 3.3V. Bei einer I2C-Taktfrequenz von 100 kHz laufen die Hd44780-Displays auch ohne externe Pullups.
Viele Grüsse, Stephan
Hallo again,
bei meinem Display mit I2C-Interface Bundle von AZ-Deli. habe ich die beiden I2C Anschlüsse mal gegen VDD mit dem Ohmmeter gemessen. Es kam bei beiden 4,7 K heraus.
Sieht also so aus, als wenn das Interface schon die beiden Leitungen auf 5 Volt zieht.
Braucht man dann noch Pullups auf 3,3 Volt?
Das sollte funktionieren. Einfach mal ausprobieren.