Forum: Mikrocontroller und Digitale Elektronik ATMega 128 Feststellen von einem Tastendruck


von Peter M. (killbill)


Lesenswert?

Hallo zusammen,
ich komme gerade irgendwie nicht weiter, ich würde gerne eine Methode 
schreiben, die einen Tastendruck von der Tastatur registriert und davon 
abhängig dann eben etwas macht. Das ganze soll über die USART 
Schnittstelle vom Mikrocontroller laufen, ich habe mir testweise auch 
schon ein Echo Programm geschrieben, das funktioniert soweit auch schon, 
allerdings wird da eben immer auf eine Eingabe gewartet und ich möchte 
eben, dass das Programm weiterläuft wenn es mal keine Eingabe gibt und 
dann nicht weiter wartet.

Da ich dabei nicht wirklich weitergekommen bin, wollte ich mir Schritt 
für Schritt anschauen was mit dem UCSR1A Register passiert, wenn ich 
Tasten drücke, allerdings kommt mir hier auch etwas komisch vor.
Und zwar habe ich folgende Methode mal erstellt:
1
unsigned char getc_USART1()
2
{
3
  while((UCSR1A & (1<<RXC1)) == 0);
4
  return UDR1;
5
}

Wenn ich das dann in der folgenden Funktion so ausführe funktioniert es 
auch, das Programm bleibt an der while Schleife von getc_USART1() stehen 
bis ich in der Konsole eine Taste drücke.
1
int main_test()
2
{
3
  init_USART1();
4
  unsigned char temp = 0;
5
  while(1)
6
  {
7
    temp = UCSR1A;
8
    getc_USART1();
9
  }
10
}

Wenn ich das allerdings so ausführe bleibt das Programm im Debugger in 
der while((UCSR1A & (1<<RXC1)) == 0); Schleife hängen, es sollte doch 
das gleiche sein wie wenn ich die Funktion getc_USART1() aufrufe?
Was hier eventuell noch wichtig zu erwähnen ist, ich benutze einen 
Simulator für den Mikrocontroller. Nennt sich AVRORA gdbserver in den 
Debug Einstellungen, falls das jemanden was sagt.
1
int main_test()
2
{
3
  init_USART1();
4
  unsigned char temp = 0;
5
  while(1)
6
  {
7
    while((UCSR1A & (1<<RXC1)) == 0);
8
          return UDR1;
9
  }
10
}

Aber das ist eigentlich nebensächlich, wichtiger wäre mir wenn ihr mir 
weiterhelfen könnt, wie ich immer nur einfach prüfen kann, ob eine Taste 
gedrückt wurde an der Tastatur oder nicht, ich habe das auch schon wie 
folgt versucht, nur funktioniert das nicht, egal ob ich eine Taste 
drücke oder nicht, es wird immer 255 zurückgegeben.
1
unsigned char lookc_USART1()
2
{
3
  if((UCSR1A & (1<<RXC1)) == 1)
4
    return UDR1;
5
  else
6
    return 0xff;
7
}

Vielen Dank schonmal und anbei noch das Datenblatt vom Mikrocontroller.

http://www.atmel.com/images/doc2467.pdf
S.188 ff.

von Karl H. (kbuchegg)


Lesenswert?

Peter M. schrieb:

> Was hier eventuell noch wichtig zu erwähnen ist, ich benutze einen
> Simulator für den Mikrocontroller. Nennt sich AVRORA gdbserver in den
> Debug Einstellungen, falls das jemanden was sagt.

Nicht wirklich.
Aber ich traue Simulatoren an dieser Stelle auch nicht wirklich über den 
Weg. Speziell wenn es darum geht, externen Input korrekt in die 
Simulation einfliessen zu lassen

>
1
int main_test()
2
> {
3
>   init_USART1();
4
>   unsigned char temp = 0;
5
>   while(1)
6
>   {
7
>     while((UCSR1A & (1<<RXC1)) == 0);
8
>           return UDR1;
9
>

und warum breakst du dich aus main raus, sobald du ein Character von der 
UART kriegst?


> weiterhelfen könnt, wie ich immer nur einfach prüfen kann, ob eine Taste
> gedrückt wurde an der Tastatur oder nicht,

vergiss die Tastatur. Dein µC weiss nichts von einer Tastatur. Der hat 
eine UART, eine serielle Schnittstelle. An der ist entweder ein byte 
angekommen oder es ist keines angekommen. Wer auf der Gegenseite die 
Bytes auf den Weg bringt, spielt keine Rolle. Das kann eine Tastatur 
sein, muss es aber nicht.

>   if((UCSR1A & (1<<RXC1)) == 1)

Ein Vergleich auf 1 ist nicht dasselbe wie die Forderung, dass das 
Ergebnis der Und-Verknüpfung ungleich 0 sein muss. Auch eine 4 wäre 
ungleich 0. Insbesondere hängt es von der genauen Bitposition des Bits 
RXC1 innerhalb des Registers ab, welche Zahl dann konkret übrig bleibt, 
sollte das Bit tatsächlich auf 1 sein.

Du kennst doch die Technik schon.
Hier
1
unsigned char getc_USART1()
2
{
3
  // warte solange bis auf der UART ein Zeichen reinkommt
4
  while((UCSR1A & (1<<RXC1)) == 0)
5
    ;
6
...

D.h. du hast hier gefragt, ob der Inhalt des Registers UCSR1A zu 0 wird, 
nachdem man alle anderen Bits bis auf das Bit RXC1 ausblendet.

Und jetzt drehst du das eben um. Nur darfst du eben nicht vergleichen ob 
das Ergebnis von dieser Und-Operation gleich 1 wäre.

Das Gegenteil von
1
   ...   == 0

lautet
1
   ...   != 0

und nicht
1
   ...   == 1

Steht links vom Vergleich beispielsweise ein Ausdruck, der ausgerechnet 
4 ergibt, dann ist die Bedingung
1
     ... 4 == 0
nicht erfüllt. Das gegenteil davon muss daher erfüllt sein.
1
         4 != 0
ist auch tatsächlich richtig. Aber
1
         4 == 1
ist es nicht.
Wenn du forderst, dass der ausgerechnete Wert 1 ergeben muss, dann muss 
da auch wirklich 1 rauskommen und nichts anderes. Es gibt aber viele 
Zahlen, die nicht 0 sind, aber allerdings auch nicht 1.

: Bearbeitet durch User
von Peter M. (killbill)


Lesenswert?

Ja so langsam habe ich auch fast die Befürchtung, dass mir der Simulator 
einen Streich spielt.
Ich hätte das ganze in der Uni mal ausprobieren sollen mit dem "realen" 
Mikrocontroller...

Der break aus der Main hat nicht wirklich einen Sinn ja, ich habe da im 
Prinzip nur den Code aus der einen Methode kopiert um zu sehen, ob da 
das gleiche rauskommt wenn ich das in der main direkt ausführe, was ja 
nicht passiert ist, wieso auch immer, ist jetzt aber auch erstmal nicht 
so wichtig.

Die Tastatur habe ich explizit erwänht, damit ich deutlich machen kann, 
dass es hier nicht um die Taste auf dem Mikrocontroller geht, aber hätte 
man sich wahrscheinlich auch schon denken können, wenn es um die UART 
Schnitstelle geht.

Der Erklärung bezüglich der Vergleiche von dir konnte ich nicht folgen. 
ein Bit hat doch immer nur den Zustand 0 oder 1, dementsprechend wenn 
ich jetzt sage, dass Und-Operation ungleich 0 ist, kann ich doch auch 
sagen, dass die 1 sein soll, weil etwas anderes kann ja "ein" Bit nicht 
sein, wenn es ungleich 0 sein soll, jetzt mal abgesehen von den 
undefinierten Zuständen.

Ich habe jetzt aber auch nochmal ein bisschen recherchiert und auch noch 
bei euch hier ein Tutorial bei euch zu dem Thema gefunden:
https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Der_UART
Ich habe das dann jetzt mal für meinen Anwendungszweck angepasst, aber 
ich komme nach wie vor nur in den else Zweig, egal wie oft ich eine 
Taste vorher drücke...
1
int main_test()
2
{
3
  init_USART1();
4
  unsigned char temp = 0;
5
  while(1)
6
  {
7
8
    if ( (UCSR1A & (1<<RXC1)) )
9
    {
10
      // Zeichen wurde empfangen, jetzt abholen
11
      uint8_t c;
12
      c = getc_USART1();
13
      // hier etwas mit c machen z.B. auf PORT ausgeben
14
      temp = c;
15
    }
16
    else
17
    {
18
      temp = 'f';// Kein Zeichen empfangen, Restprogramm ausführen...
19
    }
20
21
  }
22
}

Dann habe ich es auch noch mit der folgenden if-Abfrage versucht
if ( (UCSR1A & (1<<RXC1)) != 0 )
Das war ja dein Vorschlag, wenn ich dich richtig verstanden habe, hier 
komme ich aber ebenfalls auch immer nur in den else Zweig.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Peter M. schrieb:

> Der Erklärung bezüglich der Vergleiche von dir konnte ich nicht folgen.
> ein Bit hat doch immer nur den Zustand 0 oder 1

wir arbeiten in C aber nicht mit einzelnen Bits. Wir arbeiten immer mit 
einem Byte. Das besteht aus 8 Bit.

Allerdings kann man in einem Byte bestimte Bits auf 1 setzen oder auf 0 
setzen.

Genau das passiert hier auch. Angenommen das Bit mit dem Namen RXC wäre 
das Bit3 im Register
1
                   RXC1
2
  +---+---+---+---+---+---+---+---+
3
  |   |   |   |   |   |   |   |   |
4
  +---+---+---+---+---+---+---+---+

dann kriegst du mittels Zugriff auf das Register
1
    UCSRA1

das komplette Register - alle 8 Bit. Die können zb so stehen
1
    01101001
da sind einige Bits auf 0 und einige auf 1.
Gut.
Die anderen interessieren dich nicht, dich interessiert nur das Bit 3, 
welches das RXC ist. Um dessen Wert festzustellen, möchtest du also die 
anderen weg haben. Damit die weg sind, zwingst du sie auf 0. Mit einer 
Und Operation und einer entsprechenden Mask
1
    if( UCSRA1 & ( 1 << RXC1 ) )

rechnerisch machst du auf Bitebene
1
    UCSRA1         01101001
2
    (1<<RXC1)      00001000     &
3
                -------------
4
                   00001000

du zwingst also alle Bits ausser dem Bit RXC1 auf 0. An der Position 
RXC1 bleibt damm im Ergebnis ein 1 Bit zurück (so wie hier) oder eben 
ein 0 Bit.
Aber das ganze Byte hat nicht den Zahlenwert 1! Das Bitmuster 00001000 
entspricht dezimal dem Zahlenwert 8 und nicht 1. Da wird das ganze 
entstehende Byte dem Vergleich unterzogen.
Du solltest dich eigentlich schon mit den sog. Bitoperationen beschäftgt 
haben! LEd einschalten, LED ausschalten, Taster abfragen oder eben auch 
feststellen welchen Zustand ein Bit in einem Register hat. Das alles 
sind immer nur Variationen von ein und demselben: Mittels Und und Oder 
Operationen bzw. entsprechenden Masken einzelne Bits in einem Byte zu 
manipulieren. In der µC-Programmierung ist das eine Grundtechnik, die du 
im Schlaf beherrschen musst.
Bitmanipulation


>
1
int main_test()
2
> {
3
>   init_USART1();
4
>   unsigned char temp = 0;
5
>   while(1)
6
>   {
7
> 
8
>     if ( (UCSR1A & (1<<RXC1)) )
9
>     {
10
>       // Zeichen wurde empfangen, jetzt abholen
11
>       uint8_t c;
12
>       c = getc_USART1();
13
>       // hier etwas mit c machen z.B. auf PORT ausgeben
14
>       temp = c;
15
>     }
16
>     else
17
>     {
18
>       temp = 'f';// Kein Zeichen empfangen, Restprogramm ausführen...
19
>     }
20
> 
21
>   }
22
> }

Zeig bitte gaze Programme und wenn du 'irgendetwas machen' willst, dann 
lass dein Programm etwas machen, was der Compiler nicht wegoptimieren 
kann. Zum Beispiel einen wirklichen Port Zugriff
1
...
2
     if ( (UCSR1A & (1<<RXC1)) )
3
     {
4
        uint8_t c = getc_USART1();
5
        PORTB = c;
6
     }

> Dann habe ich es auch noch mit der folgenden if-Abfrage versucht
> if ( (UCSR1A & (1<<RXC1)) != 0 )

das ist dasselbe.
In C verlangt ein if keinen Vergleich. In C verlangt ein if einen 
Ausdruck, den es auswertet. Entweder ergibt der Ausdruck ungleich 0, 
dann wird der then Teil genommen, oder der Ausdruck ergibt 0, dann wird 
der else Teil genommen. Ja, in C ist auch ein Vergleich einfach nur eine 
Operation, die ein Ergebnis liefert und nichts besonderes.

: Bearbeitet durch User
von Peter M. (killbill)


Lesenswert?

Ja du hast natürlich recht mit der Byte Betrachtung, da stand ich wohl 
ganz schön auf dem Schlauch.

Welchen Teil willst du den noch vom Programm, es fehlt ja eigentlich nur 
noch die init_USART1 Methode, aber an der kanns ja nicht liegen, weil 
meine Echo Programm, also warte auf Eingabe und gib diese über USART auf 
der Konsole aus funktioniert tadellos.

Im then Zweig ist doch auch schon die getc_USART1 Methode ein 
Portzugriff, aber ich probiere nachher den Vorschlag von dir aus, nur 
habe ich da nicht besonders viele Hoffnungen...

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Peter M. schrieb:
> Ja du hast natürlich recht mit der Byte Betrachtung, da stand ich wohl
> ganz schön auf dem Schlauch.
>
> Welchen Teil willst du den noch vom Programm,

die Uart Initialisierung. Gibst du den Empfänger überhaupt frei? Hast du 
Interrupts eingeschaltet?

> noch die init_USART1 Methode, aber an der kanns ja nicht liegen, weil
> meine Echo Programm, also warte auf Eingabe und gib diese über USART auf
> der Konsole aus funktioniert tadellos.

Darauf gebe ich nichts.
Im Laufe der Jahre habe ich gelernt, alles in Frage zu stellen ehe es 
nicht überprüft wurde. Und sei es noch so trivial. Und in einer 
erklicklichen Anzahl von Fällen habe ich damit durchschlagenden Erfolg.

: Bearbeitet durch User
von Peter M. (killbill)


Lesenswert?

ok dann hier nochmal die Uart Initialisierung
1
void init_USART1()
2
{
3
  //UBRR1H = 0x0;        //ist schon per Default auf 0
4
  UBRR1L = 0x19;        //UBRR1L = 25
5
  UCSR1B |= (1<<TXEN1);    //Sender einschalten
6
  UCSR1B |= (1<<RXEN1);    //Empfänger einschalten
7
8
  //UCSR1C |= (1<<UCSZ11);  //8 Datenbits; schon per Default auf 8 Datenbits eingestellt
9
  //UCSR1C |= (1<<UCSZ10);
10
11
  UCSR1C |= (1<<USBS1);    //2 Stopbits
12
13
  //UCSR1C &= ~(1<<UPM11);  //keine Parität; schon per Default eingeschaltet
14
  //UCSR1C &= ~(1<<UPM10);
15
16
  //UCSR1C &= ~(1<<UMSEL1);  //asynchroner Mode; per Default schon eingeschaltet
17
}

Ich weiß zwar nicht wie da was falsch sein kann, wenn das Echo Programm 
funktioniert, aber vielleicht werde ich ja jetzt eines besseren belehrt.

Empfänger habe ich wie in den Kommentaren auch beschrieben aktiviert und 
funktioniert ja auch im Echo Programm, Interrupts nutze ich nicht, 
zumindestens nicht bewusst, die setzen wir erst aktiv im nächsten 
Versuch ein, daher gehe ich davon aus, dass es wohl hier auch gehen muss 
ohne das man die Interrupts selbst aktiviert oder selber welche nutzt.

Ich habe es eben noch mit deinem Vorschlag ausprobiert, das folgende in 
den then Zweig einzufügen
PORTB = c;
Das Programm springt aber nach wie vor immer nur in den else Zweig, 
unabhängig davon ob ich eine Taste drücke oder nicht.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Ich liebe es, wenn ich mir aus mehreren Postings die Einzelteile 
zusammensuchen muss.

Brenn das mal. Brenne es genau so und nicht anders. Die einzigen 
Anpassungen sind bei F_CPU an deine tatsächliche Taktfrequenz und bei 
BAUD auf die zu verwendende Baudrate erlaubt.
1
#define F_CPU 4000000UL
2
#define BAUD 9600UL
3
4
#include <avr\io.h>
5
#include <util\setbaud.h>
6
7
void uartInit()
8
{
9
  UBRR1H = UBRRH_VALUE;
10
  UBRR1L = UBRRL_VALUE;
11
12
#if USE_2X
13
  UCSR1A |= (1 << U2X1);
14
#else
15
  UCSR1A &= ~(1 << U2X1);
16
#endif
17
18
  UCSR1B = (1<<TXEN1) | (1<<RXEN1);
19
  UCSR1C = (1<<UCSZ01) | (1<<UCSZ00) | (1<<USBS1);
20
}
21
22
int main()
23
{
24
  uartInit();
25
26
  while( 1 ) {
27
    if( UCSR1A & (1 << RXC1)) {
28
      char c = UDR1;
29
            
30
      while (!(UCSR1A & (1 << UDRE1)))
31
        ;
32
      UDR1 = c;
33
    }
34
  }
35
}

von Peter M. (killbill)


Lesenswert?

So dann melde ich mich mal zurück.
Zunächst erst einmal Danke! Ich habe den Fehler nun gefunden, bzw. es 
funktioniert jetzt wenigstens.
Also zuerst einmal habe ich den Programmcode von dir getestet und es hat 
auf Anhieb funktioniert.
Dann habe ich nochmal meinen Code probiert und es hat plötzlich auch 
funktioniert.
Allerdings habe ich jetzt etwas anders gemacht und zwar habe ich im 
Debugger die Endlosschleife jetzt automatisch immer durchlaufen lassen, 
bei meinen letzten Versuchen bin ich immer Zeile für Zeile mit Step Into 
oder Step over im Debugger durchgegangen und da hat es nicht 
funktioniert, selbst wenn ich um die 20 Schleifendurchgänge gemacht 
habe.
Zudem habe ich auch festgestellt, wenn ich im else Zweig einen Zugriff 
auf den UART Port mache, also z.B. UDR1 = c; dann springt er beim 
manuellen durchgehen im Debugger nach einem weiteren Schleifendurchgang 
tatsächlich in den then Zweig, wenn ich allerdings im else Zweig keinen 
Zugriff auf den UART Port mache, beispielsweise einfach nur temp = c 
dann springt er nicht in den then Zweig, selbst nach 20-30 
Schleifendurchgängen nicht, allerdings wenn ich  das Programm im 
Debugger wieder automatisch laufen lasse, funktioniert es auch mit temp 
= c im else Zweig.

Ich schätze mal mir hat dann wohl entweder der Simulator oder der 
C-Compiler, oder auch beide zusammen einen Streich gespielt.
Der Vergleich mit ==1 wie in meinem ersten Post dargestellt war 
natürlich auch ein ziemlich dämlicher Fehler :P

Zum Abschluss möchte ich mich nochmal herzlich bedanken und vielleicht 
hat ja noch jemand eine Antwort darauf, wodurch der Fehler verursacht 
wurde.

von Karl H. (kbuchegg)


Lesenswert?

Peter M. schrieb:

> tatsächlich in den then Zweig, wenn ich allerdings im else Zweig keinen
> Zugriff auf den UART Port mache, beispielsweise einfach nur temp = c
> dann springt er nicht in den then Zweig, selbst nach 20-30
> Schleifendurchgängen nicht, allerdings wenn ich  das Programm im
> Debugger wieder automatisch laufen lasse, funktioniert es auch mit temp
> = c im else Zweig.

Ich denke was du hier siehst, das sind Auswirkungen des OPtimizers 
deines Compilers, der dein Programm umstellen darf, solange die nach 
aussen sichtbaren Effekte dieselben bleiben. D.h. zum Beispiel dass der 
OPtimizer Variablen hinauswerfen darf, wenn von ihnen nichts abhängt. 
Wenn du nur zu, "Spass an der Freude" an eine Variable x etwas zuweist, 
aber mit diesem x weiter nichts machst, dann hat diese Zuweisung an x ja 
keine Auswirkungen ausser das sie Zeit verbraucht. Also kann der 
Optimizer die Zuweisung rauswerfen. Womit dann unter Umständen die ganze 
Variable sinnlos geworden ist. Denn eine Variable an die nichts 
zugewiesen wird und deren Wert nirgends verwendet wird, die braucht auch 
keiner. Wenn aber diese hinausgeworfene Zuweisung das einzige war, was 
in einem else (beispielsweise) passiert, dann ist auch das else sinnlos 
geworden usw. usw.
Compiler sind recht gut darin, derartige Dinge zu entdecken und deinen 
Programmtext entsprechend umzubauen.

Ich denke, dass so etwas in dieser Richtung dir passiert ist.

: Bearbeitet durch User
von Peter M. (killbill)


Lesenswert?

ok hört sich sinnvoll an, also sollte ich mir für das nächste mal 
merken, lieber immer auf einen PORT zugreifen und eben etwas über UART 
ausgeben, LEDS blinken lassen, etc.

Ist dann die Begründung, dass irgendwann doch der then Zweig ausgeführt 
wird, dass der Compiler dann feststellt, dass er immer und immer wieder 
an den gleichen Punkt kommt und dann eben doch diese sinnlose Zuweisung 
durchführt?

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Peter M. schrieb:
> ok hört sich sinnvoll an, also sollte ich mir für das nächste mal
> merken, lieber immer auf einen PORT zugreifen und eben etwas UART
> ausgeben lassen, oder LEDS blinken lassen, etc.

genau.

> Ist dann die Begründung, dass irgendwann doch der then Zweig ausgeführt
> wird, dass der Compiler dann feststellt, dass er immer und immer wieder
> an den gleichen Punkt kommt und dann eben doch diese sinnlose Zuweisung
> durchführt?

Nein.
Das Problem ist, dass das was du im Debugger siehst nicht mehr viel mit 
dem tatsächlich übersetzten Programm zu tun hat. Sprich: du kannst 
deinem Debugger nicht mehr vertrauen, dass seine Anzeige, wo das 
Programm gerade steht auch der Realität entspricht. Denn das was du 
geschrieben hast, stimmt nicht mehr 1:1 mit dem überein, was der 
Compiler/Optimizer dann daraus gemacht hat. Die Zeilen deines Programmes 
finden sich nicht mehr im tatsächlich laufenden Programm wieder. Der 
Debugger muss aber seinen Pfeil der gerade abzuarbeitenden 
Anweisung/Zeile irgendwo hinstellen. Und genausogut kann es im Programm 
dann durch Umstellungen durch den Optimizer soweit kommen, dass es für 
das was gerade abgearbeitet wird überhaupt an dieser Stelle keine 
Entsprechung in deinem ursprünglichen Programm gibt.

D.h. in Kurzform: Wenn der Optimizer aktiv war, dann sind derartige 
Dinge im Debugger mit Vorsicht zu geniessen. Da stimmt nicht alles, was 
du angezeigt bekommst. D.h. bei der Aussage "funktioniert nicht" ist es 
auch immer gut zu wissen: Siehst du im Debugger etwas überraschendes 
oder verhält sich die tatsächliche Hardware nicht so wie erwartet. Wenn 
im Debugger einzelne Anweisungen scheinbar übersprungen werden, dann ist 
das noch kein Grund zur Panik - bei aktivem Optimizer. Ist der Optimizer 
abgeschaltet, dann allerdings besteht Grund zur Panik :-)

: Bearbeitet durch User
von Peter M. (killbill)


Lesenswert?

ah ok jetzt bin ich schon ein ganzes Stück schlauer, Danke!

Ich denke ich sollte mich dann mal schlau machen wo ich den Optimizer in 
Eclipse deaktivieren kann, macht das ganze ja dann beim Debuggen doch um 
einiges leicher, oder habe ich dann andere/neue Probleme, wenn ich den 
Optimizer deaktiviere?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.