Die einfachste Methode, ein Hindernis zu erkennen, ist sicher die Verwendung von Fühlern. Sie betätigen bei Berührung einen elektrischen Kontakt, der dann im Programm abgefragt werden kann und zu einer Änderung der Bewegungsrichtung führt. Wir verwenden in der AG eine kleine Platine, auf der zwei Taster aufgelötet sind, an denen jeweils ein langer Draht befestigt ist, der eben wie ein Fühler den Raum vor dem Roboter abtastet. Da ein Fühler nach links und der andere nach rechts zeigt, kann sogar unterschieden werden, auf welcher Seite das Hindernis ist und entsprechend reagiert werden. So ist es naheliegend, dass ein eher links befindliches Hindernis rechts umfahren wird und umgekehrt.
Die Platine mit den Tastschaltern wird mit Heißkleber auf einen Legostein geklebt, so dass der Sensor bequem am Roboter befestigt und auch wieder leicht entfernt werden kann. Am besten klebt man die Platine so auf, dass die angelöteten Kabel nach oben weggehen.
Die drei Kabel haben folgende Bedeutung:
blau: Signal für linken Fühler, einstecken in A13
gelb: Signal für rechten Fühler, A14
schwarz: Batterie Minus, irgendwo in Y einstecken
Im Programm können die beiden Taster über die Funktionen
HindernisLinks()
und
HindernisRechts()
abgefragt werden. Die Funktionen liefern 1, wenn der Kontakt betätigt ist und 0 sonst.
Man wird im Programm also etwas schreiben wie:
. . .
if ( HindernisLinks() == 1 )
{
// linkes Hindernis erkannt
. . .
}
. . .
Aber was soll eigentlich genau passieren, wenn ein Hindernis erkannt wurde?
Am einfachsten ist es, wenn wir nur kurz anhalten. Das ist dann sinnvoll, wenn mehrere Roboter hintereinander auf einer Linie fahren. Da nie alle genau gleich schnell fahren, kann es zu Kollisionen kommen. Durch das kurze Anhalten bei Berührung der Fühler wird dies vermieden.
Das könnte man folgendermaßen programmieren, und zwar in der Schleife des Linienfolgers, bevor die beiden Liniensensoren eingelesen und ausgewertet werden:
. . .
// Hindernis?
if ( ( HindernisLinks() == 1 ) || ( HindernisRechts() == 1 ) )
{
// kurz anhalten
setzeMotorLinks( 0 );
setzeMotorRechts( 0 );
warteMillisec( 2000 );
}
. . .
Die Motoren brauchen danach nicht wieder extra eingeschaltet zu werden, denn das passiert ja sowieso später beim Auswerten der Liniensensoren.
Eine andere Möglichkeit wäre es, einem auf der Linie stehenden Hindernis auszuweichen und danach die Fahrt auf der Linie fortzusetzen, wie es bei manchen Roboter-Wettbewerben gefordert wird. Dabei gehen wir davon aus, dass es sich bei den Hindernissen um Flaschen der ähnliche kleine Gegenstände handelt, sie also klein und leicht zu umfahren sind. Der Roboter soll nun zunächst einer Linie folgen, wie wir das schon programmiert haben. Erkennt er jedoch ein Hindernis, soll er kurz zurück fahren, sich nach links oder rechts drehen, ein Stückchen weg von der Linie fahren, sich dann wieder parallel zur Linie drehen, am Hindernis vorbeifahren und dann wieder zurück zur Linie finden.
Und hier haben wir auch schon die eigentliche Herausforderung: wie finden wir zurück zur Linie? Einfach einen kleinen Ausweichkurs zu programmieren ist einfach, sowas haben wir beim Fahren des Quadrates ja schon gemacht. Aber der wird je nach Akkuzustand und Bodenbeschaffenheit jedesmal leicht variieren, so dass wir nie wieder ganz exakt auf die Linie treffen werden. Die Linie muss also „automatisch“ gefunden werden.
Nun, grundsätzlich ist das gar nicht so schwer: wir müssen beim Umfahren der Linie nur dafür sorgen, dass der Roboter ungefähr in Richtung der Linie zeigt und ihn dann einfach geradeaus fahren lassen, bis einer seiner Lichtsensoren dunkel wird (anders herum formuliert: so lange geradeaus fahren wie beide Sensoren hell sind). Ist das erreicht, kann wieder die normale Funktion der Linienverfolgung weitergehen.
Das Programm könnte also ungefähr so aussehen (die Details wurden weggelassen, da sie sich je nach Abmessungen der Roboter ändern können, und etwas sollt Ihr ja auch noch machen:
. . .
// Hindernis?
if ( HindernisLinks() == 1 )
{
// rechts herum ausweichen
. . .
// nun wieder geradeaus fahren und die Linie suchen
setzeMotorLinks( 1 );
setzeMotorRechts( 1 );
hellLinks = SensorLinks();
hellRechts = SensorRechts();
while ( ( hellLinks > 50 ) && ( hellRechts > 50 ) )
{
hellLinks = SensorLinks();
hellRechts = SensorRechts();
}
}
. . .
Man darf nicht vergessen, in der Schleife die Sensorwerte neu einzulesen, sonst würden wir ja immer dieselben Werte verwenden. Außerdem müssen die Sensorwerte auch vor der Schleife aktualisiert werden, damit der erste Schleifendurchlauf passt.
Da man solche Situationen häufig antrifft, bei denen die Schleifenbedingung von Werten abhängt, die eigentlich erst in der Schleife bekannt werden, kann man die Schleife auch so schreiben, dass zuerst der Schleifenrumpf ausgeführt wird und dann erst geprüft wird, ob die Schleife nochmal durchlaufen werden soll:
. . .
// Hindernis?
if ( HindernisLinks() == 1 )
{
// rechts herum ausweichen
. . .
// nun wieder geradeaus fahren und die Linie suchen
setzeMotorLinks( 1 );
setzeMotorRechts( 1 );
do
{
hellLinks = SensorLinks();
hellRechts = SensorRechts();
}
while ( ( hellLinks > 50 ) && ( hellRechts > 50 ) )
}
. . .
Achtung: das Wörtchen do
nicht vergessen!
Alternativ könnte man auf das Setzen der beiden Variablen verzichten und stattdessen direkt die Sensorwerte in der Bedingung schreiben, also:
. . .
// Hindernis?
if ( HindernisLinks() == 1 )
{
// rechts herum ausweichen
. . .
// nun wieder geradeaus fahren und die Linie suchen
setzeMotorLinks( 1 );
setzeMotorRechts( 1 );
while ( ( SensorLinks() > 50 ) && ( SensorRechts() > 50 ) )
{
}
}
. . .
Hier hat man sogar eine leere Schleife! Aber Achtung: die leeren geschweiften Klammern darf man keinesfalls weglassen.
Hmmm, und wieso benutzen wir überhaupt diese Variablen? Man könnte doch auch alle anderen Bedingungen im Programm direkt mit den Sensorwerten schreiben!
Nun ja, stimmt schon, und wie so oft kommt es auf die Umstände an. Das Speichern in Variablen ist dann sinnvoll, wenn die Abfrage der Sensoren relativ lange dauert, wie wir beim Ultraschall-Abstandssensor sehen werden. Oder man möchte einen Messzeitpunkt festhalten, also in Variablen speichern, und kann dann „in Ruhe“ diesen Zustand abfragen und darauf reagieren. Letzteres ist zum Beispiel dann wichtig, wenn man viele Sensorwerte speichern möchte, die sich auch noch schnell ändern. Dann ist es am besten, alle Sensoren möglichst gleichzeitig zu erfassen (man sagt auch, den Prozess-Zustand erfassen) und danach mit diesen gespeicherten Werten weiter zu arbeiten. So hat man immer einen konsistenten Zustand mit zusammenpassenden Sensorwerten.
Zurück zum Finden der Linie, es geht sogar noch einfacher: die verbesserte Version des Linienfolgers („Pro-Version“) arbeitet mit einer Korrektur-Variable, die angibt, auf welche Seite der Linie sich der Roboter von ihr entfernt hat. Anfangs wird diese Variable auf 0 gesetzt, so dass der Roboter zunächst auf eine weiße Fläche gesetzt werden kann und dann geradeaus fährt, um die Linie zu suchen. Genau dies können wir hier ausnutzen, indem wir die Korrektur-Variable nach Umfahren des Hindernisses auf 0 setzen und die normale Linienverfolgung weiterlaufen lassen. Der Roboter wird dann wie beim Starten von sich aus geradeaus vorwärts fahren bis er eine Linie erkennt und ihr dann wieder folgen:
. . .
// Hindernis?
if ( HindernisLinks() == 1 )
{
// rechts herum ausweichen
. . .
// nun wieder geradeaus fahren und die Linie suchen. Dazu verwenden wir einfach die Korrektur-Variable
korrektur = 0;
}
. . .