Forum: Compiler & IDEs Probleme mit Codeoptimierung/Timer_Wait


von Torsten Müller (Gast)


Lesenswert?

Hallo zusammen,

ich sitze hier schon recht lange und suche nach einer Lösung für mein 
Problem - leider ohne Erfolg

Habe ein Atmega16 und programmiere mit WinAVR.
habe ein Uhrenquarz der ein OC Timerinterrupt auslöst.
In dem Interruptfunktion wird eine globale Variable erhört.
Funktion timer_get() funktioniert

In der timer_wait funktion habe ich Probleme mit der while-schleife.
wenn ich im Makefile den Optimierungsgrad auf 0 stelle oder
in die while-Schleife z.B.setLED(2); reinschreibe
geht die Funktion. Dann "optimiert" der Compiler die Funktion nicht mehr 
so, dass sie nicht mehr geht - bleibt dann aber in der Schleife hängen 
und geht nicht weiter (finde ich komisch sollte er nicht die so 
"optimieren", dass er die einfach weglässt?)



uint32_t timer_get(){  //liefert Zeit im ms seit Anschalten
  return (timer_overflows * 125)>>4;
}


void timer_wait(uint32_t delay){
  uint32_t old_timer_value = timer_get();
  uint32_t i;

  while (old_timer_value + delay > timer_get()); //geht nur wenn 
optimierungsgrad = 0
//sonst bleibt er hängen und schaltet LED(1) nicht ein
  setLED(1); //zu testzwecken


}

//die folgende While-Schleife geht auch mit optimierungsgrad = s
//hier "optimiert" der compiler die schleife nicht so, dass sie nicht 
mehr geht.
// aber ich möchte ja nicht eine LED anschalten müssen, damit die 
Schleife geht. ist nur für testzwecke drin.
  while (old_timer_value + delay > timer_get()){
     setLED(3);
  };


kann ich hier ein anderen befehl reinschreiben, damit der compiler die 
schleife nicht "zerstört"
oder kann ich die Optimierung temporär ausschalten?

Vielen Dank für eure Hilfe

Torsten

von Oliver (Gast)


Lesenswert?

Normalerweise optimiert der gcc Programme so, daß sie hinterher genauso 
fehlerhaft sind wie vorher, nur schneller und/oder kleiner.

An den Codeschnipseln kann man nichts erkennen - bitte das ganze 
Programm posten.

Oliver

von Falk B. (falk)


Lesenswert?

@ Torsten Müller (Gast)

>Habe ein Atmega16 und programmiere mit WinAVR.
>habe ein Uhrenquarz der ein OC Timerinterrupt auslöst.
>In dem Interruptfunktion wird eine globale Variable erhört.
>Funktion timer_get() funktioniert

Guggst du hier, das geht escht ab.

Sleep Mode
Interrupt

>so, dass sie nicht mehr geht - bleibt dann aber in der Schleife hängen
>und geht nicht weiter (finde ich komisch sollte er nicht die so
>"optimieren", dass er die einfach weglässt?)

Ich wette drei Bier, dass da ein volatile fehlt.

>oder kann ich die Optimierung temporär ausschalten?

Nöö, nicht nötig.

MFG
Falk

von Torsten Müller (Gast)


Lesenswert?

Hallo

ihr seit echt super. Und danke auch für die schnelle Antwort.

Also es lag wirklich an dem volatile.

das mit dem volatile habe ich allerdings noch nicht so ganz verstanden. 
Hab auch nochmal im Skript nachgeschaut.
Also volatile braucht man, wenn das Programm und die 
Nebenläufigkeit/Interrupt auf die gleichen Daten zugreifen. (Also bei 
mehr als 8 bit- Variablen)

im skript seht:
Lösung für dieses Problem:
-compiler muss variablen vor jedem Zugriff aus dem Speicher laden und 
anschließend zurückschreiben.
=> attribut volatile

Aber wenn er den Interruptzugriff wieder rückgängig macht 
(timer_overflows++;), dann verzählt der sich doch, oder?












@Oliver (Volatile ist jetzt eingebaut) Bitte Kommentare ignorieren ;-)
#include <avr/signal.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include "Timer.h"

volatile static uint32_t timer_overflows; //volatile ist wichtig sonst 
geht die Funktion timer_wait() nicht mehr


SIGNAL(SIG_OVERFLOW2){
//sollte einmal im Jahr (365Tage) zurückgesetzt werden sonst läuft der 
Zähler über
//also wenn timer_overflows== 4.036.608.000 ist

  timer_overflows++;
}


void timer_init(){
// Verwendet Interrupt 2
   //Auf den externen Quarz stellen (S.129)
  ASSR|= (1<<AS2);

  //Kontrollregister setzen
  //-Taktteilung auf 0 einstellen
  //-Normaler Zählerbetrieb
  //-Keine Veränderung von OC0
  TCCR2 = (1<<CS20);


  //DER ZÄHLER LÄUFT JETZT BEREICHTS!

  //Interrupts setzen
  //-Interrupt bei Überlauf

  TIMSK |= (1<<TOIE2);

  //Globaler
  sei();
}



uint32_t timer_get(){
/*
32,768 kHz
=> 30,5175781 µs/Takt
=> 7,8125 ms / TimerOverflow (265Takte)
=> 7,8125 ms / Tick
return (Ticks*7,8125ms/Tick)
return (Ticks * 78125ms / 10000 Ticks)
return (Ticks * 5^7 ms / (5^4 * 2^4) Ticks)
return (Ticks * 5^3 ms /        2^4 Ticks)
return (Ticks * 125 ms /        2^4 Ticks)
return (Ticks * 125)>>4  (für ms)


*/
  return (timer_overflows * 125)>>4;
}



//!!!Achtung wenn
void timer_wait(uint32_t delay){
  uint32_t old_timer_value = timer_get();
  uint32_t i;

  while (old_timer_value + delay > timer_get());

}

von Torsten Müller (Gast)


Lesenswert?

Hallo ich nochmal

wie kann ich mir eigentlich den Maschinencode anschauen?
Hab allerdings davon noch keine Ahnung.

von Karl H. (kbuchegg)


Lesenswert?

Torsten Müller wrote:

> im skript seht:
> Lösung für dieses Problem:
> -compiler muss variablen vor jedem Zugriff aus dem Speicher laden und
> anschließend zurückschreiben.
> => attribut volatile
>
> Aber wenn er den Interruptzugriff wieder rückgängig macht
> (timer_overflows++;), dann verzählt der sich doch, oder?

Selbstverständlich ist damit gemeint:
Der Compiler muss Variablen vor jedem Zugriff aus dem Speicher
laden und anschliessend, nach dem die Variable verändert wurden,
wieder zurückschreiben.


In Summe etwas unglücklich ausgedrückt. volatile teilt dem
Compiler eigentlich mit, dass er mit dieser Variablen keinerlei
Optimierung machen darf, weil sich diese auf Wegen ändert, die
der Compiler nicht einsehen kann.

Es geht um diese pathologischen Fälle


uint8_t xyz;

...


  xyz = 0;

  while( xyz == 0 ) {
    ... irgendwas in dem xyz nicht vorkommt, zb
    PORTA = 0x00;
    PORTA = 0xFF;
  }

in diesem Code gibt es für xyz keine Möglichkeit, wie sich diese
Variable innerhalb der Schleife verändern könnte. Der Optimizer
könnte daher auf die Idee kommen, sich mal auszurechnen, was das
denn für die Schleifenabfrage  while( xyz == 0 ) bedeutet:
"Wenn sich xyz innerhalb der Schleife nicht ändern kann und xyz
vor der Schleife auf 0 gesetzt wird, dann muss xyz offensichtlich
Zeit seines Lebens in der Schleife immer 0 sein und die Schleife
wird nie abgebrochen. Wenn aber die Schleife sowieso nie abgebrochen
werden kann, braucht es auch keinen Test dafür. Ergo ist obiger Code
völlig äquivalent zu

   while( 1 ) {
      PORTA = 0x00;
      PORTA = 0xFF;
   }

und da hier die Variable xyz gar nicht mehr zugewiesen oder abgefragt
werden muss, läuft dieser Code schneller ab. Die nobelste Aufgabe
eines Optimizers ist es, für seinen Programmierer Laufzeit aus dem
Code herauszuholen. Ergo werde ich diese Optimierung nehmen"

Das es noch einen zweiten Ausführungspfad gibt, in dem xyz über
einen Interrupt verändert werden kann, kriegt der Optimizer ganz
einfach nicht mit.

Mittels volatile bei der Variablen xyz kannst du dem Optimizer einen
Strich durch die Rechnung machen. Sie verbietet ganz einfach jegliche
Optimierung, die auf einer Analyse der Verwendung von xyz beruht.
Statt dessen muss der Wert von xyz jedesmal aus dem Speicher
geholt werden, bzw. muss in den Speicher geschrieben werden, wenn
das ursprüngliche Programm dies verlangt.

Nimm mal folgenden Code:

  int i;
  i = 5;
  i = 7;

Warum soll der Compiler die Zuweisung mit 5 ausführen. i wird
sofort danach mit 7 überschrieben, die Zuweisung von 5 hat daher
keinerlei erkennbare Auswirkung. Wird sie wegoptimiert, so ändert
sich am Verhalten von i gar nichts. Nach den beiden Zuweisungen
hat i auf jeden Fall den Wert 7.

Und jetzt überleg dir mal, was so eine Optimierung wohl für
Auswirkungen hätte, wenn sie zb hier zuschlagen würde

    PORTA = 0x00;
    PORTA = 0xFF;

Genau: DIe Zuweisung von 0x00 an den PORTA wäre komplett sinnlos
und könnte wegoptimiert werden. Den nach Ablauf der Sequenz hätte
PORTA auf jeden Fall den Wert 0xFF.
Das du als Programmierer die erste Zuweisung nicht aus Jux und
Tollerei machst, sondern dass du damit einen Puls am PORTA
erzeugen willst, kann der Compiler ja nicht wissen.

Und jetzt rate mal, wie die 'Variable' PORTA wohl definiert sein
wird?

von Falk B. (falk)


Lesenswert?

@ Torsten Müller (Gast)

>Also volatile braucht man, wenn das Programm und die
>Nebenläufigkeit/Interrupt auf die gleichen Daten zugreifen. (Also bei
>mehr als 8 bit- Variablen)

NEIN! Das ist so nicht richtig. Auch bei 8 Bit wird volatile benötigt!

>Aber wenn er den Interruptzugriff wieder rückgängig macht
>(timer_overflows++;), dann verzählt der sich doch, oder?

???
Wer sollte den Interruptzugriff rückgängig machen?

1
volatile static uint32_t timer_overflows; //volatile ist wichtig sonst
2
geht die Funktion timer_wait() nicht mehr

Volatile allein reicht NICHT. Der Zugriff muss auch noch atomar 
sein. Siehe Interrupt. Und 32 Bit sind auf einem 8-Bit Prozessor 
ganz sicher nicht atomar. D.h., alle Zugiffe auf timer_overflows 
audsserhalb der ISR müssen atomar gebaut werden. Siehe Link.

1
SIGNAL(SIG_OVERFLOW2){
2
//sollte einmal im Jahr (365Tage) zurückgesetzt werden sonst läuft der
3
Zähler über
4
//also wenn timer_overflows== 4.036.608.000 ist
5
6
  timer_overflows++;
7
}

- Signal ist veraltet, nutze ISR()
- Nutze doch einfach den Vorteiler, dann gibt es weniger Überläufe. 
Siehe Sleep Mode .

1
uint32_t timer_get(){
2
/*
3
32,768 kHz
4
=> 30,5175781 µs/Takt
5
=> 7,8125 ms / TimerOverflow (265Takte)
6
=> 7,8125 ms / Tick
7
return (Ticks*7,8125ms/Tick)
8
return (Ticks * 78125ms / 10000 Ticks)
9
return (Ticks * 5^7 ms / (5^4 * 2^4) Ticks)
10
return (Ticks * 5^3 ms /        2^4 Ticks)
11
return (Ticks * 125 ms /        2^4 Ticks)
12
return (Ticks * 125)>>4  (für ms)
13
14
15
*/
16
  return (timer_overflows * 125)>>4;
17
}

Das muss atomar gemacht werden. und zwar so.

1
uint32_t timer_get(){
2
/*
3
32,768 kHz
4
=> 30,5175781 µs/Takt
5
=> 7,8125 ms / TimerOverflow (265Takte)
6
=> 7,8125 ms / Tick
7
return (Ticks*7,8125ms/Tick)
8
return (Ticks * 78125ms / 10000 Ticks)
9
return (Ticks * 5^7 ms / (5^4 * 2^4) Ticks)
10
return (Ticks * 5^3 ms /        2^4 Ticks)
11
return (Ticks * 125 ms /        2^4 Ticks)
12
return (Ticks * 125)>>4  (für ms)
13
14
15
*/
16
  volatile uint32_t tmp;
17
  cli();
18
  tmp = timer_overflows;
19
  sei();
20
  return (tmp*125)>>4;
21
22
}

MFG
Falk

von Simon K. (simon) Benutzerseite


Lesenswert?

Es gibt viele Leute, die volatile mit Atomar verwechseln.

Volatile verhindert aber nur Optimierungen die wg. Unkenntniss des 
Compilers hinsichtlich Interrupts gemacht werden (aber nicht dürfen).

Atomar heißt, dass während der Veränderung eines Wertes (Read/Write) 
kein Interrupt dazwischenkommen darf.

von Torsten Müller (Gast)


Lesenswert?

wow...

erst mal danke für eure ausführlichen Antworten.

Das muss ich erst mal verdauen...

Aber ich glaube das habe ich immer noch nicht ganz verstanden.

Das Thema ist nicht ganz einfach oder?



@Karl heinz Buchegger
>Selbstverständlich ist damit gemeint:
>Der Compiler muss Variablen vor jedem Zugriff aus dem Speicher
>laden und anschliessend, nach dem die Variable verändert wurden,
>wieder zurückschreiben.
Aber dann ist doch die Änderung wieder weg?! Und der "Timer" würde nicht 
mehr richtig sein. (siehe auch nächsten Abschnitt)



>>Also volatile braucht man, wenn das Programm und die
>>Nebenläufigkeit/Interrupt auf die gleichen Daten zugreifen. (Also bei
>>mehr als 8 bit- Variablen)
>
>NEIN! Das ist so nicht richtig. Auch bei 8 Bit wird volatile benötigt!
>
>>Aber wenn er den Interruptzugriff wieder rückgängig macht
>>(timer_overflows++;), dann verzählt der sich doch, oder?
>
>???
>Wer sollte den Interruptzugriff rückgängig machen?

der Compiler?


das habe ich in einem anderen Forum gefunden:


>http://www.wer-weiss-was.de/theme9/article92054.html
>An volatile-Variablen werden keine Optimierungen ausgeführt, da der
>Compiler annimmt, daß sich der Inhalt jederzeit ändern kann, z. B. durch
>andere Programme oder (Hardware-)Interrupts.
>
>Es scheint ja doch ein bisschen Verwirrung um "volatile" zu geben. Dabei
>ist es ganz einfach, es ist das Gegenteil von "register"!
>
>Zur Optimierung von Rechenprozessen versucht der Compiler, so viele Werte
>wie moeglich in den Prozessor-Registern zu halten. Das kann unter
>Umstaenden in die Hose gehen, weil sich Speicherinhalte auch ohne Wissen
>des Compilers aendern koennen (z.B. Timer). Mit "volatile" wird der
>Compiler angewiesen, einen Code zu erzeugen, der jedesmal bei Benutzung
>der volatile-Variablen direkt auf die Speicheradresse zugreift und diese
>niemals in einem Prozessor-Register haelt ...
>
>cu Stefan.


Also volatile verhindert optimierungen - so wie meine schöne Schleife, 
die dann nicht mehr ging.

ich habe in dem "Bericht"

>http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html

was von "The header ‘signal.h’ declares a type called sig_atomic_t which 
is guaranteed to be modifiable safely in the presence of asynchronous 
events. " gefunden.
Könnt ihr mir dazu was sagen?

dieses atomar... also das brauche ich bei einer 8 Bit maschine (bei 
Nebenläufigkeit), wenn ich variablen mit mehr als 8 Bit benutze, oder?

Aber so wie ich das im meinem Skript verstehe hilft das volatile aber 
auch bei int (2 Byte?) variablen. Siehe Anhang. Oder habe ich das falsch 
verstanden? S.24 Sagt aber, dass ich vor kritischen Zugriffen auf 
gemeinsame Daten Interrupts sperren muss. Aber dann können Interrupts 
verloren gehen. (->falsche Zeit) jeder Interruptfehler bedeutet 7,8ms. 
Auf lange Zeit gesehen, eine ganz schön krasser fehler. Dann hilft mir 
der 32,768 kHz Quarz auch nichts. :-(


Ich hänge mal ein Ausschnitt von meinem Script an, bei dem ich ein paar 
Sachen markiert habe.

gibt es bei µC "spezielle atomare Maschinenbefehle" (siehe Skript S.24)


Das ist nicht einfach...

Vielen dank nochmal für eure hilfe.

von Torsten Müller (Gast)


Angehängte Dateien:

Lesenswert?

Skript 1

von Torsten Müller (Gast)


Angehängte Dateien:

Lesenswert?

Skript 2

von Torsten Müller (Gast)


Angehängte Dateien:

Lesenswert?

Skript 3

von Falk B. (falk)


Lesenswert?

@ Torsten Müller (Gast)

>Das Thema ist nicht ganz einfach oder?

Ja ;-)

>>Der Compiler muss Variablen vor jedem Zugriff aus dem Speicher
>>laden und anschliessend, nach dem die Variable verändert wurden,
>>wieder zurückschreiben.
>Aber dann ist doch die Änderung wieder weg?!

???
Variable vom RAM in CPU Register laden
Variable in CPU-Registern ändern
Variable in den RAM zurückschreiben

Klassische Read-Modify-Wrtie Operation.

>>???
>>Wer sollte den Interruptzugriff rückgängig machen?

>der Compiler?

???
RÜCKGÄNG macht der Compiler GAR nichts. Er kann nur bisweilen 
feststellen, dass eine Variable nie gelesen wird und damit ungenutzt ist 
und er sie damit rausschmeisst (bei aktiver Optimierung).

>http://www.wer-weiss-was.de/theme9/article92054.html
>An volatile-Variablen werden keine Optimierungen ausgeführt, da der
>Compiler annimmt, daß sich der Inhalt jederzeit ändern kann, z. B. durch
>andere Programme oder (Hardware-)Interrupts.

Stimmt.

>
>Es scheint ja doch ein bisschen Verwirrung um "volatile" zu geben. Dabei
>ist es ganz einfach, es ist das Gegenteil von "register"!

Stimmt NICHT!

>Zur Optimierung von Rechenprozessen versucht der Compiler, so viele Werte
>wie moeglich in den Prozessor-Registern zu halten. Das kann unter
>Umstaenden in die Hose gehen, weil sich Speicherinhalte auch ohne Wissen
>des Compilers aendern koennen (z.B. Timer). Mit "volatile" wird der
>Compiler angewiesen, einen Code zu erzeugen, der jedesmal bei Benutzung
>der volatile-Variablen direkt auf die Speicheradresse zugreift und diese
>niemals in einem Prozessor-Register haelt ...

Stimmt fast. Irgendwann muss der Wert mal ins Prozessor-Register, denn 
nur dort kann damit gerechnet werden. Aber es wird dort nur solange wie 
unbedingt nötig verweilen. Ein Lesezugriff in verschiedenen 
C-Anweisungen leist IMMMER WIEDER NEU, verschiedene Schreibzugriffe 
SCHREIBEN IMMER WIEDER NEU.

>Also volatile verhindert optimierungen - so wie meine schöne Schleife,
>die dann nicht mehr ging.

Genau.

>dieses atomar... also das brauche ich bei einer 8 Bit maschine (bei
>Nebenläufigkeit), wenn ich variablen mit mehr als 8 Bit benutze, oder?

Ja.

>Aber so wie ich das im meinem Skript verstehe hilft das volatile aber
>auch bei int (2 Byte?) variablen. Siehe Anhang. Oder habe ich das falsch

NEIN. Atomar und volatile hängen meist zusammen, sind aber dennoch 
verschiedene Sachen. Lies die Links nochmal und denk drüber nach.

volatile: Verhindert "Wegoptimieren" und Zwischenspeichern von Variablen 
in CPU-Registern
atomar: Verhindert das Unterbrechen eines Varablenzugiffs, der mehrere 
CPU-Befehle benötigt, weil die Daten breiter als die CPU-Register sind 
(z.B. 32 Bit auf 8-Bit CPU)

>gemeinsame Daten Interrupts sperren muss. Aber dann können Interrupts
>verloren gehen. (->falsche Zeit) jeder Interruptfehler bedeutet 7,8ms.

NEIN! Die Sperre ist nur SEHR kurz, typisch eine Handvoll CPU-Takte. In 
der Zeit sollte kein Interrupt verloren gehen.

>Auf lange Zeit gesehen, eine ganz schön krasser fehler. Dann hilft mir
>der 32,768 kHz Quarz auch nichts. :-(

DOCH! Locker bleiben. Ruhe bewahren. Rechnen. Denken.

>gibt es bei µC "spezielle atomare Maschinenbefehle" (siehe Skript S.24)

Jeder Maschinenbefehl ist atomar. Aber manchmal braucht man zum Zugriff 
auf grosse Variablen mehrere Maschinenbefehle. In diese Sequenz kann ein 
Interrupt dazwischenfunken -> SCHLECHT

>Das ist nicht einfach...

Wenns einfach wäre würde es jeder machen ;-)

MFG
Falk

von Falk B. (falk)


Lesenswert?

@ Torsten Müller (Gast)

>Dateianhang: 3.jpg (335,5 KB, 8 Downloads)
>Skript 3

Das Eingerahmte oben links ist ein wenig irritierend und bisweilen 
falsch. Denn WENN Variabeln optimiert in CPU-Registern gespeichert 
werden, dann MUSS der Compiler auch direkt darauf zugreifen und 
ausserdem können diese Register nicht mehr für die normalen Berechnungen 
verwendet werden (logisch!). Und dann werden diese Register auch im 
Interrupt nicht mehr gesichert.

MFG
Falk

von Simon K. (simon) Benutzerseite


Lesenswert?

Falk Brunner wrote:
> @ Torsten Müller (Gast)
>>Es scheint ja doch ein bisschen Verwirrung um "volatile" zu geben. Dabei
>>ist es ganz einfach, es ist das Gegenteil von "register"!
>
> Stimmt NICHT!

So Falsch ist das unter Umständen garnicht.

Im Endeffekt heißt "register", dass die Variable ihre ganze Lebenszeit 
in einem Register verbringt (oder?). Und volatile verursacht (!) eben, 
dass die Variable ihre ganze Lebenszeit im Hauptspeicher liegt.

Allerdings sind nur die Ursachen gegenteilig. Der Sinn, bzw. Zweck beim 
Einsetzen der Schlüsselwörter hat nämlich keinen Zusammenhang.

von Andreas K. (a-k)


Lesenswert?

Simon K. wrote:

> Im Endeffekt heißt "register", dass die Variable ihre ganze Lebenszeit
> in einem Register verbringt (oder?).

Register hiess mal, dass der Compiler, so er kann und will, das in ein 
Register packen sollte.

Aber eigentlich heisst das heute garnichts mehr. Vor 3 Jahrzehnten 
hatten C-Compiler oft keine selbstständige Registeroptimierung. Ist 
heute anders und dementsprechend sinnlos ist das geworden.

> Und volatile verursacht (!) eben,
> dass die Variable ihre ganze Lebenszeit im Hauptspeicher liegt.

Praktisch meist ja, theoretisch nicht. Immerhin sind auch I/O-Register 
üblicherweise "volatile", liegen aber nicht im Hauptspeicher.

Das heisst nur, dass die Variable bei jedem Zugriff aus Sicht des 
Programmierers zu ebendiesem Zeitpunkt von dort geholt werden soll / 
darin reingeschrieben werden soll, wo diese Variable auch aus Sicht 
anderer Funktionen liegt.

Grundsätzlich könnte ein Compiler auch eine "volatile" Variable in ein 
Register legen, vorausgesetzt sie läge aus Sicht jeder sie benutzenden 
Funktion permanent in eben diesem Register. Also in einem globalen 
Register.

PS: Zu jener Zeit, zu der "register" noch eine Bedeutung hatte, 
existierte "volatile" noch garnicht. Weil mangels Optimierung unnötig. 
Als die Optimierung kam und "volatile" nötig wurde, wurde "register" 
unnötig.

von Torsten Müller (Gast)


Lesenswert?

Ich bedanke mich ganz herzlich für eure Hilfe.

Liebe Grüße
Torsten

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.