Asuro und Labyrinthe aus Linien - machbar?
Hallo,
ich möchte meinem Asuro so programmieren, daß er aus einem kreisfreien Labyrinth herausfindet (also immer nächstlinken Weg nehmen). Die Idee war weiters, nur rechtwinklige Kreuzungen zuzulassen, damit man es vielleicht schaffen kann, nach 90° Kurven die Linie wiederzufinden. Sackgassen könnte man mit 180°-Wendungen lösen oder damit, der Sackgasse eine Schlaufe anzufügen, die den Roboter in die Richtung leitet, wo er herkam (hoffe, man kann sich das vorstellen). Das Ziel wird durch einen Gegenstand gekennzeichnet, dessen Erreichen wird mit PollSwitch() erkannt.
Verwenden möchte ich für alles die Linienfolgeeinheit (Erweiterungen der Hardware sollten nicht geschehen laut Aufgabenstellung), habe aber bis jetzt ziemliche Probleme damit, den Roboter auf der Spur zu halten.
Bis jetzt habe ich mal die Sensoren und die LED gegen Umgebungslicht abgeschottet, wie es im Forum mehrfach empfohlen wird, nun geht es schon etwas besser. Ich werde noch weiter mit den Werten rumprobieren, wollte aber doch mal in die Runde fragen, ob ihr die Idee für machbar haltet und wie gut eure Roboter im Linienfolgen sind und wo die Grenzen liegen, zum Beispiel, wie groß der Durchmesser von Kreisen sein soll, die der Roboter schafft.
Danke, desenfrenada
Ein kleines Video als beweis, das es möglich ist.
Hallo,
habe ein paar Tage darüber nachgedacht :-k und 3 Abende lang programmiert [-o< .
Dabei ist dieses Video herausgekommen.
Ein bisschen Doku finet Ihr auch auf meiner Webseite
Martin
Liste der Anhänge anzeigen (Anzahl: 2)
Hallo,
Hier erst mal meine ADC Routine:
Ist leicht abgeändert vom Original aber trotsdem danke an Rakke. Sein Code hat den Vorteil, das die Odometriesenoren im vergleich zu den anderen häufiger abgefragt werden und daher die Messung genauer wird.
Code:
/***************************************************************************
SIGNAL (SIG_ADC)
Interuptfunktion zum Auswerten mehrerer ADC-Kanäle nach einer vorgebbaren
Konfigurationsliste. Zugriff auf global definierte Variablen für die ADC-Kanäle.
last modification:
Ver. Date Author Comments
------- ---------- -------------- ---------------------------------
beta1 31.03.2005 Robotrixer counting odometrie tics
sto1 29.07.2005 stochri Wheelspeed,hysteresis
rakke 20.12.2005 Rakke Konfigliste, glob. Var für Line, Bat etc
------- ---------- -------------- ---------------------------------
***************************************************************************/
#define ADC_CTRL_MANYS 12 ///>Zahl der Stützpunkte der configliste
#define ADC_CTRL_ODO_LEFT 1
#define ADC_CTRL_ODO_RIGHT 0
#define ADC_CTRL_LINE_LEFT 3
#define ADC_CTRL_LINE_RIGHT 2
#define ADC_CTRL_SWITCHES 4
#define ADC_CTRL_BATTERY 5
// der folgende define erleichtert das berechnen des Wertes ADMUX.
// admux_pre_calc = (0 <<REFS1) // AVCC with external ...
// | (1 <<REFS0) // ... capacitor at AREF pin;
// | (1 <<ADLAR); // Ergebnis in ADCH, ADCL links ausrichten
#define ADMUX_PRE_CALC 0x60
const unsigned char adc_mux_config_list[ADC_CTRL_MANYS] = {
WHEEL_RIGHT,
WHEEL_LEFT,
IR_RIGHT,
WHEEL_RIGHT,
WHEEL_LEFT,
IR_LEFT,
WHEEL_RIGHT,
WHEEL_LEFT,
SWITCH,
WHEEL_RIGHT,
WHEEL_LEFT,
BATTERIE
};
SIGNAL (SIG_ADC)
{
// für die Odometrie:
static unsigned char flag[2]; ///> merkt sich für jede Seite, ob steigende oder fallende Flanke
static unsigned char adc_cnt=0; ///> Merker für die Auswertung
unsigned char adc_channel; ///> Kanal für die Auswertung
unsigned char side; ///> side = 0: linkes, side = 1: rechtes Rad
unsigned char adc_lo; ///> zum Zwischenspeichern des ADC_lo_Wertes
unsigned char adc_hi; ///> zum Zwischenspeichern des ADC_hi_Wertes
unsigned int temp; ///> Hilfsvariable
const unsigned char grenzlow[2]={167,142}; // {LL,RL}
const unsigned char grenzhigh[2]={173,148}; // {LH,RH}
static unsigned int switcheshist[4]={0,0,0,0};
// ADCL muss zuerst gelesen werden! Sonst können sich zwei Wandlungen überschneiden.
adc_lo= ADCL; // Zwischenspeichern des ADC-Wertes in temp. Variable
adc_hi= ADCH; // Zwischenspeichern des ADC-Wertes in temp. Variable
if(autoencode==ADC_RUN) // Aus Kompatibilitätsgründen und weil sehr praktisch für debugging wird autoencode weiter benutzt
{
adc_channel = adc_mux_config_list[adc_cnt]; // nachgucken: welcher Kanal wurde gerade gewandelt? Fürs bessere Verständnis als eigene Variable eingeführt. Wenns mal knapp wird, lässt sich hiereine sparen...
// Für die Auswertung je nach gewähltem Kanal unterschiedlich verfahren
switch(adc_channel)
{
// Odometriesensoren. Code von stochri weitgehend übernommen.
case ADC_CTRL_ODO_LEFT:
case ADC_CTRL_ODO_RIGHT:
/*
Unterscheidung nach steigender und fallender Flanke. Bei steigender Flanke wird
nur der encoder_counter der jeweiligen Seite hochgesetzt und das Flag für "gestiegene
Flanke" gesetzt.
Die Seite bestimmt sich aus "adc_channel". Da encoder[0] aus AD-Eingang=1 und encoder[1]
aus AD-Eingang=0 kommen, muss die Seite über XOR getauscht werden, um mit WEJA/stochri
kompatibel zu bleiben.
Wenn die Bytes mal ganz arg knapp werden, lässt sich hier eins sparen...
*/
side = adc_channel ^ 1;
// Hysteresis included 25.6.2005 stochri
if ( (adc_hi < grenzlow[side]) && (flag[side] == TRUE)) // falling edge
{
encoder[side] ++;
flag[side] = FALSE;
}
if ( (adc_hi > grenzhigh[side]) && (flag[side] == FALSE)) // rising edge
{
encoder[side] ++;
flag[side] = TRUE;
}
break;
// Liniensensoren. Liefert die gleichen Werte wie "LineData"
case ADC_CTRL_LINE_LEFT:
case ADC_CTRL_LINE_RIGHT:
side = (adc_channel & 0x01)^ 1; // nur das kleinste Bit vom adc_channel auswerten
temp = (((unsigned int)adc_hi)<<8) + ((unsigned int)adc_lo);
line[side] = temp>>6; // Ergebnis ist links ausgerichtet
break;
// Kollisionstaster. Liefert die gleichen Werte wie "Pollswitch" und funktioniert genauso gut. Hm.
case ADC_CTRL_SWITCHES:
temp = ((((unsigned int)adc_hi)<<8) + ((unsigned int)adc_lo))>>6; // align right
temp = (unsigned char)(((10240000L/(long)temp-10000L)*62L+5000L)/10000);
switcheshist[3] = switcheshist[2]; // shiftregister
switcheshist[2] = switcheshist[1]; // shiftregister
switcheshist[1] = switcheshist[0]; // shiftregister
switcheshist[0] = temp;
if (switcheshist[0] == switcheshist[1] && switcheshist[0] == switcheshist[2] && switcheshist[0] == switcheshist[3]) // nur wenn alle werte gelich sind, wird wert übernommen.
switches = temp; // ansonsten bleibt switches beim alten wert.
break;
// Batteriespannung. Liefert die gleichen Werte wie "Batterie" und funktioniert genauso gut. Der Vollständigkeit halber aufgeführt.
case ADC_CTRL_BATTERY:
u_Bat = (((unsigned int)adc_hi)<<8) + ((unsigned int)adc_lo);
u_Bat = u_Bat >>6;
break;
}
/*
ADC-MUX für die nächste AD-Wandlung auf den nächsten Kanal der Konfigliste umschalten.
*/
adc_cnt = adc_cnt + 1; // nächstes Listenelement
if (adc_cnt >= ADC_CTRL_MANYS) // Listenende erreicht?
adc_cnt = 0; // Dann von vorne anfangen!
ADMUX = ADMUX_PRE_CALC + adc_mux_config_list[adc_cnt];// aus der config-Liste den nächsten Kanal holen
}
/*
Schließlich noch die nächste Konversion starten: das ADSC-Bit wird nach Beendigung der Wandlung durch den
ATmega auf null gesetzt.
Dies muss außerhalb der "if(autoencode)" erfolgen, sonst wird der Interrupt nicht mehr ausgelöst!
Soll also - z. B. wenn die Zeit mal knapp ist - der Code "gekürzt" werden, dann über autoencode = 0 ausschalten.
*/
ADCSRA = ADCSRA | (1<<ADSC);
}// Ende der rakke'schen ADC-Interruptroutine
Und hier Bilder von meiner Linienverfolgungseinheit:
Damit bin ich eigentlich sehr zufrieden.