Roboter – II

Roboter – II

Nach nun einiger Zeit, möchte ich erneut über den Fortgang des bereits unter http://www.freeduino.de/blogs/matthiasg/roboter-i vorgestellten Roboters informieren.

Der Zwischenstand des letzten Males

Zunächst mussten beide – noch unzureichend befestigte – Seitenwände miteinander verbunden werden. Das alte Konstrukt wurde dazu aufgelöst, Schienen aus Alu-Vierkant-Profil gesägt und Löcher hinein gebohrt.

Eine einzelne Schiene, die die Roboterteile später zusammenhält

Das Zusammenschrauben des Roboters – deutlich erkennbar das in den Fliesen festgetretene Aluminium…

Das verschraubte Endkonstrukt ist nicht nur unheimlich stabil, sondern auch vergleichbar leicht. Obwohl die Länge ca. 60 cm beträgt, liegt das (geschätzte) Gewicht (ohne Motoren und Akku) bei ungefähr 4 Kilogramm.

Alle Aluschienen sitzen, der Roboter hält

Da nun das Grundgerüst stand, war es Zeit, einen vernünftigen Motorblock einzusetzen. Der ursprünglich vorgestellte hatte sich als zu schwer, unhandlich und platzfressend heraus gestellt. Deshalb musste dieser ersetzt werden – das Grundmaterial gleich den Wänden.

Der Motorblock wird eingesetzt – rechts ist eine Scheibe zu erkennen

Aufgrund der neuen Platzsituation mussten zwei Lüfter (die ohnehin überflüssig waren) weichen, und auch die Kugellager entfernt werden (sie saßen minimal schief im Gehäuse).

Die neue Haltevorrichtung besteht aus Buchenholz, das mit Hilfe eines 22mm-Bohrers auf passende Größe gebracht wurde. Eine Aluschiene mit verkleinerter Bohrung verhindert ein Rutschen.

Der gesamte Motorblock kann sich mehr als sehen lassen und gleicht fast (aber nur fast) Präzisionsarbeit.

Der neue Motorblock von oben – gut erkennbar ist, dass der andere Motor passgenau hinein ragt

Von unten…

Unterhalb des Motorblockes ist Platz erkennbar (s. folgendes Bild), darauf befestigen wir in Zukunft eine dünne Holzplatte, auf die praktisch Kopfüber die Elektronik zum Steuern der Motoren verschraubt wird.

Von unten…

Unterhalb des Motorblockes ist Platz erkennbar (s. folgendes Bild), darauf befestigen wir in Zukunft eine dünne Holzplatte, auf die praktisch Kopfüber die Elektronik zum Steuern der Motoren verschraubt wird.

Gut erkennbar ist die spätere Position der Elektronik-Halterung.

Auch die Ketten konnten wir erfolgreich aufspannen, sodass der Roboter komplett fahrbereit war – die Verbindung der Motoren mit dem Akku bestätigten dies.

Die Ketten in Position

Die Motoren werden provisorisch verkabelt

Die Kette ist tatsächlich in der Lage das bisherige Gewicht (mit Akku und Motren ca. 8 – 9 Kilogramm) zu tragen – der Roboter fährt in Schrittgeschwindigkeit. Vorne fehlt noch ein erhöhtes Führungsrad, das wir aus Zeitgründen nicht einbauen konnten. Dieses sorgt später dafür, dass auch Hindernisse überwunden werden können.

Nach einem Standortwechsel konnten wir die Elektronik nun einbauen und testen: Tatsächlich – ein kleines Programm auf dem Arduino konnte ein Breakout von Sparkfun ansteuern, über den Pc war es möglich, den Roboter zu steuern.

Der nächste Schritt war die Unabhängigkeit vom Netzteil, dazu haben wir einen Akku mit in den Roboter gestellt (12 V / 7,2 Ah, Blei-Gel), aufgrund von Kurzschlüssen, die wir versehentlich verursacht haben, sicherheitshalber einen Temperaturfühler angelegt, den Laptop geschultert und alles angeschlossen: Ein großer Fehler.

Ein Kamikaze-Versuch, wie sich heraus stellen sollte. Zu sehen ist im hinteren Bereich, auf den Motoren aufliegend, der Arduino.

Sobald der Kontakt zwischen Akku und Motor-Driver geschlossen wurde, gab es eine laute Explosion und eine hohe Stichflamme: Der Kondensator auf dem Board war uns abgeraucht.

Der abgerauchte Kondensator ist rechts gut zu erkennen.

Damit dieser Fehler nicht noch einmal passiert: Was genau haben wir falsch gemacht? Am Netzteil hatte es funktioniert, deshalb haben wir wohl zu leichtfertig die Gerätschaft zusammen gesteckt. Vermutlich fehlte ein Widerstand? Auch den Einbau einer Feinsicherung werden wir demnächst wohl in Angriffnehmen. Jedoch: Welchen Wert sollte diese haben?

Wir wissen nun, dass der Roboter bis jetzt funktioniert. Der nächste Schritt wäre das Verbauen von Abstandssensorik, um den Roboter autonom fahrbar zu machen. Jedoch werden wir dafür wohl noch etwas Zeit brauchen…

Viele Grüße aus dem Pott und “Stay tuned”,

Arithmetik

Arithmetik

Arithmetik Arithmetische Operatoren umfassen Addition, Subtraktion, Multiplikation und Division. Sie geben die Summe, Differenz, das Produkt oder den Quotienten zweier Operatoren zurück.

y = y + 3; x = x – 7; i = j * 6; r = r / 5;

Die Operation wird unter Beibehaltung der Datentypen durchgeführt. 9 / 4 wird so zum Beipiel zu 2 und nicht 2,25, da 9 und 4 Integer sind und keine Nachkommastellen unterstützen. Dies bedeuted auch, dass die Operation überlaufen kann wenn das Resultat größer ist als der Datentyp zulässt. Wenn die Operanden unterschiedliche Datentypen haben wird der größere Typ verwendet. Hat zum Beispiel eine der Nummern (Operanden) den Datentyp ‘float’ und der andere ‘int’, so wir Fliesskomma Mathematik zur Berechnung verwendet. Bemerkung:Wähle variable Größen die groß genug sind um die Werte der Ergebnisse zu speichern. Sei Dir bewusst an welcher Stelle die Werte überlaufen und auch was in der Gegenrichtung passiert. z.B. bei (0 – 1) oder (0 – – 32768). Für Berechnungen die Brüche ergeben sollten immer ‘float’ Variablen genutzt werden. Allerdings mit dem Bewustsein der Nachteile: Großer Speicherbedarf und langsame Geschwindigkeit der Berechnungen. Nutze den Form Operator z.B. (int)myFloat um einen Variablen Typen spontan in einen anderen zu verwandeln. Zum Beispiel wird mit i = (int)3.6 die Variable i auf den Wert 3 setzen. gemischte Zuweisungen Gemischte Zuweisungen kombinieren eine arithmetische Operation mit einer Variablen Zuweisung. Diese werden üblicherweise in Schleifen gefunden, die wir später noch genau Beschreiben wird. Die gängigsten gemischten Zuweisungen umfassen:

x ++ // identisch mit x = x + 1, oder Erhöhung von x um +1 x — // identisch mit x = x – 1, oder Verminderung von x um -1 x += y // identisch mit x = x + y, oder Erhöhung von x um +y x -= y // identisch mit x = x – y, oder Verminderung von x um -y x *= y // identisch mit x = x * y, oder Multiplikation von x mit y x /= y // identisch mit x = x / y, oder Division von x mit y

Bemerkung: Zum Beispiel führt x *= 3 zur Verdreifachung des alten Wertes von ‘x’ und weist der Variablen ‘x’ des Ergebnis der Kalkulation zu. vergleichende Operatoren Der Vergleich von Variablen oder Konstanten gegeneinander wird oft in If-Anweisungen durchgeführt, um bestimmter Bedingungen auf Wahrheit zu testen. In den Beispielen auf den folgenden Seiten wird ?? verwendet um eine der folgenen Bedingungen anzuzeigen:

x == y // x ist gleich wie y x != y // x ist nicht gleich wie y x < y // x ist weniger als y x > y // x ist mehr als y x <= y // x ist weniger oder gleich wie y x >= y // x ist größer oder gleich wie y

logische Operatoren Logische Operatoren sind normalerweise eine Methode um zwei Ausdrücke zu vergleichen und ein TRUE oder FALSE je nach Operator zurückliefern. Es gibt drei logische Operatoren AND, OR und NOT die oft in If-Anweisungen verwendet werden:

Logisch AND: if (x > 0 && x < 5) // nur WAHR wenn beide // Ausdrücke WAHR sind   Logisch OR: if (x > 0 || y > 0) // WAHR wenn einer der // Ausdrücke WAHR ist   Logisch NOT: if (!x > 0) // nur WAHR wenn der // Ausdruck FALSCH ist
Datentypen

Datentypen

byte Byte speichert einen 8-bit numerischen, ganzzahligen Wert ohne Dezimalkomma. Der Wert kann zwischen 0 und 255 sein.

byte someVariable = 180; // deklariert ‘someVariable’ // als einen ‘byte’ Datentyp

int Integer sind der verbreitetste Datentyp für die Speicherung von ganzzahligen Werten ohne Dezimalkomma. Sein Wert hat 16 Bit und reicht von -32.767 bis 32.768.

int someVariable = 1500; // deklariert ‘someVariable’ // als einen ‘integer’ Datentyp

Bemerkung: Integer Variablen werden bei Überschreiten der Limits ‘überrollen’. Zum Beispiel wenn x = 32767 und eine Anweisung addiert 1 zu x, x = x + 1 oder x++, wird ‘x’ dabei ‘überrollen’ und den Wert -32,768 annehmen. long Datentyp für lange Integer mit erweiterer Größe, ohne Dezimalkomma, gespeichert in einem 32-bit Wert in einem Spektrum von -2,147,483,648 bis 2,147,483,647.

long someVariable = 90000; // deklariert ‘someVariable’ // als einen ‘long’ Datentyp

float Ein Datentyp für Fliesskomma Werte oder Nummern mit Nachkommastelle. Fliesskomma Nummern haben eine bessere Auflösung als Integer und werden als 32-bit Wert mit einem Spektrum von -3.4028235E+38 bis 3.4028235E+38.

float someVariable = 3.14; // deklariert ‘someVariable’ // als einen ‘float’ Datentyp

Bemerkung: Flieskomma zahlen sind nicht präzise und führen möglicherweise zu merkwürdigen Resultaten wenn sie verglichen werden. Ausserdem sind Fliesskomma berechnungen viel langsamer als mit Integer Datentypen. Berechnungen mit Fliesskomma Werten sollten nach Möglichkeit vermieden werden. arrays Ein Array ist eine Sammlung von Werten auf die mit einer Index Nummer zugegriffen wird. Jeder Wert in dem Array kann aufgerufen werden, indem man den Namen des Arrays und die Indexnummer des Wertes abfragt. Die Indexnummer fängt bei einem Array immer bei 0 an. Ein Array mus deklariert werden und optional mit Werten belegt werden bevor es genutzt werden kann.

int myArray[] = {wert0, wert1, wert2…}

Genau so ist es möglich ein Array zuerst mit Datentyp und Größe zu deklarieren und später einer Index Position einen Wert zu geben.

int myArray[5]; // deklariert Datentyp ‘integer’ als Array mit 6 Positionen myArray[3] = 10; // gibt dem 4. Index den Wert 10

Um den Wert eines Arrays auszulesen, kann man diesen einfach einer Variablen mit Angabe des Arrays und der Index Postion zuordnen.

x = myArray[3]; // x hat nun den Wert 10

Arrays werden oft für Schleifen verwendet, bei dem der Zähler der Schleife auch als Index Position für die Werte im Array verwendet wird. Das folgende Beispiel nutzt ein Array, um eine LED zum flickern zu bringen. Mit einer for-Schleife und einem bei 0 anfangenden Zähler wird eine Indexpostion im Array ausgelesen, an den LED Pin gesendet, eine 200ms Pause eingelegt und dann das selbe mit der nächsten Indexpostion durchgeführt.

int ledPin = 10; // LED auf Pin 10 byte flicker[] = {180, 30, 255, 200, 10, 90, 150, 60}; // Array mit 8 verschiedenen Werten void setup() { pinMode(ledPin, OUTPUT); // Setzt den OUTPUT Pin }   void loop() { for(int i=0; i<8; i++) // Schleife gleicht der Anzahl { // der Werte im Array, gezählt // wird hier von 0 bis 7 analogWrite(ledPin, flicker[i]); // schreibt den Indexwert auf die LED delay(200); // 200ms Pause } }
Variablen

Variablen

Variablen Eine Variable ist die Benennung eines numerischen Wertes mit einem Namen und Speicherplatz für die spätere Verwendung in einem Programm. Wie der Name schon vermuten lässt kann der Wert der Variablen kontinuierlich verändert werden, im Gegensatz zu Konstanten deren Wert im Programmablauf konstant bleibt. Eine Variable muss deklariert werden und optional mit einem Wert versehen werden. Das folgende Beispiel deklariert eine Variable ‘inputVariable’ und ordnet ihr dann den Wert vom analogen Pin 2 zu:

int inputVariable = 0; // deklariert eine Variable und // setzt ihren Wert auf 0 inputVariable = analogRead(2); // setzt den Wert der Variable gleich // mit dem Wert von Analog Pin 2

‘inputVariable’ ist die Variable selber. Die erste Zeile erklärt, dass ihr Datentyp ‘int’ ist, das ist der Kurzausdruck für Integer. Die zweite Zeile gibt der Variablen den Wert des analogen Pin 2. Damit wird der Wert des Pins überall im Code verfügbar. Wenn der Wert einer Variablen zugeordnet oder verändert wird kann man seine Wert testen, um zu sehen ob er bestimmte Bedingungen erfüllt. Ein Beispiel wie man mit Variablen sinnvoll arbeiten kann zeigt der folgende Code. Hier wird getestet ob ‘inputVariable’ weniger als 100 ist. Ist dies der Fall wird der Wert 100 der ‘inputVariablen’ zugeordnet und das Programm verwendet diesen Wert als Pause (delay). Der minimale Wert von ‘inputVariable’ ist somit in jedem Fall 100.

if (inputVariable < 100) // prüft, ob Variable kleiner als 100 { inputVariable = 100; // wenn wahr, ordne Wert von 100 zu } delay(inputVariable); // benutzt Variable als Verzögerung

Bemerkung: Variablen sollten immer möglichst deutlich beschreibende Namen erhalten um den Code besser lesbar zu machen. Variablen Names wie ’tiltSensor’ oder ‘pushButton’ helfen dem Programmierer und jedem Leser besser zu verstehen was die Variable bewirkt. Namen wie var oder value sagen wenig aus und wurden hier nur als Beispiel verwendet. Als Namen kann alles verwendet werden dass nicht bereits ein Schlüsselwort in der Arduino Sprache ist. Variablen Deklaration Alle Variables müssen vor der Benutzung deklariert werden. Eine Variable zu deklarieren bedeutet ihren Typ wie z.B. int, long, float, etc. zu definieren, einen Namen zu vergeben und optional einen Anfangswert. Dies muss nur einmal im Programm vorgenommen werden. Danach kann der Wert zu jedem Zeitpunkt durch Berechnungen oder verschiedenste Zuordnungen geändert werden. Das folgende Beispiel deklariert ‘inputVariable’ als ‘int’, also Integer Datentyp und setzt den Anfangswert auf 0. Dies nennt man eine ‘einfache Zuordnung’.

int inputVariable = 0;

Eine Variable kann an vielen Orten im Programm deklariert werden. Der Ort der Deklaration bestimmt welche Programmteile Zugriff auf die Variable haben. Variablen Geltungsbereich (=scope) Eine Variable kann am Anfang des Programms vor ‘void setup()’ deklariert werden, lokal innerhalb von Funktionen und manchmal auch innerhalb eine Anweisungsblocks wie zum Beispiel einer Schleife. Wo die Variable deklariert wird bestimmt ihren Geltungsbereich, oder die Fähigkeit bestimmter Programmteile, auf den Wert der Variablen zuzugreifen. Eine globale Variable kann von jeder Funktion und Anweisung des Programms gesehen und benutzt werden. Diese Variable wird zu Beginn des Programms deklariert, noch vor der setup() Funktion. Eine lokale Variable wird nur innerhalb einer Funktion oder eindes Codeblocks (z.B. Schleife) definiert. Sie ist nur sichtbar und nutzbar innerhalb der Funktion bzw. des Codeblocks in der sie deklariert wurde. Deswegen ist es möglich zwei oder mehr Variablen mit dem selben Namen in verschiedenen Teilen des Programms unterschiedliche Werte enthalten. Durch die Sicherstellung, dass nur die Funktion selber Zugriff auf seine eigenen Variablen hat, wird das Programm vereinfacht und man reduziert das Risiko von Fehlern. Das folgende Beispiel zeigt anschaulich wie man Variablen auf verschiedene Weisen deklarieren kann und zeigt ihre Geltungsbereiche.

int value; // ‘value’ ist sichtbar // für jede Funktion (=global) void setup() { // kein Setup erforderlich }   void loop() { int g; // ‘g’ ist innerhalb der loop-Funktion // sichtbar, auch innerhalb der Schleife for (int i=0; i<20;) // ‘i’ ist nur sichtbar { // innerhalb der for-Schleife i++; } float f; // ‘f’ ist erst ab hier sichtbar }
Struktur

Struktur

Struktur Der grundlegende Aufbau der Arduino Programmiersprache ist relativ einfach und teilt sich in mindestens zwei Teile auf. Diese zwei benötigten Teile oder Funktionen umschliessen Blöcke von Anweisungen.

void setup() { anweisungen; }   void loop() { anweisungen; }

Hierbei ist setup() die Vorbereitung und loop() ist die Ausführung. Beide Funktionen sind notwendig damit das Programm ausgeführt werden kann. Die Setup Funktion sollte der Variablen Definition folgen, die noch davor aufgeführt werden muss. Setup muss als erste Funktion in einem Programm druchlaufen werden. Es wird nur einmal ausgeführt und dient dem Setzen von PinMode oder der Initiierung der seriellen Kommunikation. Nach der setup() Funktion folgt die loop() Funktion. Sie beinhaltet Programmcode, der kontinuierlich in einer unendlichen Schleife ausgeführt wird – Eingänge auslesen, Ausgänge triggern, etc. Diese Funktion ist der Kern von allen Arduino Programmen und erledigt die Hauptarbeit. setup() Die setup() Funktion wird einmalig aufgerufen wenn das Programm startet. Benutze diese Funktion um PinModes zu setzen oder die serielle Kommunikation zu starten. Die setup() Funktion muss in jedem Programm vorkommen, auch wenn sie keine Anweisungen enthält.

void setup() { pinMode(pin, OUTPUT); // ‘pin’ als Ausgang definieren }

loop() Nach dem Durchlaufen der setup() Funktion macht die loop() Funktion genau das was ihr Name vermuten lässt und läuft in einer endlosen Schleife. Damit wird dem Programm ermöglicht mit dem Arduino Board über Änderungen, Reaktionen und Kontrollen zu interagieren.

  void loop() { digitalWrite(pin, HIGH); // schaltet ‘pin’ ein delay(1000); // Pause für eine Sekunde digitalWrite(pin, LOW); // schaltet ‘pin’ aus delay(1000); // Pause für eine Sekunde }

Funktionen Eine Funktion ist ein Block von Programmcode, der einen Namen hat und eine Reihe von Anweisungen, die beim Aufruf der Funktion ausgeführt werden. Die Funktionen void setup() und void loop() wurden bereits erklärt. Es gibt noch weitere eingebaute Funktionen, die später behandelt werden. Eigene Funktionen zu Schreiben macht Sinn, um sich wiederholende Aufgaben zu vereinfachen und um die Übersichtlichkeit der Programmstruktur zu fördern. Funktionen werden erstellt indem zuerst der Type der Funktion definiert wird. Dieser ist identisch mit dem Datentyp des zurückgegebenen Wertes, so wie zum Beispiel ‘int’ für den Integer Typen. Wenn kein Wert zurückgegeben werden soll, so wird der Funktionstyp ‘void’ verwendet. Nach der Definition des Types wird der Name festgelegt und in Klammern alle Parameter, die der Funktion übergeben werden sollen.

Typ FunktionsName(parameter) { anweisungen; }

Die folgende Integer Typ Funktion ”delayVal()” wird genutzt um eine Delay Wert, also eine Verzögerung in ein Programm durch Auslesen eines Potentiometers einzubauen. Zuerst wird eine lokale Variable ‘v’ erstellt. Als nächstes wird ‘v’ mit der Stellung des Potentiometers gleichgesetzt, das einen Wert zwischen 0 und 1023 haben kann. Der Wert wird dann durch 4 dividiert um auf eine Skala von 0 bis 255 zu kommen und am Ende wird das Ergebnis der Funktion zum Hauptprogramm zurückgegeben.

int delayVal() { int v; // erstelle temporäre Variable ‘v’ v = analogRead(pot); // lese Potentiometer Wert v /= 4; // Umrechnen von 0-1023 auf 0-255 return v; // errechneten Wert zurückgeben }

{} geschweifte Klammern Geschweifte Klammern (auch ‘geschwungene Klammern’ genannt) definieren den Anfang und das Ende von Funktions- und Anweiungsblöcken so wie bei der ‘void loop()’ Funktion und auch bei der ‘for’ und ‘if’ Anweisung.

typ funktion() { anweisungen; }

Eine öffnende geschwungene Klammer ‘{‘ muss immer von einer schliessenden geschwungenen Klammer gefolgt werden ‘}’. Hier spricht man oft davon, dass die Anzahl der Klammern ausgeglichen sein müssen. Unausgeglichene Klammern führen oft zu kryptischen, undurchschaubaren Fehlern im Kompiler, die manchmal schwer zu finden sind, vor allem in großen Programmen. Die Arduino Programmierumgebung hilft dabei die Augeglichenheit der geschwungenen Klammern zu prüfen. Dafür muss man einfach eine Klammer auswählen oder kurz hinter dieser klicken, und das logisch verbundene Gegenstück wird optisch hervorgehoben. ; Semikolon Ein Semikolon muss am Ende einer Anweisung verwendet werden und dient zur Trennung der Elemente eines Programmes. Ein Semikolon wird auch werwendet um die Elemente einer ”for” Schleife zu trennen.

int x = 13; // deklariert Variable ‘x’ als Integer mit Wert 13

Bemerkung: Das Vergessen eines Semikolons am Ende einer Zeile führt zu einem Fehler im Kompiler. Die Fehlerbeschreibung kann dabei sehr eindeutig sein und auf das fehlende Semikolon direkt hinweisen, das muss aber nicht der Fall sein. Wenn eine undurchschaubarer oder scheinbar unlogischer Fehler gemeldet wird, sollte deswegen als erstes fehlende Semikolons in der Nähe des gemeldeten Fehlers ergänzt werden. /*… */ Block Kommentare Block Kommentare oder mehrzeilige Kommentare sind Textbereiche die vom Programm irgnoriert werden. Sie werden für längere Beschreibungen oder Kommentare verwendet und helfen anderen Autoren Programmteile zu verstehen. Sie fangen mit /* an und enden mit */ und können mehrere Zeilen umfassen.

  /* Dies ist eine eingefügter Block Kommentar bitte den schliessenden Kommentar nicht vergessen – Diese müssen ausgeglichen sein */

Weil Kommentare vom Programm ignoriert werden und damit keinen Speicherplatz verbrauchen sollten sie großzügig verwendet werden. Mit Block Kommentaren kann man auch ganze Programmteile zum Debuggen ungültig machen. Bemerkung: Man kann einzeilige Kommentare in einen Block Kommentar integrieren. Aber es ist nicht möglich einen zweiten Block Kommentar zu einzuschliessen. // Einzeilige Kommentare Einfache einzeilige Kommentare werden mit einem // am Anfang der Zeile definiert und enden mit dem Ende der Zeile. Sie werden vom Programm ignoriert und verbrauchen keinen Speicherplatz.

// dies ist ein einzeiliger Kommentar

Einzeilige Kommentare werden oftmals nach Anweisungen verwendet um mehr Informationen über die verwendete Anweisung zu vermitteln oder um Notizen für zukünftige Änderungen am Code festzuhalten.