Dies ist eine alte Version des Dokuments!
Meine Damen und Herren, wir freuen uns Ihnen den Graffitibot vorstellen zu dürfen.
Unser Team, bestehend aus Isabel Yasemin Dautz, Jasmin Tamara Pelz und Felix Fürnhammer, hat in den letzten Monaten einen ausgeklügelten Roboter gebaut, der sowohl fahren, als auch an Wänden malen soll!
Wie kamen wir darauf? Im Voraus sollten wir uns Gedanken für die zu bauenden Roboter machen, da Felix die Idee für einen sich im Raum orientierenden Roboter hatte und sowohl Isabel als auch Jasmin von einem malenden Roboter angetan waren, haben wir diese beiden harmonierenden Aspekte miteinander verbunden.
Die Idee des GraffitiBots war folgende:
Wir bauen einen Roboter, der sich im Raum orientieren, Hindernissen ausweichen und eine geeignete Wand finden kann, die er dann bemalt.
Das Bild, welches er malen soll, wird auf einer C# Oberfläche an einem Laptop gezeichnet.
Die Koordinaten werden dann in Schritte für die Schrittmotoren umgewandelt. Diese Schritte werden dann über einen zweiten, per USB am Laptop angeschlossenen, Arduino per Bluetooth an den Arduino im Roboter gesendet.
Zudem wurden wir von Jasmins Vater unterstützt, dieser hat uns nicht nur eine Werkstatt mit Metallfräse, Metallsäge und einer Drehmaschine zur Verfügung gestellt, sondern auch einen C#-Crashkurs an 4 Wochenenden gegeben. Er hat sich sogar bereit erklärt alles zu bezahlen, da er den fertigen Roboter am Ende des Projektes gerne behalten wollte.
Wir wünschen euch viel Spaß beim weiteren Lesen der Projektdokumentation und hoffen wir konnten unsere Projektarbeit anschaulich darstellen.
Hierzu haben wir nach einer gründlichen Einführung in der Handhabung der zu benutzenden Maschinen viele Arbeitsstunden in der Werkstatt von Jasmins Vater verbracht.
Für sämtliche Teile wurde Aluminium benutzt, da es vergleichsweise leicht, aber auch stabil ist. Begonnen haben wir mit dem Zuschneiden des Bodens, der Befestigung des Stützrades, der vorderen beiden Räder und deren Motoren. Nach und nach wurden die grundlegenden Teile, wie Akkus und das Relais, platziert, sowie fixiert. Es wurde eine Art „Schutzbox“ entwickelt, um sehr empfindliche Teile, wie den Arduino Mega, als auch seine Verdrahtung zu schützen. An den Seiten dieser Box befinden sich Schalter, die zwar zur Zeit nicht genutzt werden, aber voll funktionstüchtig sind. Ein Display kann zur Übertragung von Texten genutzt werden und Bohrungen am Rand dienen als Eingänge der Kabel.
Das Malgestell steht orthogonal zum Boden, besitzt eine Art quadratisches „Außenskelett“, das die Achsen hält, somit besitzt der Graffitibot insgesamt eine Höhe von 57 cm und eine Breite von 63 cm. Die Y-Achse wird durch zwei Zahnriemen geführt, von der eine über einen Schrittmotor angetrieben wird. Die X-Achse besteht aus einer Gewindestange die ebenfalls von einem Schrittmotor in Rotation versetzt wird.
Die ursprüngliche Verbindung zwischen PC und Roboter, also die Verbindung zwischen C# und Arduino, bestand aus der standardisierten USB-Schnittstelle, die auch für die Programmübertragung von PC auf Arduino benutzt wird. Da es natürlich nicht praktikabel ist, den Roboter mit einem langen Kabel mit dem Pc zu verbinden ist, stehen zwei Methoden der Funkanbindung zur Verfügung. Und zwar Bluetooth und WLAN. Da das Bluetoothmodul kostengünstiger ist, schon verfügbar war und auch letztendlich die Anbindung einfacher war, haben wir uns für diese entschieden. Aus der Sicht des Programmierers zeigt sich der angenehme Umstand, dass die Bluetoothkopplung keinerlei zusätzlichen Code erfordert, da die Kommunikation zwischen PC und Arduino in der gewohnten Weise funktioniert. Die ursprünglich angedachte Nutzung der im Laptop integrierten Bluetoothschnittstelle war problematisch in der Parametrierung, darum haben wir uns entschlossen einen zweiten Arduino per Kabel direkt an den PC anzuschließen, der nur als Schnittstellenwandler zwischen PC/USB und Arduino/Bluetooth wirkt. Für diese Verschaltung ist keine einzige Codezeile nötig. Innerhalb des C#-Programms wird nur die COM-Schnittstelle des Arduino angegeben. In der C#-Software wird die Treiberfunktionalität der guten RS232 Schnittstelle verwendet, was sich auch in der Limitierung der Datenübertragungsgeschwindigkeit auf 115000baud wiederspiegelt.
Die Ansteuerung der Ultraschallsensoren erfolgt über die Software. Es ist bekannt, dass die Schallgeschwindigkeit keine eigentliche Naturkonstante ist, sondern im Nennbereich 330 m/s liegt und starken Schwankungen durch Luftfeuchtigkeit und Temperatur ausgesetzt ist. Da aber die beiden in der Software angesprochenen Temperatursensoren identischen Umweltbedingungen ausgesetzt sind, ist ihre Hauptaufgabe, die parallele Ausrichtung des Roboters zur Wand, trotzdem erfüllt.
Pins | Bauelement |
---|---|
GND(4x) | Ultraschallsensoren |
GND(3x) | Schalter |
5+7 | Motor (x-Achse) |
1+3 | Motor (y-Achse) |
10+11 | Schalter |
12+13 | Schalter |
18+19 | Bluetoothsensor |
24+25 | Display |
26+27 | Display |
28+29 | Display |
30+31 | Display |
32+33 | Ultraschallsensor(Echo+Trig) |
36+37 | Ultraschallsensor(Echo+Trig) |
40+41 | Ultraschallsensor(Echo+Trig) |
44+45 | Ultraschallsensor(Echo+Trig) |
50+52 | Rechter Schrittmotor |
51+53 | Linker Schrittmotor |
Damit der GraffitiBot an eine Wand malen kann, muss er diese natürlich zunächst finden!
Zur Lösung dieser Aufgabe stehen uns zwei Schrittmotoren und die vier Ultraschallsensoren (von denen momentan nur zwei genutzt werden) zur Verfügung.
Außerdem haben wir während des Projektes zwei Libraries verwendet, die uns die Arbeit um einiges erleichtert haben. Zum einen <AccelStepper>, eine Library die das gleichzeitige ansteuern mehrerer Schrittmotoren erleichtert und dazu Funktionen beinhaltet, welche die Geschwindigkeit und Beschleunigung regeln. Zum anderen <NewPing>, die ein sequentielles Ablesen der Ultraschallsensoren ohne die pulseIn Funktion ermöglicht. Die pulseIn Funktion, die standardmäßig in der Arduino IDE enthalten ist, blockt beim Ablesen der Ultraschallsensoren die Schrittmotoren und wenn kein Echo empfangen wird, legt sie gerne mal das ganze Programm für ungefähr fünf Sekunden lahm.
Hier ein Zustandsdiagramm zur Fahrweise:
if(stepper2_on){ //stepper 2 (analog für Stepper 1) if(cm[1] <=100 && cm[1]!=0){ //Wenn der Abstand kleiner als 100 cm ist und nicht 0 if(stepper2.distanceToGo()==0){ stepper2.move((cm[1]-2)*29); //(abstand - 2 cm) um von cm in schritte umzurechnen *29 stepper2_on=false; } } else{ // wenn die Distanz gleich 0, also größer 100 cm dreht sich der Roboter solange bis er etwas findet if(stepper2.distanceToGo()==0){ stepper2.move(200); // Stepper1 fährt hier 800 Schritte } stepper2_on=false; }
Sobald der Abstand zum Objekt drei cm oder weniger beträgt, also das Rad des Roboters das Objekt berührt, stoppt der jeweilige Motor und der Roboter versucht den anderen Ultraschallsensor ebenfalls direkt vor die Wand zu fahren.(alles für stepper 2 gilt analog auch für stepper 1)
if(cm[1] <=3 && cm[1]!=0){ // stepper 2 fährt nicht weiter wenn ein objekt 3 cm oder weniger vor dem Sensor ist stepper2_on=false; } else{ stepper2_on=true; }
Im Optimalfall steht der Roboter jetzt parallel zur Wand und wartet auf die Eingabe des Bildes, welches er malen soll. Sollte der Roboter jedoch auf ein anderes Objekt als eine Wand (Stuhl,Tisch usw.) treffen wird er dennoch versuchen zuerst einen und dann den anderen Ultraschallsensor auf drei cm an das Objekt heranzufahren und wenn besagtes Objekt nicht zufällig der Breite des Roboters entspricht fährt er immer weiter dagegen. Das Programm funktioniert also momentan nur in einem leeren Raum.
Findet der Roboter allerdings kein Objekt im Abstand von maximal 100 cm vor ihm, fährt er eine Spirale, bis er ein Objekt findet. Generell darf das Objekt maximal einen Meter Abstand haben, sonst erkennt er es nicht. Die Ultraschallsensoren fangen ab etwa 100- 150 cm ungenaue Messungen zu liefern, da wir aber für die Berechnung der zu fahrenden Strecke genaue Werte brauchen, haben wir den maximalen zu messenden Abstand auf 100 cm gelegt.
Sollte der Roboter kein Objekt finden, so fängt er an sich langsam im Uhrzeigersinn zu drehen und sucht die Umgebung nach einem Objekt ab.(siehe im ersten Codeblock „else{}“)
Wir hatten die Idee unseren Roboter etwas an die Wand zeichnen zu lassen, das wir vorher mit der Maus auf einer Programmoberfläche gemalt haben.
Dazu haben wir zwei verschiedene Programmiersprachen benutzt. Für das Zeichnen mit der Maus haben wir C# verwendet, für das Ansteuern der
Schrittmotoren hingegen Arduino.
Man kann sich unser Programm als eine abgespeckte Version von Paint vorstellen. Bewegt man die Maus, solange die linke Maustaste gedrückt ist,
wird eine Linie gezeichnet. Ist die linke Maustaste nicht (mehr) gedrückt, wird auch nicht gezeichnet. So sieht das dann aus:
Im folgenden Beispiel sieht man, dass die Zeichnung nicht (false) ausgeführt werden soll, solange die linke Maustaste nicht gedrückt ist
(MouseUp Event):
void Form1_MouseUp(object sender, MouseEventArgs e) { meine_Zeichnung = false; }
Ist die linke Maustaste nun gedrückt (MouseDown Event), dann kann auch gezeichnet werden (true). Die Punkte (Mauskoordinaten) werden dann zu
einer Liste hinzugefügt und darin gespeichert:
void Form1_MouseDown(object sender, MouseEventArgs e) { Punkte.Add(new List<Point>()); meine_Zeichnung = true; }
Erst wenn man die Maus bewegt (MouseMove Event), kann eine Linie entstehen, da man bekanntlich zwei Punkte braucht, um eine Linie zeichnen zu können. Wenn also die Zeichnung erfolgen soll, wird zu den bisherigen Punkten immer der aktuelle Punkt (Mausposition) hinzugefügt. 'Refresh()' löst das Paint-Ereignis aus, das man braucht, damit eine sichtbare Linie entsteht:
void Form1_MouseMove(object sender, MouseEventArgs e) { if (meine_Zeichnung) { Punkte[Punkte.Count - 1].Add(e.Location); this.Refresh(); } }
Das Paint-Event wird ausgelöst, sobald die Maus bei gedrückter linker Taste bewegt wird. Für alle Punkte der Liste gilt, dass, wenn mehr als
ein Punkt vorhanden ist, eine Linie mit Hilfe der Graphics-Klasse gezeichnet (DrawLines) wird. Um das auf der Oberfläche sichtbar zu machen,
benutzen wir den Stift (Pen) mit der Farbe schwarz und einer Breite von 3 Pixeln. Die Punkte werden dafür in ein Array gespeichert, da immer
der neueste Punkt mit vorherigen Punkt verbunden werden soll:
void Form1_Paint(object sender, PaintEventArgs e) { foreach (List<Point> points in Punkte) { if (points.Count > 1) e.Graphics.DrawLines(new Pen(new SolidBrush(Color.Black), 3), points.ToArray()); } }
Nun können wir also etwas mit der Maus zeichnen und die Punkte der Zeichnung an Arduino senden. Jetzt müssen wir nur noch die Zeichnung in
Schrittmotorenbewegungen umwandeln, damit der Roboter unsere Zeichnung an die Wand malen kann. Eine wichtige Voraussetzung ist folgende:
Wir haben uns wieder für die AccelStepper-Library entschieden, da die Schrittmotoren der Achsen gleichzeitig angetrieben werden müssen. Wenn sie das nicht täten, würde zuerst die x-Achse, danach die y-Achse befahren werden, was bedeuten würde, dass zwei Punkte statt durch eine Linie (schwarz) durch zwei Linien (grau) verbunden werden:
Wir haben vorhin den empfangenen x- und y-Wert als 'xNEU' und 'yNEU' deklariert, nun fahren wir folgendermaßen fort:
schritteX = (xNEU - xALT)*1000; // Hier werden die Schritte, die der Stift auf der x-Achse zurücklegen soll, berechnet. Dazu // ziehen wir den alten x-Wert vom aktuellen ab. Diese Differenz wird mit 1000 multipliziert, da // die Stepper eine hohe Impulsrate brauchen, damit ein erkennbarer "Schritt" zu sehen ist. // Ist die Zahl negativ, so bewegt sich der Stepper in die andere Richtung. schritteY = (yNEU - yALT)*1000; // Das gleiche gilt natürlich auch für die y-Achse. if((schritteX>3000 && schritteY>3000) ||(schritteX<-3000 && schritteY<-3000)) // Wenn die Differenz beider! Achsenwerte größer als 3000 ist (also eigentlich 3), dann wurde die // gezeichnete Linie unterbrochen und womöglich woanders fortgeführt. Da der Roboter dann nicht // die Punkte durch eine Linie verbinden soll, muss der Stift absetzen. Gleiches gilt auch, wenn // die Differenz kleiner als -3000 (-3) ist. { //Stift von der Wand entfernen (zurückfahren) // Da der Stift nicht "richtig" abgesetzt werden kann, muss der Roboter einen kleinen Schritt // zurückfahren. } if((schritteX>3000 || schritteY>3000) ||(schritteX<-3000 || schritteY<-3000)) // Auch wenn nur die Differenz der Werte einer! Achse größer als 3000 bzw. kleiner als -3000 ist, // kann die Linie unterbrochen worden sein. Daher muss auch hier der Stift von der Wand abgesetzt // werden. { //Stift von der Wand entfernen (zurückfahren) } stepperX.move(schritteX); // Nun sollen die Schrittmotoren die berechneten Schritte ausführen. Das erfolgt über den // 'move'-Befehl. Das gilt für beide Achsen. stepperY.move(schritteY); if((schritteX>3000 && schritteY>3000) ||(schritteX<-3000 && schritteY<-3000) // Da die zu zeichnende Linie im nächsten Schritt weitergeht, muss der Stift wieder an die Wand // angesetzt werden. Weil das nur nötig ist, wenn der Roboter sich vorher ein wenig von der Wand // entfernt hat, gilt hier die gleiche Bedingung wie oben: Die Differenz der Achsenwerte muss // größer als 3000 bzw. kleiner als -3000 sein (sie hat sich im Schritt darüber ja nicht geändert). { //Stift wieder an die Wand (ranfahren) } if((schritteX>3000 || schritteY>3000) ||(schritteX<-3000 || schritteY<-3000)) { //Stift wieder an die Wand (ranfahren) } xALT = xNEU; // Die Achsenwerte 'xNEU' und 'yNEU' müssen nun 'xALT' und 'yALT' zugewiesen werden, da im nächsten // Schritt eine Linie von dem ehemaligen "neuen" (jetzt "alten") Punkt zum jetzt "neuen" Punkt // (neu eingelesene Werte) gezeichnet werden soll. yALT = yNEU; Serial.print("Punkt_bitte"); // Nachdem der Roboter eine Linie gezeichnet hat, schreibt er über die serielle Schnittstelle an // C#, dass die nächsten Werte kommen können. Dieser Schritt ist notwendig, da Arduino nicht zu // viele Werte auf einmal erhalten und verarbeiten kann. }