Sandplotter |
Der Sandplotter ähnelt einem menschlichen Arm. Zeichnen geht, aber die Zeichnungen mit einem Stift sind recht krakelig, da das Programm schrittweise arbeitet. Die Stiftzeichnung (Kritzelbild rechts) soll ein Herz und eine 8 darstellen. In dem etwas poetischeren Medium Sand dagegen, sehen die Sachen ganz in Ordnung aus. Der Plotter hat einen zweigeteilten Arm. Die beweglichen Armteile sind etwa gleich lang, dadurch bleiben die Gleichungen zur Umrechnung von XY-Koordinate in die beiden Beugewinkel einfach. Der ganze Arm kann durch den 3ten Motor angehoben werden, für ein "GEHE ZU". Der "Stift" ist einfach eine kurze Legostange, die sich am Armende in den Sand bohrt. Das Programm auf dem NXT liest eine Datei mit Plotbefehlen. Jeder Befehl besteht aus 3 Teilen. Die erste Zahl gibt den Ziehmodus an (Stift hoch, Stift runter). Die beiden folgenden Zahlen beschreiben die relative Bewegung des Armes. Da wir nicht mit "Tasks" programmiert haben, muss die Bewegung in kleinen Schritten erfolgen. Da die Steuerdateien relative Bewegungen angeben, sind sie recht schwer von Hand zu berechnen. Wir haben uns dafür ein kleines Java Programm geschrieben. Der Plotter kalibriert sich vor jeder Zeichnung. Dazu fährt er den Unterarm nach rechts, bewegt dann den Oberarm bis an den Berührungssensor um dann den Unterarm wieder nach links zu fahren, bis er blockiert. Der Heber wird auch kalibriert, in dem er sich bis zum Anschlag dreht und danach 270° zurück in die "Arm unten" Position. Das kleine Java Programm zum Generieren der Steuerdateien besteht aus einem Zeichenfeld, in dem mit dem Mauszeiger gemalt werden kann. Die XY-Koordinaten des Mauszeigers werden dabei in Drehwinkel für die Motoren umgerechnet. Für die entsprechend Interessierten sind hier die Formeln angegeben. Der Faktor 6.8 in der ersten Gleichung ergibt sich aus der Untersetzung im Oberarmgelenk: -(6.8 * (90.0 + 180.0 / Math.PI * Math.acos(0.5 * Math.sqrt(x * x + y * y)) - 180.0 / Math.PI * Math.atan(x / y))); Für den Unterarm gilt: 2.0 * 180.0 / Math.PI * Math.acos(0.5 * Math.sqrt(x * x + y * y)) Da der Plotter relative Steuerbefehle erwartet, wird jeweils die Differenz zwischen zwei Punkten abgelegt. Das Programm ist recht schlicht und man muss häufig klicken, damit die Linien nicht zu zackig werden. Das Beispielbild soll eine Schiff in Wellen darstellen. Das war wohl schon eine Überforderung für den Zeichner und den Plotter. Immerhin reicht es für Herzen, Achten und Schlangen. Ein Beispiel für einen perfekten Plotter kann man im NXTLog auf www.mindstorms.com bewundern, wenn man nach "nilsvoelker" sucht. NXC Quelltext: #define OBERARM OUT_C #define UNTERARM OUT_B #define HEBER OUT_A #define OBERARM_POLLER_KANAL IN_1 #define OBERARM_POLLER SENSOR_1 #define HEBEWINKEL 150 #define GEHEZU_AKTION 1 #define LINIEZU_AKTION 2 sub playATone() { SoundPlayToneType sptArgs; sptArgs.Frequency = 440; sptArgs.Duration = 400; // 1 sptArgs.Loop = false; sptArgs.SoundLevel = 3; SysSoundPlayTone(sptArgs); } sub playErrorTone() { SoundPlayToneType sptArgs; sptArgs.Frequency = 440; sptArgs.Duration = 400; // 1 sptArgs.Loop = false; sptArgs.SoundLevel = 3; SysSoundPlayTone(sptArgs); } /* Hebt den Stift */ inline void stiftHoch() { if (MotorRotationCount(HEBER) > -50) { RotateMotor (HEBER,-40,HEBEWINKEL); } } /* Senkt den Stift */ inline void stiftRunter() { if (MotorRotationCount(HEBER) < -50) { RotateMotor (HEBER,30,HEBEWINKEL); } } inline void schreib(int A, int B) { RotateMotor (OBERRARM,30,A); RotateMotor (UNTERARM,30,B); } task main () { byte plotDatei; int fSize; /* Kalibriere, Schreibarm an das Gehäuse. Heber gegen den Poller und dann nach unten */ SetSensorTouch (OBERARM_POLLER_KANAL); // Unterarm zurück OnFwd (UNTERARM,-10); Wait (2000); Off(UNTERARM); // Oberarm an Poller OnFwd (OBERARM,30); while (OBERARM_POLLER==0) { Wait(100); } Off(OBERARM); // Unterarm anlegen OnFwd (UNTERARM,10); Wait (3000); Off(UNTERARM); // Heber zurück OnFwd(HEBER,20); Wait(4000); Off(HEBER); // Heber auf null RotateMotor (HEBER,-30,270); ResetRotationCount(OUT_ABC); stiftHoch(); if (NO_ERR == OpenFileRead("plotdatei.txt",fSize,plotDatei)) { int aktion; // Unterarmwinkel int A; // Oberarmwinkel int B; bool eof=false; while (true != eof) { string buf; if(ReadLnString(plotDatei,buf) != NO_ERR) { eof = true; break; } aktion=StrToNum(buf); if(ReadLnString(plotDatei,buf) != NO_ERR) { eof = true; break; } A=StrToNum(buf); if(ReadLnString(plotDatei,buf) != NO_ERR) { eof = true; break; } B=StrToNum(buf); switch (aktion) { case GEHEZU_AKTION : stiftHoch(); schreib(A,B); break; case LINIEZU_AKTION : stiftRunter(); schreib(A,B); break; default: eof=true; } } } else { playErrorTone(); } CloseFile(plotDatei); playATone(); } |