Dies ist eine alte Version des Dokuments!
Zum aktuellen Zeitpunkt ist malokal ein autonomer Zeichenroboter, der seine Anweisung von MalokalRemote erhält. MalokalRemote funktioniert mit Text oder Vektorgrafiken, welche beliebig angepasst, verschoben und skaliert werden können. Malokal arbeitet mit absoluten Größenangaben, sodass der Untergrund exakt bezeichnet werden kann. Alternativ kann malokal manuell ferngesteuert werden, sowohl mit den Pfeiltasten auf einem Computer, mit touch input (via TouchOSC) oder dem Beschleunigungssensors eines Telefons. Bei malokal geht es uns vor allem um das fertige und funktionierende Produkt. Dieses Ziel haben wir bisher erreicht.
Entwickelt von Vinzenz Müller und Leon Klaßen
Berlin 2014/2015
Grundsätzlich kann gesagt werden, dass wir unseren Fokus von Anfang an nicht auf den Hardware-Aufbau gelegt haben. Primär haben wir an der Software gearbeitet, und die malokal-Hardware dabei nur schrittweise weiterentwickelt und verbessert. Das hatte zwar zur Folge, dass wir drei verschiedene Prototyp-Versionen (siehe unten) gebaut haben. Gleichzeitig hat der Fokus auf die Software aber auch dafür gesorgt, dass der malokal am Ende recht gut funktioniert.
Das Grundgerüst von malokal besteht aus Holz. Auf der Grundplatte befinden sich Arduino Mega, WiFly, Breadboard und Servo-Motor für den Stift. Auf der Unterseite der Grundplatte ist das Stützrad befestigt und der Akku angebracht. Im vorderen Bereich von malokal sind die Schrittmotoren und der Stift platziert (siehe Bilder).
malokal dreht sich, in dem er seine Räder entgegengesetzt laufen lässt. Sitzt der Stift dabei genau zwischen den Motoren, ändert sich die Position des Stifts theoretisch nicht, er würde sich um seine eigene Achse drehen und einen sauberen Punkt malen. Außerdem verringert sich der Winkelfehler beim Drehen mit genau ausgerichteten Motoren enorm-
Für gute Zeichenergebnisse ist es also sehr wichtig, dass die Motoren sowie der Stift richtig platziert sind. Zum einen müssen die Schrittmotoren zueinander möglichst genau ausgerichtet sein. Das heißt, dass die Achsen beider Motoren auf einer Linie (am besten in jeder Dimension) sein müssen. Weiterhin sollte der Stift genau in der Mitte dieser Linie liegen.
Anfangs haben wir die sterblichen Überreste eines noch im Robotik-Raum liegenden Roboters benutzt. Die Motoren waren hier mehr schlecht als recht eingeklemmt. Sie konnten zum einen leicht verrutschen, und zum anderen nur schwer in eine genaue Position gebracht werden. Auch die Stifthalterung wurde nur sehr provisorisch gehalten. Nachdem malokalOS und malokalRemote betriebsbereit waren, entschieden wir uns für einen zweiten Aufbau, der der Software gerecht wurde. Dank der Positionierung der Motoren an einem rechten Winkel an der Holzkonstruktion konnten erhebliche Verbesserung erzielt werden. Zur Präsentation des Projekts wurde dann nochmal die Motorhalterung mit Hilfe von Stahlwinkeln aus dem Baumarkt sowie die Stiftposition überarbeitet.
Wie oben bereits beschrieben, bewegt sich malokal mit Hilfe von zwei NEMA-17 Schrittmotoren. Diese ermöglichen sehr präzise Bewegungen, da sie „schrittweise“ angesprochen werden. Damit ergeben sich aber komplexere Anforderungen bzgl. der Ansprache der Motoren. Ein einfaches „Strom an“ und „Strom aus“ funktioniert nicht. Abhilfe geben Schrittmotor-Treiber (siehe „Elektronik“).
Natürlich sollte malokal auch zeichnen können. Dazu installierten wir einen Stift, der in einem Alu-Rohr mittels Servo-Motor hoch und runter gefahren werden kann. Ein Gummi sorgt dabei für die nötig Kraft richtung Boden. Wir haben dabei bewusst (bis jetzt) auf komplizierte Konstruktionen (wie etliche aus den Vorjahren) verzichtet.
Das „Gehirn“ von Malokal ist ein Arduino Mega. Wir haben anfangs einen Arduino Nano benutzt, da dieser sehr kompakt ist, und wir schon im Kurs damit gearbeitet haben. Im Laufe der Zeit wurde malokalOS, das auf dem Arduino läuft, immer größer, es kamen diverse Bibliotheken dazu und die Speicherung der Fahrbefehle in einem Array war sehr begrenzt (trotz Ringpuffer). Diese und andere Gründe bewegten uns dann dazu, doch auf einen deutlich größeren (sagt ja schon der Name) Arduino MEGA umzusteigen. Vier UARTs, mehr Speicher und mehr Power vereinfachten vieles.
Da, wie bereits gesagt, das Ansprechen der Schrittmotoren etwas komplexer ist, nutzten wir Schrittmotor-Treiber. Per Mikrocontroller sagt man diesen, dass man einen Schritt fahren möchte, und in welche Richtung dieser laufen soll. Der Treiber übernimmt dann die Ansprache des Schrittmotors.
Für die WLAN Kommunikation haben wir ein WiFly-WLAN-Modul verbaut.
Energie bezieht malokal aus einem an der Unterseite der Grundplatte angekletteten LiPo-Akku mit 7,2 V.
1 × Ardunio MEGA Klon von DFRobot
1 × WiFly RN-XV von Roving Networks
2 × Nema 17 Schrittmotoren
2 × Gummierte Räder
1 × frei drehendes Laufrad
2 × Stepper Motor Driver von Pololu
1 × Servo
1 × Stift, wechselndes Modell
1 × LiPo-Akku mit 7,2 V
Die Software rund um malokal gliedert sich in verschiedene Bereiche und Aufgaben.
Der Benutzer arbeitet mit malokalRemote, einer App auf dem Computer. Hier wird vorgegeben, was malokal zeichnen soll. Dafür kann Text oder auch vektorbasierte Grafiken verwendet werden. Diese Vektordaten müssen dann für malokal in gerade Strecken und Winkel zerlegt werden.
Alternativ kann zur Steuerung manuellRemote verwendet werden, wobei keine vorgegebenen Daten, sondern der direkte Benutzer-Input zum zeichnen verwendet wird.
Die Daten werden jetzt mit unserem eigenen Protokoll per OSC und WiFi an malokal übertragen.
Dann werden diese Fahr- und Drehdaten im malokalOS, welches auf dem Arduino auf malokal läuft, ausgewertet. Hier wird die Strecke abgefahren, wobei sich immer Drehen und geradeaus Fahren abwechseln. Mit sehr kleinen Abständen erreichen wir so trotzdem eine fast runde Form.
MalokalRemote ist die Steuerung von malokal. Dabei geht es darum, die Daten und Befehle zum Zeichnen soweit aufzubereiten, das Malokal diese mit wenig eigener Rechenleistung umsetzen kann. Ausserdem soll ein gut benutzbares User Interface geboten werden, was mit heutigen Standards mithalten kann. Aus diesem Grund haben wir viel Zeit in den Aufbau und die Gestaltung der remote gelegt.
MalokalRemote basiert auf einem Processing-Sketch und ist in Java geschrieben.
MalokalRemote kann als Eingabe sowohl Text als auch Vektorgrafiken im SVG Format verwenden. Kern ist das virtuelle Papier, eine maßstäbliche Abbildung des späteren Zeichenuntergrundes. Die Größe und Ausrichtung des Papiers kann eingestellt werden.
Die Zeichnung kann dann mit verschieden Einstellungen wie Schriftart, Größe, Position und Drehung angepasst werden. Dabei aktualisiert sich das virtuelle Papier ständig.
Am oberen Rand wird die Verfügbarkeit von malokal dargestellt, welche sich aus den Alive-Signalen ergibt. Kein Balken heisst hier, das die Remote nicht mit dem richtigen WLAN verbunden ist, rot, das malokal nicht verfügbar ist, und grün, das malokal verbunden ist.
Malokal merkt sich die verbindung zur letzten remote. Sollte malokal noch nicht verbunden sein, wird mit „Verbinden“ die IP Adresse der remote übermittelt und malokal stellt sich auf diese neue Adresse ein.
Wenn alles eingestellt und eingerichtet ist, kann die Zeichnung gestartet werden. Die Verfügbarkeitsanzeige wechselt zu einem zweistufigen Balken, wobei die erste Stufe den Stand der Datenübertragung und die zweite den wirklichen Zeichenstand anzeigt. Dieser Zeichenstand wird auch im virtuellen Papier laufend aktualisiert.
Beim Beenden der remote werden alle Einstellungen gespeichert und beim nächsten öffnen wieder hergestellt.
Für die Fahrbefehle müssen wir zunächst aus der Quelle (Text oder SVG) Punkte erzeugen, die wir dann abfahren können. Dafür verwenden wir die Library „Geomerative“ von Ricard Marxer. Als Einstellung für den Punktabstand haben wir uns für dynamisch entschieden, um bei einer langen Strecke keine unnötigen Zwischenpunkte zu erhalten, aber kurze und enge kurven Präzise darstellen zu können (siehe Bild links). Gerade für Kurven, wie zum Beispiel im Buchstaben „a“, hat sich diese Einstellung bewährt. Andere Buchstaben werden mit weniger Punkten dargestellt, z.B. benötigt ein kleines „l“ nur 4 Punkte (siehe Bild links). Je mehr Punkte erzeugt werden, desto mehr Fahrbefehle werden auch benötigt. Damit verlängert sich die Zeit, die malokal zum Zeichnen braucht. Mit unseren Einstellungen können wir die Punktanzahl bei gutem Ergebnis bestmöglich reduzieren.
Die Library erzeugt ein Array der Punkte, welches wir dann weiterverarbeiten. Für die maßstäbliche Darstellung benötigen wir für jede Seite die Extrempunkte, also den am weitesten links, oben, rechts und unten liegenden Punkt. Daraus lässt sich auch die Größe berechnen. Die Library stellt dazu verschiede Funktionen bereit, jedoch lieferten diese keine zuverlässigen Daten. Deshalb haben wir die Extrempunktberechnung selber implementiert. Dabei gehen wir iterativ durch die Liste der Punkte und schauen, ob der aktuelle Punt extremer als der bisherige extremste Punkt für die entsprechende Seite ist.
Ein großer Bonus der verwendeten Library ist die Rückgabe der Punkte gruppiert nach Pfaden. Ein L besteht zum Beispiel aus nur einem geschlossenen Pfad, ein O aus zwei, einem inneren und einem äußeren. So ist es leicht, den Stift auf Pfaden zu senken, aber während dem Übergang von einem Pfad zum anderen keine Linie zu ziehen.
Alle erzeugten Punkte entsprechen in ihren Werten schon hier den späteren Abmessungen in mm.
Aus den Punkten werden nun die Fahrbefehle für die zwei Motoren von malokal erzeugt. Malokal geht zu beginn davon aus, das er sich mit dem Stift exakt an der oberen linken Ecke des Papiers befindet und nach Rechts ausgerichtet ist. Zunächst wird eher präventiv der Stift gehoben, falls dieser noch gesenkt ist.
Dann fährt malokal zum ersten Punkt, senkt den Stift, zeichnet den ersten Pfad, hebt den Stift, fährt zum zweiten Pfad und so weiter. Abschließend kehrt malokal zum Ausgangspunkt und Winkel zurück. Daran kann schön die Genauigkeit und Winkelfehler beim Fahren erkannt werden. Im Idealfall sollte malokal in exakt der gleichen Position enden, wie er begonnen hat.
Für die Berechnung der Fahrbefehle sind zwei Funktionen entscheidend, die im folgenden kurz erläutert werden.
„calcDistance“ berechnet die Entfernung zwischen zwei Punkten. Dafür wird der aktuelle und nächste Punkt als Eingabe benötigt. Hier verwenden wir den Satz des Phytagoras. Diese Strecke muss malkokal zwischen zwei Punkten geradeaus fahren.
float calcDistance(float a_x, float a_y, float b_x, float b_y) { return sqrt(sq(a_x-b_x)+sq(a_y-b_y)); //Phytagoras }
„calcAngle“ berechnet den Winkel zwischen zwei Vektoren. Dafür werden der vorherige, der aktuelle und der nächste Punkt benötigt. Um diesen Winkel muss sich malokal dann drehen, um anschließend die nächste gerade Strecke zu fahren. Damit unnötig große Drehungen vermieden werden, können zu große Winkel durch eine kleinere Drehung in die andere Richtung ersetzt werden. So erzeugt eine Drehung von 350° nach links das selbe Ergebnis wie eine Drehung um -10° nach rechts.
float calcAngle(float a_x, float a_y, float b_x, float b_y, float c_x, float c_y) { float angle1; float angle2; angle1 = atan2(a_y-b_y, a_x-b_x); angle2 = atan2(b_y-c_y, b_x-c_x); float angle=degrees(angle2-angle1); if (angle<-180) { angle=360+angle; } if (angle>180) { angle=angle-360; } return angle; }
Damit wir sicher sein können, das die Befehle stimmen, werden die Punkte im virtuellen Papier nach der vorgegebenen Punkteliste erzeugt, und die Linien nach den vorgegebenen Fahr und Drehbefehlen. Wenn beides übereinander passt, ist der Code richtig umgesetzt.
Uns war von Beginn an wichtig, dass malokal einfach zu bedienen ist und Spaß macht. In den ersten Versionen von malokalRemote konnte man die Zeichnung durch Variablen in einer Textdatei beeinflussen. Um eine Zeichnung zum Beispiel zu verschieben, musste man das Programm beenden, die richtige Datei finden, den Wert in einer Variablen-Deklaration ändern und das Programm neu starten.
Die Elemente des User Interface sind in einer Leiste am unteren Rand der remote untergebracht. Wir verwenden hier die Library „controlP5“ von Andreas Schlegel. Die Library implementiert verschiedene UI-Elemente innerhalb von Processing. Zum Beispiel verwenden wir einen RadioButton, um zwischen Text und SVG-Modus zu wechseln. Die Texteingabe erfolgt über Textfields. Für die Größe und Position verwenden wir das Element Numberbox. Außerdem noch diverse Buttons, zum Beispiel zum Starten oder Verbinden.
Ohne Anpassung erscheint controlP5 in einem blauen Look. Texte und Labelbeschriftungen werden in Großbuchstaben und einem Pixelfont gerendert.
Um die controlP5-Elemente unseren ästhetischen Vorstellungen anzupassen (siehe Bild unten), war einiges an Aufwand nötig. Auch haben sich die Elemente im Laufe der Entwicklung geändert, sie wurden umpositioniert und neu sortiert.
Damit ein Button so aussieht, wie wir es uns vorstellen, muss einiges angepasst werden.
PFont control_font=createFont("Helvetica Neue", 12); cp5.setFont(control_font);
Zunächst haben wir den Pixelfont global ersetzt.
Center = cp5.addButton("Center") .setPosition(484, control_zero_height+70) .setSize(98, 20) .setCaptionLabel("Zentrieren") .setColorActive(tintpressedcolor) .setColorBackground(backcolor) .setColorForeground(tintcolor); Center.getCaptionLabel().toUpperCase(false);
Dann werden die Elemente ergänzt. Hier handelt es ich um den Button, der die Zeichnung mit Bild visualisiert. Der Button wird über die Variable Center identifiziert. So können wir in später de- oder aktivieren, die Farbe anpassen oder die Beschriftung ändern. Zunächst wird die Position des Buttons absolut festgelegt, anschließend die Größe (Höhe mal Breite). Es folgt die Beschriftung und diverse Farben, die der Button zu verschiedenen Größen annehmen soll. Anschließend muss noch mit toUpperCase(false) dem Label des Buttons erklärt werden, das „Zentrieren“ nicht als „ZENTRIEREN“ dargestellt werden soll.
Wir haben uns während der Entwicklung gerade bei den Großbuchstaben und dem Bitfont gefragt, warum keine einfacheren Standardwerte gewählt wurden. Letztendlich lassen sich die Elemente aber zum Glück in fast jedem Aspakt anpassen, wenn man die richtigen Befehle dafür kennt. Ständiger Begleiter war in dieser Phase die JavaDoc Reference zu controlP5, welche alle Elemente und die Anpassungsmöglichkeiten enthält.
ControlP5 stellt z.B. für Buttons eine automatische Funktion zur Verfügung, die bei einem Klick ausgeführt wird. Diese Event-Funktion heißt wie das Element. Für den Center Button wird also Center() ausgeführt.
public void Center() { ... }
Alle anderen Elemente erzeugen bei Interaktion auch Events, die mit
public void controlEvent(ControlEvent theEvent) { if(theEvent.isFrom(Center)) { ... } }
abgefangen werden können. Mit dieser Funktion aktualisieren wir dann zum Beispiel das virtuelle Papier, wenn sich eine Einstellung geändert hat.
ManuellRemote ist eine Alternative Steuerung von malokal. Hierbei geht es nicht um das genaue Zeichnung einer Vorlage, sondern die direkte Steuerung durch Eingabe. ManuellRemote ist recht spät im Projekt entstanden, macht aber großen Spass.
Wir wollten zum einen zum Zeichnen eine direkte Eingabe haben, als auch zum ferngesteuerten Ausrichten, bevor malokal dann mit malokalRemote etwas zeichnet.
ManuellRemote basiert auf der App „TouchOSC“. Mit dieser App kann man sich am Rechner eigene UI bauen und mit den richtigen OSC-Befehlen verlinken. Die fertige UI wird auf das Telefon geladen.
Leider kann auf die Signale aus TouchOSC zum Beispiel in Bezug zur Häufigkeit der Nachrichten wenig Einfluss genommen werden. Wir haben uns deshalb entschieden, alle Nachrichten erst an malokalRemote zu senden, die diese dann selektiv an malokal weiterleitet. MalokalRemote erzeugt zwei Gleitkommazahlen für vorwärts/rückwärts und rechts/links im Bereich von -1.0 bis +1. 0 entspricht dabei keiner Bewegung, -1.0 bzw. 1.0 für die maximale Bewegung in die entsprechende Richtung.
Ergänzt wird manuellRemote um eine Nachricht für den Stift, die entweder 0.0 oder 1.0 enthält und von malokal entsprechend interpretiert wird.
ManuellRemote kann in zwei Arten betrieben werden. Entweder steuert man man mit zwei Fadern für die vor/zurück und rechts/links Bewegung (Bild links), oder verwendet das im Telefon eingebaute Gyroskop (bild rechts), um malokal durch Handy-Neigung zu steuern.
Ergänzt wird die maunellRemote durch die Pfeiltasten am Rechner, der malokalRemote ausführt. Damit können zum Beispiel vor dem Zeichnen kleine Winkelkorrekturen vorgenommen werden.
Die reinen Signale -1 bis 1 werden jetzt von malokalRemote in Geschwindigkeiten für den linken und rechten Motor übersetzt.
speed_left=round(current_vertical*500.0+current_horizontal*-1*500.0); speed_right=round(current_vertical*500.0+current_horizontal*500.0); if (speed_left>500.0) { speed_left=500.0; } if (speed_left<-500.0) { speed_left=-500.0; } if (speed_right>500.0) { speed_right=500.0; } if (speed_right<-500.0) { speed_right=-500.0; }
Dabei wird als Höchstgeschwindigkeit 500mm/s verwendet. Dieser Wert hat sich experimentell ergeben. Der Faktor -1 bis 1 wird mit dieser Geschwindigkeit multipliziert. Der Wert für vor/zurück bildet die Basis. Die Werte für Drehen werden auf diesen Wert addiert bzw. subtrahiert. Anschließend werden die Geschwindigkeiten bei +500 und -500 gekappt, weil sonst bei kombinierten vorwärts und Kurve-fahren der Wert 500 überschritten werden würde.
Die fertigen Geschwindigkeiten werden jetzt per OSC an malokal weitergeleitet und von malokalOS umgesetzt.
MalokalOS und malokalRemote sind über WLAN Verbunden und kommunizieren über das Protokoll OSC. OSC steht für Open Sound Control und ist ursprünglich eine Remote-Steuerung für den Musikbereich und speziell DJs. OSC hat den Vorteil, das es leicht zu verwenden ist. Eine OSC Nachricht besteht aus dem „Pattern“, also einer Art Identifikation, und Argumenten, also zum Beispiel Zahlen, die der Nachricht angehängt werden.
Auf MalokalOS verwenden wir zur Implementierung von OSC die Library "ArdOSCForSerial", welche ursprünglich von recotana entwickelt und von Felix Bonowski angepasst wurde. In der remote kommt "oscP5" von Andreas Schlegel zum Einsatz.
Im folgenden wird das von uns entworfene und umgesetzte Protokoll kurz vorgestellt.
/ml/setup
Setup ist ein Befehl von remote an malokal, der die aktuelle IP-Adresse der Remote enthält. So kann sich malokal auf eine remote „Koppeln“, auch wenn mehrere Instanzen der malokalRemote laufen. Im malokalOS löst der Befehl einen Neustart der Wifly-Karte aus, die ihre OSC-Packete anschließend an die neue Adresse sendet.
/ml/alive
Malokal sendet alle 3 Sekunden ein „Lebenszeichen“. Dieses beinhaltet den Index des aktuell ausgeführten Befehls in der Befehls-Queue, einen boolean, ob malokal gerade pausiert oder nicht, und einen boolean, ob malokal gerade „etwas zu tun hat“, also Einträge aus der Queue abarbeitet.
Zentrales Element des Protokolls ist die Übertragung der Queue-Befehle. Damit kein Befehl verloren geht oder in der falschen Reihenfolge übertragen wird, haben wir uns ein Kontrollsystem überlegt. Entscheidend ist, das malokal selber einen Befehl mit einem bestimmten Index anfordert, und die remote darauf hin den Befehl samt angefordertem Index überträgt. Malokal kann dann entscheiden, ob der Befehl nach dem index „gültig“ war. Wenn nicht, kann er den Befehl neu anfordern, sonst geht er zum nächsten über.
/ml/q/start
Start ist ein Befehl von malokalRemote an malokalOS, der die Länge der aktuellen Befehlsliste enthält. So weiss malokal, wie viele Elemente abgefragt werden müssen. Soll malokal zurückgesetzt werden, kann ein Start mit 0 als Länge übertragen werden.
/ml/q/req
Die Anfrage von malokal an die remote mit dem aktuellen Index, den malokal selbst verwaltet und hochzählt.
/ml/q/res
Die Antwort von remote auf die Anfrage. Die Antwort enthält den index des übertragenen Befehls und zwei Float-Werte, welche den Befehl repräsentieren. Der erste Wert bestimmt, welches Bauteil angesprochen wird, der zweite Wert ist der eigentliche Befehlswert.
1.0 — linker Motor (mm, Vorzeichen für Richtung)
2.0 — rechter Motor (mm, Vorzeichen für Richtung)
3.0 — beide Motoren (mm, Vorzeichen für Richtung)
4.0 — Drehung (Grad, Vorzeichen für Richtung)
5.0 — Stift (+1.0 hoch, -1.0 runter)
Eine Drehung um 90° wird dann zb mit 4.0, 90.0 abgebildet.
/ml/mt/speed
Mit dem Speed Befehl können für beide Motoren manuelle Geschwindigkeiten festgelegt werden, wie z.B. beim Steuern mit manuellRemote. Dieser Befehl enthält zwei Float-Zahlen. Die erste ist die Geschwindigkeit für den linken Motor, die zweite für den Rechten. Die Geschwindigkeiten werden als mm/s übergeben. So werden die Motoren an der Queue vorbei angesprochen.
/ml/mt/pen
Pen ist eine Ergänzung für manuellRemote. Er enthält einen boolean, der dem Stift an der Queue vorbei den Befehl zum heben oder senken gibt.
Das Betriebssystem von Malokal, geschrieben in C++, ausgeführt auf einem Arduino MEGA. Es kümmert sich um das Empfangen von neuen Fahrbefehlen, deren Verarbeitung und der Ansteruerung der Motoren und des Stiftes.
MalokalOS ist in vier Klassen eingeteilt. Die Hauptklasse kümmert sich um die Verwaltung und das Zusammenspiel der anderen Klassen und allgemeine Aufgaben wie das Setup der Wifly oder der OSC Kommunikation. „Queue“, „Stepper“ und „Pen“ sind Klassen mit einer speziellen Aufgabe.
In der Haupklasse findet das initiale Setup nach dem Start statt. Von allen Klassen wird eine setup()-Funktion aufgerufen. Es werden ein Queue-Objekt, ein Pen-Objekt und 2 Stepper-Objekte erzeugt. Auch der OSC Client, Server und Nachrichten, welche Empfangen werden sollen, werden eingerichtet.
Ausgelöst durch „Verbinden“ in der malokalRemote wird getSetupTunnel() aufgerufen, was die Initialisierung der Wifly-Karte übernimmt. Diese Karte merkt sich die letzte Konfiguration, sodass dieser Schritt nicht jedes mal gestartet werden muss. Wir haben die Wifly zuerst mit der "WiFlyHQ"-Library von harlequin-tech betrieben. Felix Bonowski hat diese dann aber glücklicherweise stark abgespeckt und daraus die "MinimalWiFly"-Library gemacht. Das spart uns wertvollen RAM und Verzögerungen und ruckeln beim empfangen von Nachrichten. Die Einrichtung der Wifly kann dann mit wenigen Zeilen Code gestartet werden (siehe unten). Die Variable ip_char wird dynamisch mit der von malokalRemote übergebenen Adresse gefüllt.
bool success =wifly.setup( 115200, // baud rate true, // should we set the Module to the specified baudrate if it is currently configured othewise "wlan-name", // Your Wifi Name (SSID) "wlan-passwort", // Your Wifi Password "malokal", // Device name for identification in the network 0, // IP Adress of the Wifly. 0 = dhcp 9000, // WiFly receive port - incoming packets have to be sent there ip_char, // Where to send outgoing Osc messages 9001, // outgoing port - outgoing packets will be sent there MinimalWiflyConfig::PROTO_UDP //protocol bit (see datasheet or library source code) );
Der wichtigeste Teil der Hauptklasse ist die Loop-Funktion. Diese Funktion wird durch den Arduino so oft wie möglich aufgerufen und bringt alle Elemente zusammen.
Zuerst werden die neusten OSC nachrichten abgefragt und die loop-Funktion der Befehls-Queue aufgerufen.
void loop(){ // nach neuen osc schauen server.availableCheck(); queue.loop();
Wenn es dann Elemente in dieser Befehlsqueue gibt, wird der aktuelle Eintrag abgefragt.
if (queue.getLength()>0) { //Wenn Queue nicht leer float current_entry_mode=queue.getCurrentEntry(0); // aktueller queue eintrag float current_entry_data=queue.getCurrentEntry(1);
Jetzt werden alle Komponenten wie die Motoren überprüft, ob sie noch etwas zu tun haben. Ist das nicht der Fall, und der aktuelle Befehl ist für diese Komponente (z.B. den linken Motor) gedacht, wird der aktuelle Befehlswert dorthin weitergeleitet und anschließend die loop-Funktion von allen Bestandteilen aufgerufen.
// LINKER MOTOR ------------ if (stepperLeft.getIsReady() && current_entry_mode==1.0) { //wenn er nicht beschäftigt ist und in Queue angesprochen wurde stepperLeft.drive(current_entry_data); // entfernung in mm } ... stepperLeft.loop(); ...
Nun wird geprüft, ob das Element des aktuellen Eintrags fertig ist. Das dauert in der Regel ein paar Durchläufe. Wenn die Komponente (z.B. der linke Motor) fertig ist, wird der Befehl von der Befehlsliste entfernt. Im nächsten Durchgang würde dann der folgende betrachtet werden, wenn es einen gibt.
if( // wenn das element des aktuellen mode fertig ist, dann zum nächsten schritt ( (current_entry_mode==1.0 && stepperLeft.getIsReady())|| ... ) ) { queue.removeFromQueue(); } } }
Die Hauptklasse implementiert für den Empfang von OSC Nachrichten noch diverse „Tunnelfunktionen“, die die empfangene Nachricht dann an die richtige Unterklasse weiterleiten.
Die Queue-Klasse implementiert die Befehlsliste von Malokal. Die Queue als Array ist dabei zentrales Element. Dieses Array ist als ein Ringpuffer mit der Länge 100 aufgebaut. Die Länge ist davon abhängig, wie viel RAM im Arduino zur verfügung steht. Ringpuffer bedeutet, das neue Elemente hinten angehangen werden, und wenn die Liste voll ist, wieder von vorne die alten Elemente überschrieben werden. Die Queue implementiert zwei wichtige Variablen: current Index und newest Index. Diese Variablen werden nie zurückgesetzt. Möchte man z.B. Befehl Nr 150 auslesen, wird die Modulo-Division benutzt. Diese gibt den Rest der ganzzahligen Division zurück. Wenn man 150%100 ausführt, erhält man 50, was dann im Bereich 0 bis 100 liegt. Genau nach diesem Prinzip ist die Queue-Liste implementiert. Current index ist der Befehl der Queue, der gerade ausgeführt wird (Nr. 5). Elemente davor (Nr. 1–4) wurden ausgeführt und bleiben in der Liste, bis sie überschrieben werden. Newest Index ist der „jüngste“ Befehl, der in die Queue aufgenommen wurde. Immer, wenn in der Queue noch Platz ist, werden neue Befehle nachgeladen. Newest Index wandert dann nach vorne. Die „virtuelle“ Länge der Queue ergibt sich aus newest Index - current Index.
Die Queue-Klasse muss also wenn Platz ist neue Befehle anhängen, den aktuellen Stand voransetzen, wenn der Befehl fertig ausgeführt wurde, und die aktuelle Länge zurückgeben.
Für die Steuerung der Schrittmotoren haben wir eine eigene Klasse geschrieben. Dabei ist jeder Motor ein seperates Objekt.
Wird ein Motor mit einem Befehl angesprochen, der die zu fahrende Entfernung in mm enthält, werden diese zunächst in Steps umgerechnet. Ein Stepper-Motor, der mit einem Stepper-Treiber betrieben wird, erhält einen kurzen Stromimpuls, was einen „Schritt“ auslöst. Unsere Stepper machen eine Umdrehung mit 200 Steps. Für eine bessere Genauigkeit verwenden wir 8tel Schritte. Dadurch ergeben sich 1600 Schritte für eine volle Umdrehung.
Die Anzahl der Schritte ergibt sich also aus der zu fahrenden Strecke geteilt durch die Strecke eines Schritts.
$steps=\frac{s_{Fahren}}{s_{Schritt}}$
Die Konstante $s_{Schritt}$ haben wir vorher bestimmt. Unsere Stepper benötigen 1600 Schritte für eine Umdrehung. Das Rad hat einen Umfang von 282mm.
$s_{Schritt}=\frac{282mm}{1600}=0,17625$
Mit dieser Konstanten können wir jetzt im Code die Schritte berechnen. fabs bewirkt hier, das die Schrittanzahl immer positiv ist. Negative Streckenlängen kommen vor, wenn malokal rückwärts fahren soll. Dafür werden die Treiber „umgepolt“, die Anzahl der Schritte muss aber trozdem wieder positiv sein.
steps=fabs(newDistance/0.17625);
Bei einer Drehung drehen wir beide Motoren gegenläufig, wodurch wir den Stift exakt in der Mitte halten können. So erzeugen wir eine genau gezeichnete Ecke.
Bei der Drehung wird ein Kreis beschrieben. Deshalb ergibt sich die Distanz, die beide Stepper fahren müssen, aus der Bogenlänge mit Winkel und Radabstand als Parameter.
$s_{Drehen}=pi * Radabstand * (\frac{Drehwinkel}{360°})$
Den Radabstand haben wir mit 150mm gemessen. Im Code wird die Formel wie folgt umgesetzt, wobei current_entry_data unserem Drehwinkel entspricht. Die tatsächliche Anzahl der Steps wird dann wie oben beschrieben aus der Strecke in mm berechnet.
float dreh_Distanz=PI*rad_abstand*((current_entry_data)/360.0); stepperRight.drive(round(dreh_Distanz*-1)); stepperLeft.drive(round(dreh_Distanz));
Die Stepper-Klasse enthält auch eine Loop-Funktion, die aus der Hauptklasse aufgerufen wird.
In dieser macht der Motor durch einen Stromimpuls immer dann einen Step, wenn der vorgegebene Intervall zwischen zwei Steps abgelaufen ist und noch Steps ausgeführt werden sollen.
Dieser Intervall hängt von der aktuellen Geschwindigkeit ab. Ist der Intervall kürzer, fährt Malokal schneller. Die Geschwindigkeit wird durch die Beschleunigung berechnet, die im folgenden erläutert wird.
Ausserdem implementiert die Stepper-Klasse noch eine Rückmeldung, über die die Hauptklasse abfragen kann, ob schon alle Steps „abgefahren“ wurden, also der Motor fertig und bereit für neue Befehle ist.
Bei den ersten Versuchen hat sich gezeigt, das bei höheren Geschwindigkeiten ein apruptes starten und stoppen der Genauigkeit schadet, da malokal eine träge Masse ist, die sich einer Geschwindigkeitsänderung entgegensetzt, und die Räder beim ersten Start leicht durchdrehen, also Schlupf erzeugen. Wir sind für das zeichnen aber darauf angwiesen, das die Strecke wie geplant auch exakt gefahren wird. Um den Schlupf und die Trägheit auszugleichen, verwenden wir eine Beschleunigung bzw. ein Abbremsen bei jeder Strecke.
Die besten Ergebnisse erzielen wir mit einer konstanten Kraft, welche bei einer linearen Beschleunigung herrscht.
Die Umsetzung klingt zwar trivial, allerdings sind Schrittmotoren nicht sonderlich trivial zu bedienen. Daher folgt hier eine kurze Erklärung der Umsetzung von Beschleunigung und Abbremsen.
Die Geschwindikeit der Motoren wird durch die Zeit zwischen zwei Schritten bestimmt. Umso kürzer diese Zeit ist, desto schneller dreht sich der Motor.
Beim Beschleunigen bzw. Bremsen müssen diese Zeitabstände also von „sehr lang“ zu „normal“ (also die Zielgeschwindigkeit) bzw. von „normal“ zu „sehr lang“ geändert werden.
Erklärt wird das Prozedere an Hand der Beschleunigung. Das Abbremsen funktioniert analog, nur dass ein paar Vorzeichen geändert werden müssen ;)
Hierfür gibt es folgende Parameter:
Beschleunigung
$a$ $[\frac{mm}{s}^2]$
Startgeschwindigkeit
$v_0$ $[\frac{mm}{s}]$
Aktuelle Geschwindigkeit
$v$ $[\frac{mm}{s}]$
Zeit seit Beschleunigungsstart
$t$ $[s]$
Intervall (Zeit) zwischen letztem und nächstem Step
$t_i$ $[s]$
Die Formel zur Berechnung der aktuellen Geschwindigkeit lautet
$v = v_0 + a * t$
Aus dieser Geschwindigkeit muss nun noch das Step-Intervall ($t_i$) berechnet werden.
$t_i = \frac{\text{Strecke pro Step}}{v}$
Weiterhin muss nun noch festgelegt werden, wann die Beschleunigung enden soll. Das ist der Fall, wenn die momentane Geschwingkeit gleich der Zielgeschwingkeit ist.
Die Geschwindigkeit wird dabei vom Start der Beschleunigung bis zum Hochpunkt berechnet. Ist dieser erreicht, wird das Bremsen eingeleitet. Dabei sind allerdings die berechneten Intervalle so gedeckelt, dass nur die Höchstgeschwindigkeit $v_{max}$ erreicht werden kann.
Außerdem sind die Intervalle beim Start der Beschleunigung theoretisch sehr, sehr lang. Das würde dazu führen, dass der Motor am Anfang z.B. 10 Sekunden für den ersten Schritt macht. Um das zu verhindern, ist eine Abfrage implementiert, die die Geschwindigkeit anfangs auf $1 \frac{mm}{s}$ setzt.
if (current_v<1.0) { current_v=1.0; } //Deckelung if (current_v>v_max) { current_v=v_max; }
Den Beschleunigunggswert a kannten wir natürlich vorher noch nicht. Anfangs haben wir uns überlegt, dass es sinnvoll wäre, nach 1 Sekunde 10 cm gefahren zu sein. So richtig schön war das aber nicht, da das Beschleunigen dann bei einer Zielgeschwindigkeit von $10 \frac{mm}{s}$ genau 1 Sekunde dauern würde. Wenn aber höhere Geschwindigkeiten gefahren werden, würde die Beschleunigung dementsprechend länger dauern. Daher entschieden wir uns dafür, unseren Faktor a nach der gesetzten Geschwindigkeit zu bestimmen. Nach 2 Sekunden sollte malokal immer auf Höchstgeschwindigkeit kommen, das bedeutet, dass bei $100 \frac{mm}{s}$ Zielgeschwindigkeit mit $50 \frac{mm}{s}^2$ beschleunigt wird.
$a=v_{max}/2$
Pen ist die einfachste Klasse. Sie enthält die Funktionen up() und down(), die dann das Servo des Stifts ansprechen.
void up() { pen_motor.write(60); loop_steps=1000.0; } void down() { pen_motor.write(25); loop_steps=1000.0; }
Das heben und senken dauert einen Moment, was durch ein „Loop-Timer“ realisiert wird. Dabei wird ein Zähler in jedem Druchgang verkleinert, bis er kleiner als 0 ist. Der verwendete Wert 1000.0 wurde experimentell ermittelt.
Besser wäre natürlich, die Wartezeit nicht von der Ausführungsgeschwindigkeit abhängig zu machen, sondern eine zeitbasierte Messung mit einem experimentellen Wert zu verwenden. Es könnte auch gemessen werden, wie lange das Servo für die vollen 180° benötigt, und dann die Wartezeit von dem zu ändernden Winkel abhängig zu machen. Wir hatten jedoch mit dem Loop-Code bisher keine Probleme, weshalb wir diese Idee bisher nicht umgesetzt haben.