Forum: Mikrocontroller und Digitale Elektronik Servo mit Millis anstatt delay steuern.


von Benni B. (Gast)


Lesenswert?

Hallo ihr Lieben,

ich habe einen wireless Gamepad an einem Arduino und möchte 
Servobewegungen auslösen. Mit delay im Servoprogramm führt es ab ca. 
1000 Millisekunden delay im Servoprogramm zu Störungen. Unter 1000 
Millisekunden funktioniert der Gamepad. Jetzt ist meine Frage, ob das 
Servoprogramm mit Millis anstelle delay funktioniert.

Hier mein Beispielcode vom Servo:



(code)
#include <Servo.h> //Die Servobibliothek wird aufgerufen. Sie wird 
benötigt, damit die Ansteuerung des Servos vereinfacht wird.


Servo servoblau; //Erstellt für das Programm ein Servo mit dem Namen 
„servoblau“


void setup()
{
servoblau.attach(8); //Das Setup enthält die Information, dass das Servo 
an der Steuerleitung (gelb) mit Pin 8 verbunden wird. Hier ist natürlich 
auch ein anderer Pin möglich.
}


void loop()

{ //Im „loop“ wird über den write-Befehl „servoblau.write(Grad)“ das 
Servo angesteuert. Zwischen den einzelnen Positionen gibt es eine Pause, 
damit das Servo genug Zeit hat, die gewünschten Positionen zu erreichen.

servoblau.write(0); //Position 1 ansteuern mit dem Winkel 0°

delay(3000); //Das Programm stoppt für 3 Sekunden

servoblau.write(90); //Position 2 ansteuern mit dem Winkel 90°

delay(3000); //Das Programm stoppt für 3 Sekunden

(/code)

von Joachim B. (jar)


Lesenswert?


von Wolfgang (Gast)


Lesenswert?

Benni B. schrieb:
> Mit delay im Servoprogramm führt es ab ca.
> 1000 Millisekunden delay im Servoprogramm zu Störungen.

Die Funktion yield() kennst du?

von Conny (Gast)


Lesenswert?

Benni B. schrieb:
> ich habe einen wireless Gamepad an einem Arduino und möchte
> Servobewegungen auslösen. Mit delay im Servoprogramm führt es ab ca.
> 1000 Millisekunden delay im Servoprogramm zu Störungen. Unter 1000
> Millisekunden funktioniert der Gamepad. Jetzt ist meine Frage, ob das
> Servoprogramm mit Millis anstelle delay funktioniert.

Aha, wieder mal ein Salamibrot. Ein wireless Gamepad an einem Arduino 
(welcher Arduino?) bedeutet einen Receiver am Arduino, welcher dann auch 
etwas Software benötigt um als Eingabegerät zu funktionieren. Und dieser 
Softwareteil wird hier nicht gezeigt, stellt aber wahrscheinlich das 
Problem dar.

Deinen gezeigten Beispielcode hast du vermutlich so noch nie laufen 
lassen. Es gibt keinen Grund weshalb dieses kleine Codesegment nicht 
funktionieren sollte, außer daß ihm die abschliessende Klammer fehlt.

Ein 'delay' macht nichts anderes als in einer Schleife die Millisekunden 
zu zählen, und wenn du anstelle der 'delay' Funktion selbst die 
Millisekunden zählst, wird das an deinem Problem nichts ändern.

> Hier mein Beispielcode vom Servo:
>
1
 
2
> void loop()
3
> 
4
> { //Im „loop“ wird über den write-Befehl „servoblau.write(Grad)“ das 
5
> Servo angesteuert. Zwischen den einzelnen Positionen gibt es eine Pause, 
6
> damit das Servo genug Zeit hat, die gewünschten Positionen zu erreichen.
7
> 
8
> servoblau.write(0); //Position 1 ansteuern mit dem Winkel 0°
9
> 
10
> delay(3000); //Das Programm stoppt für 3 Sekunden
11
> 
12
> servoblau.write(90); //Position 2 ansteuern mit dem Winkel 90°
13
> 
14
> delay(3000); //Das Programm stoppt für 3 Sekunden
15
>

von Benni B. (Gast)


Lesenswert?

Hi, Yield() kenne ich nicht. Werde ich nachlesen.
Dies ist das Programm vom wireless Gamepad mit delay:


(code)
#include <PS2X_lib.h>  //for v1.6
#include <Servo.h> //Die Servobibliothek wird aufgerufen. Sie wird 
benötigt, damit die Ansteuerung des Servos vereinfacht wird.
Servo servoblau; //Erstellt für das Programm ein Servo mit dem Namen 
„servoblau“

PS2X ps2x; // create PS2 Controller Class

//right now, the library does NOT support hot pluggable controllers, 
meaning
//you must always either restart your Arduino after you conect the 
controller,
//or call config_gamepad(pins) again after connecting the controller.
int error = 0;
byte type = 0;
byte vibrate = 0;

void setup(){
 Serial.begin(57600);
servoblau.attach(8); //Das Setup enthält die Information, dass das Servo 
an der Steuerleitung (gelb) mit Pin 8 verbunden wird. Hier ist natürlich 
auch ein anderer Pin möglich.
//CHANGES for v1.6 HERE!!! **************PAY ATTENTION*************

 error = ps2x.config_gamepad(13,11,10,12, true, true);   //setup pins 
and settings:  GamePad(clock, command, attention, data, Pressures?, 
Rumble?) check for error

 if(error == 0){
   Serial.println("Found Controller, configured successful");
   Serial.println("Try out all the buttons, X will vibrate the 
controller, faster as you press harder;");
  Serial.println("holding L1 or R1 will print out the analog stick 
values.");
  Serial.println("Go to www.billporter.info for updates and to report 
bugs.");
 }

  else if(error == 1)
   Serial.println("No controller found, check wiring, see readme.txt to 
enable debug. visit www.billporter.info for troubleshooting tips");

  else if(error == 2)
   Serial.println("Controller found but not accepting commands. see 
readme.txt to enable debug. Visit www.billporter.info for 
troubleshooting tips");

  else if(error == 3)
   Serial.println("Controller refusing to enter Pressures mode, may not 
support it. ");

   //Serial.print(ps2x.Analog(1), HEX);

   type = ps2x.readType();
     switch(type) {
       case 0:
        Serial.println("Unknown Controller type");
       break;
       case 1:
        Serial.println("DualShock Controller Found");
       break;
       case 2:
         Serial.println("GuitarHero Controller Found");
       break;
     }

}

void loop(){
   /* You must Read Gamepad to get new values
   Read GamePad and set vibration values
   ps2x.read_gamepad(small motor on/off, larger motor strenght from 
0-255)
   if you don't enable the rumble, use ps2x.read_gamepad(); with no 
values

   you should call this at least once a second
   */



 if(error == 1) //skip loop if no controller found
  return;

 if(type == 2){ //Guitar Hero Controller

   ps2x.read_gamepad();          //read controller

   if(ps2x.ButtonPressed(GREEN_FRET))
     Serial.println("Green Fret Pressed");
   if(ps2x.ButtonPressed(RED_FRET))
     Serial.println("Red Fret Pressed");
   if(ps2x.ButtonPressed(YELLOW_FRET))
     Serial.println("Yellow Fret Pressed");
   if(ps2x.ButtonPressed(BLUE_FRET))
     Serial.println("Blue Fret Pressed");
   if(ps2x.ButtonPressed(ORANGE_FRET))
     Serial.println("Orange Fret Pressed");


    if(ps2x.ButtonPressed(STAR_POWER))
     Serial.println("Star Power Command");

    if(ps2x.Button(UP_STRUM))          //will be TRUE as long as button 
is pressed
     Serial.println("Up Strum");
    if(ps2x.Button(DOWN_STRUM))
     Serial.println("DOWN Strum");


    if(ps2x.Button(PSB_START))                   //will be TRUE as long 
as button is pressed
         Serial.println("Start is being held");
    if(ps2x.Button(PSB_SELECT))
         Serial.println("Select is being held");


    if(ps2x.Button(ORANGE_FRET)) // print stick value IF TRUE
    {
        Serial.print("Wammy Bar Position:");
        Serial.println(ps2x.Analog(WHAMMY_BAR), DEC);
    }
 }

 else { //DualShock Controller

    ps2x.read_gamepad(false, vibrate);          //read controller and 
set large motor to spin at 'vibrate' speed

    if(ps2x.Button(PSB_START)) {                  //will be TRUE as long 
as button is pressed
         Serial.println("Start is being held");
    servoblau.write(0); //Position 1 ansteuern mit dem Winkel 0°
    delay(500); //Das Programm stoppt für 0,5 Sekunden
     servoblau.write(20); //Position 2 ansteuern mit dem Winkel 20°
    delay(500); //Das Programm stoppt für 0,5 Sekunden
  }


    if(ps2x.Button(PSB_SELECT))
         Serial.println("Select is being held");

          if(ps2x.Button(PSB_SELECT)){
          servoblau.write(0); //Position 1 ansteuern mit dem Winkel 0°
    delay(600); //Das Programm stoppt für 0,6 Sekunden
     servoblau.write(20); //Position 2 ansteuern mit dem Winkel 20°
    delay(300); //Das Programm stoppt für 0,3 Sekunden
  }


     if(ps2x.Button(PSB_PAD_UP)) {         //will be TRUE as long as 
button is pressed
       Serial.print("Up held this hard: ");
       Serial.println(ps2x.Analog(PSAB_PAD_UP), DEC);
      }
      if(ps2x.Button(PSB_PAD_RIGHT)){
       Serial.print("Right held this hard: ");
        Serial.println(ps2x.Analog(PSAB_PAD_RIGHT), DEC);
      }
      if(ps2x.Button(PSB_PAD_LEFT)){
       Serial.print("LEFT held this hard: ");
        Serial.println(ps2x.Analog(PSAB_PAD_LEFT), DEC);
      }
     if(ps2x.Button(PSB_PAD_DOWN)) {              //will be TRUE as long 
as button is pressed
    Serial.println("down just pressed");
    servoblau.write(90); //Position 1 ansteuern mit dem Winkel 0°
    delay(1000); //Das Programm stoppt für 3 Sekunden
    servoblau.write(45); //Position 2 ansteuern mit dem Winkel 90°
   delay(1000);
      }


      vibrate = ps2x.Analog(PSAB_BLUE);        //this will set the large 
motor vibrate speed based on
                                              //how hard you press the 
blue (X) button

    if (ps2x.NewButtonState())               //will be TRUE if any 
button changes state (on to off, or off to on)
    {



        if(ps2x.Button(PSB_L3))
         Serial.println("L3 pressed");
        if(ps2x.Button(PSB_R3))
         Serial.println("R3 pressed");
        if(ps2x.Button(PSB_L2))
         Serial.println("L2 pressed");
        if(ps2x.Button(PSB_R2))
         Serial.println("R2 pressed");
        if(ps2x.Button(PSB_GREEN))
         Serial.println("Triangle pressed");

    }


    if(ps2x.ButtonPressed(PSB_RED))             //will be TRUE if button 
was JUST pressed
         Serial.println("Circle just pressed");

    if(ps2x.ButtonReleased(PSB_PINK))             //will be TRUE if 
button was JUST released
         Serial.println("Square just released");

    if(ps2x.NewButtonState(PSB_BLUE))            //will be TRUE if 
button was JUST pressed OR released
         Serial.println("X just changed");



    if(ps2x.Button(PSB_L1) || ps2x.Button(PSB_R1)) // print stick values 
if either is TRUE
    {
        Serial.print("Stick Values:");
        Serial.print(ps2x.Analog(PSS_LY), DEC); //Left stick, Y axis. 
Other options: LX, RY, RX
        Serial.print(",");
        Serial.print(ps2x.Analog(PSS_LX), DEC);
        Serial.print(",");
        Serial.print(ps2x.Analog(PSS_RY), DEC);
        Serial.print(",");
        Serial.println(ps2x.Analog(PSS_RX), DEC);
    }


 }


 delay(50);

}
(/code)

von Helmut -. (dc3yc)


Lesenswert?

Könntest du bitte die Code-Tags richtig benutzen? In eckigen Klammern 
und nicht in runden, dann würden sie auch funktionieren!

von Dumpfbacke (Gast)


Lesenswert?

Benni B. schrieb:
> Dies ist das Programm vom wireless Gamepad mit delay:

So ein langer Quellcode gehört in einen Anhang, nicht in den
laufenden Text. Dieses Verhalten nennt man Anstand, Rücksicht auf
die Leserschaft die man erreichen will. Dies scheint heutzutage
mehr und mehr nicht mehr üblich zu sein.

Lies und verinnerliche: Wichtige Regeln - erst lesen, dann posten!

von Conny (Gast)


Lesenswert?

Benni B. schrieb:

> void loop(){
>    /* You must Read Gamepad to get new values
>    Read GamePad and set vibration values
>    ps2x.read_gamepad(small motor on/off, larger motor strenght from
> 0-255)
>    if you don't enable the rumble, use ps2x.read_gamepad();
>    with no values you should call this at least once a second
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Hier im Kommentar steht die Ursache und die Lösung deines Problems. Alle 
Programmpfade in der Schleife müssen innerhalb von 1 Sekunde beendet 
werden, damit ps2x.read_gamepad() rechtzeitig wieder aufgerufen wird. 
Sonst gibt es vermutlich eine Störung/Abbruch der Verbindung zum 
Controller.

von Benni B. (Gast)


Angehängte Dateien:

Lesenswert?

Hi, also kann ich mit Gamepad keinen Roboterarm länger als eine Sekunde 
steuern? Gibts da eine Lösung? Sorry mit den Code Tags, kanns jetzt 
nicht mehr bearbeiten, aber hier im Anhang nochmal das Programm.

von Axel R. (axlr)


Lesenswert?

1
     if(ps2x.Button(PSB_PAD_DOWN)) {              //will be TRUE as long as button is pressed
2
    Serial.println("down just pressed");
3
    servoblau.write(90); //Position 1 ansteuern mit dem Winkel 0°
4
    delay(1000); //Das Programm stoppt für 3 Sekunden
5
    servoblau.write(45); //Position 2 ansteuern mit dem Winkel 90°
6
   delay(1000);
7
      }

müsstest dann so in etwa umschreiben:
warte keine 1000 milliSekunden, sondern nur 10 und merke Dir, dass du 
dort warst und zähle einen merker um eins höher und verschwinde da 
wieder an den Anfang, um den PS-Controller auslesen zu können. Dann, 
wenn du wiederkommst, schaust du nach, ob der zähler schon auf 100 steht 
(10 x 100 macht ja 1000).
Naja; und wenn dein zähler auf 100 oder sogar mehr als 100 steht, setzt 
du ihn auf null zurück und lässt den Servoblau weiterdrehen. Vorher 
nicht. Solange dein Zählermerkerding kleiner 100 ist, machst Du garnix, 
sondern kehrst brav nach oben zurück.

von Benni B. (Gast)


Lesenswert?

Hi, wie würde der Code dann aussehen?
Danke,
Benni

von Johannes S. (Gast)


Lesenswert?


von Benni B. (Gast)


Lesenswert?

Hey, ich bin programmiermässig noch Anfänger. Kann vielleicht jemand den 
Code abändern, dass ich das Programm mal sehen kann, wenn es länger als 
eine Sekunde wird?
Vielen Dank im Vorraus,
Benni

von Axel R. (axlr)


Lesenswert?

Na genau so, wie geschrieben...

1
   if(ps2x.Button(PSB_PAD_DOWN)) {   //will be TRUE as long as button is pressed
2
    Serial.println("down just pressed");
3
    if (merkerdings==0) servoblau.write(90); //Position 1 ansteuern mit dem Winkel 0°
4
    delay(10); //Das Programm stoppt für 10mSek
5
merkerdings++;
6
if(merkerdings>=100){
7
merkerdings=0;    
8
servoblau.write(45);
9
} //Position 2 ansteuern mit dem Winkel 90°
10
   delay(1000);
11
      }

Hat doch mit "programmieren jetzt nix zu tun. zuhören und machen.
Variabelndeklaration usw. muss man noch machen. Chic ists auch nicht. 
Select case usw. wäre hübscher.

also, so ähnlich...
merkerdings müsste auf "1", dann servo auf 45 drehen und wenn fertig, 
merkerdings wider auf "0". sonst dreht der immerhinunher

: Bearbeitet durch User
von Benni B. (Gast)


Lesenswert?

Hi, das Servo fährt nur in die Mittelstellung, also 90°.
Was muss ich ändern, damit die zweite Position angefahren wird?
Dankeschön,
Benni

von Stefan F. (Gast)


Lesenswert?

Benni,
du überstrapazierst die Hilfsbereitschaft der Leute. Du musst deine 
Sachen schon selbst programmieren oder Geld anbieten.

Es ist besser, für den Anfang etwas eigenes zu programmieren, als fremde 
Codes ändern zu wollen die man (noch) nicht versteht.

Ein Tipp von mir: Achte mal darauf, dass die Kommentare im Quelltext mit 
dem übereinstimmen, was da wirklich passiert. Denn falsche Kommentare 
sind schlimmer als gar keine.

von Lemuschikraul (Gast)


Lesenswert?

Wolfgang schrieb:

> Die Funktion yield() kennst du?

Nein?!

von Benni B. (Gast)


Lesenswert?

yield kenne ich nicht. Wie funktioniert sie?

von Stefan F. (Gast)


Lesenswert?

Benni B. schrieb:
> yield kenne ich nicht. Wie funktioniert sie?

Hast du dir die Beschreibung von yield() auf der Arduino Webseite 
angeschaut? Wie lauten deine konkreten Fragen dazu?

Es läuft darauf hinaus, das wir nicht für dich das Lernen übernehmen 
können.

von Einer K. (Gast)


Lesenswert?

Benni B. schrieb:
> yield


Auf AVR Arduinos wird es von delay() aufgerufen. Man kann sich damit ein 
Kooperatives Multitasking bauen.

Beispiel:
1
/**
2
 * Basisprogramm: 
3
 * Das Blink Beispiel aus der Arduino IDE
4
 * 
5
 * Zusaetzliche nebenlaeufige Funktionalitaet: 
6
 * Eine Ablaufsteuerung
7
 * 
8
 * Nach betaetigen des Schalters startet die Absaugung und danach
9
 * die Maschine. Nach abschalten stoppt erst die Maschine und danach die 
10
 * Absaugung
11
 * 
12
 * 
13
 *    Ablaufdiagramm - Zeitdiagramm
14
 *    
15
 *        schalter  _----------_____  Schalterstellung
16
 *        absaugung _-------------__  Verzoegertes abschalten
17
 *        maschine  ____-------_____  Verzoegertes einschalten
18
 *        
19
 *        Der Schalter arbeitet invers und ist entprellt
20
 *        
21
 *
22
*/
23
24
#include <CombieTimer.h>
25
#include <CombiePin.h>
26
#include <CombieTypeMangling.h>
27
28
using namespace Combie::Pin;
29
using namespace Combie::Timer;
30
using namespace Combie::Millis;
31
32
TasterGND<2> schalter; // zwischen Pin und GND(invertierend)
33
OutputPin<3> absaugung;
34
OutputPin<4> maschine;
35
36
37
EntprellTimer    entprell { 20_ms};  // Schalter entprellen
38
RisingEdgeTimer  ton      {500_ms};  // steigende Flanke wird verzoegert
39
FallingEdgeTimer toff     {500_ms};  // abfallende Flanke wird verzoegert
40
 
41
void setup(void) 
42
{
43
  schalter.initPullup();
44
  absaugung.init();
45
  maschine.init();
46
  pinMode(LED_BUILTIN, OUTPUT); // unveraendert aus Blink.ino
47
}
48
49
void yield(void) 
50
{
51
  bool schalterMerker  =  entprell  =  schalter; 
52
  absaugung  =  toff  =  schalterMerker;
53
  maschine   =  ton   =  schalterMerker;
54
}
55
56
void loop(void) // unveraendert aus Blink.ino
57
{
58
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
59
  delay(1000);                       // wait for a second
60
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
61
  delay(1000);                       // wait for a second
62
}



Stefan ⛄ F. schrieb:
> Hast du dir die Beschreibung von yield() auf der Arduino Webseite
> angeschaut?
Die gibt es nicht!
Zumindest nicht für die AVR Arduinos.

von Stefan F. (Gast)


Lesenswert?

>> Hast du dir die Beschreibung von yield() auf der Arduino Webseite
>> angeschaut?

Arduino Fanboy D. schrieb:
> Die gibt es nicht! Zumindest nicht für die AVR Arduinos.

https://www.arduino.cc/en/Reference/SchedulerYield

Der Text ist allerdings sehr mager.

von Conny (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Benni B. schrieb:
>> yield
>
>
> Auf AVR Arduinos wird es von delay() aufgerufen. Man kann sich damit ein
> Kooperatives Multitasking bauen.

Kann man machen, aber der TO kann das sicher nicht.

Und in seinem Fall gäbe es eine wunderschöne Rekursion, da die Funktion 
ps2x.read_gamepad() aus der PS2X_lib, die mindestens einmal jede Sekunde 
aufgerufen werden soll, ebenfalls delay() benutzt.

von Einer K. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
>>> Hast du dir die Beschreibung von yield() auf der Arduino Webseite
>>> angeschaut?
>
> Arduino Fanboy D. schrieb:
>> Die gibt es nicht! Zumindest nicht für die AVR Arduinos.
>
> https://www.arduino.cc/en/Reference/SchedulerYield
>
> Der Text ist allerdings sehr mager.

Glückwunsch!
Du hast den https://www.arduino.cc/en/Reference/Scheduler für die ARM 
Arduinos gefunden.
Ist nur leider eine andere Baustelle.



Conny schrieb:
> wunderschöne Rekursion
Das sollte man schon vermeiden.
evtl. ist gar das der Grund, es nicht in der Doku zu erwähnen.
Im Code Kommentar steht "Für Library Entwickler" o.ä.

von Sebastian (Gast)


Lesenswert?

Benni B. schrieb:
> Hey, ich bin programmiermässig noch Anfänger. Kann vielleicht jemand den
> Code abändern, dass ich das Programm mal sehen kann, wenn es länger als
> eine Sekunde wird?
> Vielen Dank im Vorraus,
> Benni

Hi Benny,

das ist nicht so ganz einfach.

Es ist natürlich einfach, den Servo ohne delay() anzusteuern. Du merkst 
dir den aktuellen Zustand und den millis()-Wert zu dem dieser Zustand 
hergestellt wurde. Und dann prüfst du bei jedem Durchlauf durch loop() 
ob millis()-zustandmillis grösser als deine Wartezeit geworden ist, und 
wenn ja stellst du den nächsten Zustand her und merkst dir wieder 
millis().

Schwierig wird es, weil dann natürlich Tastendrücke auftreten können 
während der Servo sich bewegt, und du entscheiden musst was du dan 
machen willst.

LG, Sebastian

von Benni B. (Gast)


Lesenswert?

Ich bräuchte einen Denkanstoss mit Millis, also was eingefügt wird und 
wo es eingefügt wird.

von ... (Gast)


Lesenswert?


von Stefan F. (Gast)


Lesenswert?

Benni B. schrieb:
> Ich bräuchte einen Denkanstoss mit Millis, also was eingefügt wird
> und wo es eingefügt wird.

Der Denkanstoß sind die Zustandsautomaten, die dir Joachim empfohlen 
hat. Zu seinen Links kann ich ergänzend noch anbieten:
http://stefanfrings.de/multithreading_arduino/index.html

Du musst das aber selbst lesen und umsetzen.

von Sebastian W. (wangnick)


Lesenswert?

Benni B. schrieb:
> Ich bräuchte einen Denkanstoss mit Millis, also was eingefügt wird und
> wo es eingefügt wird.
1
int status_servoangle;
2
unsigned long status_millis;
3
4
void setup () {
5
   status_millis = millis();
6
   status_servoangle = 0;
7
   servoblau.write(status_servoangle);
8
} 
9
   
10
void loop () {
11
   unsigned long now_millis = millis();
12
   if (now_millis-status_millis>=3000) {
13
      status_millis = now_millis;
14
      status_servoangle = 90-status_servoangle; // 0->90->0->90->... 
15
      servoblau.write(status_servoangle);
16
   }
17
}

LG, Sebastian

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.