Forum: Compiler & IDEs GCC + Interrupt


von Christian S (Gast)


Lesenswert?

Hallo,
wenn ich meinen AVR in den sleep-mode schicke und ihn per ext.
Interrupt aufwecken will (LOW-Pegel) habe ich folgendes Problem. Die
ISR wird ausgeführt, und wenn die Taste nach ausführung der ISR
immernoch gedrückt ist, wird die ISR nochmal ausgeführt (bis ich die
Taste loslasse) -> wie kann ich dies unterbinden, denn die ISR soll nur
einmal ausgeführt werden.

Warum kann ich keine Funktionsaufrufe in meine ISR reintun, wie z.B.
senden über uart-> wird nur Müll gesendet

Danke schon mal für eure Hilfe.

von mthomas (Gast)


Lesenswert?

Abhängig von der Einstellung koennen externe Interrupts so lang
"triggert", wie die "Ausloesebedingung" erfuellt ist. Abhilfe:
Interrupt in der ISR deaktivieren und im Hauptprogramm erst dann wieder
aktiveren, wenn "Taste losgelassen", danach "sleep".

von Marian (Gast)


Lesenswert?

>Warum kann ich keine Funktionsaufrufe in meine ISR reintun, wie z.B.
>senden über uart-> wird nur Müll gesendet

Das würde ich auch gerne mal wissen. Habe das nämlich auch gerade
festgestellt.

von peter dannegger (Gast)


Lesenswert?

Man kann sehr wohl Unterfunktionen in Interrupts aufrufen, man muß bloß
ungefähr wissen, wie lange diese Funktionen dauern.

UART-Funktionen gehören leider zu denen, die sehr sehr lange dauern.

Wenn Du also viele tausende Zyklen im Interrupt verbrätst, verhungert
Dir derweil Dein Mainprogramm und sämtliche anderen Interrupts.


Du darfst aber keine Funktionen aufrufen, die selber Interrupts
verwenden. Das gibt dann einen Deadlock (einer blockiert den anderen
und wartet dann auf den anderen), da ja in einem Interrupthandler alle
anderen Interrupts gesperrt sind.
Es gibt da zwar Tricks, aber die sollte nur der anwenden, der wirklich
genau weiß, was er tut.


Weitere gerne benutzte Performancekiller in Interrupts sind:
LCD-Ausgaben, Delay-Schleifen >100µs, float-Rechnungen und printf().
Wobei natürlich gilt, Ausnahmen bestätigen die Regel.


Peter

von Christian S (Gast)


Lesenswert?

Hallo Peter,
danke für Deine Antwort (natürlich auch an die anderen).
Wie kann ich es erreichen, dass mein AVR (tiny2313) die ISR nur einmal
ausführt?

von Rolf Magnus (Gast)


Lesenswert?

So wie es "mthomas" oben schreibt. Du deaktivierst den Interrupt in
der ISR.

von Marian (Gast)


Lesenswert?

Ich habe auch eine Frage zu den Unterfunktionen in den
Interruptroutinen. Wie kann ich meinem µC dann beibringen, per Uart
etwas zu senden, wenn der Interrupt ausgelöst wurde. Z.B. wenn ich
einen Timer verwende und dadurch das Overflow Interrupt ausgelöst wird
und ich dann etwas per Uart verschicken möchte. Ich möchte quasi eine
Zeit hochzählen und jede Millisekunde diese Zeit per Uart verschicken.
Kann man im Main-Programm irgendwie sagen, dass: Wenn Interrupt
ausgelöst, schicke die Zeit per Uart.
Also in C-Syntax:
if (Signal Overflow(oder sonstwas))
{
uart_puts(blabla)
}

Wisst ihr wie ich es meine?
Wäre für jede Hilfe dankbar...denke da jetzt schon ne ganze Weile
drüber nach und habe noch keine richtige Lösung gefunden.

von Karl H. (kbuchegg)


Lesenswert?

> Ich möchte quasi eine Zeit hochzählen und jede Millisekunde
> diese Zeit per Uart verschicken.

Sicher kannst Du das.
Deine main() ist dann im Grunde nichts anderes als eine
Endlosschleife. Dort benutzt Du dann ein Flag (globale Variable).
Ist diese gesetzt worden, dann verschickst Du die Zeit per UART
und setzt das Flag zurueck. Die Timer-Interrupt Funktion hat dann
die Aufgabe, in bestimmten Zeiteinheiten genau dieses Flag zu
setzen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Ich möchte quasi eine Zeit hochzählen und jede Millisekunde diese
> Zeit per Uart verschicken.

Dann sollte deine UART aber recht schnell sein.

Faustregel: bei 9600 Bd braucht ein Zeichen bereits ca. 1 ms.

von Marian (Gast)


Lesenswert?

Schonmal danke für eure Antworten. Hab das jetzt auch hinbekommen, d.h.
ich kann senden wann immer ich möchte(je nach Timereinstellung). Nun
habe ich eine andere Frage.
Wie Jörg ja schon anmerkte, wird es wohl schwierig jede ms die Zeit zu
übermitteln (hat auch nichts funktioniert ;)). Jetzt habe ich folgendes
probiert: Ich lasse mein Uart nur jede Sekunde die Zeit übermitteln, das
funktioniert auch soweit, nur das es ein Delay gibt(Jede Minute fehlen
ca. 2 Sekunden). Ich benutze die fdevopen und printf Funktionen um dies
zu realisieren und denke mir, dass diese Funktionen zu viel Performance
schlucken bzw. einfach nicht schnell genug sind.
Liege ich mit meiner Vermutung richtig? Wenn ja, was könnte ich denn
sonst machen um den Timestamp per Uart zu versenden?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Da du die Zeit in der ISR hochzählst, sollte es egal sein,
wie lange das Ausgeben der Zeit anschließend dauert, solange
du kein `roll over' hast, also die Ausgabe nicht summa summarum
langsamer als das Weiterschalten ist.

von Marian (Gast)


Lesenswert?

Hmm...aber woran liegt es dann? Und wie kann ich genau feststellen, dass
meine Ausgabe nicht langsamer als das weiterschalten ist?
Ich habe den Timer jetzt 2 Stunden laufen lassen und er geht jetzt ca.
5 Minuten nach. Gibt es ne gute einfache Möglichkeit um zu überprüfen,
ob der Timer seinen Job richtig macht? Ich lasse im Moment nebenher
noch eine LED im Takt des Timers (also 1 s) blinken und da hat sich
nach 2 Stunden noch nichts sichtbares bemerkbar gemacht...

von Rolf Magnus (Gast)


Lesenswert?

> Und wie kann ich genau feststellen, dass meine Ausgabe nicht
> langsamer als das weiterschalten ist?

Also wenn du sie nur noch einmal pro Sekunde machst, ist sie wohl eher
nicht zu langsam.

> Ich habe den Timer jetzt 2 Stunden laufen lassen und er geht jetzt
> ca. 5 Minuten nach.

Wie taktest du den Timer denn?

von Marian (Gast)


Lesenswert?

Ich take ihn folgendermaßen: Da ich F_CPU 4 MHz habe, teile ich den Takt
durch 8. Daraus folgt eine Taktfrequenz von 500KHz für den Timer. Also
ein Takt entspricht nun 0,000002 Sekunden. Um ihn also auf 1 ms
einzustellen, muss ich den Timer bis 500 zählen lassen. um dies zu
erreichen lasse ich ihn jeweils 2 mal bis 250 zählen. Dann ist doch
meine 1ms korrekt oder?
Hier mein C-Code dazu:

void Timerinit(void)
{
  TIMSK=0x01;  //Timer/Counter Interrupt Mask
  TCNT0=0x05;  //255-Registerinhalt:=250
  TCCR0=0x02;  //Timer/Counter Control Register auf CK/8
  sei();    //All Interrupt enable
}
Das ist im Moment meine Interruptroutine für die eine Sekunde:

SIGNAL(SIG_OVERFLOW0)/* signal handler for tcnt0 overflow interrupt */
{
if (T==2000)//Wenn der timer 2 mal durchgelaufen ist,ist 1ms vergangen
  {
    timestamp++;
    if ( !(PINC & (1<<PINC5)) )
    {
      PORTC=0xFF;
    }
    else
    {
      PORTC=0x00;
    }
    T=0;
  }
  else
  {
    T++;
  }
}
Hilft dir das weiter um mir bei meinem problem zu helfen?
kann es sein, daß der Takt des µC nicht ganz stimmt(Ich nutze den
internen).

von Marian (Gast)


Lesenswert?

Ich sehe gerade, daß bei if(T==2000) ein kleiner Fehler im Kommentar
ist, da müsste jetzt stehen: Wenn er 2000 mal durchgelaufen ist, ist
eine Sekunde vergangen ;)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Für einen genauen Zeitgeber nimmt man keinen Overflow-
Interrupt, sondern lässt den Kanal im CTC-Modus (clear timer
on compare match) laufen und nimmt den compare match Interrupt.

Falls der Timer 0 in deinem AVR noch kein CTC kann, solltest du
besser über einen anderen Kanal nachdenken.

von Rolf Magnus (Gast)


Lesenswert?

Ich meinte eher, wo die 4MHz herkommen. Falls das der interne
RC-Oszillator ist, brauchst du dich über so eine Ungenauigkeit nämlich
gar nicht wundern.

von Rolf Magnus (Gast)


Lesenswert?

Mir fällt aber grad noch was auf.
1
if (T==2000)//Wenn der timer 2 mal durchgelaufen ist,ist 1ms vergangen
2
{
3
   timestamp++;  
4
   //...
5
   T=0;
6
}
7
 else
8
{
9
   T++;  
10
}

Der Code erhöht timestamp erst nach 2001 Timerdurchläufen.
Mach es mal so rum:
1
T++;
2
if (T==2000)
3
{
4
   timestamp++;  
5
   //...
6
   T=0;
7
}

von Marian (Gast)


Lesenswert?

OK danke für eure Hilfe, ich habe nun veruscht den CTC-Mode zu verwenden
und bin (wie sicherlich klar war) auf einige Probleme gestoßen. Als
erstes weiß ich nicht genau, ob ich den CTC-Mode richtig initialisiert
habe und wollte euch deswegen fragen, ob mein Code dazu richtig ist.

void Timerinit(void)
{
TIMSK=0x02;  //Timer/Counter Interrupt Mask
OCR0 =0xFA;  //Output Compare Register = 250
TCCR0=0xA;  //Timer/Counter Control Register auf CK/8 und CTC-Mode
sei();    //All Interrupt enable
}
Nun noch eine Frage: Meine LED blinkt jetzt wieder im
Sekunden-Takt(dekne ich zumindest) aber jetzt sagt mein UART nichts
mehr. Wieso? Ich habe doch nur den Timermoder verändert, also wieso mag
das Uart jetzt nicht mehr?

Nächste Frage:
Wie heißt die Interruptroutine für den CTC-Mode?
Etwa so:
SIGNAL(SIG_OUTPUT_COMPARE0) ???
Mich wundert halt, das die LED blinkt aber der Uart nicht mehr senden
will :(...dieser CTC-Mode ist am Anfang doch ganz schön schwer zu
begreifen. Habe da noch einige Fragen zu aber vielleicht klären mich ja
die nächsten Beiträge von euch auf :)...schonmal vielen Danke.

Gruß Marian

von Marian (Gast)


Lesenswert?

Ok jetzt bin ich ganz verwirrt!!!
Ich hatte den Vorschlag von Rolf angenommen und mein T++ vor die
if-Schleife gelegt(War beim Nachrechen auch ganz logisch). Jetzt hab
ich das T++ wieder an die vorherige Stelle getan und siehe da: Der Uart
sendet wieder...und anscheinend auch ein schöner 1 Sekundentakt.
Also liebe Profis, woran liegt das??? Wieso will Uart nicht senden,
wenn ich meine Variable vor der Schleife hochzähle?

Gruß der verwirrte Marian

von Marian (Gast)


Lesenswert?

Will mich keiner entwirren? :(

von Karl H. (kbuchegg)


Lesenswert?

Weil sich aus dem was du bisher erzaehlt hast
kein Zusammenhang zwischen der UART und deinem
Timerinterrupt ergibt.

Du musst also noch irgendwas anderes veraendert
haben.

von Marian (Gast)


Lesenswert?

Nein ich habe nichts verändert außer dem Timer-Modus und die ISR. Wie
schon geschrieben nur das T++ vor die if-schleife gelegt und sonst nur
den CTC-Modus aktiviert. Deswegen bin ich ja auch so verwirrt :)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

CTC-Modus ist sowas von trivial, irgendwas musst du komplett
vergeigt haben.  Aber sorry, die Schnipsel sind mir bisschen
zu zusammenhanglos, als dass ich persönlich zu viel Zeit da
rein investieren möchte.

Schreib' mal die von dir gewünschte Funktionalität auf, und
es wird sich sicher jemand finden, der dir dafür ein
Programmgerippe entwirft.  Das geht m. M. n. schneller.

von Marian (Gast)


Angehängte Dateien:

Lesenswert?

Wieso sorry? Musst du doch auch nicht. Du hast mir doch eh schon so oft
geholfen aber ich dachte halt, das meine Problemstellung genügt, damit
ihr Profis damit was anfangen könnt.
Also nochmal das was ich möchte:
Grunddaten: Atmega32 L mit 4 MHz(intern) und WinAVR
Programmiere über ISP (aber das ist auch nicht so wichtig).

Ich wollte halt zum Timerverständniss mal versuchen, einen Timer zu
schreiben, der mir jede Sekunde die aktuelle Zeit(seit Programmstart)
per Uart verschickt. Diese Zeit soll in 8 Stellen Hex ausgegeben
werden(und optional noch in dezimal).
Dazu habe ich (mittlerweile) den CTC-Modus verwendet(wurde mir ja
empfohlen).

Das die "UHR" nachgeht, ist mir jetzt aufgrund des
Frequenz/Temperaturverhaltens auch klar geworden. Also habe ich
eigentlich nur noch folgendes Problem.
Ich denke, das sich mein Programm (fast) selbsterklärt, weswegen ich es
angehangen habe.
Nun nochmal die Frage, die mich so verwirrt hat.
Wenn ich in der ISR das T++ for die If-Schleife schreibe, will mein
Uart nicht mehr senden! Habe es mehrfach probiert.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Habe es mehrfach probiert.

Ist halt kein Windows. :-))  Der Fehler ist nicht zufällig, sondern
systematisch.

> Wenn ich in der ISR das T++ for die If-Schleife schreibe, will mein
> Uart nicht mehr senden!

Ja klar.  Jetzt, nachdem ich da nochmal draufgeguckt habe, wird mir
auch klar, warum.  Deine UART sendet ja nur, wenn im Hauptprogramm die
Variable T mit dem Wert 2000 angetroffen wird.  Wenn du das T++ in der
ISR vorziehst, setzt die ISR aber beim Erreichen von 2000 sofort die
Variable wieder auf 0 zurück, das Hauptprogramm sieht also nie mehr
eine 2000.

Hier noch wahllose Kommentare:

> typedef unsigned long int   uint32_t;

Dafür bitte #include <stdint.h> nehmen.

> TIMSK=0x02; //Timer/Counter Interrupt Mask
> TCCR0=0xA;  //Timer/Counter Control Register auf CK/8 und CTC-Mode

Das schreibt man besser symbolisch:

TIMSK = (1 << OCIE0);
TCCR0 = (1 << WGM01) | (1 << CS01);

> OCR0 =0xFA; //Output Compare Register = 250

Warum denn in Hex=?

OCR0 = 250;

oder besser gleich:

#define F_CLOCK 2000
OCR0 = F_CPU / (8ul * F_CLOCK)

(Das F_CLOCK kannst du dann für all die anderen magischen Zahlen 2000
wiederverwenden.)

> PORTC=0x00;

Überflüssig, ist default.


> /*Set Frame format :8data, 1stop bit */
> UCSRC = (1<<URSEL) | (3<<UCSZ0) ;

Auch überflüssig, 8N1 ist ebenfalls default (hat ja auch Sinn).

> void uart_puts (char *s)

Benutzt du gar nicht, du nimmst ja stdio.

> if ( !(PINC & (1<<PINC5)) )   // Pinkontrolle ob sie auf "1" oder
"0" ist

Hat nicht recht Sinn.  Du fragst den Eingangspegel an einem Pin ab,
das als Ausgang geschaltet ist.  Da kannst du gleich PORTC abfragen,
es
sei denn, du erwartest, dass draußen jemand einen Kurzschluss anbringt.
;-)

Einfacher ist allerdings

PORTC ^= 0xFF;

>if (T==2000)
>{
> fdevopen(uart_putc, NULL,0);
> printf("S%08lx %li\n\r",timestamp,timestamp);

Bitte nur einmal fdevopen() am Anfang von main()...

von Marian (Gast)


Lesenswert?

Ersteinmal Danke Jörg! Ich hätte nicht gedacht, daß in diesem kleinen
Programm schon soviele "Fehler" enthalten sind. Danke das du mich
darauf hinweist! Aber ich habe mal wieder (hoffe ich nerve dich nicht
allzusehr) einige Fragen dazu:

Das mit dem T++ und Uart ist mir gestern auch noch klar geworden
(genauso wie du es mir erklärt hast).

Nun noch ein paar Anmerkungen (Fragen) von mir ;).
> typedef unsigned long int   uint32_t;
Das kann ich einfach weglassen, wenn ich die stdint.h einbinde? D.h. er
erkennt dann automatisch das uint32_t als 32 bit Variable?

Ok die neue Schreibweise, um die einzelne Bits zu setzen, nehme ich mir
jetzt auch an. Warum Hex? Tja keine Ahnung, hab ich am Anfang mal
gesehen und dachte halt, man macht es so...wusste nich das ich auch
einfach den Dezimalwert ins Register schreiben kann.

Das mit dem Default, hm....mir wurde gesagt, wenn man etwas nutzen
will, sollte man es so initialisieren wie man es möchte, damit keine
Missverständinsse oder Fehler auftreten. Aber OK, werde es mir aneignen
:).

Nun zu den beiden größten Probleme:
Laut Tutorial ist das doch die einzige Möglichkeit zu überprüfen, was
für einen Pegel ein PIN hat. Wie kann ich denn sonst herausbekommen, ob
mein PIN (wo die LED dran ist) auf "1" oder "0" ist.
Was meinst du mit: einfacher ist allerdings PORTC ^=0xFF; ? Könntest du
das bitte erläutern?

Und deinen letzten Satz verstehe ich auch nicht ganz. Meinst du, daß
ich fdevopen einfach vor die Schleife ziehen soll, damit es nicht
jedesmal mit ausgeführt wird? Kannst du mir vielleicht auch sagen,
warum das besser ist?

So long,
Gruß Marian

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> Ersteinmal Danke Jörg! Ich hätte nicht gedacht, daß in diesem
> kleinen Programm schon soviele "Fehler" enthalten sind.

Naja, Kleinigkeiten.

> Das kann ich einfach weglassen, wenn ich die stdint.h einbinde?
> D.h. er erkennt dann automatisch das uint32_t als 32 bit Variable?

Genau dafür ist stdint.h da.  Ist ein mit C99 neu hinzugekommener
Header, weil man erkannt hat, dass die Programmierer sehr oft (vor
allem bei hardwarenaher Programmierung) Datentypen benötigen, deren
Bitbreite sie exakt benennen möchten.  Indem man die Bereitstellung
dieser Typen der Implementierung (als Compiler bzw. Library)
überlässt, werden die Applikationen in dieser Hinsicht komplett
portabel.

> Tja keine Ahnung, hab ich am Anfang mal gesehen und dachte halt, man
> macht es so...wusste nich das ich auch einfach den Dezimalwert ins
> Register schreiben kann.

0xFA ist doch einfach eine Zahl, genau wie 250 (oder mit dem
gepatchten Compiler 0b11111010).  Man nimmt zweckmäßigerweise die
jeweils ausdrucksstärkste Zahl.  Für eine Zeitkonstante wie hier ist
das sicher dezimal, da unser Gehirn sich daran gewöhnt hat, so zu
denken.  Um alle Bits eines Ports im DDR-Register auf Ausgang zu
schalten, ist sicher 0xFF (oder 0b11111111) die bessere Darstellung.

> Das mit dem Default, hm....mir wurde gesagt, wenn man etwas nutzen
> will, sollte man es so initialisieren wie man es möchte, damit keine
> Missverständinsse oder Fehler auftreten.

Ist Geschmackssache.  Da man bei Controllern immer begrenzte
Ressourcen hat, sehe ich persönlich keinen Sinn drin, einen ordentlich
dokumentierten default state nicht auch zu benutzen.  Man kann und
darf sich ja z. B. auch drauf verlassen, dass in C alle statischen und
globalen Variablen mit 0 initialisiert worden sind.  Sie nochmal
separat im Programmablauf auszunullen ist daher auch nur
Ressourcenverschwendung.

> Laut Tutorial ist das doch die einzige Möglichkeit zu überprüfen,
> was für einen Pegel ein PIN hat. Wie kann ich denn sonst
> herausbekommen, ob mein PIN (wo die LED dran ist) auf "1" oder
"0"
> ist.

Du hast zwei Register für den Port.  PORTx spiegelt den gegenwärtigen
Zustand des Port-Ausgabe-Registers, PINx liest direkt von den Pins.
Normalerweise sollten beide natürlich die gleichen Werte haben für auf
Ausgabe geschaltete Pins.  Wenn du jetzt auf ein Portpin logisch 1
anlegst, aber extern jemand sehr viel Strom zieht (durch einen
Kurzschluss, aber z. B. auch, indem er eine LED direkt an den Port
anklemmt und damit den Pegel so weit runterzieht, dass er nicht mehr
sicher eingangsseitig noch 1 ergeben würde), dann unterscheiden sich
die Werte.  Daher liest man, um den gegenwärtigen Zustand eines
Ausgangs zu erfahren, normalerweise das PORTx-Register zurück, nicht
das PINx-Register.

> Was meinst du mit: einfacher ist allerdings PORTC ^=0xFF; ? Könntest
> du das bitte erläutern?

Die XOR-Verknüpfung mit 1 ist ein `toggle', d. h. eine
XOR-Verknüpfung
eines kompletten 8-Bit-Wertes mit 0xFF bewirkt ein Umschalten aller
Bits in den jeweils anderen Zustand.  Genau das passiert hier.  Falls
du mit der C-typischen ,,Kurzschlussschreibweise'' noch nicht
vertraut
bist, verdeutliche dir, was genau die ausgeschriebene Anweisung

PORTC = PORTC ^ 0xFF;

macht.

> Und deinen letzten Satz verstehe ich auch nicht ganz. Meinst du, daß
> ich fdevopen einfach vor die Schleife ziehen soll, damit es nicht
> jedesmal mit ausgeführt wird? Kannst du mir vielleicht auch sagen,
> warum das besser ist?

Weil man einen stream nur einmal öffnet und danach bis zum Schließen
benutzen kann.  Effektiv öffnest du jedesmal einen neuen stream,
benutzt den dann aber nicht (da du den Rückkehrwert von fdevopen()
ignorierst).  Intern wird das so lange gut gehen, bis malloc() keinen
Speicher mehr verfügbar hat, danach gibt die Funktion nur noch NULL
zurück.

von Marian (Gast)


Lesenswert?

Danke für die sehr ausführliche Erklärung :).

Das Thema ist jetzt für mich beendet.

Nun mal eine neue Frage :)...man kann ja nie auslernen.

Kann man in einem Programm zwei unterschiedliche Uarts nutzen?
Das heißt, kann ich einmal mit 9600 Baud senden und dann nochmal später
mit 115200 Baud?
Nochmal zu Erklärung:

Uart_init1 (mit 9600 Baud);
uart_init2 (mit 115200 Baud);

void main()
{
 if (Fall A)
 {
 Uart_init1();
 uart_puts("Hallo mit 9600 Baud");
 }
 if (Fall B)
 {
 Uart_init2();
 uart_puts("Hallo mit 115200 Baud");
 }
}

Wisst ihr wie ich es meine?

Wäre mal wieder dankbar für eine Antwort :)

Gruß Marian

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Na klar, warum nicht?

Aber mach besser einen neuen Thread auf für ein neues Thema.

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.