40BFF59F-6773-4BCB-B689-24166AC489E2

Wand Plotter mit einem Raspberry Pico

Von am 06.02.2023

Was ein Raspberry Pico ? Was unterschiedet ihn zum Raspberry Pi ?

Der Raspberry Pi ist ein kleiner, leistungsfähiger Einplatinencomputer, der für verschiedene Anwendungen wie z.B. das Erstellen von Servern, das Steuern von Haushaltsgeräten oder das Erstellen von Medienzentren verwendet werden kann. Der Raspberry Pi verfügt über mehrere Anschlüsse für Peripheriegeräte wie Tastaturen, Mäuse und Monitore, und es gibt verschiedene Modelle mit unterschiedlichen Leistungsmerkmalen und Preisen.

Raspberry Pi Family Photo

Der Raspberry Pi Pico ist ein neueres Produkt, das auf dem RP2040-Mikrocontrollerchip von Raspberry Pi basiert. Es ist im Vergleich zum Raspberry Pi deutlich kleiner und kostengünstiger und eignet sich besonders gut für einfache, ressourcensparende Anwendungen wie z.B. das Steuern von Geräten oder das Sammeln von Daten. Der Raspberry Pi Pico hat weniger Anschlussmöglichkeiten als der Raspberry Pi und ist weniger leistungsfähig, aber dafür auch einfacher zu programmieren und zu verwenden.

Der Unterschied zum Arduino

Pico Vs. Arduino

Der Arduino ist ebenfalls ein Mikrocontroller, der für einfache, ressourcensparende Anwendungen wie das Steuern von Geräten oder das Sammeln von Daten geeignet ist. Im Vergleich zum Raspberry Pi Pico ist der Arduino jedoch eine etabliertere Plattform mit einer größeren Nutzerbasis und einer breiteren Auswahl an Hardware- und Software-Erweiterungen. Der Arduino ist auch etwas leistungsfähiger als der Raspberry Pi Pico und hat eine größere Auswahl an Anschlussmöglichkeiten. Allerdings ist er auch teurer und komplizierter zu programmieren.

Warum der RP2040-Mikrocontrollerchip besonders ist?

Der RP2040-Mikrocontrollerchip, der im Raspberry Pi Pico verwendet wird, hat einige Vorteile gegenüber dem Arduino. Zunächst einmal ist der RP2040 leistungsfähiger als der Arduino, insbesondere in Bezug auf die Rechenleistung und die Taktfrequenz. Der RP2040 verfügt auch über mehr Arbeitsspeicher als der Arduino, was ihn für anspruchsvollere Aufgaben geeignet macht.

Ein weiterer Vorteil des RP2040 ist die Unterstützung für das Python-Programmiermodell. Viele Entwickler bevorzugen Python wegen seiner einfachen Syntax und seiner Beliebtheit in der Informatikindustrie. Der RP2040 bietet die Möglichkeit, Python-Code direkt auf dem Mikrocontroller auszuführen, was für Entwickler, die Python verwenden, eine attraktive Option darstellen könnte.

Zusätzlich zu diesen Vorteilen bietet der Raspberry Pi Pico eine breite Palette von Anschlussmöglichkeiten, darunter USB, UART, I2C und SPI, die es ermöglichen, verschiedene Peripheriegeräte anzuschließen und zu steuern. Der Arduino bietet zwar auch eine Reihe von Anschlussmöglichkeiten, aber der Raspberry Pi Pico hat hier einen leichten Vorsprung.

Insgesamt bietet der Raspberry Pi Pico eine leistungsfähigere und flexiblere Plattform als der Arduino, die insbesondere für Entwickler attraktiv sein könnte, die Python verwenden und anspruchsvollere Anwendungen erstellen möchten.

Arduino-C++:

int ledPin = 13;

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  digitalWrite(ledPin, HIGH);
  delay(1000);
  digitalWrite(ledPin, LOW);
  delay(1000);
}

MicroPython:

from machine import Pin
import time

led = Pin(13, Pin.OUT)

while True:
  led.value(1)
  time.sleep(1)
  led.value(0)
  time.sleep(1)

Wie man sieht, sind die beiden Codebeispiele ziemlich ähnlich, aber es gibt einige Unterschiede in der Syntax und in den verwendeten Bibliotheken. Der Arduino-Code verwendet C++ und die Arduino-Bibliotheken, während der MicroPython-Code Python und die MicroPython-Bibliotheken verwendet. MicroPython ist in der Regel etwas einfacher zu lesen und zu verstehen als Arduino-C++, da es eine einfache, intuitive Syntax hat und weniger Zeichen benötigt.

Wandplotter Logik mit einem Pico:

Symbolbild von linkedline

Um den einfache Nutzbarkeit von einem Pico auf zu zeigen, schauen wir uns mein Lieblingsprojekt an. Der Wandplotter ist ein klasse Beispiel, um eine neue Plattform zu testen.

Als ersten sehen wir uns die Daten zum Drucken an. Hierfür verwenden wir Vektor Daten in Form von svg Dateien an. Wenn wir eine SVG Datei als TXT öffnen, bietet sich folgendes Bild:

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	    viewBox="0 0 744.09448819 1052.3622047"
   height="297mm"
   width="210mm" style="enable-background:new 0 0 1500 1500;" xml:space="preserve">
<g>
	<path d="M454.4,408.4h98v19.7h-74.5v60.5h68.8v19.4h-68.8v82.3h-23.5V408.4z"/>
	<path d="M609.4,408.4v107.7c0,40.8,18.1,58,42.4,58c27,0,44.3-17.8,44.3-58V408.4h23.8v106.1c0,55.9-29.4,78.8-68.8,78.8
		c-37.3,0-65.3-21.3-65.3-77.7V408.4H609.4z"/>
	<path d="M887.7,584.5c-8.6,4.3-25.9,8.6-48,8.6c-51.3,0-89.9-32.4-89.9-92.1c0-57,38.6-95.6,95-95.6c22.7,0,37,4.9,43.2,8.1
......

SVG (Scalable Vector Graphics) ist ein XML-basiertes Format zur Beschreibung zweidimensionaler Grafiken. Die Formatierung von SVG-Grafiken erfolgt über XML-Tags und Attribute, die die Formen, Farben und Transformationen der Grafik beschreiben.

Ein grundlegendes SVG-Dokument besteht aus einem Wurzelelement, das <svg> genannt wird, und enthält eine Liste von Formen und Bildern, die als Kinder des <svg>-Elements definiert sind. Jede Form oder jedes Bild wird als ein einzelnes Element beschrieben, das ein bestimmtes Tag hat. Hier sind einige häufig verwendete Formen-Tags:

  • <rect> – definiert ein Rechteck
  • <circle> – definiert einen Kreis
  • <ellipse> – definiert eine Ellipse
  • <line> – definiert eine Linie
  • <polyline> – definiert eine mehrseitige Linie
  • <polygon> – definiert ein Polygon

Jede Form hat Attribute, die ihre Größe, Position und Farbe beschreiben. Hier sind einige häufig verwendete Attribute:

  • x und y – legen die Position der Form auf der x- und y-Achse fest
  • width und height – legen die Größe der Form fest
  • fill – legt die Füllfarbe der Form fest
  • stroke – legt die Farbe des Rahmens (Strokes) der Form fest
  • stroke-width – legt die Breite des Rahmens fest

Neben den Grundformen unterstützt SVG auch komplexere Formen, wie z. B. Pfade, und bietet eine Vielzahl von Optionen für die Textformatierung. Pfade werden mit dem <path>-Element definiert, das eine breite Palette von Pfadbefehlen zur Erstellung komplexer Formen unterstützt. Text wird mit dem <text>-Element formatiert, mit dem Sie die Schriftart, die Größe und andere Formatierungsoptionen für den Text festlegen können.

Insgesamt ist die Formatierung von SVG-Grafiken recht flexibel und leistungsfähig und ermöglicht es Ihnen, eine breite Palette von Grafiken zu erstellen, von einfachen Formen bis hin zu komplexen Illustrationen.

SVG TO G-Code

G-Code ist eine Art von Maschinensprache, die hauptsächlich in der CNC-Fertigung und in der 3D-Druckbranche verwendet wird. Es wird verwendet, um Maschinen anzuweisen, bestimmte Bewegungen und Funktionen auszuführen, um ein Werkstück herzustellen oder zu bearbeiten.

Jede Zeile im G-Code-Programm enthält einen einzelnen Befehl, der von der Maschine interpretiert wird. Ein Befehl besteht aus einem oder mehreren G-Code-Befehlen und eventuell einer Liste von Parametern, die die Bedingungen des Befehls beschreiben.

Ein Beispiel eines einfachen G-Code-Befehls ist “G1 F500 X100 Y100”, was bedeutet, dass die Maschine mit einer Geschwindigkeit von 500 Millimeter pro Minute auf die Koordinaten X100 und Y100 bewegt werden soll.

Insgesamt ist G-Code ein mächtiges Werkzeug, um CNC-Maschinen und 3D-Drucker präzise und reproduzierbare Ergebnisse zu liefern.

G-Gode und Vektor Grafiken sind verschiedene Formate, haben aber eine nutzbare Gemeinsamkeit. Sie können hierarchisch gelesen werden und können in ein Koordinatensystem projiziert werden. Eine Umwandlung ist hierbei auch mit einfachen Mitteln zu verwirklichen. 

Wir nutzen in diesem Fall eine Python Bibliothek namens svg_to_gcode. Diesen können wir leider wegen einem fehlenden XML-Parser Bibliothek  nicht auf dem Pico laufen lassen, aber da wir den für den Pico einen Desktop benötigen, so lassen wir diesen einfach dort laufen. Unser G-Gode befindet sich nun im Ordner ‘path/to/output.gcode’ und diesen können wir später in das Verzeichnis vom Pico laden.

from svg_to_gcode.svg_parser import parse_svg

# Parse the SVG file
path_data = parse_svg('path/to/input.svg')

# Check and adjust the coordinates to be positive
for path in path_data:
    for command in path:
        for i, coord in enumerate(command):
            if i % 2 == 0 and coord < 0:
                command[i] = -1 * coord

# Generate the Gcode
gcode = svg_to_gcode.gcode_from_svg(path_data)

# Save the Gcode
with open('path/to/output.gcode', 'w') as f:
    f.write(gcode)

Vom G-Code in die reale Welt:

Alle Vorbereitungsschritte sind nun erledigt und wir können mit der Programmierung vom Pico beginnen. Da es keine speziellen Anforderungen beim Bespielen vom Microcontroller gibt, werde ich hierfür auf die offizielle Dokumentation verweisen:https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html

Wir werden nun eine main.py erstellen und den Code Teil für Teil implementieren:

Als erstes laden wir die benötigten Bibliotheken:

from time import sleep 
import math
from machine import Pin, PWM 

Diese benötigen wir für einen Delay, mathematische Funktionen und das ansteuern von Pins und einem Servo Motor.

Als nächsten Schritt machen wir uns das Leben einfacher und generieren eine Klasse Stepper für die einzelnen Steppermotoren. Hierfür ist die Funktionsweise und der Unterschied von herkömmlichen Elektromotoren wichtig.

Abbildung 3: Stepper Motor mit Signalen Wikipedia open Source

Stepper-Motoren sind speziell für präzise Positionierung entwickelt worden. Sie arbeiten indem sie in kleinen Schritten (Steps) rotieren und dabei eine präzise Position beibehalten. Jeder Schritt ist dabei gleich groß und kann genau gesteuert werden. Dies macht Stepper-Motoren ideal für Anwendungen, in denen eine genaue Positionskontrolle erforderlich ist, wie beispielsweise in CNC-Maschinen, 3D-Druckern und Robotern.

DC-Motoren (Gleichstrommotoren) hingegen sind einfachere Motoren, die einen gleichmäßigen Drehmomentausgang liefern. Sie sind nicht so präzise wie Stepper-Motoren, aber dafür einfacher zu verwenden und zu kontrollieren. DC-Motoren eignen sich besser für Anwendungen, bei denen keine präzise Positionierung erforderlich ist, wie beispielsweise in Lüftern, Pumpen und Antrieb für Fahrzeuge.

Durch mehrere Spulen kann ein Steppermotor realisiert werden. Dies benötigt dafür auch mehrere Leitungen zu dem Motor. In unserem Fall sind es 4 Phasen. Wenn wir nun wie in der Abbildung 3, zeitversetzt die jeweiligen Phasen nacheinander unter Spannung setzten, erzeigen wir eine Drehbewegung. Die Anzahl dieser Iteration ist dabei ein Stepp* der Anzahl der Phasen.

Kommen wir nun zu unserem Code und setzten wir das gelernte in eine Klasse um.

class Stepper:
    
    sequence_forw = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
    sequence_rev = [[0,0,0,1],[0,0,1,0],[0,1,0,0],[1,0,0,0]]
    sleeptime= 0.001

Die Klasse benötigt eine Sequenz für die Rotation. Diese Speichern wir in einem Array. Weil die Steuerung schneller schaltet, als die Massenträgheit des Motors überwunden werden kann, müssen wir jeden dieser Schritte mit einer Pause versetzten. 1-3 Millisekunde ist hierbei ein sehr guter Wert.

def __init__(self,pins, currentlength, umfang):
        self.pins=pins
        self.currentlength = currentlength
        self.umfang = umfang
        self.stepps= 2048
        self.singleStepLen= umfang/self.stepps

Wenn wir die Klasse initialisieren, wollen wir auch Werte übergeben. Wichtig ist hierbei die Position, Dimension und die Ausgänge vom Motor. Beim Plotter entspricht die Position nur dem Zusammenspiel der 2 Seile. Es reicht deshalb, dem Stepper zu sagen, wie lang die aktuelle Länge ist und wie groß die Veränderung pro (Teil-)Umdrehung ist. Ein Stepper Motor hat übrigens meistens rund 2048 Stepps für eine gesamte Umdrehung.

Die mathematische Lösung:

Eine vermutlich sehr komplizierte Aufgabe kann durch die mathematischen Fähigkeiten von Mittelschülern einfach gelöst werden. So auch das Problem der Steuerung. Wir haben unsere Grafiken schon in ein Koordinatensystem umgewandelt und haben nun einen Punkt erreicht, in dem wir Steppermotoren und Druckdaten sehr gut zusammenführen können.


Abb3. Mathematische Erklärung von Linkedline

Ich erspare mir die Erklärung der Abbildung und lasse diese auf euch wirken.
Wenn euch nun dein Licht auf geht, können wir fortfahren und alle wichtigen Funktionen von unserer Klasse erstellen.

def calcSteps(self,slen):
        stepps=slen/self.singleStepLen
        return math.floor(stepps) # wir runden unsere Ausgabe

def goto(self,newlen):
            job= self.calcSteps(newlen - self.currentlength )    
            self.currentlength=job # neue länge muss in der Klasse gespeichert erden
            return job
        
def doStep(self,steps,stepPer):
        for i in range(stepPer):
            if steps > 0:
                self.step_for(steps%4)
                steps= steps -1
            elif steps < 0:
                self.step_bac(steps%4)
                steps= steps +1
            else:
                break
        return steps
                   
    
            
def step_for(self,sIndex):
            step= self.sequence_forw[sIndex]
            for i in range(len(self.pins)):
                self.pins[i].value(step[i])
                sleep(self.sleeptime)
                
                
def step_bac(self,sIndex):
            step= self.sequence_rev[sIndex]
            for i in range(len(self.pins)):
                self.pins[i].value(step[i])
                sleep(self.sleeptime)
                

Wir haben nun alle wichtigen Funktionen. Wir können mit Stepper.goto(neueLänge) die Anzahl der Stepps berechnen, um von Länge A zu Länge B zu kommen. Der Rückgabewert ist je nach Veränderung der Länge, ein positiver oder negativer. Wenn wir diesen an Stepper.doStep(Stepps) schicken. wird die Sequenz ausgeführt.

Aktuell sollte der Code so aussehen:

from time import sleep
import math
from machine import Pin, PWM


class Stepper:
    
    sequence_forw = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
    sequence_rev = [[0,0,0,1],[0,0,1,0],[0,1,0,0],[1,0,0,0]]
    sleeptime= 0.001
    

    def __init__(self,pins, currentlength, umfang):
        self.pins=pins
        self.currentlength = currentlength
        self.umfang = umfang
        self.stepps= 2048
        self.singleStepLen= umfang/self.stepps

    def calcSteps(self,slen):
        import math
        stepps=slen/self.singleStepLen
        return math.floor(stepps)

    def goto(self,newlen):
            job= self.calcSteps(newlen - self.currentlength )    
            self.currentlength=job
            return job
        
    def doStep(self,steps,stepPer):
        for i in range(stepPer):
            if steps > 0:
                self.step_for(steps%4)
                steps= steps -1
            elif steps < 0:
                self.step_bac(steps%4)
                steps= steps +1
            else:
                break
        return steps

    
            
    def step_for(self,sIndex):
            step= self.sequence_forw[sIndex]
            for i in range(len(self.pins)):
                self.pins[i].value(step[i])
                sleep(self.sleeptime)
                
                
    def step_bac(self,sIndex):
            step= self.sequence_rev[sIndex]
            for i in range(len(self.pins)):
                self.pins[i].value(step[i])
                sleep(self.sleeptime)
                

Aus der Klasse raus in die Logik

Wie steigen aus der Klasse aus und legen unsere Pins fest, im selben Atemzug, können wir unseren Stepper initialisieren.

# GPIO für Steuersignal
servo_pin = 0

# Pins für den ersten Stepper definieren
pin1_1 = Pin(2, Pin.OUT)
pin1_2 = Pin(3, Pin.OUT)
pin1_3 = Pin(4, Pin.OUT)
pin1_4 = Pin(5, Pin.OUT)

# Pins für den zweiten Stepper definieren
pin2_1 = Pin(6, Pin.OUT)
pin2_2 = Pin(7, Pin.OUT)
pin2_3 = Pin(8, Pin.OUT)
pin2_4 = Pin(9, Pin.OUT)

# Stepper-Objekte erstellen
stepper1 = Stepper([pin1_1, pin1_2, pin1_3, pin1_4],0,10)
stepper2 = Stepper([pin2_1, pin2_2, pin2_3, pin2_4],0,10)

# Initialisierung PWM-Signal
servo = PWM(Pin(servo_pin))
servo.freq(50)

Öffnen unsere Lokale Version vom G-Code und laden es in eine Liste:

with open(filename, "r") as f:
    listData = f.readlines()

listData hat nun für jede Zeile ein Commando im Stiele von:

G90;
M5;
M5;
G1 F1000 X761.216815 Y81.931605;
M3 S255;
G1 F300 X764.251418 Y63.723614;
G1 X752.112820 Y88.000998;
G1 X764.251418 Y91.035600;
G1 X767.286209 Y63.723614;
G1 X782.459597 Y91.035600;
G1 X882.603734 Y91.035600;
G1 X752.112820 Y57.654221;


Wichtig ist Hiebei:

M5= Spindel Stop —> verwenden wir um den Servo ( Stift) anzuheben

M3= Spindel Start —> verwenden wir um den Servo ( Stift) zu senken

G1 = Bewege dich zu …

Wir müssen nun den Wert aus jeder Zeile Lesen. Beim Nero ist es einfach, beinhaltet die Zeile den Wert M5, dann mache … , wenn M3 dann …:

def servocontrol(job):
    if "M5" in job:
        for pos in range(3000,6000,20):
            setServoCycle(pos)
    if "M3" in job:
        for pos in range(6000,3000,-20):
            setServoCycle(pos)

    
def setServoCycle (position):
    servo.duty_u16(position)
    sleep(0.01)

Beim ablesen der Koordinaten wird es schwieriger, aber auch nicht unlösbar. Im selben Atemzug können wir unsere mathematisch hochkomplizierte Lösung anwenden:

def plotJob(commandline,xOff,yOff,xmax):
    newX = float(commandline("X")[1].split(" ")[0])+xOff
    newY = float(commandline("Y")[1].split(";")[0])+yOff

    len1= math.sqrt(newX**2+newY**2)
    len2= math.sqrt((xmax-newX)**2  +newY**2)
    return len1,len2

Wir müssen nur mehr jede einzelne Zeile durchgehen und schon haben wir unseren Plotter fertig.

 for i in listData:
        print(i)
        servocontrol(i)
        if "X" in i :
            job1,job2=plotJob(i,0,0,30)
            print(job1,job2)
            stepsfor1= stepper1.goto(job1)
            stepsfor2= stepper2.goto(job2)
            abs1= math.fabs(stepsfor1)
            abs2= math.fabs(stepsfor2)
            stepPer1=1
            stepPer2=1
            
            if abs1>abs2 and abs2>0 :
                stepPer1=abs1/abs2 
            elif abs1<abs2 and abs1>0 :
                stepPer2=abs2/abs1 

            while True:
                stepsfor1=stepper1.doStep(stepsfor1,stepPer1)
                stepsfor2=stepper2.doStep(stepsfor2,stepPer2)
                if stepsfor1 == 0 and stepsfor2 == 0:
                    break

Fazit:

Der gesamte Code hat nun in formatierter Version rund 140 Zeilen Code und macht nun die Kernaufgabe unseres Plotters. Ich persönlich bin sehr beeindruckt über die Einfachheit der Umsetzung. Natürlich ist die Umsetzung nun eine erste Version eines Studenten und sicherlich ausbaufähig. Dennoch machte es unglaublich viel Spaß.

Meine Arduinoversion hat rund 2.900 Zeilen und bietet sicherlich mehr Funktionen. Dennoch ist diese Version eine grandiose Chance, genau die selbe Begeisterung für dieses Projekt zu entwickeln, als ich.

Ich habe absichtlich den Code auf das wesentlich reduziert. Somit wirst du nach und nach auf kleinere Probleme stoßen. Aber keine Angst, jedes Problem ist mit einem einfachen Trick zu lösen.

The comments are closed.