Technical Reports and Working Papers Angewandte Datentechnik (Software Engineering) Dept. Of Electrical Engineering and Information Technology The University of Paderborn Graphen und elementare Algorithmen/ Roboterprogrammierung Tutorial zum Projekt „Angewandte Programmierung“ F. Belli, A. Hollmann, S. Padberg Copyright All rights including translation into other languages is reserved by the authors. No part of this report may be reproduced or used in any form or by any means - graphically, electronically or mechanically, including photocopying, recording, taping or information and retrieval systems - without written permissions from the authors and or projects. Technical Report 2006/2 (Version 2.0, März 2009) D-33095 Paderborn, Pohlweg 47-49 http://adt.upb.de Tel.:++49-5251-603445 Fax: 603246 email: [email protected] Zusammenfassung Dieses Tutorial dient zur Vorbereitung auf das Projekt „Angewandte Programmierung“ im Grundstudium des Studiengangs Elektrotechnik/Informationstechnik. Im Anschluss an eine Klärung verwendeter Begriffe, wie z.B. von Adjazenzmatrizen und Adjazenzlisten zur Darstellung von Graphen, werden elementare Algorithmen für Tiefen- und Breitensuche vorgestellt. Es folgen Hinweise auf die Implementierung des eingeführten graphtheoretischen Instrumentariums in der Programmiersprache C. Zusätzlich wird eine Einführung in die Roboterprogrammierung mit dem RV-M1 gegeben. Gliederung 1. 2. Einleitung ........................................................................................................................... 2 (Un-)Gerichtete Graphen – formale Einführung................................................................ 5 2.1. Begriffliche Klärung .................................................................................................. 6 2.2. Darstellung von Graphen ........................................................................................... 7 2.3. Algorithmen auf Graphen........................................................................................... 9 2.3.1. Breitensuche ....................................................................................................... 9 2.3.2. Tiefensuche ...................................................................................................... 12 3. Matrizen und Felder in C ................................................................................................. 15 4. Roboterprogrammierung (RV-M1) .................................................................................. 21 4.1. Abkürzungsverzeichnis ............................................................................................ 21 4.2. Einführung des Roboters RV-M1 ............................................................................ 21 4.3. Roboterarm............................................................................................................... 24 4.3.1. Roboterhand ..................................................................................................... 25 4.4. Drive Unit (DU) ....................................................................................................... 26 4.5. Teaching Box (TB)................................................................................................... 27 4.5.1. Ansteuerungsbefehle ........................................................................................ 27 4.6. Einzelbefehleingabe mit Hilfe eines PCs ................................................................. 29 4.7. Ein kleines „RoboterASM“ Programm (Linux-Bash) ............................................. 30 5. Ansteuerung des Roboters mit Hilfe der Programmiersprache C .................................... 32 5.1. C-Code-Gliederung und Einbinden von Header-Dateien ........................................ 32 5.2. Verarbeitungsprozeß vom Quellcode zur ausführbaren Datei ................................. 34 5.3. Ein kleines C-Programm für den Roboter................................................................ 36 Universität Paderborn Lehrstuhl Angewandte Datentechnik 1. Einleitung Viele Probleme lassen sich durch Objekte und deren Beziehungen zueinander modellieren. „Modellieren“ bedeutet in diesem Zusammenhang, dass das Weltbild auf den betrachteten, relevanten Teil des Ganzen und dessen Eigenschaften reduziert wird. Ein anschauliches, nichtsdestoweniger exaktes Mittel dafür stellen Graphen zur Verfügung, die aus Knoten und Kanten bestehen. Knoten sind Elemente einer Menge gleichartiger Objekte; Kanten hingegen stellen die Beziehungen zwischen den Objekten dar. Folgende Beispiele mögen die Verwendung von Graphen in der Technik und dem Alltag veranschaulichen. Eine elektrische Schaltung besteht aus verschiedenen Bauteilen wie z.B. Transistoren, Widerständen, Kondensatoren etc., die durch Leitungen miteinander verbunden sind. Es ergeben sich Fragen, wie beispielsweise die Bauteile dimensioniert werden oder ob die Schaltung korrekt funktioniert, wenn sie hergestellt wird. Abbildung 1: Schaltplan 1 Bei der Analyse von Schaltwerken wird die Funktionsweise diverser Komponenten grafisch und/oder tabellarisch dargestellt. Dabei ist es üblich, den Knoten und Kanten des jeweiligen Graphen besondere Bedeutungen zuzuweisen. Diese Sachverhalte werden Sie später im Studium, z.B. in der Digitaltechnik, kennen lernen. In diesem Tutorial geht es darum, dass Graphen als allgemeines Darstellungsmittel eingeführt werden und vor allem der Umgang mit Graphen durch geeignete Algorithmen demonstriert wird. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 2 Universität Paderborn Lehrstuhl Angewandte Datentechnik Sequential Network Circuit Diagram State Diagram x=0 x=0 I y1 & ≥1 T y2 & D ≥1 x=1 U D A D x=1 x=1 F/F & & F/F B x clock x=0 a y1 y2 A B C D 0 0 1 1 0 1 1 0 x=1 C x=0 U 0 1 1 0 Truth Table (State Assignments) Abbildung 2: Schaltplan 2 Nicht nur für technische Inhalte, sondern auch für das tägliche Leben werden komplexe Zusammenhänge oft grafisch dargestellt, wie z.B. das Schienennetz der Bahn durch einen Verbund von Bahnhöfen. Ebenso sind Städte durch Straßen verbunden. Kürzeste oder schnellste Wege zwischen zwei Bahnhöfen/Städten oder eine Rundreise, bei der jeder Bahnhof bzw. jede Stadt besucht wird, sind hier von Interesse. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 3 Universität Paderborn Lehrstuhl Angewandte Datentechnik Abbildung 3: Liniennetzplan Programmablaufpläne oder Steuerflussgraphen, wie sie in der Vorlesung Datenverarbeitung vorgestellt wurden, bilden ein weiteres Beispiel. Abbildung 4: Programmablaufplan Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 4 Universität Paderborn Lehrstuhl Angewandte Datentechnik 2. (Un-)Gerichtete Graphen – formale Einführung Ein gerichteter Graph G=(V, E) besteht aus einer Menge V von Knoten (engl. vertex bzw. node) und einer Menge E von (gerichteten) Kanten (engl. arc bzw. edge), wobei gilt E⊆V×V, d.h. E stellt eine Teilmenge des kartesischen Produktes V×V dar. Beispiel (s. Abbildung 5): V = {0,1,2,3,4}; E = {(0, 4), (1, 0), (1, 2), (1, 3), (3, 2), (4, 4)} Die Kante (4, 4) wird auch als Schleife (self loop) bezeichnet. 0 4 1 2 3 Abbildung 5: Ein gerichteter Graph Bei einem ungerichteten Graphen G=(V, E) ist die Kantenmenge E wie folgt definiert: E ⊆ {{x,y} | x,y ∈ V} Beispiel (s. Abbildung 6): V = {a, b, c, d, e} E = {{a, e}, {b, a}, {b, c}, {b, d}, {d, c}, {e}} a e b c d Abbildung 6: Ein ungerichteter Graph Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 5 Universität Paderborn Lehrstuhl Angewandte Datentechnik 2.1. Begriffliche Klärung Im Zusammenhang mit Graphen existieren eine Reihe von Begrifflichkeiten, von denen einige elementare im Folgenden erläutert werden. Man spricht von einem Teilgraphen G’=(V’, E’) eines Graphen G=(V, E), wenn gilt: E’⊆ E und V’⊆ V. Der Knotengrad eines Knotens in einem ungerichteten Graphen entspricht der Summe der Kanten, die in dem Knoten enden. Für den Knoten b des Graphen aus Abbildung 6 beträgt der Knotengrad beispielsweise 3. Im gerichteten Graphen unterscheidet man zusätzlich den Eingangsgrad (Summe der Kanten, die im Knoten eingehen) als auch den Ausgangsgrad (Summe der ausgehenden Kanten). Der Knotengrad eines Knotens entspricht der Summe aus dem Eingangsgrad und Ausgangsgrad des Knotens. Beispielsweise hat der Knoten 1 des Graphen aus Abbildung 5 den Ausgangsgrad 3 und Eingangsgrad 0. Ein Weg bzw. Pfad in einem Graph besteht aus einer Folge von Knoten, die durch Kanten miteinander verbunden sind: (v1, v2, ..., vk) Die Länge des Weges beträgt dann k-1. Im gerichteten Graphen gilt für Pfade: (vi, vi+1) ∈ E, im ungerichteten {vi, vi+1} ∈ E mit i∈ {1,...,k-1}. Ein ungerichteter Graph ist zusammenhängend, wenn von jedem Knoten ein Pfad zu allen anderen Knoten des Graphen existiert. Im gerichteten Graphen wird vom starken Zusammenhang gesprochen. Wenn für einen Weg (v1, v2, ..., vk) gilt, dass seine Kanten paarweise verschieden sind und v1=vk ist, so nennt man diesen Weg im gerichteten Graph einen Zyklus, im ungerichtetem Graphen nennt man solch einen Weg Kreis. Ein Euler-Weg bzw. Euler-Kreis enthält jede Kante eines Graphen genau einmal. Ein Hamilton-Kreis enthält jeden Knoten genau einmal. Von Leonard Euler stammt das bekannte Königsberger Brückenproblem. Es lautet: Gibt es einen Rundweg, so dass man genau einmal die sieben Brücken des Pregels überquert und beim Ausgangspunkt wieder ankommt? Abbildung 7: Königsberger Brückenproblem Oft ist es notwendig, den Knoten und Kanten eines Graphen weitere Informationen zuzuordnen. Kantengewichte können beispielsweise dafür benutzt werden, um Entfernungen zwischen Knoten oder bestimmte Kosten zu ermitteln. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 6 Universität Paderborn Lehrstuhl Angewandte Datentechnik 2.2. Darstellung von Graphen Um Graphen bzw. Graphalgorithmen in Programmen zu behandeln, ist eine geeignete Repräsentation notwendig. Gebräuchliche Arten der Darstellung sind durch die Adjazenzmatrix und die Adjazenzliste gegeben. Dazu werden Knotennamen auf ganze Zahlen abgebildet. Im Weiteren wird davon ausgegangen, dass die Implementierung in einer Programmiersprache erfolgt, in der der Zugriff auf ein Feld mit n Komponenten mit den Indizes von 0,..., n-1 erfolgt. Für den Graphen in Abbildung 5 ist eine Abbildung auf Zahlen nicht notwendig; die Knotenbezeichner können hier direkt als Indizes verwendet werden. Für den Graphen in Abbildung 6 ist eine Abbildung auf die Zahlen von 0,...,4 erforderlich. Im Allgemeinen erfolgt eine Abbildung auf die Zahlen von 0,...,|V|-1; die Schreibweise |V| gibt die Kardinalität der Menge V an, d.h. die Anzahl der Knoten des Graphen. Bei der Darstellung als Adjazenzmatrix wird eine Matrix der Dimension |V|×|V|, bestehend aus booleschen Variablen, genutzt. Ein Eintrag [x][y] wird in der Matrix auf true bzw. 1 gesetzt, falls im gerichteten Graphen eine Kante (x,y) existiert. Eine Kante {x,y} eines ungerichteten Graphen wird aus Vereinfachungsgründen in den Einträgen [x][y] und [y][x] gespeichert, obwohl dieses eine redundante Form der Speicherung darstellt. Falls zwischen zwei Knoten keine Kante existiert, wird der Eintrag im Feld auf false bzw. 0 gesetzt. Der Graph aus Abbildung 5 sieht als Adjazenzmatrix wie folgt aus: 0 1 2 3 4 0 0 0 0 0 1 1 1 0 1 1 0 2 0 0 0 0 0 3 0 0 1 0 0 4 0 0 0 0 1 Eine weitere Darstellungsart ist die Adjazenzliste. Zu jedem Knoten werden die ihm adjazenten (benachbarten) Knoten in einer Liste gespeichert. Dementsprechend sieht der Graph aus Abbildung 5 wie folgt aus: 0 4 1 0 2 3 2 3 2 4 4 Die nachfolgende Tabelle enthält eine Gegenüberstellung der beiden Darstellungsarten bezüglich der Laufzeit verschiedener Operationen (im O-Kalkül). Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 7 Universität Paderborn Lehrstuhl Angewandte Datentechnik Adjazenzmatrix Adjazenzliste Speicherbedarf O(|V|∗|V|) O(|V|+|E|) Einfügen einer Kante O(1) O(1) Einfügen eines Knotens O(|V|) O(1) Löschen einer Kante O(1) O(|V|) Löschen eines Knotens O(|V|∗|V|) O(|V|+|E|) In der Regel werden Adjazenzmatrizen nur dann benutzt, wenn der Graph genügend viele Kanten enthält (im worst case gilt für die Anzahl der Kanten eines Graphen: |E| = |V|2). Ansonsten wird relativ viel Speicherplatz verschwendet. Allein das Initialisieren der Matrix kostet quadratisch viel Zeit in Bezug auf die Anzahl der Knoten. Somit kann allein das Initialisieren die dominierende Laufzeit für einen Algorithmus darstellen, während der eigentliche Algorithmus in Linearzeit in Bezug auf die Anzahl der Knoten abläuft. Werden Kantengewichte im Graphen benötigt, so können die Einträge innerhalb der Matrix benutzt werden, d.h. anstatt true und false werden Kantengewichte eingetragen, die zugleich anzeigen, ob eine Kante existiert. An den Positionen der Matrix, die mit false gekennzeichnet wurden, muss nun ein Wert stehen, der ein unzulässiges Kantengewicht darstellt, wie z.B. -1 für Kantengewichte ∈ ℕ oder −∞ für Kantengewichte ∈ ℤ. Falls keine Kantengewichte angegeben sind, wird in der Regel von einer Gewichtung von 1 ausgegangen. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 8 Universität Paderborn Lehrstuhl Angewandte Datentechnik 2.3. Algorithmen auf Graphen Für Graphen existiert eine Vielzahl von Algorithmen. Zwei elementare, oft benutzte Algorithmen werden im Folgenden vorgestellt – die Tiefensuche (engl. depth-first search) und Breitensuche (engl. breadth-first search). Beide Verfahren dienen dazu, systematisch die Knoten und Kanten eines Graphen zu überprüfen, bis das gesuchte Objekt gefunden wird – falls vorhanden. Damit lassen sich weitere Fragestellungen beantworten, wie zum Beispiel, ob ein Graph zusammenhängend ist, ob er Zyklen enthält oder ob zwischen zwei Knoten ein Weg existiert. Ausgangspunkt für die Suche stellt ein (beliebiger) Startknoten dar, von dem dann die anderen Knoten aus „besucht“ werden (sofern möglich). 2.3.1. Breitensuche Bei der Breitensuche werden jeweils die Knoten besucht, die die gleiche Entfernung zum Startknoten haben, d.h. es werden alle Knoten mit der Pfadlänge k zum Startknoten vor allen Knoten der Pfadlänge k+1 besucht, d.h. ausgehend von einem Startknoten s werden zunächst alle Knoten besucht, die die Entfernung 1 haben, danach die Entfernung 2, 3, ... usw. Das nachfolgende Beispiel verdeutlicht die Vorgehensweise der Breitensuche. Der Startknoten sei Knoten 5. Zunächst wird Knoten 1 besucht (Entfernung beträgt 1 zum Startknoten). Ein Knoten bekommt eine graue Schraffierung, wenn er zum ersten Mal besucht wird. Eine Kante wird gestrichelt dargestellt, wenn sie zum ersten Mal „durchlaufen“ wurde: 1 5 1 5 0 0 2 4 3 2 4 3 Anschließend werden die Knoten 4 und 5 besucht (Entfernung beträgt 2). Knoten 5 wurde bereits besucht und wird daher nicht weiter betrachtet. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 9 Universität Paderborn Lehrstuhl Angewandte Datentechnik 1 5 1 5 0 0 2 4 2 4 3 3 Es folgen die Knoten mit der Entfernung 3. Dieses sind die Knoten 0 und 3. 1 5 1 5 0 0 2 4 2 4 3 3 Nachfolgeknoten von 0 und 3 haben die Entfernung 4, wobei die Nachfolger 1 und 5 des Knotens 0 sowie der Nachfolger 0 des Knotens 3 schon betrachtet wurden. 1 5 1 5 0 0 2 4 3 2 4 3 Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 10 Universität Paderborn Lehrstuhl Angewandte Datentechnik 1 5 1 5 0 0 2 4 2 4 3 3 Zuletzt wird von Knoten 2 aus Knoten 5 besucht, der allerdings schon besucht wurde. 1 5 0 2 4 3 Der folgende Algorithmus (dargestellt in einem Pseudocode, d.h. vereinfachter Schreibweise) stellt eine einfache Breitensuche dar. Die Funktion BFS hat die drei Parameter G (der Graph G=(V, E)), s (der Startknoten) und ein Feld visited[] der Größe |V|, in dem vermerkt wird, ob ein Knoten bereits besucht wurde. Zudem wird eine verkettete Liste Q benutzt, die nach dem first-in first-out-Prinzip (FiFo) arbeitet. Für die Liste Q gibt es die Funktion enqueue zum Einfügen eines Elementes und die Funktion dequeue, die das erste Element der Liste löscht und zurückgibt (Erinnerung: „queue“ bedeutet „Warteschlange“). Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 11 Universität Paderborn Lehrstuhl Angewandte Datentechnik BFS(G, s, visited[]) 1 Q ←∅ 2 ENQUEUE(Q,s) 3 while Q ≠ ∅ 4 u ← DEQUEUE(Q) 5 visited[u] := true 6 for each node v ∈ successors(u) 7 if visited[v] = false 8 ENQUEUE(Q,v) Der Funktion BFS arbeitet wie folgt: - In Zeile 1 wird die Liste initialisiert – angedeutet durch die leere Menge. - Dann wird der Startknoten in die Liste eingefügt, in Zeile 2. - Solange die Liste nicht leer ist (Zeile 3), d.h. solange noch Knoten existieren, die noch nicht besucht wurden, wird zunächst ein Knoten u aus der Liste herausgeholt (Zeile 4) - und als besucht markiert (Zeile 5). - Für jeden Nachfolgeknoten v von u (d.h. (u,v) ∈ E) (Zeile 6) wird geprüft, ob dieser bereits besucht wurde (Zeile 7). - Falls nicht, wird v in die Liste eingefügt (Zeile 8). 2.3.2. Tiefensuche Im Gegensatz zur Breitensuche, wo jeweils Knoten mit der gleichen Entfernung zum Startknoten besucht werden, besteht die Strategie der Tiefensuche darin, immer tiefer im Graphen zu suchen, sofern möglich. Das folgende Beispiel soll das Prinzip der Tiefensuche verdeutlichen. Die Tiefensuche wird im Knoten 5 gestartet. Gibt es mehrere Möglichkeiten einen Nachfolgeknoten zu wählen, so wird im Beispiel der Knoten mit dem kleineren Index gewählt. Im Beispiel wird somit als nächstes der Knoten 1 besucht (andere Knoten stehen hier nicht zur Auswahl). Ein Knoten bekommt eine graue Schraffierung, wenn er zum ersten Mal besucht wird. Eine Kante wird gestrichelt dargestellt, wenn sie zum ersten Mal „durchlaufen“ wurde. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 12 Universität Paderborn Lehrstuhl Angewandte Datentechnik 1 5 1 5 0 0 2 4 2 4 3 3 Nach Knoten 1 wird der Knoten 4 besucht. Dort besteht die Wahl, zunächst Knoten 0 oder Knoten 3 zu besuchen. Es wird der Knoten mit dem kleineren Index gewählt, d.h. Knoten 0. 1 5 1 5 0 0 2 4 2 4 3 3 Im Knoten 0 gibt es 3 mögliche Nachfolger: 1, 2 und 5. Wieder wird der Knoten mit dem kleinsten Index gewählt – also Knoten 1. Bei Besuch des Knoten 1 wird nun festgestellt, dass dieser bereits besucht worden ist. Dies hat zur Folge, dass die Suche im Knoten 1 nicht mehr fortgesetzt wird. Es erfolgt ein (rekursiver) Rücksprung zum Knoten 0. Von dort aus wird nun Knoten 2 besucht. 1 5 1 5 0 0 2 4 3 2 4 3 Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 13 Universität Paderborn Lehrstuhl Angewandte Datentechnik Von Knoten 2 aus wird Knoten 5 besucht. Da Knoten 5 bereits besucht wurde, erfolgt ein Rücksprung zu Knoten 2. Dort gibt es allerdings keine weiteren Nachfolger, so dass ein zweiter Rücksprung zu Knoten 0 erfolgt. Von dort wird nochmals Knoten 5 besucht. 1 5 1 5 0 0 2 4 2 4 3 3 Es erfolgt ein Rücksprung bis zum Knoten 4. Von dort aus werden die Knoten 3 und dann der Knoten 0 besucht. 1 5 1 5 0 0 2 4 3 2 4 3 Es erfolgen Rücksprünge von Knoten 0 zu 3, von 3 zu 4 und von 4 zu 1. Dort wird noch einmal Knoten 5 besucht. Abschließend erfolgt ein Rücksprung zum Knoten 5, in dem die Suche gestartet wurde. Da keine weiteren Nachfolgeknoten von Knoten 5 aus existieren, wird die Tiefensuche beendet. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 14 Universität Paderborn Lehrstuhl Angewandte Datentechnik 1 5 0 2 4 3 Der nachfolgende Pseudocode stellt eine rekursive Variante der Tiefensuche dar. Die Tiefensuche DFS hat die drei Parameter G (der Graph G=(V, E)), v (ein (Start-)Knoten) und ein Feld visited[] der Größe |V|, in dem vermerkt wird, ob ein Knoten bereits besucht wurde. DFS(G, u, visited[]) 1 visited[u] := true 2 for each node v ∈ successors(u) 3 if visited[v] = false 4 DFS(G, v, visited) - In Zeile 1 wird der Knoten v als besucht markiert. - Für jeden Nachfolgeknoten v von u (d.h. (u,v) ∈ E) (Zeile 2) wird geprüft, ob dieser bereits besucht wurde (Zeile 3). - Falls nicht, wird die Tiefensuche rekursiv aufgerufen. Die Tiefensuche kann ebenfalls nicht-rekursiv implementiert werden – ähnlich wie die Breitensuche. Dafür ist die FiFo-Liste durch eine last-in first-out-Liste (LiFo) zu ersetzen. 3. Matrizen und Felder in C In der Programmiersprache C gibt es verschiedene Möglichkeiten, eine Matrix bzw. ein Feld mit 2 Dimensionen anzulegen. Im Folgenden wird beschrieben, wie eine n×n-Matrix, die aus int-Werten bestehen soll, angelegt werden kann. Falls die Dimension der Matrix beim Programmieren bekannt ist und nur lokal innerhalb einer Funktion benötigt wird, reicht die folgende Deklaration: int matrix[5][5]; Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 15 Universität Paderborn Lehrstuhl Angewandte Datentechnik Mit Beendigung des umfassenden Blocks (in der Regel mit Funktionsende) wird der Speicher automatisch wieder freigegeben. Das folgende Konstrukt darf erst seit dem ANSI-C99 Standard benutzt werden - die Größe der Matrix bestimmt sich hier erst zur Laufzeit: int n; ... //n bestimmt sich zur Laufzeit n = ... ... int matrix[n][n]; Falls die Gültigkeit mit Blockende/Funktionsende nicht aufhören darf oder sich die Größe erst zur Laufzeit bestimmt (Compiler bis ANSI-C90 Standard), muss der Speicher dynamisch angefordert werden. Dieses wird durch den nachfolgenden Programmcode geleistet: int n; int i,j; int **ppMatrix; //n = ... ppMatrix = (int **) malloc (n*sizeof(int*)); for(i=0; i<n; i++) ppMatrix[i] = (int*) malloc(n*sizeof(int)); //Initialisierung/Berechnung for(i=0; i<n; i++) for(j=0; j<n; j++) ppMatrix[i][j] = 0; //Speicherfreigabe for(i=0; i<n; i++) free(ppMatrix[i]); free(ppMatrix); Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 16 Universität Paderborn Lehrstuhl Angewandte Datentechnik Nun folgt eine detailliertere Betrachtung. Die folgende Deklaration legt eine Zeigervariable namens ppMatrix an. Typ ist ein Zeiger auf einen int-Zeiger (**). int **ppMatrix; Anschließend wird ein dynamisches Feld der Länge n angelegt, dessen Komponenten aus Zeigern auf int bestehen: ppMatrix = (int **) malloc (n*sizeof(int*)); Für n=4 sieht das Feld nun folgendermaßen aus: Die folgende Schleife legt nun den „eigentlichen“ Speicher für die Matrix an: for(i=0; i<n; i++) ppMatrix[i] = (int*) malloc(n*sizeof(int)); Die Freigabe des Speichers läuft wiederum spaltenweise in zwei Schritten ab: Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 17 Universität Paderborn Lehrstuhl Angewandte Datentechnik for(i=0; i<n; i++) free(ppMatrix[i]); free(ppMatrix); Alternativ zu dem eben beschriebenen Verfahren ist es möglich, ein eindimensionales Feld der Größe n2 anzulegen und dieses über einen entsprechenden Index anzusprechen: int *pMatrix, n; ... pMatrix = (int *) malloc (n*n*sizeof(int)); Der Zugriff auf das Feld sieht nun wie folgt aus: for(i=0; i<n; i++) for(j=0; j<n; j++) pMatrix[i*n+j] = 0; Die Speicherfreigabe erfolgt durch: free(pMatrix); Vorteilhaft an dieser Alternative ist die einfachere Art der Speicheranforderung und -freigabe. Allerdings ist der Zugriff auf das Feld schwieriger bzw. in der Regel fehlerträchtiger. Bei der Verwendung von Matrizen (Feldern) kann der Bedarf entstehen, die Matrix zur Laufzeit zu verkleinern oder zu vergrößern. Eine denkbare Lösung ist es, eine zweite Matrix der neuen benötigen Dimension zu erstellen, die Werte der alten Matrix zu kopieren und schließlich die alte Matrix freizugeben. Eine schnellere bzw. effizientere Möglichkeit bietet sich in C mittels des Befehls realloc an: void *realloc (void *pointer, int size); Wurde mittels der Funktion malloc dynamischer Speicher angefordert, so lässt sich mittels realloc die Größe des Speicherbereichs nachträglich ändern. Beim Aufruf von realloc wird der Zeiger auf den Anfangsbereich des Speichers übergeben (pointer) sowie die neue Größe (size). Die Daten des Speicherbereichs bleiben bis zum Minimum aus der alten und neuen Größe unverändert. Zurückgegeben wird die Anfangsadresse des (neuen) Speicherbereichs oder NULL, falls die Anforderung nicht erfüllt worden kann. Im folgenden Beispiel wird ein Feld, dessen Speicher dynamisch angefordert wurde, um eine weitere Komponente vergrößert: Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 18 Universität Paderborn Lehrstuhl Angewandte Datentechnik int n=5; int *pMatrix = (int*) malloc (n*sizeof(int)); ... pMatrix = (int *) realloc(pMatrix, (n+1)*sizeof(int)); Analog lässt sich auch ein zweidimensionales Feld vergrößern. Das nachfolgende Beispiel deutet an, wie ein dynamisch angelegtes Feld der Dimension 4×4 auf 5×5 erweitert werden kann: Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 19 Universität Paderborn Lehrstuhl Angewandte Datentechnik Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 20 Universität Paderborn Lehrstuhl Angewandte Datentechnik 4. Roboterprogrammierung (RV-M1) In der Industrie werden seit den sechziger Jahren des vorigen Jahrhunderts Industrieroboter eingesetzt, um gefährliche Arbeiten zu erledigen oder monotone Vorgänge nicht von Menschen durchführen zu lassen. In diesem Projekt wird der Industrieroboter „RV – M1“ der Firma Mitsubishi verwendet. Die Abbildungen stammen aus dem Benutzerhandbuch des Roboters. 4.1. Abkürzungsverzeichnis DU LH PC Pos. RAM RH TB Drive Unit Linke Hand Personal Computer Position Random Access Memory Rechte Hand Teaching Box 4.2. Einführung des Roboters RV-M1 Abbildung 1 zeigt den Aufbau des Robotersystems. Zu diesem System gehören neben den motorgesteuerten Roboterarmen bzw. der motorgesteuerten Händen, auch die „Drive Units (DUs)“ (die Ansteuerungsrechner) im Schaltschrank, die „Teaching Boxes (TBs)“, mit der die Roboter manuell gesteuert werden können und ein PC, der die Ansteuerbefehle später an die Drive Units senden wird. In diesem Projekt werden zwei Roboter mit Hilfe von zwei DUs und einem PC angesteuert (siehe Abbildung 2 ). Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 21 Universität Paderborn Lehrstuhl Angewandte Datentechnik Abbildung 1 Das Robotersystem (zwei RV-M1 von Mitsubishi) Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 22 Universität Paderborn Lehrstuhl Angewandte Datentechnik Qualitätsdepots 1&2 Roboter 2 Förderband 2 LH RH 1 LH RH Greifarm Roboter 1 Förderband Ausschussablage Greifarm Förderband Qualitätsablage Förderband Qualitätsablage Förderband Ausschussablage Förderband Qualitätsablage Objektmatrix Abbildung 2 Schematischer Aufbau des Projektsystems Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 23 Universität Paderborn Lehrstuhl Angewandte Datentechnik 4.3. Roboterarm Die Namen der Gelenke des Roboters entsprechen denen des menschlichen Körpers (siehe Abbildung 3 ). Der Bewegungsraum des Roboters ist abgegrenzt, es ergeben sich folgende Bereiche: Drehung (Rumpf/Mittelstück): Drehgeschwindigkeit (Rumpf/Mittelstück): Drehung (Schulter): Drehgeschwindigkeit (Schulter): Drehung (Ellenbogen): Drehgeschwindigkeit (Ellenbogen): Neigungswinkelbereich (Handgelenk): Neigungswinkelgeschwindigkeit (Handgelenk): Drehung (Handgelenk): Drehgeschwindigkeit (Handgelenk): max. 300° max. 120° / s max. 130° max. 72° / s max. 130° max. 72° / s ±90° max.100° / s max. 300° max. 120° / s bel Abbildung 3 Bezeichnungen und Arbeitsraum Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 24 Universität Paderborn Lehrstuhl Angewandte Datentechnik 4.3.1. Roboterhand Die Roboterhand (siehe Abbildung 4 ) kann eine maximale Masse von 3,5kg tragen, wobei die Haltekraft in 16 Schritten erhöht werden kann. Die maximale Griffweite ist mit 60mm erreicht. Die Hand wird über das Spiralkabel an die DU angeschlossen und kann die binären Zustände offen (O, open) und geschlossen (C, closed) annehmen. Abbildung 4 Roboterhand Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 25 Universität Paderborn Lehrstuhl Angewandte Datentechnik 4.4. Drive Unit (DU) Abbildung 5 zeigt das Bedienfeld und die Rückseite der DU. Das Bedienfeld bietet einfache Schaltmöglichkeiten, die auf das interne Programm Einfluss nehmen kann. EMG. STOP unterbricht einen Programmablauf (Error-LED blinkt), es ist ein Power-Reset zum erneuten Starten notwendig. STOP unterbricht einen Programmablauf (ein laufender move-Befehl wird noch ausgeführt). START beginnt einen unterbrochenen oder neuen Programmablauf von vorn. Es gibt je einen separaten Anschluss für die TB und für die serielle Schnittstelle, über welche ein Programm vom PC an die DU gesendet werden kann. Der technische Aufbau der DU bleibt bei diesem Projekt unverändert. Rückansicht Abbildung 5 Drive Unit Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 26 Universität Paderborn Lehrstuhl Angewandte Datentechnik 4.5. Teaching Box (TB) Wie in Kapitel 4.2 schon erwähnt wurde, ist es möglich, den Roboterarm und die Rotoberhand manuell zu steuern. Dazu wird die in Abbildung 6 gegebene TB verwendet. Die Funktionen der wichtigsten Tasten werden im Kapitel 4.5.1 erläutert. Abbildung 6 Teaching Box 4.5.1. Ansteuerungsbefehle Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 27 Universität Paderborn Lehrstuhl Angewandte Datentechnik Folgende Befehle werden im Projekt für die Einführung (manuelle Ansteuerung) benötigt: NST (+ENT) ORG (+ENT) Anfahren der Nestposition (Kalibrierung Mechanik und Elektronik) Anfahren der Origin-Position X+/B+ X-/BY+/S+ Y-/SZ+/E+ Z-/EP+ PR+ R◄O► ►C◄ Rumpfdrehung im Uhrzeigersinn Rumpfdrehung entgegen dem Uhrzeigersinn Schulter aufwärts Schulter abwärts Ellebogen aufwärts Ellebogen abwärts Handgelenk aufwärts Handgelenk abwärts Drehung Handgelenk im Uhrzeigersinn Drehung Handgelenk entgegen dem Uhrzeigersinn Öffnen der Hand Schließen der Hand Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 28 Universität Paderborn Lehrstuhl Angewandte Datentechnik 4.6. Einzelbefehleingabe mit Hilfe eines PCs Die Befehle der TU und viele weitere können auch über eine serielle Schnittstelle an die DU gesendet werden, um den Roboter zu steuern. Die Befehle, die über die Schnittstelle als ASCII-Zeichen an die DU übertragen werden, bestehen nur aus zwei Buchstaben und optional aus einem oder mehreren Parametern. Beispiel: NST (TB) -> NT In unserem Projekt wird ein PC mit Linux als Betriebssystem im Shell-Modus (Eingabeaufforderung) verwendet. Daher lautet die Befehlsfolge für das obere Beispiel: echo “NT“ > ttyS0 echo “NT“ > ttyS1 für Roboter 1 (an serieller Schnittstelle 0) und für Roboter 2 (an serieller Schnittstelle 1) Um die Bewegungsgeschwindigkeit eines Roboters einzustellen, wird folgender Befehl verwendet: SP <Geschwindigkeit (0 bis 9)>, <Beschleunigungsparameter (H oder L)> z.B.: echo “SP 9, H“ > ttyS0 für max. Geschwindigkeit für Roboter 1 Da sich die Roboterbefehlssprache sehr einer hardwarenahen Programmiersprache („Assembler“) ähnelt, kann im Folgenden der Begriff „RobASM“ verwendet werden. Sämtliche Befehle sind im Handbuch des Roboters aufgelistet. Desweiteren sind im Projektverzeichnis des PCs zwei Links auf die seriellen Schnittstellen angelegt, um die Angabe „ttySx“ zu umgehen. Die Namen der Links lauten „robo1“ und „robo2“. Somit ist es möglich, die Einzelbefehle, wie folgt, zu senden: echo “<Befehl>“ > robo1 echo “<Befehl>“ > robo2. oder Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 29 Universität Paderborn Lehrstuhl Angewandte Datentechnik 4.7. Ein kleines „RoboterASM“ Programm (Linux-Bash) Die Befehle, die in Kapitel 4.6 erläutert wurden, werden immer sofort ausgeführt. Um ein RobASM-Programm zu erstellen, müssen Zahlen, die die Reihenfolge angeben, vor jeden Befehl angegeben werden. Die nummerierten Befehle werden in das RAM der DU geladen, um diese später durch den Befehl „RN“ zu starten. Ein ausführbares Programm kann durch eine Linux-Bash-Datei realisiert werden. Eine BashDatei enthält Unix-Kommandozeilenbefehle, die nacheinander abgearbeitet werden. Den Kopf der Datei bildet die Anweisung „#!/bin/zsh“. Diese gibt an, dass die folgenden Befehle in einer Unix/Linux Eingabeaufforderung mit Namen „zShell“ ausgeführt werden sollen. Beispiel (Linux-Bash code) der Datei: „Mini-Programm.robasm“: #!/bin/zsh echo "10 NT" > robo1 echo "11 SP 3" > robo1 echo "12 TI 10" > robo1 echo "13 MO 1" > robo1 echo "14 TI 10" > robo1 # # # # # # Folgendes Programm mit Linux zShell ausfuehren Roboter 1: Fahre in die Nestposition Roboter 1: Geschwindigkeit auf 3 setzen Roboter 1: 1 sec warten Roboter 1: Bewegung zur einprogrammierten Pos.1 Roboter 1: 1 sec warten echo "15 MJ 0, 0, 0, +90, 0" > robo1 # Roboter 1: Handgelenk 90deg nach oben bewegen echo echo echo echo echo echo echo "16 "17 "18 "19 "22 "23 "24 GC" > robo1 GO" > robo1 GC" > robo1 GO" > robo1 TI 10" > robo1 MO 1" > robo1 ED" > robo1 # # # # # # # Roboter Roboter Roboter Roboter Roboter Roboter Roboter 1: 1: 1: 1: 1: 1: 1: Hand schliessen Hand oeffen Hand schliessen Hand oeffen 1 sec warten Bewegung zur einprogrammierten Pos.1 Programmende echo "PD 1, -2.7, +287.4, +333.3, -90.2, -3.1" > robo1 # Roboter 1: Pos.1 definieren echo "TI 5" > robo1 # Roboter 1: 0,5 sec warten Durch die folgenden manuellen Eingaben wird das obige Programm 1)geladen, dann 2)gestartet: 1) ./Mini-Programm.robasm 2) echo "RN" > robo1 Hinweis: Die Befehle „PD 1,…“ und „TI 5“ werden in dieser Reihenfolge als erstes ausgeführt, ohne den Befehl „RN“ aufzurufen, da keine Programmzeilennummern definiert sind. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 30 Universität Paderborn Lehrstuhl Angewandte Datentechnik Es kann auch eine reine txt-Datei an den Roboter gesendet werden, es gilt: 1) cat Mini-Programm.txt > robo1 2) echo "RN" > robo1 (oder: ./run.sh, startet beide Roboter gleichzeitig) Beispiel der Datei „Mini-Programm.robasm“ als txt-Datei: 10 11 12 13 14 NT SP TI MO TI 3 10 1 10 15 MJ 0, 0, 0, +90, 0 16 17 18 19 22 23 24 GC GO GC GO TI 10 MO 1 ED PD 1, -2.7, +287.4, +333.3, -90.2, -3.1 TI 5 Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 31 Universität Paderborn Lehrstuhl Angewandte Datentechnik 5. Ansteuerung des Roboters mit Hilfe der Programmiersprache C In diesem Projekt sollen die bereits vorliegende C-Bibliothek „robot.c“ verwendet werden. Ein C-Programm dieses Projekts muss die Anweisung: #include "robot.h" enthalten. Im Weiteren können die Prototypen, die Kopfzeilen der Funktionen abgeschlossen mit Semikolon, der Datei robot.h verwendet werden. Eine Liste der Befehle liegt im Anhang 1.1 des Aufgabenblatts vor. Mit den Funktionen können Konstrukte gebildet werden, die für die Lösung der Aufgaben notwendig sind. 5.1. C-Code-Gliederung und Einbinden von Header-Dateien Wenn ein großes Programm mit der Programmiersprache C implementiert werden soll, empfiehlt es sich nicht nur Unterfunktionen über der Hauptfunktion int main() in einer Datei zu erstellen, sondern den C-Code in verschiedene Dateien aufzugliedern. Dies hat den Vorteil, daß einzelne Funktionalitäten voneinander getrennt werden können und dadurch der Code übersichtlicher wird. Es werden mehrere Moduln erzeugt, die zunächst getrennt kompiliert und später durch den Linker verknüpft werden (siehe dazu Kapitel 5.2). Folgende Konvention erleichtert die Modularisierung: Die Hauptfunktion int main() wird separat vom übrigen C-Code in die Hauptdatei main.c gespeichert. In der Hauptfunktion werden nur die Aufgaben aufgerufen, die ausgeführt werden sollen. Die Aufgaben werden in getrennten Dateien (jedoch in einem gemeinsamen Ordner) abgelegt (z.B.: aufg1.c, aufg2.c, usw.). Diese Aufgaben-Dateien sollten dann die Pseudo-Hauptfunktion int aufgx() enthalten. Dann müssen die Funktionen der Aufgaben-Dateien in die Datei main.c eingebunden werden. Beispiel: main.c aufgx.c #include “aufgx.h“ #include “aufgx.h“ int main() { aufgx(); return 0; } int aufgx() { ... return 0; } Dazu werden Header(.h)-Dateien verwendet. Eine Einbindung der c-Datei selbst würde bei der Linker-Phase zu einem Fehler führen, da dann der Code mehrfach eingebunden würde. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 32 Universität Paderborn Lehrstuhl Angewandte Datentechnik Header-Dateien enthalten die Prototypen der Funktionen aus der zugehörigen C-Code(.c)Datei; zusätzlich werden Datentyp-Definitionen und Präprozessor-Befehle aus dem zugehörigen C-Code ausgelagert. Beispiel: main.c aufgx.c #include “aufgx.h“ #include “aufgx.h“ int main() { aufgx(); return 0; } int aufgx() { ... return 0; } aufgx.h int aufgx(); Wird in der Hauptdatei die Aufgaben-Headerdatei eingebunden, so können die Funktionen der Aufgaben-Datei aufgerufen werden. Dabei bedeutet: #include “aufgx.h“, daß die Hauptdatei, sowie die Aufgaben- und Aufgaben-Header-Datei in einem Verzeichnis liegen müssen, damit der Verarbeitungsprozeß die Header-Datei und die entsprechende C-Code-Datei berücksichtigt. Die bekannte Schreibweise mit eckigen Klammern #include <aufgx.h> bedeutet, daß die Header-Datei sich in einem System-Verzeichnis (des Compilers) befindet (Systembibliotheken). Selbstverständlich können wiederum die Aufgaben-Dateien weitere Unteraufgaben-Dateien über Header-Dateien einbinden. Es kann vorkommen, daß in der Hauptfunktion Aufgaben, sowie Unteraufgaben aufgerufen werden sollen, wobei die Aufgaben-Datei die Unteraufgaben-Datei einbindet. Dann entsteht das „Problem“, daß im Dateienverbund die Unteraufgaben-Header-Datei mehrfach eingebunden wird. Dies kann durch eine „Präprozessor-Anweisung“ verhindert werden. Beispiel: aufgx.h #ifndef AUFGX_H_ #define AUFGX_H_ ... int aufgx(); ... #endif // AUFGX_H_ // falls Makro AUFGX_H_ noch nicht def.=> springe in Bedingung // und definiere Makro AUFGX_H_ // Ende des “Blocks“ Es folgt der Verarbeitungsprozeß vom Quellcode zur ausführbaren Datei. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 33 Universität Paderborn Lehrstuhl Angewandte Datentechnik 5.2. Verarbeitungsprozeß vom Quellcode zur ausführbaren Datei Die folgende Abbildung 7 zeigt die Verarbeitungsstufen vom Quellcode zur ausführbaren Datei. main.c aufgx.c Uaufgy.c #include „aufgx.h“ #include „Uaufgy.h“ … #include „aufgx.h“ … #include „aufgy.h“ … main.c aufgx.c Uaufgy.c … … … main.o aufgx.o Uaufgy.o … … … linker main.exe (Unix: a.out) … Abbildung 7 Verarbeitungstufen Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ compiler … preprocessor #include „Uaufgy.h“ … source code Uaufgy.h header-files aufgx.h 34 Universität Paderborn Lehrstuhl Angewandte Datentechnik Während der Präprozessor-Verarbeitungsphase werden die definierten PräprozessorAnweisungen (z.B. #include, #ifndef, #define und #endif) expandiert und die Kommentare werden entfernt. Der Präprozessor löst die Makros und die Anweisungen auf. Anschließend wird der modifizierte Quellcode dateiweise kompiliert und es wird für jede Quellcode-Datei getrennt der Objekt-Code erstellt. Falls Syntax- oder Semantik-Fehler vorliegen, wird die Übersetzung mit einer Fehlermeldung abgebrochen. Typische Syntax-Fehler sind z.B. falsch geschriebene Anweisungen. Ein typischer Semantik-Fehler ist die Verwendung von nicht deklarierten Variablen im Quellcode. Erst durch den Linker werden die einzelnen Objekt-Codes zusammen mit anderen ObjektCodes, wie z. B. C-Bibliotheken, verknüpft. Hier wird zusätzlich überprüft, ob Objekt-Code doppelt vorhanden ist und es wird, falls nötig, eine entsprechende Fehlermeldung ausgegeben. Wenn keine Fehler vorliegen, wird die ausführbare Datei erstellt, welche im jeweiligen Betriebssystem gestartet werden kann. Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 35 Universität Paderborn Lehrstuhl Angewandte Datentechnik 5.3. Ein kleines C-Programm für den Roboter Zur Einführung in die Roboter-Programmierung sei folgendes Beispiel gegeben: Roboter 1 soll den ersten Stein der Objektmatrix auf das Förderband setzen und mit Hilfe der Rückgabedaten des Förderbandsystems einen identifizierten Metall-Stein in das Qualitätsdepot 1, RH bzw. einen identifizierten weißen Stein zurück an die alte Matrix Position legen. Da das Robotersystem einer hardwarenahen Programmiersprache („Assembler“) ähnelt, müssen Marken gesetzt werden, damit durch Sprung zu einer Marke Unterfunktionen definiert werden können. Sämtliche Funktionen, die in der Prozedur-Funktion int ItemSort(robot *r1) aufgerufen werden, stammen aus der Roboter-Bibliothek robot.c. Die Funktionen der Bibliothek werden im Aufgabenblatt aufgeführt. Realisierung: /***************************************************** * File: main.c WORKS !! * * Gruppe 0: Sascha Padberg 7654321 * * * * Date: 11.02.2009 * * Description: main function for Project * *****************************************************/ #include "robot.h" #include "ItemSort.h" int main() { robot rob_r1; robot *r1=&rob_r1; Variablendefinition für Roboter 1 ItemSort(r1); Aufrufen der Funktion ItemSort für Roboter 1 return 0; Ende des Programms } /***************************************************** * File: ItemSort.h WORKS !! * * Gruppe 0: Sascha Padberg 7654321 * * * * Date: 09.03.2009 * * Description: Header file of Example-Program * * for Tutorial * *****************************************************/ #ifndef ITEMSORT_H_ #define ITEMSORT_H_ int ItemSort(robot *r1); #endif /* ITEMSORT_H_ */ Makrodefinition von Header-Datei ItemSort.h beginnen Prototyp der Funktion ItemSort Ende der Makrodefinition Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 36 Universität Paderborn Lehrstuhl Angewandte Datentechnik /***************************************************** * File: ItemSort.c WORKS !! * * Gruppe 0: Sascha Padberg 7654321 * * * * Date: 09.03.2009 * * Description: Example-Program for Tutorial * *****************************************************/ #include "robot.h" #include "ItemSort.h" int ItemSort(robot *r1) { init(r1,"ItemSort.txt",true,"r1"); // Erstelle eine txt-Datei nest(r1); speed(r1,3); sleep(r1,10); move(r1,1); sleep(r1,10); set_counter(r1,11,0); set_counter(r1,12,1); set_counter(r1,13,0); Roboterinitialisierung mit Setzen des Matrix-Spaltenzählers (11) und Qualitätsdepotzählers (13) auf 0, sowie des Zeilenzählers (12) auf 1. mark(r1,30); go_to(r1,100); Überspringe Programmteile, die für alle Steine gelten. mark(r1,32); output_bit(r1,-13); output_bit(r1,-15); move_handed(r1,1,true); move_handed(r1,2,true); move_handed(r1,3,true); hand_open(r1); move(r1,2); output_bit(r1,+14); sleep(r1,20); output_bit(r1,-14); mark(r1,50); input_direct(r1); if_equal(r1,0,60); test_bit(r1,+13,400); test_bit(r1,+14,500); go_to(r1,50); end(r1); mark(r1,60); sleep(r1,2); input_direct(r1); go_to(r1,50); mark(r1,100); compare_counter(r1,12); sleep(r1,20); output_bit(r1,-12); if_equal(r1,1,200); /* if_equal(r1,2,210); if_equal(r1,3,220); if_equal(r1,4,230);*/ Lösche die Outputbits 13 und 15 zur Freigabe des Förderbands. Bewege gehaltenen (true) Stein (x,y) zum Förderband. Setze Stein und fahre zur Pos. 2. Schalte Förderband ein, warte 2 sec, schalte Förderband aus. Lese solange das Eingangsbit aus, bis Daten von Förderband vorhanden sind weißer Stein: führe Unterprg 400 aus Metall-Stein: führe Unterprg 500 aus Auswahl der Matrix-Spalte (hier nur für die erste Spalte definiert) Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 37 Universität Paderborn mark(r1,200); increment_counter(r1,11); compare_counter(r1,11); if_equal(r1,1,250); /* if_equal(r1,2,258); if_equal(r1,3,266); if_equal(r1,4,240);*/ Lehrstuhl Angewandte Datentechnik Auswahl der Matrix-Zeile (hier nur für die erste Zeile definiert) /*mark(r1,210); ... */ mark(r1,250); move(r1,10); move(r1,11); hand_close(r1); move_handed(r1,15,true); move_handed(r1,10,true); go_to(r1,32); Gehe zu erster Stein-Position Greife Stein Bewege Stein in eine sichere Position /*mark(r1,258); ... */ mark(r1,400); move(r1,6); move(r1,7); hand_close(r1); move_handed(r1,6,true); move_handed(r1,1,true); output_bit(r1,+13); go_to(r1,600); mark(r1,500); move(r1,2); move(r1,4); move(r1,9); move(r1,5); hand_close(r1); move_handed(r1,9,true); move_handed(r1,4,true); output_bit(r1,+15); output_bit(r1,-12); go_to(r1,1300); mark(r1,600); compare_counter(r1,12); if_equal(r1,1,650); /* if_equal(r1,2,700); if_equal(r1,3,750);*/ mark(r1,650); compare_counter(r1,11); if_equal(r1,1,800); /* if_equal(r1,2,850); if_equal(r1,3,900);*/ Gehe zur Ausschußablage Greife Stein Bewege Stein in eine sichere Position Setze Ausgangsbit für Förderband (weißer Stein) Gehe zur Qualitätsablage Greife Stein Bewege Stein in eine sichere Position Setze Ausgangsbits für Förderband (Metall-Stein) Auswahl der Matrix-Spalte zur Rücklage des weißen Steins (hier nur für die erste Spalte definiert) Auswahl der Matrix-Zeile zur Rücklage des weißen Steins (hier nur für die erste Zeile definiert) /*mark(r1,700); ... */ Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 38 Universität Paderborn Lehrstuhl Angewandte Datentechnik mark(r1,800); move_handed(r1,10,true); move_handed(r1,15,true); hand_open(r1); move(r1,10); move(r1,1); end(r1); Bewege weißen Stein zurück zur Matrix-Position Rücklage des weißen Steins Gehe zur Ruhe-Pos. (Pos. 1) Programmende /*mark(r1,850); ... */ mark(r1,1300); increment_counter(r1,13); Auswahl des Qualitätsdepots zur Ablage des Metall- Steins (hier nur für das Depot 1, RH definiert) mark(r1,1360); compare_counter(r1,13); if_equal(r1,1,1400); /* if_equal(r1,2,1470); if_equal(r1,3,1500); if_equal(r1,4,1570); if_equal(r1,5,2000);*/ mark(r1,1400); move_handed(r1,1,true); move_handed(r1,8,true); move_handed(r1,50,true); move_handed(r1,55,true); hand_open(r1); move(r1,50); move(r1,1); output_bit(r1,+12); end(r1); Bewege Metall-Stein zum Qualitätsdepot 1, RH. Ablage des Metall- Steins Gehe zur Ruhe-Pos. (Pos. 1) Programmende /*mark(r1,1470); ... */ // Positionsdefinitionen pos_def(r1,1,-2.7,+287.4,+333.3,-90.2,-3.1); pos_def(r1,2,-255.2,+125.9,+333.3,-91.1,+4.7); pos_def(r1,3,-285.2,+102.1,+92.9,-89.2,+19.3); pos_def(r1,4,-241.2,-106.4,+250.5,-89.4,-24.0); pos_def(r1,5,-244.0,-107.6,+95.3,-87.9,-23.9); pos_def(r1,6,-241.5,-150.4,+333.2,-91.1,+4.7); pos_def(r1,7,-288.0,-215.4,+92.6,-91.6,+53.5); pos_def(r1,8,+259.2,+47.9,+250.4,-89.4,-10.4); pos_def(r1,9,-220.3,-108.9,+119.0,-92.3,-23.9); pos_def(r1,10,+80.5,+325.8,+97.8,-88.6,+17.2); pos_def(r1,11,+72.8,+240.1,+21.2,-90.5,+17.2); pos_def(r1,15,+73.1,+241.1,+30.4,-89.9,+17.2); pos_def(r1,50,+359.5,+23.1,+106.3,-90.5,-3.8); pos_def(r1,55,+321.3,+19.4,+36.9,-92.4,-5.8); destroy(r1); // Schließe die txt-Datei ab. return 0; // Gehe zurück zur Main-Funktion } Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 39 Universität Paderborn Lehrstuhl Angewandte Datentechnik Literaturhinweise • Robert Sedgewick, Algorithms in C. Addison-Wesley Publishing Company, Inc., 1990. - ISBN 0-201-51425-7 • T.H. Cormen, C.E. Leiserson, R.L. Rivest, Introduction to Algorithms, The MIT Press, 2001. - ISBN 0-26-253196-8 • R. Diestel, Graphentheorie. Springer-Verlag, 2006. - ISBN 3-540-67656-2. • L. Volkmann, Fundamente zur Graphentheorie. Springer-Verlag, 2001. - ISBN 3-21182774-9 Universität Paderborn, ADT, Tutorial zum Projekt „Angewandte Programmierung“ 40