Home
Erste Vorstellung
Theorie
Hardware
- Baugruppen
Software
- Entwicklung
- Quellcode
Ergebnis
Das Team
Quellen und Links
|
Die Entwicklung unserer Software
Grob lassen sich die Entwicklungsschritte unseres Roboters in folgende Abschnitte unterteilen:
1. Der Prozessor gibt eine Rechteckspannung an die Steppercontroller aus und ermöglicht so einfache Fahrt
2. Der Roboter fährt eine bestimmte Anzahl von Schritten
3. Wir führen den Navigator ein
4. Der Roboter reagiert auf Kontakt
5. Beschleunigen und Abbremsen
6. Der Roboter reagiert mit Entfernungssensoren auf ein Hindernis
7. Suche eines IR-Feuers
1. Einfache Fahrt
Im ersten Abschnitt ging es darum, auf die Steppercontroller eine Rechteckspannung (als Clock-Signal)
und die notwendigen Signale für die Motorfunktionen (Motor an, Drehrichtung, Step-Modus) auszugeben.
Um ein möglichst gutes Fahrverhalten in Bezug auf Beschleunigen und Abbremsen zu erreichen, war es
unser Ziel, eine in einem Frequenzbereich von 500-1000 Hz in möglichst kleinen Schritten veränderbare
Schrittfrequenz zu erhalten.
Da unsere Planung vorsah, eine seitliche Abweichung des Roboters von der Fahrtstrecke durch die
Sensoren von zwei optischen Maussensoren zu erkennen, und durch Abbremsen eines Motors zu korrigieren, war es
notwendig, die beiden Motoren mit unabhängig variierbaren Frequenzen anzusteuern.
Eine der Möglichkeiten dies zu realisieren, bestand daraus, zwei der unabhängigen Counter des Microcontrollers zu
nutzen. In diesem Fall würden die Counter mit der Frequenz des durch den Prescaler geteilten
Prozessortaktes hochgezählt werden, und bei Erreichen eines definierten Wertes einen Interrupt
auslösen. Auf den Interrupt je eines Counters würden dann jeweils der Zustand auf einem zugehörigen
Pin geändert werden, und auf diesem Pin so eine Rechteckspannung erzeugt werden. Mit den beiden
Countern hätten wir die Möglichkeit, beide Motoren mit unabhängigen Frequenzen anzusteuern.
Das Problem dieses Ansatzes besteht darin, das der Prescaler den Prozessortakt nur durch einige feste
Werte teilen kann (8, 64, 256, 1024), und es daher fraglich ist, ob wir den gewünschten Bereich der
Ausgabefrequenz zusammen mit einer guten Variierbarkeit der Frequenz erzielen können. Dazu wäre es
sinnvoll, den Counter mit einer möglichst hohen Frequenz zu erhöhen, (daraus folgt, daß in einem Takt der
Motorfrequenz viele Takte der Counterfrequenz liegen, eine Änderung der Counterfrequenz also feine
Änderungen der Motorfrequenz ermöglicht) bei der die Größe des Counters (8 Bit) aber dennoch
ausreicht, um eine niedrige Anfahrfrequenz zu erhalten.
Counterfrequenz / 256 = 500Hz
Unser Problem war, das wir bei der Berechnung der Frequenz einen Fehler gemacht hatten, indem wir
nicht beachteten, das wir nur die Frequenz berechneten, mit welcher der Pin umgeschrieben wird. Der
Motor hingegen macht auf die ansteigende Flanke des Rechtecksignals hin einen Schritt, die
Motorfrequenz ist also nur die Hälfte der berechneten Frequenz. Die Motorfrequenz läßt sich mit folgender
Formel beschreiben:
Prozessortakt / Prescaler / Wert des 8-Bit-Registers / 2 = Motorfrequenz
Mit den fehlerhaften Überlegungen schien es uns nicht möglich, die Fahrfrequenz mit der geschilderten
Methode auszugeben. Daher mußten wir noch eine zweite Idee entwickeln, die Frequenz zu erzeugen:
Diese zweite Möglichkeit war, mit einem Counter zwei Zählregister zu erhöhen, deren Größe zu prüfen
und bei einem bestimmten Wert die Ausgabepins zu ändern.
Dabei wäre es möglich gewesen, die geschilderten Problem zu umgehen, da wir zusätzlich zu den 8 Bit
des Counters noch 8 Bit des Registers, und damit mehr Variationsmöglichkeiten zwischen Prescaler und
Counter-Größe gehabt hätten. Das Problem dabei wäre natürlich ein erheblich größerer Rechenaufwand
gewesen, insbesondere da die Aufgabe des Interrupts durch eine ständige Kontrolle der Register hätte
ersetzt werden müssen.
Nach Erkennen des Fehlers beschränkten wir uns auf die Nutzung der 8 Bit Counter 0 und 2. Das
Toggeln der Ausgabepins vereinfachte sich ebenfalls, da die Counter einen Modus besitzen, in dem sie
bei Erreichen der oberen Schranke automatisch einen Pin toggeln. Damit fährt der Roboter nun, je nach
Einstellung der Motordrehrichtung geradeaus oder er dreht sich um seinen Mittelpunkt.
2. Definierte Strecke
Im zweiten Abschnitt erweiterten wir das Programm um einen Zähler der gefahrenen Schritte, so das es
möglich wurde, den Roboter eine bestimmte Strecke fahren zu lassen.
Dies lies sich einfach verwirklichen, indem wir den bisher ungenutzten Interrupt eines der Counter dazu
verwendeten, die Distance-Routine aufzurufen, die ein 16 Bit Register erniedrigte (Durch zwei 8-Bit
Register realisiert, sobald ein Register auf Null gezählt ist, wird das zweite erniedrigt, dann das erste
wieder heruntergezählt). Dieses Register enthält die noch zu fahrende Anzahl an Schritten (aufgrund des
oben geschilderten das Doppelte).
Wenn das obere Register Null erreicht, springen wir im Programm in die Stop-Routine, die in diesem
Entwicklungstand ausschließlich die Motoren ausschaltet. Es muss also berücksichtigt werden, daß die
letzten 256 Schritte nicht ausgeführt werden. kompensieren lässt sich dieser Fehler durch eine Erhöhung
des oberen Registers um eins.
3. Der Navigator
Der Roboter ist nun in der Lage eine definierte Strecke zu fahren. Unsere Planung sah vor, das später
ein zweiter Prozessor (Navigator) einen Weg berechnen und dem ersten in einfacher Form, eben in der
Form von aufeinander folgenden Strecken mit bestimmter Länge übermitteln würde. Drehungen würden
genau wie gerade Fahrt aus einer definierten Strecke, aber unterschiedlichen Drehrichtungen der Motoren
bestehen.
Da der zweite Prozessor nicht vorhanden war, führten wir als Simulation die Navi-Routine in unserem
Programm ein. Ihre Aufgabe besteht darin, in bestimmten Registern die Fahrtstrecke und Art (Drehung
oder Gerade) zu setzen.
Sobald diese Werte vorhanden sind (was später der zweite Prozessor erledigen würde, daher die Input-
Routine, die später die Kommunikation der Chips aufnehmen soll), führt das Programm die eigentliche
Fahrroutine unabhängig vom Navigator (Routine/Prozessor) aus. Nach Durchführen des Fahrbefehls, das
heißt erreichen der Stop-Routine, springt das Programm wieder in die Navi-Routine.
Um verschiedene Fahrbefehle in einer Reihenfolge durchzuführen, nutzen wir ein Register als
Statusregister NA. Die Navi-Routine fragt die Bits dieses Register nacheinander ab. Ein gesetztes Bit in
diesem Register führt dazu, daß die Navi-Routine in einen Teil springt, der einen bestimmten Fahrbefehl
lädt. Wenn in diesem Teil das NA-Register wieder geändert wird, läßt sich erreichen, das nach einem
bestimmten Fahrbefehl zwangsläufig ein definierter anderer durchgeführt wird. Als einfaches Beispiel fährt
der Roboter nun eine Strecke, dreht um, fährt zurück, dreht um,.............
4. Erste Interaktion mit der Umwelt
In diesem Abschnitt geht es zum ersten Mal darum, den Roboter auf seine Umwelt reagieren zu lassen.
Mechanisch erreichten wir dies durch zwei mit "Fühlern" versehenen Schaltern an der Frontseite. Diese
Schalter schließen bei Kontakt der Fühler mit einem Hindernis.
Um eine möglichst schnelle und zugleich während der Fahrt rechenarme Verarbeitung zu erreichen,
legten wir die Schalter auf die beiden externen Interrupts des Prozessors, welche die Routinen Left und
Right aufrufen.
Dadurch ist es auch einfach möglich die beiden Schalter unabhängig zu betrachten, und so grob die
Position des Hindernisses zu erkennen. Die Interrupts führen dazu, das die Motoren ausgeschaltet
werden. Außerdem schreiben die Interrupt-Routinen in das NA-Register die Bits für Rückwärtsfahren, und
je nachdem welcher Schalter ausgelöst hat, eine Drehung vom Hindernis weg. Am Ende der Routinen
springt das Programm wieder in die Navi-Routine. Durch die Reihenfolge der NA-Registerabfragen im
Hauptprogramm erreichen wir das der Roboter zuerst von dem Hindernis zurückfährt, dabei gleichzeitig
das Bit im NA-Register für Rückwärts wieder löscht, und daraufhin von dem Hindernis wegdreht. Danach
führt der Roboter wieder sein "typisches" Fahrverhalten (fahren, drehen,.....)aus.
5. Beschleunigung und Abbremsen
Bisher war der Roboter mit konstanter Geschwindigkeit gefahren. Darin lagen insofern Probleme, als
das bei geringen Drehzahlen der Roboter zwar unter allen Bedingungen sicher angefahren ist, dabei aber
einen hohen Stromverbrauch hatte, was sich auch durch eine deutliche Erwärmung der Steppercontroller
zeigte. Bei höheren Geschwindigkeiten hingegen sank der Stromverbrauch, dafür blieb der Roboter
insbesondere mit schwächeren Akkus bei Drehungen manchmal stehen.
Um sowohl das höhere Drehmoment der Motoren bei kleinen Geschwindigkeiten zum Anfahren als auch
den geringeren Stromverbrauch mit höherer Geschwindigkeit zu nutzen, änderten wir unser Programm
so, das der Roboter mit 500 Hz anfuhr und auf 1000 Hz beschleunigt. Wir erreichten dies, indem wir die
Distance-Routine um folgende Operationen erweiterten:
a.) Wir zählen ein weiteres Register hoch, das so die Zahl der gefahrenen Schritte enthält.
Solange dieses Register unter einer bestimmten Größe bleibt, verkleinern wir die obere Schranke
der Counter.
b.) Wir ändern in unserem aktuellen Programm die Geschwindigkeit nur alle 128 Schritte, das heißt in dem
Programmteil, in dem die Distance-Routine den oberen Teil des 16 Bit Registers ändert. Eine Änderung
bei jedem Schritt verursacht zwar eine sehr eindrucksvolle Beschleunigung, der Roboter zeigt aber
trotzdem das Verhalten in bestimmten Fahrsituationen stehen zu bleiben.
c.) Die Beschleunigung ist zusätzlich durch eine Abfrage abgesichert, welche die noch verbleibende Strecke
überprüft und unter einem Betrag keine Beschleunigung mehr zulässt.
d.) Ebenfalls in der Distance-Routine erhält der Roboter eine Funktion, die den Roboter abbremst (Obere
Schranke des Counters erhöht), sobald das Streckenregister unter einen Wert gefallen ist. Dabei wird
gesichert, das die Frequenz nicht unterhalb von 500 Hz verringert wird, was Stehen bleiben und hohe
Halteströme verursachen würde.
6. Abstandssensorik
Bisher konnte der Roboter Hindernisse erst bei Berührung erkennen. Als Erweiterung haben wir nun
zwei Entfernungssensoren mit einem Messbereich von bis zu 80 cm auf der Vorderseite des Roboters
angebracht. Die Sensoren geben eine der Entfernung umgekehrt proportionale Spannung aus, die wir
über den AD-Wandler des Prozessors einlesen.
Ziel war es, Hindernisse auf diese Reichweite zu erkennen, abzubremsen (im Gegensatz zum Motorausschalten
der Schalter) und ihnen auszuweichen.
Versuchsweise implementierten wir zuerst einen der Sensoren. Dazu wurde die Distance-Routine um den
Start einer AD-Wandlung des anliegenden Signals erweitert. Damit erreichten wir, das bei jedem Schritt
die Entfernung gemessen wird, also in einer idealen Auflösung, aber ohne unnötige Berechnungen.
Bei Ende der Wandlung löst der AD-Wandler einen Interrupt aus. Die aufgerufene Scan-Routine verglich
den Wert mit einem Maximalwert, und stoppte den Roboter noch einfach, wenn ein Hindernis in unter 30
cm Entfernung von dem Sensor erfasst wurde.
Anfangs hatten wir das Problem, das jeweils die erste Messung nach dem Anschalten (über
Stromversorgung, nicht bei Reset) ein falsches Ergebnis (Hindernis vorhanden) lieferte. Wir konnten dies
nur durch eine "Blind-Abfrage" ohne Verwertung des Ergebnisses am Anfang des Programms umgehen.
Als dieses Programm lief, versuchten wir auf zum Auslesen beider Sensoren überzugehen. Dazu
wechselten wir beim Auslesen zwischen den Sensoren und prüften so abwechselnd, mit der selben
Routine, die Abstände auf beiden Seiten des Roboters.
Um nun Informationen über die Lage des Hindernisses zu erhalten, hatten wir vorgesehen, nach einem
Stop aufgrund der Entfernungssensoren, in einer weiteren Routine nacheinander beide Sensoren
auszulesen und die Werte voneinander abzuziehen. Aufgrund des Vorzeichens des Ergebnisses konnte
man dann erkennen, welcher Sensor die größere Spannung und damit die kleinere Entfernung zum
Hindernis hatte. Damit wären wir in der Lage gewesen, im NA-Register die Bits für eine Drehung vom
Hindernis weg zu setzen.
Die Implementierung sah vor, eine AD-Wandlung zu starten, in einer Schleife auf das Bit zu warten,
welches das Ende der Wandlung anzeigt, den anderen Sensor ebenso auszulesen und die Werte zu
vergleichen.
Anscheinend ist dies so aber nicht möglich. Der Roboter drehte bei Fahrversuchen unabhängig vom
Hindernis immer in eine Richtung. Unsere einzige Erklärung für dieses Verhalten ist, das die AD-Wandlungen
nicht in derartig kurzen Abständen voneinander möglich sind (was laut Datenblatt von Atmel möglich
sein müsste ). Eventuell wird das Beendet-Bit vor Abschluss der Berechnung gesetzt, so das es zu
Überschneidungen kommt (unsere übrigen Aufrufe des AD-Wandlers liegen einige tausend
Prozessortakte voneinander getrennt).
Wir haben das Problem gelöst, indem wir in der Scan-Routine, welche die Entfernung überprüft, die
Messwerte der Sensoren jeweils in ein eigenes Register schreiben. Falls der Roboter nun wegen eines
Hindernisses stoppt, vergleicht die Stop-Routine diese Werte und entscheidet so, ob in dem NA-Register
das Bit für eine Rechts- oder Linksdrehung gesetzt wird.
Außerdem haben wir in der Navi-Routine die Motorbefehle für eine Rechts- und Linksdrehung so
geändert, das sich die Drehwinkel unterscheiden. Das ermöglicht dem Roboter auch aus Ecken wieder
herauszufinden.
7. IR-Empfänger
Im letzten Abschnitt haben wir dem Roboter einen IR-Empfänger angebaut, mit dem Ziel einen
entsprechenden Sender zu finden. Sender und Empfänger arbeiten mit einer modulierten IR-Strahlung,
sind daher relativ unempfindlich gegenüber Störungen. Im Betrieb gibt der Sender eine Spannung
von 4.8 Volt aus. Wenn er ein Signal empfängt, sinkt die Spannung auf 2,3 Volt.
Unsere Planung sah vor, den Empfänger mit einer Blende zu versehen und so eine Richtcharakteristik zu
erhalten. Der Roboter sollte nun Suchmanöver fahren, bis er den Sender gefunden hatte, und auf ihn
zufahren. Unsere Hoffnung Bezugs der Implementierung, das der Prozessor die 2,3 Volt als Low-Bit
interpretieren würde, würde erfüllt.
Dadurch waren wir in der Lage, den Sender direkt digital an einem Pin einzulesen. Solange an diesem Pin
ein High-Bit anlag, fuhr der Roboter ein Suchmuster in Form eines Rechteckes mit sich vergrößernden
Seitenlängen. An den Ecken vollführte der Roboter eine Drehung um 450 Grad um so das gesamte
Umfeld zu erfassen.
Wenn der Empfänger ein Signal erhielt, änderte sich die Spannung an dem Pin auf Low. Das Low-Bit
verursachte einen Stop der aktuellen Fahrbefehle. Außerdem wurden im NA-Register das Bit für einen
neuen Fahrmodus gesetzt, indem der Roboter nun geradeaus fuhr, und auf Meldungen der Sensoren
(mechanisch und optisch) nicht mehr mit Ausweichen, sondern mit einem Stop reagierte. Dadurch näherte
sich der Roboter an den Sender an und blieb dann stehen.
Hauptproblem in diesem Fall war, das der Sender nur über eine sehr geringe Leistung verfügt, das Signal
daher erst bei Entfernungen unter ca. einem Meter erfasst wird.
|
|