So erstellen Sie einen 3D-Drucker in Python

Erste Implementierung einer CNC-Maschinensteuerung auf reinem Python

CNC-Maschinensteuerungen werden in der Regel mit der Programmiersprache C oder C ++ implementiert und laufen auf Betriebssystemen ohne Betriebssystem oder Echtzeitbetriebssystemen mit einfachen Mikrocontrollern.

Ich werde beschreiben, wie man eine CNC-Steuerung, insbesondere einen 3D-Drucker, mit modernen ARM-Karten baut - Raspberry Pi mit einer modernen Hochsprache - Python. Ein derart moderner Ansatz eröffnet eine breite Palette von Integrationsmöglichkeiten mit anderen Spitzentechnologien, -lösungen und -infrastrukturen, wodurch der gesamte Projektentwickler freundlich wird.

Über das Projekt

Moderne ARM-Boards verwenden normalerweise Linux als Referenzbetriebssystem. Dies gibt uns die gesamte Linux-Infrastruktur mit allen Linux-Softwarepaketen. Wir können einen Webserver auf einem Board hosten, eine Bluetooth-Verbindung verwenden, OpenCV für die Bilderkennung verwenden, einen Cluster von Boards erstellen usw. Dies sind bekannte Aufgaben, die auf ARM-Boards implementiert werden können und für die sie sehr nützlich sein können Kundenspezifische CNC-Maschinen. Beispielsweise kann die automatische Positionierung mit compuvision für einige Maschinen sehr praktisch sein.

Linux ist kein Echtzeitbetriebssystem. Daher können wir keine Impulse mit den erforderlichen Timings erzeugen, um Schrittmotoren bei laufender Software direkt von den Platinenstiften aus zu steuern, selbst nicht als Kernelmodul. Wie können wir dann Stepper und Linux-Funktionen auf hoher Ebene verwenden?

Natürlich können wir zwei Chips verwenden - einen Mikrocontroller mit einer klassischen CNC-Implementierung; und eine ARM-Karte, die über UART mit diesem Mikrocontroller verbunden ist. Was ist, wenn für diesen Mikrocontroller keine geeigneten Firmware-Funktionen vorhanden sind? Was ist, wenn wir zusätzliche Achsen steuern müssen, die nicht im Mikrocontroller implementiert sind?

Alle Änderungen an der vorhandenen C / C ++ - Firmware erfordern viel Entwicklungszeit und -aufwand. Mal sehen, ob wir es einfacher machen und sogar Geld für Mikrocontroller sparen können, indem wir sie einfach entfernen.

PyCNC

PyCNC ist ein kostenloser Open-Source-Hochleistungs-G-Code-Interpreter und eine CNC- / 3D-Druckersteuerung. Es kann auf verschiedenen Linux-basierten ARM-Boards wie Raspberry Pi, Odroid, Beaglebone und anderen ausgeführt werden. Dies gibt Ihnen die Flexibilität, jedes Board auszuwählen und alles zu nutzen, was Linux bietet. Und Sie können die gesamte G-Code-Laufzeit auf einer Karte speichern, ohne einen separaten Mikrocontroller für den Echtzeitbetrieb zu benötigen.

Durch die Auswahl von Python als Hauptprogrammiersprache wird die Codebasis im Vergleich zu C / C ++ - Projekten erheblich reduziert, der Code für Boilerplates und Mikrocontroller reduziert und das Projekt einem breiteren Publikum zugänglich gemacht.

Wie es funktioniert

Das Projekt verwendet DMA (Direct Memory Access) auf dem Chiphardwaremodul. Es kopiert einfach den im RAM zugewiesenen GPIO-Statuspuffer in die tatsächlichen GPIO-Register. Und dieser Kopiervorgang wird von der Systemuhr synchronisiert und funktioniert völlig unabhängig von den CPU-Kernen. Somit wird eine Folge von Impulsen für die Schrittmotorachse im Speicher erzeugt und dann vom DMA genau ausgesendet.

Sehen wir uns den Code genauer an, um die Grundlagen und den Zugriff auf Hardwaremodule von Python aus zu verstehen.

GPIO

Ein Universal-Eingangs-Ausgangsmodul steuert den Pin-Status. Jeder Pin kann einen niedrigen oder hohen Zustand haben. Wenn wir den Mikrocontroller programmieren, verwenden wir normalerweise SDK-definierte Variablen, um auf diese Pins zu schreiben. So aktivieren Sie beispielsweise einen High-Status für die Pins 1 und 3:

PORTA = (1 << PIN1) | (1 << PIN3)

Wenn Sie sich das SDK ansehen, finden Sie die Deklaration dieser Variablen und sie sieht ungefähr so ​​aus:

#define PORTA (* (volatile uint8_t *) (0x12345678))

Es ist nur ein Hinweis. Nicht für den Speicherort im RAM, sondern für die Adresse des physischen Prozessors, und das eigentliche GPIO-Modul befindet sich an dieser Adresse. Zur Verwaltung von Pins können wir Daten schreiben und lesen. Der ARM-Prozessor von Raspberry Pi ist keine Ausnahme und verfügt über dasselbe Modul. Zur Steuerung von Pins können wir Daten schreiben / lesen. Die Adressen und Datenstrukturen finden Sie in der offiziellen Dokumentation zur Prozessorperipherie.

Wenn wir einen Prozess zur Laufzeit des Benutzers ausführen, startet der Prozess im virtuellen Adressraum, und auf das eigentliche Peripheriegerät kann direkt zugegriffen werden. Wir können jedoch weiterhin mit dem Gerät "/ dev / mem" auf reale physikalische Adressen zugreifen.

Hier ist ein einfacher Code in Python, der den Status einer Stecknadel mithilfe dieses Ansatzes steuert:

Teilen wir es zeilenweise auf:

Zeilen 1–6: Überschriften, Importe.
Zeile 7: Öffnen Sie den Gerätezugriff "/ dev / mem" auf die physikalische Adresse.
Zeile 8: Wir verwenden den Systemaufruf memmap, um eine Datei (in unserem Fall stellt diese Datei den physischen Speicher dar) in den virtuellen Speicher des Prozesses abzubilden. Wir legen die Länge und den Versatz des Kartenbereichs fest. Für die Länge nehmen wir die Seitengröße. Und der Offset ist 0x3F200000. Die Dokumentation besagt, dass die Busadresse 0x7E200000 GPIO-Register enthält und wir die physikalische Adresse angeben müssen. In der Dokumentation (Seite 6, Absatz 1.2.3) heißt es, dass die Busadresse 0x7E000000 der physikalischen Adresse 0x20000000 zugeordnet ist. Diese Dokumentation bezieht sich jedoch auf Raspberry 1.
Bitte beachten Sie, dass alle Modulbusadressen für Raspberry Pi 1–3 gleich sind, diese Zuordnung jedoch für RPi 2 und 3 in 0x3F000000 geändert wurde. Die Adresse hier lautet also 0x3F200000. Für Raspberry Pi 1 ändern Sie es auf 0x20200000.
Danach können wir in den virtuellen Speicher unseres Prozesses schreiben, aber er schreibt tatsächlich in das GPIO-Modul.
Zeile 9: Schließen Sie das Dateihandle, da wir es nicht speichern müssen.
Zeilen 11–14: Wir lesen und schreiben unsere Map mit dem Offset 0x08. Laut Dokumentation handelt es sich um das GPFSEL2 GPIO Function Select 2-Register. Und dieses Register steuert die Stiftfunktionen. Wir setzen (alles löschen, dann mit dem OR-Operator setzen) 3 Bits, wobei das 3. Bit auf 001 gesetzt ist und dieser Wert bedeutet, dass der Pin als Ausgang fungiert. Es gibt viele Pins und mögliche Modi für sie. Deshalb ist das Register für Modi in mehrere Register unterteilt, in denen jeweils die Modi für 10 Pins enthalten sind.
Zeile 16 und 22: Richten Sie den Interrupt-Handler "Strg + C" ein.
Zeile 17: Endlosschleife.
Zeile 18: Setzen Sie den Pin in den High-Zustand, indem Sie in das GPSET0-Register schreiben. Bitte beachten Sie, dass Raspberry Pi keine Register wie PORTA (AVR-Mikrocontroller) hat, wir können nicht den gesamten GPIO-Status aller Pins schreiben. Es gibt nur SET- und CLEAR-Register, die zum Setzen und Löschen der mit bitweisen Maskenstiften angegebenen Werte verwendet werden.
Zeilen 19 und 21: Verspätung
Zeile 20: Pin mit dem Register GPCLR0 auf Low setzen.
Zeilen 25 und 26: Pin auf Standard schalten, Eingangszustand. Schließen Sie die Speicherkarte.

Dieser Code sollte mit Superuser-Rechten ausgeführt werden, benennen Sie die Datei "gpio.py" und führen Sie sie mit "sudo python gpio.py" aus. Wenn Sie eine LED an Pin 21 angeschlossen haben, blinkt diese.

DMA

Direct Memory Access ist ein spezielles Modul, mit dem Speicherblöcke von einem Bereich in einen anderen kopiert werden können. Wir werden Daten aus dem Speicherpuffer in das GPIO-Modul kopieren. Zunächst benötigen wir einen festen Bereich im physischen RAM, der kopiert wird.

Es gibt einige mögliche Lösungen:

  1. Beispielsweise können wir einen einfachen Kerneltreiber erstellen, der uns die Adresse dieses Speichers zuweist, sperrt und meldet.
  2. In einigen Implementierungen wird virtueller Speicher zugewiesen und verwendet "/ proc / self / pagemap", um die Adresse in die physikalische zu konvertieren. Ich würde nicht empfehlen, diesen Ansatz zu verwenden, insbesondere wenn wir große Flächen zuweisen müssen. Jeder virtuell zugewiesene Speicher (auch gesperrt, siehe Kerneldokumentation) kann in den physischen Bereich verschoben werden.
  3. Alle Raspberry Pi verfügen über ein "/ dev / vcio" -Gerät, das Teil des Grafiktreibers ist und uns physischen Speicher zuweisen kann. Ein offizielles Beispiel zeigt, wie es geht. Und wir können es benutzen, anstatt unser eigenes zu erschaffen.

Das DMA-Modul selbst besteht nur aus einer Reihe von Registern, die sich an einer physischen Adresse befinden. Wir können dieses Modul über diese Register steuern. Grundsätzlich gibt es Quell-, Ziel- und Steuerregister. Schauen wir uns einen einfachen Code an, der zeigt, wie das DMA-Modul zum Verwalten des GPIO verwendet wird. Da für die Zuweisung des physischen Speichers mit "/ dev / vcio" zusätzlicher Code erforderlich ist, verwenden wir eine Datei mit einer vorhandenen Implementierung der CMAPhysticalMemory-Klasse. Wir werden auch die PhysicalMemory-Klasse verwenden, die den Trick mit Memap aus dem vorherigen Beispiel ausführt.

Teilen wir es zeilenweise auf:

Zeilen 1-3: Überschriften, Importe.
Zeilen 5–6: Konstanten mit der Kanal-DMA-Nummer und dem GPIO-Pin, die wir verwenden werden.
Zeilen 8–15: Initialisieren Sie den angegebenen GPIO-Pin als Ausgang und leuchten Sie ihn zur visuellen Kontrolle eine halbe Sekunde lang auf. Tatsächlich ist es dasselbe, was wir im vorherigen Beispiel getan haben, und zwar auf pythonischere Weise.
Zeile 17: weist 64 Bytes im physischen Speicher zu.
Zeile 18: erstellt spezielle Strukturen - Steuerblöcke für das DMA-Modul. Die folgenden Zeilen unterbrechen die Struktur dieses Blocks. Jedes Feld hat eine Länge von 32 Bit.
Zeile 19: überträgt Informationsflags. Eine vollständige Beschreibung der einzelnen Flaggen finden Sie auf Seite 50 der offiziellen Dokumentation.
Zeile 20: Quelladresse. Diese Adresse muss eine Busadresse sein, daher rufen wir get_bus_address () auf. Der DMA-Steuerblock muss mit 32 Bytes ausgerichtet werden, aber die Größe dieses Blocks beträgt 24 Bytes. Wir haben also 8 Bytes, die wir als Speicher verwenden.
Zeile 21: Zieladresse ist in unserem Fall die Adresse des SET-Registers des GPIO-Moduls.
Zeile 22: Übertragungslänge - 4 Bytes.
Zeile 23: Schritt. Wir benutzen diese Funktion nicht, setze 0.
Zeile 24: Adresse des nächsten Steuerblocks, in unserem Fall die nächsten 32 Bytes.
Zeile 25: Auffüllen, aber da wir diese Adresse als Datenquelle verwendet haben, setzen Sie ein Bit, das GPIO auslösen sollte.
Zeile 26: Polsterung.
Zeilen 28–37: Füllen Sie den zweiten DMA-Steuerblock aus. Der Unterschied besteht darin, dass wir in das CLEAR GPIO-Register schreiben und unseren ersten Block als nächsten Steuerblock festlegen, um die Übertragung zu schleifen.
Zeilen 38–39: Steuerblöcke in den physischen Speicher schreiben.
Zeile 41: Hole das DMA-Modul-Objekt mit dem ausgewählten Kanal.
Zeilen 42–43: Setzen Sie das DMA-Modul zurück.
Zeile 44: Geben Sie die Adresse des ersten Blocks an.
Zeile 45: Führen Sie das DMA-Modul aus.
Zeilen 49–52: Aufräumen. Stoppen Sie das DMA-Modul und versetzen Sie den GPIO-Pin in den Standardzustand.

Schließen Sie das Oszilloskop an den angegebenen Pin an und führen Sie diese Anwendung aus (vergessen Sie nicht die sudo-Berechtigungen). Wir werden ~ 1,5 MHz Rechteckimpulse beobachten:

DMA-Herausforderungen

Es gibt einige Dinge, die Sie berücksichtigen sollten, bevor Sie eine echte CNC-Maschine bauen. Erstens kann die Größe des DMA-Puffers offensichtlich Hunderte von Megabyte betragen. Das zweite hängt mit der Tatsache zusammen, dass das DMA-Modul für ein schnelles Kopieren von Daten ausgelegt ist.

Wenn mehrere DMA-Kanäle arbeiten, können wir die Speicherbandbreite überschreiten, und der Puffer wird mit Verzögerungen kopiert, die zu Jitter in den Ausgangsimpulsen führen können. Es ist also besser, einen Synchronisationsmechanismus zu haben.

Um dies zu überwinden, habe ich ein spezielles Design für Steuerblöcke erstellt:

Das Oszillogramm oben im Bild zeigt die gewünschten GPIO-Zustände. Die folgenden Blöcke repräsentieren die DMA-Steuerblöcke, die diese Wellenform erzeugen. "Verzögerung 1" gibt die Impulslänge an und "Verzögerung 2" gibt die Pausenlänge zwischen Impulsen an. Bei diesem Ansatz hängt die Puffergröße nur von der Anzahl der Impulse ab.

Beispielsweise würde für eine Maschine mit 200 mm Verfahrweg und 400 Impulsen pro mm jeder Impuls 128 Bytes (4 Steuerblöcke pro 32 Bytes) benötigen, und die Gesamtgröße wird ~ 9,8 MB betragen. Natürlich hätten wir mehr als eine Achse, obwohl die meisten Impulse zur gleichen Zeit auftreten würden, und es wären Dutzende von Megabyte, nicht Hunderte.

Die zweite Herausforderung im Zusammenhang mit der Synchronisation wurde durch die Einführung vorübergehender Verzögerungen durch die Steuerblöcke gelöst. Das DMA-Modul hat eine Besonderheit: Es kann auf ein spezielles Ready-Signal des Moduls warten, in das Daten geschrieben werden. Das für uns am besten geeignete Modul ist das PWM-Modul, das uns auch bei der Synchronisation hilft.

Das PWM-Modul kann die Daten serialisieren und mit fester Geschwindigkeit senden. In diesem Modus generiert es ein Ready-Signal für den FIFO-Puffer des PWM-Moduls. Schreiben Sie also Daten in das PWM-Modul und verwenden Sie sie nur zur Synchronisierung.

Grundsätzlich müssen wir ein spezielles Flag in der PERMAP des Übertragungsinformationsflags aktivieren und das PWM-Modul mit der gewünschten Frequenz ausführen. Die Implementierung ist ziemlich lang, Sie können es selbst studieren. Erstellen wir stattdessen einen einfachen Code, der mithilfe des vorhandenen Moduls präzise Impulse erzeugen kann.

Importieren Sie RPGPIO
PIN = 21
PINMASK = 1 << PIN
PULSE_LENGTH_US = 1000
PULSE_DELAY_US = 1000
DELAY_US = 2000
 
g = rpgpio.GPIO ()
g.init (PIN, rpgpio.GPIO.MODE_OUTPUT)
 
dma = rpgpio.DMAGPIO ()
für i im Bereich (1, 6):
 für i im Bereich (0, i):
 dma.add_pulse (PINMASK, PULSE_LENGTH_US)
 dma.add_delay (PULSE_DELAY_US)
 dma.add_delay (DELAY_US)
dma.run (wahr)
 
raw_input ("Drücken Sie die Eingabetaste, um zu stoppen")
dma.stop ()
g.init (PIN, rpgpio.GPIO.MODE_INPUT_NOPULL)

Der Code ist ziemlich einfach und es macht keinen Sinn, ihn aufzuschlüsseln. Wenn Sie diesen Code ausführen und ein Oszilloskop anschließen, wird Folgendes angezeigt:

Und jetzt können wir echte G-Code-Interpreter erstellen und Schrittmotoren steuern. Aber warte, es ist hier schon implementiert. Sie können dieses Projekt verwenden, da es unter der MIT-Lizenz vertrieben wird.

Hardware

Das Python-Projekt kann natürlich für Ihre Zwecke übernommen werden, aber um Sie zu inspirieren, beschreibe ich die ursprüngliche Hardware-Implementierung dieses Projekts - einen 3D-Drucker. Grundsätzlich enthält es folgende Komponenten:

  1. Himbeer-Pi 3
  2. RAMPSv1.4-Karte
  3. 4 A4988- oder DRV8825-Modul
  4. RepRap Prusa i3-Rahmen mit Ausrüstung (Endanschläge, Motoren, Heizungen und Sensoren)
  5. 12V 15A Netzteil
  6. LM2596S DC-DC-Abwärtswandlermodul
  7. MAX4420 Chip
  8. ADS1115 Analog-Digital-Wandlermodul
  9. UDMA133 IDE-Flachbandkabel
  10. Acrylglas
  11. PCB steht
  12. Steckverbindersatz mit 2,54 mm-Stufe

Das 40-polige IDE-Flachbandkabel ist für den 40-poligen Raspberry Pi-Anschluss geeignet, das andere Ende erfordert jedoch einige Arbeiten. Schneiden Sie den vorhandenen Stecker vom gegenüberliegenden Ende ab und crimpen Sie die Stecker auf die Kabeladern.

Die RAMPSv1.4-Karte wurde ursprünglich für den Anschluss an den Arduino Mega-Anschluss entwickelt, sodass es keine einfache Möglichkeit gibt, diese Karte an den Raspberry Pi anzuschließen. Mit der folgenden Methode können Sie die Platinenverbindung vereinfachen. Sie müssen weniger als 40 Drähte anschließen.

PyCNC-Referenzverbindung

Ich hoffe, dieses Anschlussdiagramm ist ziemlich einfach und lässt sich leicht duplizieren. Es ist besser, einige Stifte (2. Extruder, Servos) für die zukünftige Verwendung anzuschließen, auch wenn sie derzeit nicht benötigt werden.

Sie fragen sich vielleicht, warum wir den MAX4420-Chip benötigen? Die Raspberry Pi-Pins liefern 3,3 V für die GPIO-Ausgänge und die Pins können einen sehr kleinen Strom liefern, was nicht ausreicht, um das MOSFET-Gate zu schalten. Außerdem arbeitet einer der MOSFETs unter der Last von 10 A einer Bettheizung.

Infolgedessen wird dieser Transistor bei einer direkten Verbindung mit einem Himbeer-Pi überhitzt. Daher ist es besser, einen speziellen MOSFET-Treiber zwischen dem hochbelasteten MOSFET und dem Raspberry Pi anzuschließen. Es kann den MOSFET effizient schalten und seine Erwärmung reduzieren.

Der ADS1115 ist ein Analog-Digital-Wandler (ADC). Da der Raspberry Pi kein integriertes ADC-Modul hat, habe ich ein externes verwendet, um die Temperatur der 100-kOhm-Thermistoren zu messen. Das RAMPSv1.4-Modul verfügt bereits über einen Spannungsteiler für die Thermistoren. Der Abwärtswandler LM2596S muss auf einen 5-V-Ausgang eingestellt werden und wird zur Stromversorgung der Raspberry Pi-Platine verwendet.

Jetzt kann es auf dem 3D-Druckerrahmen montiert werden und die RAMPSv1.4-Karte sollte mit dem ausgerüsteten Rahmen verbunden werden.

Das ist es. Der 3D-Drucker wird zusammengebaut, und Sie können den Quellcode auf den Raspberry Pi kopieren und ausführen. sudo ./pycnc führt es in einer interaktiven G-Code-Shell aus. sudo ./pycnc filename.gcode führt eine G-Code-Datei aus. Überprüfen Sie die fertige Konfiguration für Slic3r.

Und in diesem Video können Sie sehen, wie es tatsächlich funktioniert.

Geschrieben von Nikolay Khabarov, Solutions Architect bei DataArt.

Möchten Sie die neuesten Entwicklungen und technischen Nachrichten direkt in Ihren Posteingang bekommen?

Ursprünglich veröffentlicht auf iotforall.com am 17. September 2017.