Forum: Mikrocontroller und Digitale Elektronik Bug durch millis() Überlauf beim Vergleichen vermeiden


von pete (Gast)


Lesenswert?

Um das Problem des Überlaufs der millis()-Funktion beim Arduino nach 49 
Tagen zu lösen, verwende ich bei Time-Outs immer folgenden Code:
1
    unsigned long timeout = millis() + 1000;
2
    while ( ((timeout - millis()) & 0x80000000) == 0 ) {
3
        ...

Ich bin mir aber nicht ganz sicher, ob das wirklich eine gute Idee bzw. 
eine gute Formulierung in C ist. Zumindest funktioniert es auch, wenn 
millis() sich 4294967295 nähert.

Hat jemand eine effizientere (= erzeugt besseren Maschienencode) oder 
schönere (= ist im Sourcecode einfach besser zu verstehen) Lösung, die 
auch funktioniert?

von derjaeger (Gast)


Lesenswert?

Wenn du Timeouts brauchst, dann zähl doch rückwärts von Start bis 0.

Also:

timeout = x
Immer wenn millis() sich verändert hat (new != old) :  timeout--
Wenn timeout = 0: Reaktion ...

von Einer K. (Gast)


Lesenswert?

Siehe:
Datei->Beispiele->Digital->BlinkWithoutDelay

von pete (Gast)


Lesenswert?

@ derjaeger: Denkbar, aber dann brauche ich zusätzlich zum Vergleich von 
timeout auf 0 oder kleiner (was ja genaugenommen nicht geht, da 
unsigned) auch noch einen timeout-- (bei 32 Bit mittelmäßgi aufwändig).

von pete (Gast)


Lesenswert?

@Arduino Fanboy D.:
1
  unsigned long currentMillis = millis();
2
 
3
  if(currentMillis - previousMillis > interval) {
4-Byte-Subtraktion plus 4-Byte-Vergleich = mehr CPU-Bedarf

von Einer K. (Gast)


Lesenswert?

pete schrieb:
> @Arduino Fanboy D.:  unsigned long currentMillis = millis();
>
>   if(currentMillis - previousMillis > interval) {
> 4-Byte-Subtraktion plus 4-Byte-Vergleich = mehr CPU-Bedarf

Du möchtest was funktionierendes!
Deins funkioniert nicht.
Dieses ja.

Es gibt natürlich noch mehr Wege, sich eine Zeitbasis zu beschaffen.
Aber wenn du millis() nutzen möchtest, dann ist genau DAS der Weg.

von pete (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Deins funkioniert nicht.

Aha. Und was genau funktioniert nicht?

von derjaeger (Gast)


Lesenswert?

>Aha. Und was genau funktioniert nicht?

Nach 49 Tagen funktionierts nicht.

von pete (Gast)


Lesenswert?

Bitte ausführen

von Einer K. (Gast)


Lesenswert?

pete schrieb:
> 4-Byte-Subtraktion plus 4-Byte-Vergleich = mehr CPU-Bedarf

Bei dir:
 4-Byte-Subtraktion
 4-Byte And
 4-Byte Vergleich

= Noch mehr CPU-Bedarf

von pete (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
>> 4-Byte-Subtraktion plus 4-Byte-Vergleich = mehr CPU-Bedarf
>
> Bei dir:
>  4-Byte-Subtraktion
>  4-Byte And
>  4-Byte Vergleich
>
> = Noch mehr CPU-Bedarf

Nö, denn nach der 4-Byte-Subtraktion wird einfach auf Overflow-Flag 
getestet.

Aber ich hatte draum gebeten, zu zeigen, warum mein Programmausschnitt 
nicht funktioniert:

derjaeger schrieb:
>>Aha. Und was genau funktioniert nicht?
>
> Nach 49 Tagen funktionierts nicht.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

derjaeger schrieb:
> Nach 49 Tagen funktionierts nicht.

Doch. Der Witz ist, dass unsigned-Subtraktionen auch bei Überläufen 
funktionieren.

Beispiel, welches das Verhalten von millis() simuliert - hier mal unter 
Linux:
1
#include <stdio.h>
2
#include <stdint.h>
3
4
int main ()
5
{
6
    uint32_t start = 0xFFFFFFFF;        // Start der Messung per millis() - kurz vor Überlauf
7
    uint32_t now = 1;                   // Ende der Messung per millis() - nach Überlauf
8
9
    printf ("%u\n", now - start);
10
    return 0;
11
}

Das Programm gibt 2 aus. Das funktioniert auch mit beliebigen anderen 
Werten.

: Bearbeitet durch Moderator
von Einer K. (Gast)


Lesenswert?

Frank M. schrieb:
> Doch. Der Witz ist, dass unsigned-Subtraktionen auch bei Überläufen
> funktionieren.

Hier eine alternative Variante des gleichen Themas:
(welche korrekt und über den gesamten Wertebereich funktioniert)
1
// Wieder eine Variation des BlinkWithoutDelay
2
3
    class SimpleTimer
4
    {
5
      private:
6
      uint32_t timeStamp;      // Zeitmerker
7
      bool reached;                // default Status: timer abgelaufen
8
    
9
      public:
10
      SimpleTimer():timeStamp(0),reached(true){}
11
      
12
      void start()
13
      {
14
        timeStamp   = millis();
15
        reached     = false;
16
      }
17
    
18
      bool operator()(const uint32_t interval) 
19
      {
20
        if(!reached) reached = millis() - timeStamp >= interval;
21
        return reached;
22
      }
23
    };
24
25
26
SimpleTimer timer; // timer Instanz anlegen
27
28
void setup()
29
{
30
  pinMode(LED_BUILTIN,OUTPUT);
31
}
32
33
void loop()
34
{
35
  if(timer(1000)) // wenn abgelaufen
36
  { 
37
    digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
38
    timer.start();
39
  }
40
}

von pete (Gast)


Lesenswert?

pete schrieb:
> Um das Problem des Überlaufs der millis()-Funktion beim Arduino nach 49
> Tagen zu lösen, verwende ich bei Time-Outs immer folgenden Code:
1
>     unsigned long timeout = millis() + 1000;
2
>     while ( ((timeout - millis()) & 0x80000000) == 0 ) {
3
>         ...

Ich geh sogar soweit, dass mein Verfahren "beweisbar" richtig ist:

Ich setze für "timeout" die Definition ein, dann folgt für den 
Vergleich:
1
     while ( ((millis() + 1000 - millis()) & 0x80000000) == 0 ) {

und damit:
1
     while ( ((1000) & 0x80000000) == 0 ) {

Nun ist klar, dass mit forschreitender Zeit, die "1000" kleiner wird, 
bis es genau nach dem Wert "0" einen Underflow gibt, bei dem es zu 
0xFFFFFFFF und kleineren Werten kommt. Um genau zu sein kommt es fast 25 
Tage lang zu Werten, dessen höchstes Bit gesetzt ist.

Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.

von Sebastian S. (amateur)


Lesenswert?

Brauchst Du denn überhaupt diesen, langen Zeitraum?
Wenn nicht: Einfach, bei der aktuellen Abfrage, den letzten Wert 
speichern und dann abfragen ob der neue Wert kleiner ist. ...und 
natürlich berücksichtigen.

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

pete schrieb:
> Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.

Die While Schleife blockiert den restlichen Code.
Sowas braucht man doch nicht, oder?

von pete (Gast)


Lesenswert?

Ein weiterer Vorteil meiner Methode ist, dass bei notwendigen Timeouts 
unter 32.7 Sekunden sie noch effizienter wird:
1
    unsigned short timeout = (unsigned short)millis() + 1000;
2
    while ( ((timeout - (unsigned short)millis()) & 0x8000) == 0 ) {
3
        ...

von pete (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Die While Schleife blockiert den restlichen Code.
> Sowas braucht man doch nicht, oder?

Doch, z.B. wenn man eine zeitlang (-> timeout) auf eine Antwort warten 
muss, sie aber verloren gehen kann und man dann z.B. erneut anfragen 
muss ...

von pete (Gast)


Lesenswert?

pete schrieb:
> Ein weiterer Vorteil meiner Methode ist, dass bei notwendigen Timeouts
> unter 32.7 Sekunden sie noch effizienter wird:
1
>     unsigned short timeout = (unsigned short)millis() + 1000;
2
>     while ( ((timeout - (unsigned short)millis()) & 0x8000) == 0 ) {
3
>         ...

und da fällt mir gleich eine Frage an die Profis ein. Ist das nicht eine 
schönere Formulierung:
1
     unsigned short timeout = (unsigned short)millis() + 1000;
2
     while ( (signed short)(timeout - (unsigned short)millis()) >= 0 ) {
3
         ...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

pete schrieb:
> Ich geh sogar soweit, dass mein Verfahren "beweisbar" richtig ist:
>
> Ich setze für "timeout" die Definition ein, dann folgt für den
> Vergleich:     while ( ((millis() + 1000 - millis()) & 0x80000000) == 0
> ) {
>
> und damit:     while ( ((1000) & 0x80000000) == 0 ) {

Du kannst die millis() hier nicht einfach so rausstreichen, Du kannst 
noch nichtmals timeout durch "millis() + 1000" einfach so ersetzen. Der 
erste Rückgabewert von millis() ist konstant. Der zweite Rückgabewert 
ändert sich laufend. Von daher halte ich Deinen "Beweis" für äußerst 
fragwürdig.

Ob Dein Verfahren tatsächlich weniger CPU-Power braucht als der 
klassische Weg:
1
unsigned long start = millis();
2
3
while (millis() - start < 1000) // ja, das ist überlauf-fest!
4
{
5
    ;
6
}

kann man nur beurteilen, wenn man sich den Assembler-Output des 
Compilers anschaut.

Das hier:

> und damit:     while ( ((1000) & 0x80000000) == 0 ) {

ist jedenfalls eine Endlosschleife.

: Bearbeitet durch Moderator
von pete (Gast)


Lesenswert?

Frank M. schrieb:
> Das hier:
>
>> und damit:     while ( ((1000) & 0x80000000) == 0 ) {
>
> ist jedenfalls eine Endlosschleife.

Klar, das war ja nur die Idee für einen richtigen Beweis. Den müsste ein 
Informatiker liefern. Das bin ich nicht.

Aber vielleicht habe ich mir die Frage selbst beantwortet, als ich 
vorige Antwort mit folgender Zeile schrieb:
1
     while ( (signed short)(timeout - (unsigned short)millis()) >= 0 ) {

Ich bin mir nur nicht sicher, dass es wirklich funktioniert (Unterschied 
allg. Theorie vs. konkrete Realität eines AVR 328)

von Einer K. (Gast)


Lesenswert?

pete schrieb:
> Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.
Gerade getestet...
Es funktioniert nicht!
Es versagt.
Definitiv

pete schrieb:
> Doch, z.B. wenn man eine zeitlang (-> timeout) auf eine Antwort warten
> muss, sie aber verloren gehen kann und man dann z.B. erneut anfragen
> muss ...
Wie gesagt, das ist blockierendes Warten.
Meist mehr Nachteile, als Voreile.
Damit meist unbrauchbar.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

pete schrieb:
> Ich bin mir nur nicht sicher, dass es wirklich funktioniert (Unterschied
> allg. Theorie vs. konkrete Realität eines AVR 328)

Einfach mal testen auf dem PC, dafür braucht man keinen AVR:
1
#include <stdio.h>
2
#include <stdint.h>
3
4
uint32_t millis1(void)
5
{
6
    return 0xFFFFFFFF;                // Startwert
7
}
8
9
uint32_t millis2(void)                // Laufender Wert
10
{
11
    return 999;                       // ergibt "true"
12
    // return 1000;                   // ergibt "false"
13
}
14
15
void u1 (void)
16
{
17
    unsigned short timeout = (unsigned short) millis1() + 1000;
18
19
    if ( (signed short)(timeout - (unsigned short)millis2()) >= 0 )
20
    {
21
        puts ("true");
22
    }
23
    else
24
    {
25
        puts ("false");
26
    }
27
}
28
29
int main ()
30
{
31
    u1 ();
32
    return 0;
33
}

Setze beliebige Return-Werte in millis1() und millis2() ein und bewerte 
den Output. Nach meinen Stichproben zu urteilen scheint es zu 
funktionieren.

von pete (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> pete schrieb:
>> Ergo, das funktioniert auf jeden Fall, ist nur nicht schön.
> Gerade getestet...
> Es funktioniert nicht!
> Es versagt.
> Definitiv

Man man man. Du lehnst Dich hier aus dem Fenster und behauptest voller 
Überzeugung etwas so dermaßen falsches! Ich habe nie verstanden, wo 
solche Leute dieses unberechtigte Selbstvertrauen hernehmen (das müßte 
man auch mal klären).

Im folgenden ein Arduino-Sketch, der die Funktionsfähigkeit des Timeout 
demonstriert:
1
// Test timeout via millis() with 16-bit variable and overflow
2
3
const int ledPin = 13;
4
char ledState = LOW;
5
unsigned short timeout, prev_time;
6
7
void setup() {
8
  Serial.begin( 115200 );
9
  pinMode( ledPin, OUTPUT );
10
  timeout = millis() + 500;     // 0.5s
11
}
12
13
void loop()
14
{
15
  unsigned short time = (unsigned short)millis();
16
17
  if ( (signed short)(timeout - time) <= 0 ) {
18
    Serial.println( time - prev_time );
19
    Serial.println( time );
20
    prev_time = time;
21
    timeout = time + 500;
22
    ledState = (ledState == LOW) ? HIGH : LOW;
23
    digitalWrite( ledPin, ledState );
24
  }
25
}

Damit dürfte geklärt sein, dass meine Lösung aus erstem Post 
funktioniert und dass diese Variante für Timeouts bis zu 32 Sekunden 
auch funktioniert, und wesentlich weniger CPU Takte kostet.

Meine Frage aus 1. Post ist aber nicht beantwortet.

Beitrag #5652458 wurde von einem Moderator gelöscht.
von A. S. (Gast)


Lesenswert?

pete schrieb:
> while ( ((timeout - millis()) & 0x80000000) == 0 ) {

Das ist faktisch das gleiche wie < 0 bei signed.

Es gibt zig Möglichkeiten, aus der Differenz signed zu machen, am Ende 
sollte man derartige Basics, weil sie immer wieder und mehrfach 
gebraucht werden, entsprechend Kapseln. Makros, Inline oder Funktionen, 
je nach Vorliebe. Mein Favorit in diesem Kontext: eine signed Funktionen 
für eine Differenz:

#dedine diff(t) ((long)(t-millis())

Oder typsicher als (inline-) Funktion.

Ich benutze natürlich als Zeitbasis eigene typedefs.

von Einer K. (Gast)


Lesenswert?

pete schrieb:
> unberechtigte Selbstvertrauen

Ja, ich muss gestehen, dass mein Test fehlerhaft war.
Es schient zu funktionieren.

von x^2 (Gast)


Lesenswert?

pete schrieb:
> Meine Frage aus 1. Post ist aber nicht beantwortet.

Es ist zu empfehlen (wg. Bitoperation) das Literal explizit auch als 
unsigned long zu kennzeichnen ("UL" Postfix).

Die realisierte Verzögerung ist im Worst-case +1 zur Gewünschten 
(Beispiel: Eine Verzögerung von 0 wird im Worst-case eine Verzögerung um 
1 sein). Das ist evtl. relevant wenn dieser Code mehrfach genutzt wird. 
Zum Beispiel 1000x eine Verzögerung von 1 ergibt im Worst-case 2000.

von pete (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> pete schrieb:
>> unberechtigte Selbstvertrauen
>
> Ja, ich muss gestehen, dass mein Test fehlerhaft war.
> Es schient zu funktionieren.

Du hast ja freundlicherweise Deinen noch großmundigeren Post selbst 
gelöscht, weil Du feststellen musstest, dass mein Code doch OK ist.

Lass das "scheint" weg, und wir sind wieder Freunde.

Ich habe in der Zwischenzeit herausgefunden, warum Dein gelöschter 
Testcode so merkwürdig arbeitet:

1) hattest Du einen Tippfehler bei (es fehlt eine 0):
>  AtomicSection timer0_millis = 0x8000000UL;  // versagt

2) Folgende Initialisierung der globalen Variable
> unsigned long timeout = millis() + 1000;
wird zu einem undefinierten Zeitpunkt vor setup() ausgeführt, so dass 
die Laufzeit von setup() von der einen Sekunde abgezogen wird.
Und komischerweise braucht die Zeile aus 1) dann so lange, dass der 
timeout bereits vorbei ist und Deine LED niemals blinkt.

3) So habe ich den timeout gar nicht gemeint, sondern wie in meinem 
Beispielprogramm.

Hier nochmal für long und mit Deiner schönen Idee, den timer zu 
verstellen, um zu testen:
1
#include <util/atomic.h>
2
#define AtomicSection ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
3
4
extern volatile unsigned long timer0_millis;
5
6
const int ledPin = 13;
7
char ledState = LOW;
8
unsigned long timeout, prev_time;
9
10
void setup() {
11
  Serial.begin( 115200 );
12
  pinMode( ledPin, OUTPUT );
13
  // Kurz vor den beiden Zeiten (25 und 50 Tage), von denen Probleme zu erwarten sind:
14
  AtomicSection timer0_millis = 0x7FFFF000UL;
15
  // AtomicSection timer0_millis = 0xFFFFF000UL;
16
  timeout = millis() + 500;     // 0.5s
17
}
18
19
void loop()
20
{
21
  unsigned long time = millis();
22
23
  if ( ((timeout - time) & 0x80000000) != 0 ) {
24
  //if ( (signed long)(timeout - time) < 0 ) {   // Äquivalent
25
    Serial.println( time - prev_time );
26
    Serial.println( time );
27
    prev_time = time;
28
    timeout = time + 500;
29
    ledState = (ledState == LOW) ? HIGH : LOW;
30
    digitalWrite( ledPin, ledState );
31
  }
32
}

von pete (Gast)


Lesenswert?

A. S. schrieb:
> pete schrieb:
>> while ( ((timeout - millis()) & 0x80000000) == 0 ) {
>
> Das ist faktisch das gleiche wie < 0 bei signed.

x^2 schrieb:
> Es ist zu empfehlen (wg. Bitoperation) das Literal explizit auch als
> unsigned long zu kennzeichnen ("UL" Postfix).
>
> Die realisierte Verzögerung ist im Worst-case +1 zur Gewünschten
> (Beispiel: Eine Verzögerung von 0 wird im Worst-case eine Verzögerung um
> 1 sein). Das ist evtl. relevant wenn dieser Code mehrfach genutzt wird.
> Zum Beispiel 1000x eine Verzögerung von 1 ergibt im Worst-case 2000.

Beides sehr wichtige Kommentare:

1) UL hinzugefügt:
1
    unsigned long timeout = millis() + 1000;
2
    while ( ((timeout - millis()) & 0x80000000UL) == 0 ) {

2) Äquivalent und schöner:
1
    unsigned long timeout = millis() + 1000;
2
    while ( (signed long)(timeout - millis()) >= 0 ) {

3)
Beides ist fast 1ms zu lang, daher die bisher beste Variante:
1
    unsigned long timeout = millis() + 1000;
2
    while ( (signed long)(timeout - millis()) > 0 ) {

Beitrag #5652512 wurde von einem Moderator gelöscht.
Beitrag #5652516 wurde von einem Moderator gelöscht.
Beitrag #5652520 wurde von einem Moderator gelöscht.
Beitrag #5652541 wurde von einem Moderator gelöscht.
Beitrag #5652548 wurde von einem Moderator gelöscht.
Beitrag #5652551 wurde von einem Moderator gelöscht.
von pete (Gast)


Lesenswert?

Schade. Das Ende dieses Threads ist mit Blödsinn versaut. Wer das 
Ergebnis wissen will, schaut bei:
Beitrag "Re: Bug durch millis() Überlauf beim Vergleichen vermeiden"
Oder bei:
Beitrag "Re: Bug durch millis() Überlauf beim Vergleichen vermeiden"

@Arduino Fanboy D.: Ich hab doch sogar Deinen "#define AtomicSection" 
und "extern volatile unsigned long timer0_millis" als schöne Idee 
bezeichnet.

@Roth: Ist das alles nötig?

von A. S. (Gast)


Lesenswert?

pete schrieb:
> Beides ist fast 1ms zu lang, daher die bisher beste Variante:

Das ist eine grundsätzliche Frage, deren Antwort man sich mit dem Aufruf 
0 bzw. 1 vergegenwärtigen sollte:

Welche Zeit mit Zeit 0 bzw. 1 erwartet wird:

0: a) 0...999µs oder b)immer sofort?
1: a) 1000 ... 1999µs oder b) 0..999µs?

a: >=0 (bzw. if diff < 0)
b: >0  (bzw. if diff <=0)

Beide Interpretationen haben ihre Berechtigung, und es macht keinen 
Sinn, eine als die einzig richtige hinzustellen. Darum sollte man immer 
wissen, wie ein Sleep(0) bzw. Sleep(1) in seinem Rtos wirkt.

Beitrag #5652737 wurde von einem Moderator gelöscht.
Beitrag #5652794 wurde von einem Moderator gelöscht.
Beitrag #5652811 wurde von einem Moderator gelöscht.
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Ich verweise an dieser Stelle auf die Nutzungsbedingungen. Auch als Gast 
sollte man innerhalb eines Threads den Namen beibehalten und nicht 
wechseln.

Ich bitte auch, beim Thema zu bleiben.

: Bearbeitet durch Moderator
von ch (Gast)


Lesenswert?

Der "rollover save timer" muss genau so aussehen:

Beitrag "Re: rollover save timer in c"

Den Wertebereich kannst Du von uint8_t auf uint32_t ändern.

von Einer K. (Gast)


Lesenswert?

ch schrieb:
> Der "rollover save timer" muss genau so aussehen:

Genau DAS ist nicht gewünscht!

von Nico W. (nico_w)


Lesenswert?


: Bearbeitet durch User
von S. R. (svenska)


Lesenswert?

Soweit ich informiert bin, ist "signed overflow" in C undefiniert, 
"unsigned overflow" hingegen nicht. Daher ist ersteres per se schlechter 
als letzteres.

Aber macht ihr nur. :-)

von DerAltePete (Gast)


Lesenswert?

Nico W. schrieb:
> Den Godbolt gibt es auch für den AVR-GCC...
>
> Kann ja jeder für sich selbst entscheiden, was er da nehmen möchte.

Schöner Beitrag, aber wenn der gcc so dämlich ist, dann hilf ihm halt 
auf die Sprünge und schreib in Deinem Beispiel statt
1
    while ( (int32_t)(timeout - millis()) > 0 );
eben
1
    while ( (int32_t)(millis() - timeout) < 0 );

Das wird nämlich zu
1
.L3:
2
        lds r24,timer
3
        lds r25,timer+1
4
        lds r26,timer+2
5
        lds r27,timer+3
6
        sub r24,r20
7
        sbc r25,r21
8
        sbc r26,r22
9
        sbc r27,r23
10
        brmi .L3

und ist damit, wie es der gesunde Menschenverstand erwarten lässt, um 
einiges effizienter als die von Dir bevorzugte Zeile:
1
  while ( (uint32_t)(millis() - start_time) < 1000 );
1
.L7:
2
        lds r24,timer
3
        lds r25,timer+1
4
        lds r26,timer+2
5
        lds r27,timer+3
6
        sub r24,r20
7
        sbc r25,r21
8
        sbc r26,r22
9
        sbc r27,r23
10
        cpi r24,-24
11
        sbci r25,3
12
        cpc r26,__zero_reg__
13
        cpc r27,__zero_reg__
14
        brlo .L7

von Nico W. (nico_w)


Lesenswert?

Ohje...

Sollte dann aber <= sein?!?

Edit: sicher nicht... Aber dennoch wiederum interessantes Verhalten.

: Bearbeitet durch User
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.