Hallo, ich versuche seit einiger Zeit, mir eine Methode ausdenken, wie man die Auslastung der CPU mit einfachen Mitteln (sprich wenigen Befehlen) ermitteln kann. Aber irgendwie stehe ich bei diesem Problem auf dem Schlauch. Ich denke mir zur Zeit, in einer Hintergrundaufgabe einen Zähler hochzuzählen und diesen Zähler dann zu vergleichen. Aber womit vergleichen? Was wäre die Basis für die Ermittlung der Auslastung? Vielleicht hat ja jemand eine Idee, um mir auf die Sprünge zu helfen. Beim Suchen im Internet bin ich bis jetzt nur auf Methoden gestossen, die sich recht kompliziert angehört haben und IMHO selber bereits reichlich Rechenzeit verbrauchen. Das wollte ich eigentlich vermeiden :-) Gruß Thorsten
Ich mach das bei mir so: In der Haupschleife schicke ich den µC in den sleep, daraus wird er 128 mal in der Sekunde geweckt und dann fährt der Controller mit dem Programm fort. Jetzt setze ich vor dem Sleep einen Portpin auf low, nach dem Sleep einen Portpin auf High. Dann mess ich mit dem Oszi das Portsignal, High heisst dann er rechnet, low er schläft. Man kann auch eine LED dranhängen und sieht an der Helligkeit wann er grad wieviel rechnet. Bei einzelnen routinen kann man es genauso machen.
Hallo Fritz, danke für die schnelle Antwort. Für die Laufzeitmessung einzelner Programmteile habe ich diese Methode auch schon eingesetzt. Funktioniert einwandfrei. Leider kann ich den µC nicht zum "Schlafen" schicken da er auch in der Hauptschleife einiges zu tun hat und damit permanent arbeiten muss. Ich dachte eigentlich mehr an eine Berechnung, bei der hinten eine Zahl zwischen 0 und 100 (oder was auch immer) rauskommt. Diese Zahl könnte ich dann weiterverarbeiten. Oder habe ich immer noch ein Brett vorm Kopf, dass mir der Zusammenhang nicht auffällt? Gruß Thorsten
also wenn er in der hauptschleife auch die ganze zeit was abarbeitet, dann hat er meiner meinung nach 100% cpu auslastung :)
Ich würde auch sagen(wie Thomas K.) das die CPU 100% last hat. Wenn du jedoch in der hauptschleife nichts hast könntest du ja da einen zähler hoch zählen lassen, und mit einem timer immer wieder auf 8 setzt. Der Stand des zählers ist die prozesslast da ja die Interrupts die Hauptschleife unterbrechen und die so nicht weiter zählen kann. so müsstest du annähernd an 0-100% kommen. Nachteil: Hauptschleife muss frei sein, ein timer wird benötigt. aber im prinzip müsste es funktionieren, ohne oszi, ohne externer hardware, oder irre ich micht? mfg Azrael
@all Danke für die Antworten. Ich habe gerade festgestellt, dass es gar nicht so leicht die richtige Frage zu stellen. Ihr habt recht: Die Auslastung ist 100% bei der Fragestellung!!!!! Daher noch ein Versuch: Ich habe zwei zyklische Tasks per Timer-Interrupt (eine schnell(50µs); eine langsam (20ms)). Dazu kommen noch azyklische Interrupts von verschiedenen Kommunikationen (USART, TWI, SPI). Ausserdem gibt es Hintergrundaufgaben in der Hauptschleife. Und jetzt die Frage: Wie kann ich die Zeit messen (in %), die die Hintergrundaufgaben von der CPU-Zeit zur Verfügung haben? Ich hoffe, die Frage diesmal richtig gestellt zu haben. @Azrael Ich denke du hast die geleiche Idee wie ich schon hatte: Einen Zähler hoch zählen lassen und dann vergleichen. Aber womit vergleichen?!?! Gruß Thorsten
int main(void){ while(1){ count++; } } SIGNAL(SIG_OVERFLOW1){ prozent = (count/typ_count)*100 count=0; } Ich hab das so gemeint. In der Schleife im main zählst du den counter immer nach oben. Wenn niemand etwas in der zwischenzeit macht, sollte immer der selbe wert beim timer_interrupt sein(das währe typ_count) dieser Wert würde 100% entsprechen. wenn jetzt andere interrupts rechenzeit verbrauchen sinkt der count Wert. -> niedrigere cpu-last. wenn immer irgend ein interrupt rechenzeit verbraucht kommt 0%raus(da ja nie die Schleife abgearbeitet werden kann. Ich habs noch nie ausprobiert, aber wenns funktioniert währe ich an dem sourcecode interessiert, für ein paar benchmarks ;) mfg Azrael
Ich denke, der Zähler müsste genau anders herum funktionieren. Wenn Maximum erreicht dann 0% Auslastung, da ja nichts anderes abgearbeitet wird. Wenn dauernd ein Interrupt ansteht dann wird der Zähler nicht mehr hoch gezählt und die CPU 100% Auslastung. Deine Rechnung würde ergeben wieviel Prozent CPU-Zeit noch verfügbar ist. Eigentlich sind beide Ergebnisse brauchbar. Ich denke mir auch das ein Zähler (count) benötigt wird. Dieser wird dann mit einem Vergleichswert (typ_count) verglichen. Kommt jetzt die Frage worauf basiert der Vergleichswert? Ich sehe zur Zeit 2 Möglichkeiten: 1. Taktzeit der CPU: Jeder Takt, der nicht für den Zähler verwendet wird, wird dabei als verbrauchte Rechenzeit gewertet. => inc count & nop würden bereits eine Auslastung von 50% ergeben (ich programmiere in Assembler :-) kann aber auch C lesen). => Dabei bereits 50% Auslastung: Ergebnis unbrauchbar. 2. Durchlaufzeit der Hintergrundaufgabe: Die Durchlaufzeit der Hintergrundaufgabe würde als Basis für den Vergleichswert dienen. Dadurch würde die Hauptschleife (while(1)) nicht gewertet. => Unabhängig von der Hauptschleifenlänge ergibt sich eine Prozentzahl die irgendwo zwischen 0-100% liegen müsste: Ergebnis schon weitaus brauchbarer, nur es wird halt nicht die Hintergrundaufgabe bewertet. Muss die eigentlich miteingerechnet werden oder darf man das vergessen? Vielleicht hat ja jemand noch eine Idee, wie dieser Vergleichswert ermittelt werden kann. @Azrael Wenn jemals brauchbarer Code herauskommt, kann ich ihn gerne posten :-) Gruß Thorsten
Also ich würde sagen, man hat einen Zähler der durch einen Timer hochgezählt wird, und immer die CPU in eine Funktion wo sie arbeiten muss hüpft steht am anfang der Befehl TImer Stopp, und am Ende Timer an. Wenn bereits ein Timer verwendet wird kann das ganze über ein flag realisiert werden. Man muss also nur wissen welche funktionen als Arbeit gelten und Welche nicht. Der Zähler hat dann die Zeit die die CPU nicht mit Arbeit verwendet hat. Um jetzt die AUslastung zu kriegen kann man jetzt a) einen weiteren zähler hochzählen lassen der immer hochzählt und dann teilen b) am Anfang der Hauptschleife, den Arbeitlosenzähler entsprechend auswerten Es könnte wie folgt aussehen char flagarbeitlos; unsigned int timer_arbeitlos,timer_gesamt; unsigned char cpuaus; void InitLastAusgabe() { timer_arbeit=timer_gesamt=0; cpuaus=0; } void timer_isr() { if(!flagarbeitlos) timer_arbeitlos++; timer_gesamt++; if(timer_gesamt==1000) cpuaus in % cpuaus=timer_arbeitlos/10; //cpuaus=timer_arbeitlos*100%/1000 } unsigned char GetCPUAus() { return cpuaus; } //eine Funktion Arbeit kann wie folgt aussehen //dabei kann das eine INterruptroutien oder ... sein hauptsache flagarbeitlos wird gesetzt void FktArbeit() { // oft auch erst die Frage nach gibt es arbeit //nein return //ja flagarbeitlos=0; //Arbeit flagarbeitlos=1; } //WIchtig ist aber allerdings das flagarbeitlos immer gelöscht und wieder gesetzt wird //um auf nummer sicher zu gehen kann in bereichen die nicht als Arbeit defeiniert sind immer wieder mal //flagarbeitlos=0; stehen
oh hab ich grad übersehen in der timer_isr bei if(timer_gesamt==1000) { cpuaus in % cpuaus=timer_arbeitlos/10; //cpuaus=timer_arbeitlos*100%/1000 timer_arbeitslos=timer_gesamt=0; //muss natürlich wieder aus null gestetzt werden } diese abfrage könnte man auch in die Hauptschleife stellen jedoch dann mit if(timer_gesamt>1000) { cpuaus=timer_arbeitslos*100/timer_gesamt; timer_arbeitlos=timer_gesamt=0; }
Ist der CPU nicht immer voll ausgelastet? Ich meine, der macht doch durchgehend was, selbst wenn der keine Aufgabe hat: while(1) { } Selbst dabei hat der dann doch durchgehend (Sprung-)befehle. Ist aber nur ne Vermutung :)
ja klar aber interesant ist ja ob er nur Sprungbefehle macht, oder ob er was "rechnet"
wenn er nur sprungbefehle mach weiss ich ich kann ihn noch mehr mit aufgaben belasten
@JOchen Wenn ich das ganze richtig verstanden habe, dann wird flagarbeitlos am Anfang einer Routine gesetzt und am Ende wieder rückgesetzt. Damit ergibt sich aber doch dass z.B. in einer ISR das flag gesetzt und wieder rückgesetzt wird ohne das die Timer-ISR überhaupt etwas davon mitbekommt, da ja ein neuer Interrupt erst abgearbeitet wird, wenn der aktuelle fertig ist. So habe ich das mit den Interrrupts bis jetzt jedenfalls verstanden. Damit würde timer_arbeitlos jedoch niemals hochgezählt und die Auslastung wäre immer 0% und das wäre dann nicht richtig. Wenn mir bei dieser Überlegung etwas entgangen ist, lass es mich bitte wissen. Aber mit dem flag könnte man z.B. einen Timer starten und stoppen und mit einem Vergleichstimer vergleichen und so die Auslastung bestimmen. Kostet halt einen Timer. Frage in dem Zusammenhang: Kennt jemand eine Möglichkeit, den Zählerstand des Watchdog-Timers bei einem AVR auszulesen? Ich denke, damit würde es relativ einfach werden, habe aber dazu noch nichts gefunden. @Jan wie JOchen schon sagte, die CPU hat immer was zu tun. Muss auch so sein. Es geht nur darum, zwischen "sinnlosen" Befehlen (mit Hilfe von Sprüngen warten in der Hauptschleife) und "sinnvollen" Befehlen (z.B. abarbeiten eines Interrupts) zu unterscheiden. Siehe auch weiter oben. Gruß Thorsten
Ja stimmt schon das erst der Interrupt vom timer wieder abgearbeitet wird wenn dieser interrupt fertig ist. Jetzt hast du 3 möglichkeiten: 1.Wenn der µC es zulässt kannst du mit unterschiedlichen interrupt prioritäten arbeiten, d.h. timer hat eine hohe was natürlich bei sehr zeit kritischen int's probleme macht da diese dann öfters unterbrochen würden(kommt bei dir Thorsten ja nicht in frage) 2.Am ende einer interrupt routine die als arbeit gilt den zähler für gesamtzeit um einen wert erhöhen, der der zeit entspricht die die cpu zum abarbeiten der Interrupt routine braucht, dazu musst du die zeit durch das T des Timers teilen, hier braucht man dann das flag nicht setzten und löschen. nur interessant wenn der INT fkt nicht komplex ist d.h. mal ist arbeit dann wieder nicht, mal nur 100µs mal 1ms 3.Interrupts einfach ignorieren, da allg. die zeit die die CPU in einer int fkt ist eh so kurz wie möglich sein soll->wenn jedoch zu lang siehe 2. (kommt ja auch nicht in frage weil du ja nur in den Timer ints steckst) naja wenn du die mainloop, und die tasks mal hier rein stellst kann ich dir mehr helfen, hab aber biss jetzt nicht so vorstellen wie den dein Code aussieht den biss jetzt wurd ich sagen das man ein timer einsparen kann, wenn mann beide task durch einen Timer steuert. und man kann die task auch so ablaufen lassen, das sie nicht als interrupts ausgeführt werden Gruß Jochen
Ich würde den Timer einfach laufen lassen. Es gibt dann eine globale Variable die die Ticks des Timers zählt die als Arbeits-ISR's abgearbeitet wurden. Dazu muß am Anfang jeder ISR der Timer ausgelesen werden und am Ende auch. Die Differenz dieser Zählerstände wird ermittelt und auf die globale Variable addiert. Man hat also in der globalen Variable die Anzahl an Timer Ticks die in den ISRs verbraucht wurden. Nun, in der Timer ISR, die ja exakt in einem festen Intervall relativ zur MCU Taktfrequenz läuft, kann man nun die insgesammt verbrauchten Timer Ticks ermitteln. Anteilmäßig davon stehen in der globalen Variable die Anzahl der Ticks die in den Arbeits-ISR verbraucht wurden. Es entsteht eine simple Verhältnissgleichung, mit der man entweder Prozentual die Auslastung errechnen kann, oder aber sogar das Verhältnis in Taktzyklen. static int32_t ISR_Ticks = 0; signal Arbeits_ISR() { ISR_Ticks -= TCCNT0; ... bla bla bla ISR_Ticks += TCCNT0 + Ticks_benötigt_für_Overhead_Berechnung; } static uint8_t Percent = 0; signal Timer0_Overflow() { Percent = (Percent + 100 * (256 + TCCNT0) / ISR_Ticks) / 2; ISR_Ticks = 0; } Das funktionert weil auf AVR's normalerweise nur einen IQR Level existiert. D.h. ISR's sind nicht verschachtelt sondern sequentiell. Gruß Hagen
@JOchen Bevor ich das ganze Programm poste, möchte ich eine kleine Zusammenfassung geben. Es besteht zur Zeit aus 4 .asm Files mit zusammen 64kB Dateigröße und ca. 2000 Zeilen. Dazu kommt noch ein Makro-File mit 12kB. Das generierte .hex-File ist dann noch 13kB groß. Dazu kommt noch, dass das ganze Projekt noch nicht fertig ist, d.h. einige Stellen sind zwar angefangen aber funktionieren noch nicht. Wenn du trotzdem einen Blick reinwerfen möchtest, kann ich es gerne reinstellen. Übersicht über das Projekt: Aufgabe: Steuerung einer Eisenbahnanlage µC: ATMega16 (zur Zeit betrieben mit internen 8MHz, um den Quarz zu sparen :-), ist also noch was rauszuholen) geplante Kommunikation: TWI zur Kommunikation mit weiteren AVR's, SPI zur Porterweiterung (siehe WIKI) geplante Tasks: 1. Timer1-ISR mit ca. 30-50µs (ergibt sich dann aus der CPU-Auslastung) für SW-PWM mit 16 Kanälen (HW-PMW hat der µC ja leider "nur" 4). Laufzeit dieser ISR ca. 20µs 2. Timer2-ISR mit 10ms, die dann über weitere Untersetzungen verschiedene Aufgaben (z.B. Rampenberechnungen) zyklisch starten soll. Laufzeit z.Z. unbekannt. 3. SPI-ISR. Ich denke im Moment an eine Frequenz von ca. 100kHz was einen Interrupt alle ca. 100µs auslöst. Laufzeit der ISR selber ca. 2µs, die nachfolgende Auswertung ca. 20µs. 4. Hauptschleife. Als erstes wird das Interrupt-Flag vom TWI abgefragt. Da ich bis jetzt noch nicht in der Lage war eine vernünftige ISR dafür zu schreiben, muss ich das Flag halt abpollen. ISR kommt dann später mal :-) Aber damit das funktioniert muss halt recht hochzyklisch abgefragt werden. Ausserdem werden noch verschiedene andere Aufgaben ausgeführt, die aber dann keine große Priorität haben. Aus diesen Angaben ergibt sich bereits eine rechnerische Auslastung allein aus 1. und 3. von 60% (bei Timer1-ISR=50µs) bis 87% (bei Timer1-ISR=30µs). Daher kam dann die Frage nach der Online Berechnung der CPU-Auslastung. Ich hoffe, du kannst dir jetzt ein besseres Bild von der ganzen Sache machen. Und wie gesagt, wenn du noch willst, kann ich den Code gerne reinstellen . @Hagen Die Idee mit dem Timer gefällt mir eigentlich auch ganz gut. Ich fürchte zur Zeit nur, dass es nicht mehr funktioniert, wenn ich auch die Auslastung einer Hintergrundaufgabe mitmessen will. Irgendwann unterbricht die ISR diese Aufgabe und die Berechnung der Timer-Ticks kommt durcheinander. Sehe ich das richtig? Wenn nur ISR's gemessen werden sollten, müsste es funktionieren. Entschuldige wenn ich mich nicht klar ausgedrückt habe. @all Danke für die ganzen Anregungen und Ideen. Gruß Thorsten
Mein Erfahrung mit internen Osz. sind sehr schlecht, da sie meist nur aus R&C bestehen und somit sehr temp. Abhängig sind.
Hallo Frankl, stimmt schon dass die internen Schwingkreise nicht besonders gut sind. Aber solange alles synchronisiert läuft (TWI, SPI) und auch sonst keine Probleme auftreten, warum nicht? Z.B. beim USART-Betrieb sollte man schon einen Quarz spendieren. Kann dann einige "unerklärliche" Probleme vermeiden. Hab es selber erfahren müssen. :-) Gruß Thorsten
Ok, dann bleibt dir nur eines übrig: In deimem Program MUSS es ja eine Hauptschleife geben. Diese pollt verschiedene Ereignisse solange bis diese eintreffen. Nun, beim Start dieses Pollen wird der aktuelle Timer ausgelesen und gespeichert. Sobald nun ein Ereignis beim Pollen zutrifft wird wiederrum der Timer ausgelesen und mit dem Startwert zu einer Differenz subtrahiert. Da du aber ganz exakt die Taktzyklen die das Pollen benötig ausrechnen kannst, und den Prescaler des Timers kennst, hast du nun die Möglichkeit exakt auszurechnen wie lange deine ISR zum Zeitpunkt des Pollens benötigt haben. Die anderen Task, die ja durch das Pollen angesprungen werden, würden in dieser Rechnung mit einfließen können, je nachdem wie du dir das wünscht. Wichtig ist eben nur eines, der Timer muß laufen, egal ob ISR oder Hauptschleife. Nur mit hilfe des Timers hast du eine sichere Taktzyklen basierte Methode. Zb. // Hauptschleife uint16_t TimerWert = TCCNT0; while (1) do { uin16_t r,t = TCCNT0; r = t; if (r <= TimerWert) r +=256; r -= TimerWert; TimerWert = t; uint16_t Percent = (t - X) / 256 * 100; if (Polled) { } } Nun, wichtig ist oben das X, es gibt an wieviele Ticks das Pollen samt Timer auslesen in der Schleife dauert. Sollte zB. der PreScaler vom Timer0 auf 1 stehen, so würde X exakt die Taktzyklen angeben. In t muß also immer mindestens die Dauer in Ticks die für das Pollen in der Hauptschleife benötigt wird stehen. Dies wären dann 0 Prozent. Sobald aber t größer ist als X heist dies das entweder eine ISR ausgeführt wurde oder aber eine Unterfunktion aus der Hauptschleife heraus aufgerufen wurde. In diesem Falle ist t > X und somit Percent > 0. Mit dieser Methode würdest du die Gesamtauslastung der MCU messen, je kleiner Percent um so häufiger wurde die Hauptschleife durchlaufen im gleichem Zeitraum. Ein andere Methode wäre den Timer0 auf Zb. exakt 1 Sekunde einzustellen. Nahc dieser Sekunde wird in der Timer ISR ein Zähler ausgewertet. Etwa so uint16_t Counter = 0; uint16_t Percent = 0; signal Timer0() { // alle 1 Sekunde Percent = (Percent + Counter / MaxCounter * 100) / 2; Counter = 0; } void Main() { while (1) { Counter++; if (Polled) { bla bla... } } } MaxCounter musst du nun so berechnen das dort die Anzahl an Schleifendurchläufen pro 1 Sekunde drinnensteht die die Mainloop MAXIMAL durchlaufen kann. Diese ließe sich von Hand berechnen oder aber du deaktivierst ALLE ISR's bis auf den Timer0 und rufts un Mainkloop keine Unterfunktionen auf. Daraus ergibt sich also die Maximale Anzahl an Aufrufen von Counter++ in der Mainloop pro Sekunde, wenn NICHTS anderes durch die MCU ausgeführt wird. Gruß Hagen
>Diese ließe sich von Hand berechnen oder aber >du deaktivierst ALLE ISR's bis auf den Timer0 und rufts un Mainkloop >keine Unterfunktionen auf. Da könnte man doch eigentlich auch eine init-Unterroutine aufrufen, die den Timer inkl. Interrupt aktiviert und eine fest definierte Schleife durchlaufen. Am Ende der Routine wird der Zählerstand des Timers ausgelesen und weis, wie viele Durchläufe pro festgelegtem Zeitraum überhaupt möglich sind. In der Hauptschleife kann man diesen Wert dann als Referenz verwenden.
@OldBug, korrekt. Man könnte aber auch den Wert von MaxCounter abhänig vom Counter ermitteln. Etwa so uint16_t Counter = 0; uint16_t MaxCounter = 1; uint16_t Percent = 0; signal Timer0() { // alle 1 Sekunde if (Counter > MaxCounter) { MaxCounter = Counter; Percent = 100; } Percent = (Percent + Counter / MaxCounter * 100) / 2; Counter = 0; } void Main() { while (1) { Counter++; if (Polled) { bla bla... } } } Nun würde das System schon während der Laufzeit die eigenen Schranken ermitteln. Nach wenigen Sekunden hätte sich MaxCounter so stabilisert das er die größt mögliche Dauer angibt die in der Main Loop ohne weiterem zusätzlichen Code verbracht wurde. Dieses System würde sich selbst kalibrieren. Percent gibt natürlich hier den Prozentsatz an in dem die MCU nichts tut. D.h. 100% bedeutet die Freizeit die die MCU hat, bei 0% ist die MCU voll ausgelasstet. Gruß hagen
Vorteile der obigen Methode liegen auf der Hand. Ob man den 8 oder 16 Bit Timer benutzt oder in welchem Interval die Timer ISR aufgerufen wird ist fast egal. Wichtig ist nur das im Durchschnitt Counter++ in der Mainloop häufiger aufgerufen wird als die Timer ISR. Nachteil ist allerdings das die Timer ISR durch höherpriveligierte ISR blockiert werden kann. D.h. gerade zum zeitpunkt wenn ein Timer OVR eintritt wird eine längrdauernde ISR ausgeführt. In diesem Moment würde eine Verfälschung entstehen. Man könnte das kompensieren indem man TCCNT0 abfragt in der Timer ISR. Desweiteren wird auch durch das Sperren der IQR's mit CLI die Methode verfälscht. Man kann dies durch eine andere Methode verhindern: void Main() { uint16_t Ticks, Percent, MinTicks = 0xFFFF, WaitCount = 0; Ticks = TCCNT0; while (1) { if (++WaitCount = 100) { WaitCount = 0; Ticks = TCCNT0 - Ticks; if (Ticks == 0) Ticks += 256; if (Ticks < MinTicks) { MinTicks = Ticks; Percent = 100; } Percent = (Percent + MinTicks / Ticks * 100) / 2; } if (Polled) { bla bla... } } } Vereinfacht dargestellt und als ungetesteter Vorschlag. Hier benötigen wir keine Timer ISR mehr. Gruß Hagen
@all Danke für alle Amregungen und Ideen. Ich versuche mich jetzt mal an der Realisierung der Auslastungsberechnung. Wie versprochen werde ich das Ergebnis dann hier posten. Gruß Thorsten
Hallo allerseits, im Anhang ist das Ergebnis der Diskussion. Es ist das komplette Testprogramm für die Routine der Auslastungsberechnung. Funktioniert eigentlich ganz gut. Ich hoffe es ist ausreichend kommentiert. Meine Makros habe ich drin gelassen. Dadurch sollte die Lesbarkeit erhöht werden. Getestet im AVR-Studio 4.08 auf ATMega16-Plattform mit verschiedenen Frequenzen. Außerdem verschiedene Belastungen und Berechnungszyklen eingestellt. Das Ergebnis sah plausibel aus. Es sollten nicht mehr allzu viele "Leichen" drin sein:-) Wer mag, viel Vergnügen. Thorsten PS: Eingestellte Tab-Breite = 2.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.