EMC2: Game-Controller zur manuellen CNC-Steuerung

1) Game-Controller identifizieren
2) Game-Controller laden
3) Pin-Namen herausfinden
4 a) Realtime-Komponenten laden
b) Referenzfahrt per Knopfdruck
c) Achsen antasten
d) Spindel kontrollieren
e) Maschine ein- und ausschalten
f) Geschwindigkeitsvorwahl für Achsensteuerung
g) Achsensteuerung
5) Zusammenfassung

Bei meiner Internet-Recherche nach einer Handsteuerung für meine CNC-Fräse bin ich ziemlich schnell bei einer interessanten Alternative gelandet: Statt einer teuren Steuerung mit Dreh-Encoder kann man auch mit ein paar Zeilen Code einen einfachen Game-Controller für 10 bis 20 Euro mit dieser Aufgabe betrauen.



Ich habe mich zugegebenermaßen noch nie mit HAL unter Linux beschäftigt und war daher erstmal etwas skeptisch. Doch zum einen gibt es in der LinuxCNC Knowledge Base einige leicht verständliche Beispiele und zum anderen unter http://www.linuxcnc.org/docs/html eine erstklassige Dokumentation der Realtime-Komponenten. Wenn man sich mal einen Abend damit beschäftigt, hat man eigentlich alles nötige Wissen, um sich sein Joypad nach eigenem Geschmack konfigurieren zu können.



1) Game-Controller identifizieren

Der erste Schritt ist die Identifizierung des Game-Controllers. Dazu gibt man einfach in einem Terminal den folgenden Befehl ein:

less /proc/bus/input/devices

Die Ausgabe listet einige Geräte auf, darunter auch den Game-Controller. Hier der Eintrag meines Logitech Game-Controllers:

I: Bus=0003 Vendor=046d Product=c21d Version=4014
N: Name=„Generic X-Box pad“
P: Phys=usb-0000:00:1d.2-1/input0
S: Sysfs=/devices/pci0000:00/0000:00:1d.2/usb4/4-1/4-1:1.0/input/input5
U: Uniq=
H: Handlers=event5 js0
B: EV=20000b
B: KEY=78db0000 400000 0 0 0 0 0 0 0 0
B: ABS=3003f
B: FF=1 7030000 0 0

Die einzig wichtige Zeile ist der exakte Name, denn später brauchen wir zur Addressierung ein Wort des Namens, das eindeutig ist. Ein Wort wie „Logitech“ oder „Generic“ wäre in diesem Fall eine schlechte Wahl, da es möglicherweise noch mehrere Geräte mit so einem Begriff im Namen gibt, doch „X-Box“ erschien mir eine gute Wahl, da ich wohl kaum mehrere X-Box-Geräte an meinem PC habe.



2) Game-Controller laden

In der INI-Datei der Maschine muss im Abschnitt [HAL] wenigstens eine Postgui HAL-Datei mit POSTGUI_HALFILE = Dateiname geladen werden. Es können auch mehrere Dateien geladen werden, falls dies der Ordnung hilft. Da ich keine weiteren Postgui-Elemente brauche, habe ich für meine Maschine nur eine einzige Datei mit dem Namen P1_postgui.hal geladen. Weiterhin muss in dem Abschnitt auch ein Eintrag HALUI = halui vorhanden sein:

[HAL]
...
POSTGUI_HALFILE = P1_postgui.hal
HALUI = halui


Die weitere Konfiguration findet nahezu ausschließlich in der postgui.hal statt. die Als erstes wird hier der Game-Controller mit loadusr geladen. Dazu muss hinter -KRAL der eindeutige Namensbestandteil notiert werden, den wir zuvor ermittelt haben:

loadusr -W hal_input -KRAL X-Box




3) Pin-Namen herausfinden

Um die Bezeichnungen der einzelnen Taster und Achsen herauszufinden, müssen wir nun EMC2 starten und dann unter Maschine -> HAL anzeigen -> Pins -> input die verfügbaren Pins anzeigen lassen. Wer einen Controller von Logitech hat, wird vermutlich weitgehend identische Pin-Bezeichnungen wie ich haben, die alle mit input.0.abs... oder input.0.btn... anfangen.

Unterschieden wird grundsätzlich zwischen Buttons, die nur einen Bit-Wert (TRUE oder FALSE) ausgeben und den Knüppel-Achsen, die einen Float-Wert, also einen variablen Wert ausgeben. Eine Sonderstellung nimmt hier das Steuerkreuz auf dem Logitech Game-Controller ein, da es zwar eigentlich nur vier Tasten hat, jedoch Float-Werte von +1 und -1 ausgibt. Doch dazu später mehr.



4a) Realtime-Komponenten laden

Für die meisten Funktionen müssen wir Realtime-Komponenten laden, um sie nutzen zu können. Diese Komponenten besitzen je nach Funktion ein oder mehrere Ein- und Ausgänge, die entweder Bit- oder Float-Werte verarbeiten können.

Eine einfache Bit-Komponente ist and2 (UND-Verknüpfung): Sie besitzt zwei Eingänge und einen Ausgang. Wenn kein oder nur ein Eingang auf TRUE steht, ist die Ausgabe FALSE. Liegt auf beiden Eingängen TRUE an, ist die Ausgabe ebenfalls TRUE. sum2 ist hingegen ein Beispiel für eine einfache Float-Komponente mit zwei Eingängen und einem Ausgang: Sie gibt die Summe der beiden Eingangswerte aus.

Jede Realtime-Komponente muss zuerst mit loadrt geladen und anschließend mit addf an einen Realtime-Thread geknüpft werden. Eine Komponente kann grundsätzlich nur für eine einzige Funktion verwendet werden. Braucht man die Komponente häufiger, ist beim Laden ihre Anzahl zu erhöhen. Mit

loadrt and2 count=2

werden zum Beispiel zwei and2 geladen, die anschließend mit

addf and2.0 servo-thread
addf and2.1 servo-thread

jeweils an einen Realtime-Thread geknüpft werden, wobei die Zähler-Nummern hinter dem Punkt immer mit 0 beginnen. Hier alle Komponenten, die ich für den Game-Controller laden muss und auf die ich in den weiteren Abschnitten noch eingehen werde:

loadrt or2 count=2
loadrt and2 count=2
loadrt not count=1
loadrt mux4 count=1
loadrt comp count=4
loadrt sum2 count=1

addf or2.0 servo-thread
addf or2.1 servo-thread
addf and2.0 servo-thread
addf and2.1 servo-thread
addf not.0 servo-thread
addf mux4.0 servo-thread
addf comp.0 servo-thread
addf comp.1 servo-thread
addf comp.2 servo-thread
addf comp.3 servo-thread
addf sum2.0 servo-thread





4b) Referenzfahrt per Knopfdruck

Um den Einstieg zu erleichtern und das Verständnis zu fördern, habe ich die einzelnen Funktionen ab hier nach einem gefühlten Schwierigkeitsgrad aufsteigend sortiert. Die einfachste Form ist eine direkte Anbindung eines Buttons an einen halui-Pin.

RT-Komponenten, halui-Pins und die Pins des Game-Controllers werden grundsätzlich mit net (en: to net = vernetzen) und einem frei wählbaren aber eindeutigen Namen verknüpft. Der halui-Pin für eine Referenzfahrt aller Achsen heißt zum Beispiel halui.home-all. Ich habe ihm den button BACK zugewiesen und die Verknüpfung home-all-axis genannt. Der Pfeil (<= oder =>) geht immer in Richtung des Datenflusses:

net home-all-axis halui.home-all <= input.0.btn-back




4c) Achsen antasten

Ja, ich habe weiter oben geschrieben, dass wir nur noch in der postgui.hal herumkritzeln. Allerdings gibt es für das Antasten der Achsen keine halui-Pins und daher müssen wir MDI-Befehle nutzen, die wir zuerst in der INI-Datei definieren müssen.

Dazu richten wir unterhalb des Abschnitts [HAL] einen neuen Abschnitt [HALUI] ein und notieren dort die MDI-Befehle jeweils für das Antasten der X-, Y- und Z-Achse. Die Befehle brauchen keine weiteren Identifizierungsmerkmale wie eindeutige Namen, da sie später einfach der Reihenfolge nach nummeriert angesprochen werden:

[HALUI]
MDI_COMMAND = G10 L20 P0 X0
MDI_COMMAND = G10 L20 P0 Y0
MDI_COMMAND = G10 L20 P0 Z0


Haben wir die INI-Datei gespeichert, können wir unser restliches Werk in der postgui.hal verrichten. Ich habe den Antast-Befehlen die Buttons X, Y und A zugewiesen:

net touch-off-x halui.mdi-command-00 <= input.0.btn-x
net touch-off-y halui.mdi-command-01 <= input.0.btn-y
net touch-off-z halui.mdi-command-02 <= input.0.btn-a




4d) Spindel kontrollieren

Ich habe lange gegrübelt, was ich mit dem Steuerkreuz anfangen könnte, bis mir die Idee kam, es zum Steuern der Spindel zu nutzen. Dummerweise hat das Steuerkreuz zwar Taster, gibt jedoch keine Bit-Werte, sondern Float-Werte aus. Die halui-Pins zum Steuern der Spindel akzeptieren jedoch nur Bit-Werte, also müssen wir die Eingabe-Werte in passende Ausgabe-Werte umwandeln.

Dies geschieht am einfachsten mit comp. Es vergleicht zwei Float-Werte miteinander und gibt einen Bit-Wert aus. Sobald Eingang 1 größer als Eingang 0 ist, ist die Ausgabe TRUE, ansonsten FALSE.

Ich nutze die X-Achse des Kreuzes (rechts/links) zum Ein- und Ausschalten der Spindel und die Y-Achse (oben/unten) zum Erhöhen oder Verringern der Drehzahl. Für jede Richtung brauchen wir jeweils ein comp, also insgesamt vier. Da eine Achse jeweils auf einer Seite +1, auf der anderen -1 und in der Mitte 0 ausgibt, haben wir beim Drücken immer einen Vergleichswert, wenn wir am anderen Eingang nichts anlegen und der Wert daher dort 0 ist.

net spindle-switch input.0.abs-hat0x-position => comp.0.in1
net spindle-switch input.0.abs-hat0x-position => comp.1.in0

net spindle-start halui.spindle.start <= comp.0.out
net spindle-stop halui.spindle.stop <= comp.1.out

net spindle-speed input.0.abs-hat0y-position => comp.2.in1
net spindle-speed input.0.abs-hat0y-position => comp.3.in0

net spindle-increase halui.spindle.increase <= comp.2.out
net spindle-decrease halui.spindle.decrease <= comp.3.out




4e) Maschine ein- und ausschalten

Wir könnten die Maschine ein- und ausschalten, indem wir den beiden halui-Pins halui.machine-on und halui.machine-off jeweils einen Button zuordnen. Das wäre jedoch ein verschwendeter Button, denn man kann die Maschine auch mit einem Button ein- und ausschalten.

Dazu brauchen wir jedoch zwei and2 und ein not. Dies ist nötig, weil eine Entscheidung anhand eines Status getroffen werden muss. Eine eingeschaltete Maschine lässt sich schließlich nicht einschalten und umgekehrt.

Den Status fragen wir über den Pin halui.machine-is-on ab und legen ihn am ersten Eingang des ersten and2 sowie am Eingang des not an. Den Ausgang des not legen wir wiederum an den ersten Eingang des zweiten and2, denn wir erinnern uns: Ein and2 gibt nur dann TRUE aus, wenn an beiden Eingängen TRUE anliegt:

net machine-is-on halui.machine.is-on => and2.0.in0
net machine-is-on halui.machine.is-on => not.0.in
net machine-is-off not.0.out => and2.1.in0

Nun legen wir den Button an die noch freien zweiten Eingänge der beiden and2. Da es sich nur um einen Button handelt, darf auch beiden Verknüpfungen nur ein Name vergeben werden:

net start-button input.0.btn-start => and2.0.in1
net start-button input.0.btn-start => and2.1.in1

Auf diese Weise können je nach Status nur an einem der beiden and2 zwei mal TRUE anliegen, was zur Folge hat, dass auch immer nur ein and2 TRUE ausgeben kann. Wir können nun jedes and2 an einen der beiden halui-Pins knüpfen:

net machine-on and2.1.out => halui.machine.on
net machine-off and2.0.out => halui.machine.off




4f) Geschwindigkeitsvorwahl für Achsensteuerung

ACHTUNG: Diesen Abschnitt habe ich noch einmal aus Sicherheitsgründen überarbeitet. Nun muss grundsätzlich wenigstens ein Geschwindigkeitsvorwahl-Button gedrückt werden, um eine Achse bewegen zu können:

Da die beiden Knüppel des Controllers keine besonders langen Achsen haben, ist es ratsam, eine Vorwahl für die maximale Geschwindigkeit einzurichten. Wenn man zum Beispiel eine Materialoberfläche manuell antasten will, wird man in der Regel mit Mikroschritten fahren wollen. Würde ein unachtsamer Druck auf das Steuerkreuz die Achse jedoch zu einer Vollgasfahrt veranlassen, könnte das unangenehme Konsequenzen haben.

Für die Vorwahl nehmen wir ein mux4, das vier Float-Eingänge hat und über zwei Selektoren festlegt, welcher der Float-Werte ausgegeben wird. Beide Selektoren akzeptieren jeweils einen Bit-Wert (TRUE oder FALSE), was vier mögliche Kombinationen ergibt.

Wir können drei verschiedene Geschwindigkeitsvorwahlen nutzen, denn die vierte ist 0, um ein versehentliches Bewegen der Achsen zu verhindern. Je nach Leistungsfähigkeit der Maschine sind hier gegebenenfalls andere Werte einzutragen:

setp mux4.0.in0 0
setp mux4.0.in1 10
setp mux4.0.in2 100
setp mux4.0.in3 1000

Um zwischen den drei Vorgaben umschalten zu können, brauchen wir zwei or2 die wir mit den beiden Selektoren verknüpfen. Ist kein Button gedrückt, ist in0 aktiv, also eine Geschwindigkeit von 0. Wird der rechte Button gedrückt, wird eine Maximalgeschwindigkeit von 10mm/min vorgegeben, beim linken sind es 100mm/min und wenn beide gleichzeitig gedrückt werden, 1000mm/min.

net speed-button-1 or2.0.in0 input.0.btn-tr
net speed-button-2 or2.1.in0 input.0.btn-tl

net speed-1 mux4.0.sel0 <= or2.0.out
net speed-2 mux4.0.sel1 <= or2.1.out
net speed-out halui.jog-speed <= mux4.0.out




4g) Achsensteuerung

Die eigentliche Steuerung der Achsen ist dann nur noch ein Kinderspiel. X- und Y-Achse habe ich konsequenterweise auf einen Knüppel gesetzt, diesem Fall auf den rechten:

net jog-x halui.jog.0.analog <= input.0.abs-rx-position
net jog-y halui.jog.1.analog <= input.0.abs-ry-position

Da die Y-Achse bei mir in die falsche Richtung lief, musste diese noch umgedreht werden. Dazu schaut man noch einmal unter Maschine -> HAL anzeigen -> Pins -> input nach dem Scale-Wert der Achse. Dieser muss einfach mit umgedrehten Vorzeichen in der postgui.hal noch einmal festgesetzt werden:

setp input.0.abs-ry-scale -32767.5

Die Z-Achse könnte man nach dem gleichen Schema einfach auf den zweiten Knüppel legen, doch habe ich mich für die beiden „Pistolen-Abzüge“ entschieden, denn damit lässt sich die Z-Achse noch leichter einhändig zum Antasten senken, als mit dem Knüppel. Um jeweils einen Abzug zum Heben und Senken der Z-Achse zu nutzen, müssen wir für mehrere Dinge sorgen:

1) Beide Abzüge müssen in ihrer Nullstellung 0 ausgeben. Das tun sie jedoch im Originalzustand nicht. Statt dessen stehen sie in Nullstellung auf -1 und voll durchgedrückt auf +1. Das lässt sich jedoch durch den jeweiligen Offset-Wert korrigieren:

setp input.0.abs-z-offset 1
setp input.0.abs-rz-offset 1


2) Ein Abzug muss beim Drücken positiv werden, der andere negativ. Das erreichen wir wie beim Umdrehen der Y-Achse durch ein negatives Vorzeichen beim Scale-Wert eines der beiden Abzüge. Da jedoch durch der Verschiebung des Offsets beide Abzüge nicht mehr bis +1, sondern bis +2 gehen, notieren wir gleich beide Scale-Werte mit doppelter Größe und ändern bei einem das Vorzeichen. Meine ursprünglichen Werte waren 127.5, also sehen die Einträge so aus:

setp input.0.abs-z-scale 255
setp input.0.abs-rz-scale -255


3) Wir müssen aus zwei Float-Werten einen bilden. Das können wir mit einer einfachen Summe beider Werte erreichen, da man üblicherweise immer nur einen Abzug betätigt und der andere während dessen 0 ausgibt. Zum Addieren der Werte nehmen wir sum2, legen die Float-Werte an die beiden Eingänge an und verknüpfen den Ausgang mit der dritten Achse:

net joy-z-up sum2.0.in0 <= input.0.abs-rz-position
net joy-z-down sum2.0.in1 <= input.0.abs-z-position
net joy-z-jog halui.jog.2.analog <= sum2.0.out




5) Zusammenfassung

Ein Game-Controller ist eine echte Alternative zu teuren Handsteuerungen, zumal er sich nahezu beliebig konfigurieren lässt. Die erforderlichen Kenntnisse hat man sich schnell angelesen und wem das zu umständlich ist, der befolgt einfach eines der vielen Tutorials unter http://wiki.linuxcnc.org/cgi-bin/wiki.pl?LinuxCNCKnowledgeBase.

Der linke Steuerknüppel und der Button B sind in meinem Beispiel übrigens noch unbelegt. Mir ist bislang kein vernünftiger Verwendungszweck eingefallen, doch wenn jemand eine gute Idee hat, möge er sie mir mitteilen und dann werde ich das hier natürlich auch beschreiben.

Wer jetzt anmerkt, dass ja auch noch der MODE-Button mit der hübschen grünen LED ungenutzt ist: Er kann am Logitech-Controller nur zwischen dem Steuerkreuz und dem Knüppel auf der linken Seite umschalten und lässt sich zu keiner weiteren Funktion überreden. Der Pin input.0.btn-mode steht statt dessen für den versenkten Button mit dem Logitech-Symbol in der Mitte vom Controller, den zumindest ich eine ganze Weile nicht als Button erkannt habe.

Von Tilman Pietzsch am 27. November 2012, 03:19 Uhr veröffentlicht | 5486 mal gelesen
Zuletzt bearbeitet am 28. November 2012, 02:15 Uhr
Themen: CNC | Elektrik | Zubehör

Kommentar schreiben


Kommentare

Kommentar von Alessandro am 28. November 2012, 17:50 Uhr:

Hi !

Nutzt du einzig und allein EMC2 um die Fräse zu steuern oder braucht man da mehrere Programme in Kombi wie z.B. Mach3 und CamBam?

Gruß
Alessandro

Antwort von Tilman am 28. November 2012, 23:17 Uhr:

EMC2 ist wie Mach3 eine Steuersoftware für CNC-Maschinen, die nach G-Code verlangt. Man braucht also noch eine CAM-Software, um aus CAD-Daten einen Maschinencode zu erstellen.


Neuen Kommentar schreiben

oder zum Antworten oben auf das Symbol hinter einem Kommentar klicken