PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : RP6 Library



SlyD
01.08.2007, 00:20
Hallo,

Update - 28.09.2007
Neue Versionen der RP6Library können nun stets von der RP6 Homepage heruntergeladen werden:
http://www.arexx.com/rp6/
bzw. direkt zur Software:
http://www.arexx.com/rp6/html/de/software.htm


Update - 11.08.2007
http://www.arexx.com/rp6/downloads/RP6Examples_20070811.zip

Tippfehler in den Makefiles korrigiert. ("RP6lib" vs. "RP6Lib")


Update - 07.08.2007
Es gibt eine neue Libary Version:
http://www.arexx.com/rp6/downloads/RP6Examples_20070807.zip

Die ACS Einstellungen wurden aus der task_ACS Funktion externalisiert und sind jetzt über einige defines in der RP6Config.h zugänglich.
Jetzt kann man gut Experimente mit anderen Einstellungen machen.
Neu ist auch ein Schwellwert für zu empfangende Impulse, der nachdem ein Hindernisses detektiert wurde unterschritten werden muss, damit wieder "Weg ist frei" gemeldet wird. Damit wird das hin und her schalten zwischen "Hindernis" und "kein Hindernis" bei bestimmten Distanzen verringert.


---------------------------------------------------------------------
Originalpost vom 1.8.2007:
Die RP6 Beispielprogramme und die RP6 Funktionsbibliothek (für RP6-Base und RP6-M32) sind nun online verfügbar:

http://www.arexx.com/rp6/downloads/RP6Examples_20070731.zip

Es wurden einige kleinere Dinge korrigiert.

---------------
Neu hinzugekommen ist ein Feature, das in diesem Thread diskutiert wurde:
https://www.roboternetz.de/phpBB2/viewtopic.php?t=32720

Nun wird automatisch überwacht, ob mindestens eine der LEDs angeschaltet ist - wenn für eine gewisse (einstellbare) Zeit keine LED aktiv war, fängt die rote Status LED 5 für eine (ebenfalls einstellbare) Zeit an zu blinken.
Das dient als Hinweis, dass der Roboter noch eingeschaltet ist. Nur damit man nicht vergisst Ihn auszuschalten. :-k

In der RP6Config.h kann man einstellen, ob man das braucht oder nicht.
Es lässt sich auch zur Laufzeit mit den zwei Funktionen
enablePowerOnWarning()
disablePowerOnWarning()
ein und ausschalten.

MfG,
SlyD

ehenkes
02.08.2007, 12:43
Auffällig ist die aufwändige Kommentierung der Programme, die diese wirklich "einsteigerfreundlich" machen. Das ist deutlich besser als bei der ASURO-Bibliothek. Bravo!

Lisbeth
04.08.2007, 10:39
Die Asuro-Bibliothek ist doch super mit Doxygen kommentiert!?
Was ist hier "besser" ? (Ich hab mir diese Bibliotehk nicht entzipped und angesehen, weil ich sie nicht brauche.)
Lisbeth

ehenkes
04.08.2007, 12:04
Es ist nicht nur die Kommentierung, sondern vor allem auch die bessere Kapselung der Hardware beim RP6. Ich möchte nicht direkt auf den Prozessorpinbezeichnungen programmieren. Das kann man doch entkoppeln, und wenn nicht, wie beim ASURO, dann muss man es eben überall exakt kommentieren. Also der Stil der Bibliothek beim RP6 gefällt mir deutlich besser. Ist aber Geschmacksache, kann man sich tagelang streiten. :)

Sternthaler
04.08.2007, 16:07
Dann 'streite' ich mit: ;-) Und zwar im Sinne von Lisbeth.

Hier nur mal ein Beispiel der, tatsächlich auch gut dokumentierten RP6-LIB: (P.S.: Die gute Formatierung in der RP6_lib geht hier mit quote leider verloren. Entschuldigung an den RP6-LIB-Entwickler, dass ich quote nutze.)

/**
* Update status LEDs with current value from shadow register.
*
* Additional info:
* This function ensures that the LED pins are not driven low to allow
* other circuitry to be connected to the I/O pads on the mainboard!
* As long as external circuits only connect the I/O pads to VCC and not to
* GND, everything should work fine, but always connect a >= 470 Ohm
* series resistor to limit maximum current!
*
*
* Example:
*
* statusLEDs.byte=0b101001;
* updateStatusLEDs();
* // this clears all LEDs and sets the LEDs STATUS1,
* // STATUS6 and STATUS4!
*
* // Other possibility:
* statusLEDs.LED2=true;
* updateStatusLEDs();
* // This sets LED2 and does not affect any other LED!
*/
void updateStatusLEDs(void)
{
DDRB &= ~0x83;
PORTB &= ~0x83;
if(statusLEDs.LED4){ DDRB |= SL4; PORTB |= SL4; }
if(statusLEDs.LED5){ DDRB |= SL5; PORTB |= SL5; }
if(statusLEDs.LED6){ DDRB |= SL6; PORTB |= SL6; }
DDRC &= ~0x70;
PORTC &= ~0x70;
DDRC |= ((statusLEDs.byte << 4) & 0x70);
PORTC |= ((statusLEDs.byte << 4) & 0x70);
#ifdef POWER_ON_WARNING
leds_on = (leds_on ? leds_on : (statusLEDs.byte && 1));
#endif
}

Hier ist ja sehr gut zu sehen, dass eine Funktion aus der RP6-LIB sehr wohl auch viel mit einzelnen Bit's zur Leitungssteuerung machen muss. Was sonst soll denn in C geschrieben werden um eine Hardware-Leitung auf +5V oder eben auf 0V zu legen? Ich kenne kein Kommando in C das da Mach_mal_LED_4_an heisst. Und wenn so eine Funktion geschrieben würden kann dort ja auch nur ein Bit im Port zur LED gewechselt werden.

@ehenkes
Was also sollen diese ständige 'Seitenhiebe'?

ehenkes
04.08.2007, 16:32
@Sternthaler: Das Beispiel sieht wirklich auch nicht gut aus. Da fehlt beim RP6 also auch die Kapselung.

Damit ihr versteht, was ich überhaupt meine, hier die iodefs.h des Nibo:


#include <avr/io.h>

/*! Red LEDs */
#define IO_LEDS_RED_PORT PORTE
#define IO_LEDS_RED_MASK 0xfc
#define IO_LEDS_RED_DDR DDRE

/*! Green LEDs */
#define IO_LEDS_GREEN_PORT PORTC
#define IO_LEDS_GREEN_MASK 0xfc
#define IO_LEDS_GREEN_DDR DDRC

/*! White LED pair */
#define IO_LED_WHITE_PORT PORTB
#define IO_LED_WHITE_BIT 5
#define IO_LED_WHITE_DDR DDRB

/*! Display illumination */
#define IO_DISP_LIGHT_PORT PORTB
#define IO_DISP_LIGHT_BIT 6
#define IO_DISP_LIGHT_DDR DDRB



/*! Line and floor sensor */
#define IO_LINE_FLOOR_EN IO_LINE_FLOOR_EN
#define IO_LINE_FLOOR_EN_PORT PORTG
#define IO_LINE_FLOOR_EN_BIT 0
#define IO_LINE_FLOOR_EN_DDR DDRG


/*! Analog channels */
#define AN_FLOOR_R 0 /*!< ADC-PIN floorsensor right */
#define AN_FLOOR_L 1 /*!< ADC-PIN floorsensor left */
#define AN_LINE_L 2 /*!< ADC-PIN liniesensor left */
#define AN_LINE_R 3 /*!< ADC-PIN liniesensor right */
#define AN_VBAT 7 /*!< ADC-PIN battery voltage */


/*! Display register select */
#define IO_DISPLAY_RS IO_DISPLAY_RS
#define IO_DISPLAY_RS_PORT PORTG
#define IO_DISPLAY_RS_BIT 3
#define IO_DISPLAY_RS_DDR DDRG

/*! Display read/write */
#define IO_DISPLAY_RW IO_DISPLAY_RW
#define IO_DISPLAY_RW_PORT PORTG
#define IO_DISPLAY_RW_BIT 4
#define IO_DISPLAY_RW_DDR DDRG

/*! Display enable */
#define IO_DISPLAY_EN IO_DISPLAY_EN
#define IO_DISPLAY_EN_PORT PORTG
#define IO_DISPLAY_EN_BIT 2
#define IO_DISPLAY_EN_DDR DDRG

/*! Display chip select 1 */
#define IO_DISPLAY_CS1 IO_DISPLAY_CS1
#define IO_DISPLAY_CS1_PORT PORTB
#define IO_DISPLAY_CS1_BIT 4
#define IO_DISPLAY_CS1_DDR DDRB

/*! Display chip select 2 */
#define IO_DISPLAY_CS2 IO_DISPLAY_CS2
#define IO_DISPLAY_CS2_PORT PORTB
#define IO_DISPLAY_CS2_BIT 7
#define IO_DISPLAY_CS2_DDR DDRB

/*! Display reset */
#define IO_DISPLAY_RST IO_DISPLAY_RST
#define IO_DISPLAY_RST_PORT PORTB
#define IO_DISPLAY_RST_BIT 0
#define IO_DISPLAY_RST_DDR DDRB

/*! Display data port */
#define IO_DISPLAY_PORT PORTA
#define IO_DISPLAY_PIN PINA
#define IO_DISPLAY_DDR DDRA


/*! Reset Ext A */
#define IO_RESET_A_PORT PORTD
#define IO_RESET_A_BIT 4
#define IO_RESET_A_DDR DDRD

/*! Reset Ext B */
#define IO_RESET_B_PORT PORTD
#define IO_RESET_B_BIT 5
#define IO_RESET_B_DDR DDRD

/*! Reset IC5 / IRCO */
#define IO_RESET_5_PORT PORTD
#define IO_RESET_5_BIT 6
#define IO_RESET_5_DDR DDRD

/*! Reset IC3 / MOTCO */
#define IO_RESET_3_PORT PORTD
#define IO_RESET_3_BIT 7
#define IO_RESET_3_DDR DDRD


/*! ISP SCK */
#define IO_ISP_SCK_PORT PORTB
#define IO_ISP_SCK_BIT 1
#define IO_ISP_SCK_DDR DDRB

/*! ISP MOSI */
#define IO_ISP_MOSI_PORT PORTB
#define IO_ISP_MOSI_BIT 2
#define IO_ISP_MOSI_DDR DDRB

/*! ISP MISO */
#define IO_ISP_MISO_PORT PORTB
#define IO_ISP_MISO_PIN PINB
#define IO_ISP_MISO_BIT 3
#define IO_ISP_MISO_DDR DDRB


/*! I2C Interface configuration */
#define I2C_BUF_SIZE 16
#define I2C_TWBR_INIT 12 /* 400 kHz */

/*! I2C Bus ID for IRCO */
#define IRCO_I2C_ID 42

/*! I2C Bus ID for MOTCO */
#define MOTCO_I2C_ID 44

Damit kann man immer sprechende(!) Symbole verwenden.

Darauf aufbauend kommen die low-level Routinen, anschließend die high-level Routinen usw. Das führt zu gut lesbaren Programmen, vor allem, wenn diese ausreichend kommentiert sind. Das fehlt beim Nibo noch sehr.

Fazit: Was ich gut finde: Viele "Sendung mit der Maus"-Kommentare wie beim RP6 und Kapselung mit iodefs.h wie beim Nibo.

Lisbeth
04.08.2007, 17:51
Geht es hier um Kommentare oder den strukturierten Aufbau einer Bibliothek?
@ehenkes:
1) doxygen ist schon ein Begriff, oder?
2) Was ist ein "Sendung mit der Maus"-Kommentar?
3) z.B MOTCO_I2C_ID: ID ist ok als Akü für Identifier, I2C sollte einem auch was sagen, aber MOTCO ist doch wieder eine strange Akü. Also was spricht denn da?
Ansonsten finde ich es gut, dass über Dokumention diskutiert wird. Auch im angeblich professionellen Bereich sieht es da meist schlecht aus. Deswegen finde ich Doxygen auch sehr gut, insbesondere wenn es so gut benutzt wird wie bei der Asuro-Dokumentation.
Wichtig ist bei einer guten Dokumentation meiner Meinung nach auch, die Absicht (das "Warum") rüberzubringen.
Gruß
Lisbeth

Sternthaler
04.08.2007, 17:54
@ehenkes
Wieso fehlt die 'Kapselung' bei meinem Beispiel aus der RP6-LIB?
Diese Frage meine ich ernst!
Für mich ist die Funktionalität schon dann gekapselt, wenn ein sinnvoller, notwendiger Softwareblock einen Funktionsnamen (plus Doku) bekommen hat.
Genau das ist doch sowohl bei dem RP6-LIB-Beispiel (und dem Rest) und eigendlich auch bei allen Funktionen aus der Asuro-LIB geschehen.
Haben wir beide eine Unterschiedliche Aufassung von Kapselung?
Natürlich kann jede Doku immer noch verbessert werden. (Schöner Begrif: "Sendung mit der Maus"-Kommentare)
Hierarchien in LIB's und 'Anwendungs'-Programmen zu machen ist selbstverständlich sinnvoll. Uneingeschrängte Zustimmung (gibt ein ACK an dich ;-) )

@Lisbeth
Super, nach dem warum zu fragen ist auch meiner Meinung nach sehr wichtig!

ehenkes
04.08.2007, 20:43
doxygen ist schon ein Begriff, oder?Ja, sicher.

Was ist ein "Sendung mit der Maus"-Kommentar?
Zum Beispiel so etwas (RP6):

// ---------------------------------------
// LEDs:

setLEDs(0b111111); // Turn all LEDs on!

// 0b111111 is a binary value and is the same as
// 63 in the decimal system.
// For this routine, the binary value is better to read, because each bit
// represents one of the LEDs.
// e.g. this:
// setLEDs(0b000001); would set only LED1
// setLEDs(0b000010); LED2
// setLEDs(0b000100); LED3
// setLEDs(0b101001); LED6, LED4, LED1 - and so on!

mSleep(1000); // delay 1000ms = 1s
setLEDs(0b000000); // All LEDs off!
mSleep(500); // delay 500ms = 0.5s


// ---------------------------------------

uint8_t runningLight = 1; // This defines the local unsigned 8 bit variable "runningLight".
// It can be accessed everywhere _below_ in this function.
// And ONLY within this function!

// ---------------------------------------
// Main loop - the program will loop here forever!
// In this program, it only runs a small LED chaselight.
while(true)
{
// Here we do a small LED test:
// ---------------------------------------
setLEDs(runningLight); // Set status LEDs to the value of the variable
// testLEDs.
// In the first loop iteration it has the value 1,
// and thus the StatusLED1 will be switched on.

runningLight <<= 1; // shift the bits of "runningLight" one step to the left.
// As there is only one bit set in this variable,
// only one LED is on at the same time.
// This results is a moving light dot like this:
// 1: 0b000001
// 2: 0b000010
// 3: 0b000100
// 4: 0b001000
// 5: 0b010000
// 6: 0b100000
//
// In decimal format that would be the numbers:
// 1, 2, 4, 8, 16, 32

// When we have reached a value > 32 (32 means Status LED6 is on),
// we need to reset the value of runningLight to 1 to start again
// from the beginning...
// Instead of "32" we could also write "0b100000".
if(runningLight > 32)
runningLight = 1; // reset runningLight to 1 (StatusLED1)

// If we want to see the running Light, we need to
// add a delay here - otherwise our human eyes would not see
// the moving light dot:
mSleep(100); // delay 100ms = 0.1s

// ---------------------------------------

}
// End of main loop
// ---------------------------------------

// ---------------------------------------
// The Program will NEVER get here!
// (at least if you don't perform a "break" in the main loop, which
// you should not do usually!)

return 0;
}
Ich finde dies für Programmier-Anfänger ideal erklärt. Da hat sich beim RP6 jemand richtig viel Mühe mit Liebe zu Einsteigern gemacht. Für diejenigen, die dies eher stört, gibt es die gleichen Programme unkommentiert. Das ist Service. :)


MOTCO_I2C_ID: ID ist ok als Akü für Identifier, I2C sollte einem auch was sagen, aber MOTCO ist doch wieder eine strange Akü. Also was spricht denn da?Nibo hat neben dem ATmega128 noch zwei ATtiny 44. MOTCO bedeutet Motor-Controller, IRCO bedeutet IR-Controller. Ich denke, das ist als Nibo-Slang für mich akzeptabel.


Wichtig ist bei einer guten Dokumentation meiner Meinung nach auch, die Absicht (das "Warum") rüberzubringen.Da stimme ich Dir zu 100% zu! Das nenne ich "Sendung mit der Maus"-Kommentar. Das fehlt in vielen Fällen, weil Entwickler irgendwann "betriebsblind" werden. Diesbezüglich finde ich Kommentare wie oben beim RP6 super.


Wieso fehlt die 'Kapselung' bei meinem Beispiel aus der RP6-LIB? Kapselung ist für mich ein Begriff aus der Objekt-Orientierten Programmierung (OOP). Siehe z.B.: http://www.henkessoft.de/C++/C++/kapselung.htm

Kapselung:
Allgemein eine Technik zur Schnittstellendefinition.
Durch Kapselung werden Realisierungsdetails hinter einer definierten Schnittstelle verborgen. Die Schnittstelle kann dadurch oft auch erhalten bleiben, wenn sich Interna ändern müssen.

Bei dem Beispiel werden Begriffe verwendet, die "Prozessor-nah" sind, wie PORTB o.ä. Wenn ich programmiere, will ich z.B. die rote, grüne oder weiße LED oder die Display-Beleuchtung einschalten oder den linken Motor auf eine bestimmte Geschwindigkeit einstellen etc. Hierzu verwendet man möglichst sprechende Funktions-/Makronamen. Hier ein typisches Beispiel vom Nibo, bei dem dies IMHO gut gelungen ist:

leds_set_displaylight(800);

gfx_move(10,10);
gfx_print_text("Nibo sagt:", 0);

gfx_move(10,30);
gfx_print_text("Hallo Welt!", 0);

int i,j;
for(i=0;i<10;++i)
{
for(j=0;j<6;++j)
{
leds_set_status(LEDS_RED,j);
delay(500);
}

leds_set_headlights(256);

for(j=0;j<6;++j)
{
leds_set_status(LEDS_ORANGE,j);
delay(500);
}

leds_set_headlights(512);

for(j=0;j<6;++j)
{
leds_set_status(LEDS_GREEN,j);
delay(500);
}

leds_set_headlights(1024);
}
Da findest Du keine einzige Prozessorpinbezeichnung! Das meine ich mit Kapselung, und bei High-Level-Funktionen wie gfx_move oder gfx_print möchte ich nicht sehen, wie das läuft, sondern es nur anwenden. Die nächsthöhere Ebene ist dann z.B.:

void textout(int x, int y, char* str, int ft)
{
gfx_move(x,y);
gfx_print_text(str,ft);
}

leds_set_displaylight(1000);

float Ubatt = SupplyVoltage();
char text[6];
float2string(Ubatt,2,text);

textout(4,10,"Versorgungsspannung:",0);
textout(4,24,text,0);
textout(60,24,"Volt",0);
Da kann man doch schon fast mitlesen, oder? ;)

Beim ASURO gibt es solche Funktionen inzwischen auch wie Go(...), Turn(...), Chirp(...). Man darf nur nicht in den Rumpf schauen, denn da fällt man bis auf die unterste Ebene durch, weil die Schichtung fehlt. ;-)


Für mich ist die Funktionalität schon dann gekapselt, wenn ein sinnvoller, notwendiger Softwareblock einen Funktionsnamen (plus Doku) bekommen hat.Aus meiner Sicht fast richtig. Wenn ich nämlich bei einer High-Level in den Funktionsrumpf schaue möchte ich nichts von PORTB oder DDRB lesen, sondern erwarte ein bis zwei Schichtungen mehr.

Also ich fasse mal meine - zugegebenermaßen total persönliche - Sicht zusammen:
Was erwarte ich?
1) Gute "Schichtung" (iodefs.h / low level / mid level / high level)
2) Kapselung (z.B. finde ich es suboptimal, dass beim ASURO bei einem Anwender-Programm in asuro.c programmiert wird, in einer Datei, in der ich Details bis zum Prozessor finde)
3) "Sendung mit der Maus"-Kommentare (Hintergründe und Absicht erklären). Mit modernen Systemen kann man solche Kommentare ein-/ausblenden.

Keiner der von mir angesprochenen Roboter-Bibliotheken erfüllt diese Forderungen komplett.
Punkt 1 und 2 ist IMHO am besten beim Nibo erfüllt. Dafür ist Punkt 3 bei ihm noch völlig unterentwickelt.
RP6 ist bezüglich Punkt 3 an manchen Stellen wirklich vorbildhaft.
ASURO schneidet selbst bei Punkt 3 nicht gut ab, was er als Einsteigermodell sollte! (er hat eine so gute Bauanleitung)
Schaut euch doch mal die Funktion Init() in asuro.c an. Das sind doch alles Pseudo-Erklärungen, die ich ohne Datenblatt des ATmega8 nicht verstehen kann.

/* Timer2, zum Betrieb mit der seriellen Schnittstelle, fuer die IR-Kommunikation auf 36 kHz eingestellt. */
TCCR2 = (1 << WGM20) | (1 << WGM21) | (1 << COM20) | (1 << COM21) | (1 << CS20);
OCR2 = 0x91; // duty cycle fuer 36kHz
TIMSK |= (1 << TOIE2); // 36kHz counter
@Sternthaler: gerade hier hatten wir doch eine sehr fruchtbare Diskussion, warum hier 36 kHz resultieren. Du warst der Erste, der mir dies schlüssig erklären konnte (dafür wirklich vielen Dank!). Das müsste aber alles hier im Programm als Kommentar drinnen stehen, aber vielleicht nicht alles direkt in der ersten Ebene, das gehört weiter unten hin, in die dritte oder vierte Ebene.

Ich hoffe, dass meine Erklärungen dazu beigetragen haben, meine zugegebenermaßen hohen Erwartungen zu beleuchten. Ich bitte ernsthaft darum, dies als konstruktive Kritik aufzufassen. Kommentare kann man nachrüsten, bei Schichtungen/Kapselungen ist dies deutlich schwieriger.

Wer sich selbst ein Bild machen möchte:
Quellen für Sourcecode:
1) Nibo: http://downloads.sourceforge.net/nibo/nibolib-20070725.zip?modtime=1185393515&big_mirror=0
2) RP6: http://www.arexx.com/rp6/downloads/RP6Examples_20070731.zip
3) ASURO: http://downloads.sourceforge.net/asuro/AsuroLib-v270rc3.zip?modtime=1176248289&big_mirror=0

PS: c't-Bot habe ich bei der Betrachtung bewusst ausgelassen, da diese Bibliothek nur wenigen bekannt ist und dieser Roboter hier kaum noch besprochen wird.

Sternthaler
05.08.2007, 01:13
Eine gelungene Zusamenfassung.
Mir gefällt deine 'Dreipunkte-Liste' recht gut. Grundsätzlich kann ich da zustimmen.
Mein einziger, auch nur persönlicher, Einwand an dieser Stelle würde Punkt 1) betreffen. Hier halte ich den Asuro/CPU und seine Lib einfach für zu klein um mehrere Funktions-Ebenen unterzubringen. Jeder Fkt-Aufruf benötigt leider Speicher, aber das kennst du ja.

Ich hoffe nicht, dass in der asuro.c programmiert wird, wenn jemand die LIB, oder auch die mitgelieferten Sourcen der CD nutzt. (Warum aber nicht, ist ja sein Asuro? Immer ketzerisch!) Meine Programme heißen immer test.c (man ist ja so faul im Makefile zu ändern.)

Mit der 36kHz-Doku gebe ich dir Recht, könnte/sollte vorhanden sein.

P.S.: Die RP6-LIB habe ich schon in der 'lesenden' mache. Ist sehr mächtig was da geliefert wird. Die Funktion task_RP6System() wird dir auf alle Fälle bei deiner 'Dreipunkte-Liste' zusagen. (Mir auch)

So gibt es eben immer Punkte in jeder LIB, die man herauspicken kann, um Pro und Contra in das entsprechende Licht zu rücken, welches man gerade selber angemacht hat.

SlyD
07.08.2007, 13:32
Update - 07.08.2007
Es gibt eine neue Libary Version:
http://www.arexx.com/rp6/downloads/RP6Examples_20070807.zip

Die ACS Einstellungen wurden aus der task_ACS Funktion externalisiert und sind jetzt über einige defines in der RP6Config.h zugänglich.
Jetzt kann man gut Experimente mit anderen Einstellungen machen.
Neu ist auch ein Schwellwert für zu empfangende Impulse, der nachdem ein Hindernisses detektiert wurde unterschritten werden muss, damit wieder "Weg ist frei" gemeldet wird. Damit wird das hin und her schalten zwischen "Hindernis" und "kein Hindernis" bei bestimmten Distanzen verringert.

---------------------------------------------------------------------

MfG,
SlyD