mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik MSP430 Interrupt verschachteln


Autor: Wolfgang (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Ich war bisher der Meinung, dass ein Interrupt ein laufendes Programm 
unterbricht, der µC sich den letzten Befehl merkt und nach Abarbeitung 
des Interruptunterprogramms an der Stelle die Programmabarbeitung 
fortsetzt, wo es unterbrochen wurde.
Folgende  Problemstellung: Mit dem TimerA wird jede Sekunde ein Programm 
angestoßen, mit welchem der AD-Wandler abgefragt wird. Die  Messwerte 
werden auf einer Smart Media Karte gespeichert.
Dazu werden 512Bytes gesammelt und in einem Rutsch auf die Karte 
übertragen.
Läuft problemlos.
Zur Auswertung der Messwerte wird die Karte entnommen und in den PC 
eingespielt.

Jetzt sollen die Daten, ohne die Karte herauszunehmen, mit RS232 an 
USART1 simultan gelesen werden.
Dazu wird vom PC ein Zeichen gesendet, um das Auslesen zu starten. Dies 
funktioniert  nur soweit, dass zwar die Daten übertragen werden, aber 
die Übertragung nicht, wie gehofft, vom Timerinterrupt unterbrochen 
wird. Erst wenn meine Schleife (Zeile: //100 Sektoren werden gelesen) 
vollständig abgearbeitet ist, wird das Timerprogramm wieder 
abgearbeitet.
Was mache ich falsch? Lt. Datenblatt hat Timer A die Priorität 6 und 
USART1 senden die Priorität 2. Oder hat die Priorität überhaupt hierfür 
eine Bedeutung?
Programmausschnitte im Anhang.


Wolfgang

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn der MSP einen Interrupt bearbeitet, werden beim Kontextwechsel 
automatisch die Interrupts gesperrt. So wird verhindert, daß die 
laufende ISR von einem anderen Interrupt unterbrochen werden kann.

Man kann aber die Interrupts in der ISR wieder freigeben, muß aber dann 
evtl. verhindern, daß eine ISR in Rekursion gerät.

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Interrupt Routinen sollten kurz gehalten werden. Bei dir steckt da ja 
das ganze Programm drin. Transfer von 50KB in der Rx Routine!

Man kann zwar Interrupts verschachteln, aber hier ist das kaum der 
richtige Weg. Ein Rx Interrupt hat das Byte zu lesen und irgendwo 
abzuspeichern und damit hat es sich. Alles andere gehört da nicht rein.

Besserer Ansatz: In der Rx Routine das Steuerzeichen einlesen und global 
speichern, oder vergleichen und ein Flag setzen. Im Hauptprogramm wird 
dies dann abgefragt und ggf. der Transfer durchgeführt. Dann kollidieren 
Rx und Timer Interrupt nicht mehr.

Das gleiche gilt natürlich auch für den Timer Interrupt. Auch da gehört 
nicht der komplette Transfer hinein, sondern der sollte dem 
Hauptprogramm nur signalisieren, dass es wieder mal soweit ist.

Autor: Wolfgang (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Aus den Antworten entnehme ich, dass mein Problem lösbar ist, wenn man 
richtig programmieren kann.
Mit „Kontextwechsel“ und „in Rekursion gerät“ kann ich noch nichts 
anfangen. Vielleicht kann mir jemand erklären, was damit gemeint ist.
@Andreas
>Besserer Ansatz: In der Rx Routine das Steuerzeichen einlesen und global
>speichern, oder vergleichen und ein Flag setzen.
Ich hatte mir schon gedacht,  dass es eine bessere Lösung geben könnte. 
Wie setzt man ein Flag?
Im überarbeiteten Anhang habe ich versucht,  zunächst Rx umzubauen. Nur 
weiß ich nicht, wie man im Hauptprogramm erkennen kann, ob ein interrupt 
sich gemeldet hat.
Für Timer Interrupt ist mir noch nichts eingefallen.
Ich würde mich freuen, wenn Du mir helfen und die dafür notwendigen 
Zeilen als Beispiel formulieren könntest.

Es kann natürlich auch jeder andere mitreden.
MfG
Wolfgang

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Globale Variablen:
  Flag für "Timer war da"
  Empfangenes Zeichen

Timer Interrupt:
  Flag setzen: "Timer war da"

UART Rx Interrupt:
  in "Empfangenes Zeichen" abspeichern

Hauptprogramm:
  Schleife:
     wenn Flag gesetzt "Timer war da":
        Flag zurücksetzen
        tu was getan werden muss wenn der Timer abgelaufen ist
     wenn Zeichen empfangen:
        tu was getan werden muss wenn ein Zeichen empfangen wurde
        "Empfangenes Zeichen" löschen

Ein Flag ist hier schlicht eine globale Variable. Schlag aber 
diesbezüglich mal unter "volatile" nach.

Jede Warteschleife in einer Interrupt-Routine ist ein potentieller 
Kandidat für einen Denkfehler. Warum wird im Rx Interrupt auf den Tx 
Status gewartet?

Autor: Wolfgang (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
>Warum wird im Rx Interrupt auf den Tx Status gewartet?
Diese Zeile entstammt einem TI-Beispiel. War dort als Echo gedacht. Hab 
die Zeile herausgenommen.
Ich habe mein Programm noch einmal nach deinen Hinweisen umgebaut.
Würde mein Hauptprogramm im Ansatz funktionieren?
Wenn ja, wie merkt das Hauptprogramm, wenn gerade „lesen_Karte“ läuft, 
dass "Timer war da" ausgelöst wurde?
MfG
Wolfgang

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nö, so sicher nicht, da ist so ziemlich alles verkehrt. Aber ich schreib 
meinen Text oben nicht nochmal ab. Anfängerkurs in Programmieren liegt 
mir nicht.

EDIT: Die Interrupt-Routinen sind ok. Das Hauptprogramm nicht. Bischen 
über die Schleifen und was da wann wie passiert nachdenken würde nicht 
schaden.

Autor: Uhu Uhuhu (uhu)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
„Kontextwechsel“: Wenn z.B. ein Interrupt ausgelöst wird, wird das 
laufende Programm unterbrochen und der Prozessor wechselt vom Kontext 
der unterbrochenen Routine in den der ISR. Hinterher wird das 
unterborchene Programm an der Stelle fortgesetzt, an der es unterbrochen 
wurde - der Prozessor schaltet zurück in den unterbrochenen Kontext.

„in Rekursion gerät“: Wenn man in einer zu lang laufenden ISR die 
Interrupts wieder freigibt und derselbe Interrupt nocheinmal ausgelöst 
wird, dann wird die laufende ISR unterbrochen und eine neue wird 
gestartet. Damit ist die ISR in Rekursion geraten.

Da ISRs in aller Regel so geschrieben sind, daß sie Rekursion nicht 
vertragen, muß man diese Situation unbedingt vermeiden.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Wolfgang (Gast)

>Ich habe mein Programm noch einmal nach deinen Hinweisen umgebaut.

Naja, die Formatierung lässt noch zu wünschen übrig.

>Würde mein Hauptprogramm im Ansatz funktionieren?

Nein.

>Wenn ja, wie merkt das Hauptprogramm, wenn gerade „lesen_Karte“ läuft,
>dass "Timer war da" ausgelöst wurde?

Die Frage solltest DU als Programmierer beantworten können! Hirnloses 
Copy & Past bringt da nix.

Da der Andreas den Anfängerkurs verweiget, hier mal ein Crash-Kurs.

Was macht dein Programm in der Hauptschleife? Die gar keine ist!

>while (!(An=='L'))lesen_Karte();

Solange das Zeichen An nicht 'L' ist, liest du die Karte. ???
Was soll das?

Willst du nicht eher dann die Karte lesen, WENN es 'L' ist?

Das hätte man sinnvollerweise besser geschrieben als

if (An=='L') lesen_Karte();

Wenn An eben nicht 'L' ist, was es die meiste Zeit ist, geht das 
Programm weiter.

Dann die nächste Schote.

while (!(Timer_war_da==1))Messwert_speichern();

Solange wie Timer_war_da NICHT ==1 ist speicherst du Messwerte, ohne 
Pause!. Und du fragst aber die ganze Zeit NICHT nach, ob denn vielleicht 
ein 'L' angekommen ist. Hmmm?

Wie wäre es damit?

while (1) {         // Hauptschleife

  if (An=='L') lesen_Karte();
  if (Timer_war_da==1) Messwert_speichern();
}

Dann gibt es noch das kleine Problem, dass du in dem jetzigen Quelltext 
die Augabe per UART ohne Interrupt machst. D.H. solange die Übertragung 
der Daten zum PC läuft wird keine neue Messung gestartet! Das willst du 
nicht. Also muss das Senden der Daten in den UART TX Interrupt! 
Messwert_speichern() bereitet nur alles vor, sprich Daten von der Karte 
in den Buffer einlesen, Zähler setzen, UART TX-Interrupt einschalten. 
Dann klappts auch mit dem Timer.

MfG
Falk

Autor: Wolfgang (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zunächst mein Dank an alle.
>Naja, die Formatierung lässt noch zu wünschen übrig.
Ich möchte mich bessern. Worauf kommt es besonders an? Ich orientierte 
mich an den TI-Beispielen.
> Hirnloses Copy & Past bringt da nix.
Gerade mein Hauptprogramm  war auf meinem Mist gewachsen, auch wenn es 
offensichtlich zumeist aus Mist besteht.
>Was macht dein Programm in der Hauptschleife? Die gar keine ist!
Das hatte ich mir schon fast gedacht, dass mein Ansatz falsch ist, aber 
eine andere "Lösung" für eine Endlosschleife  ist mir nicht eingefallen.
> DU als Programmierer
Das wird wohl nichts mehr mit mir, aber ich werde mir Mühe geben.
>Dann gibt es noch das kleine Problem,...
Für mich aber das große Problem. Diesen Absatz habe ich nicht richtig 
kapiert.
Vielleicht hat jemand noch Geduld mit mir.
Habe ich es richtig verstanden: mit ME2 |= UTXE1 + URXE1;   werden die 
Module UART für senden und empfangen zugeschaltet. Mit IE2 |= URXIE1; 
wird der interrupt für Empfang und mit Mit IE2 |= UTXIE1; für Senden
freigegeben.
Ein ankommendes Zeichen an URXD1 löst den interrupt für Empfang aus.
Wann wird ein interrupt für Senden ausgelöst?
Vielleicht bekomm ich dann die nächsten Schritte noch hin.
Wolfgang

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Für mich aber das große Problem. Diesen Absatz habe ich nicht richtig
> kapiert.

Ich bin auch nicht sicher, ob du den Schritt, den Falk hier empfiehlt, 
gleich am Anfang gehen solltest. Immer hübsch der Reihe nach kann 
anfangs sinnvoller sein.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Wolfgang (Gast)

>Ich möchte mich bessern. Worauf kommt es besonders an? Ich orientierte
>mich an den TI-Beispielen.

Gleichmässig einrücken, Zeilen nicht zu lange (80 bis 100 Zeichen), auch 
mal ein paar Leerzeichen verwenden, etc.

>Für mich aber das große Problem. Diesen Absatz habe ich nicht richtig
>kapiert.
>Vielleicht hat jemand noch Geduld mit mir.

>Habe ich es richtig verstanden: mit ME2 |= UTXE1 + URXE1;   werden die
>Module UART für senden und empfangen zugeschaltet.

Ja.

> Mit IE2 |= URXIE1;
>wird der interrupt für Empfang und mit Mit IE2 |= UTXIE1; für Senden
>freigegeben.

Ja.

>Ein ankommendes Zeichen an URXD1 löst den interrupt für Empfang aus.

Ja.

>Wann wird ein interrupt für Senden ausgelöst?

Wenn der Sendepuffer leer ist. Der UART ist in beide Richtungen 
gepuffert. D.h. beim Senden schreibst du ein Byte in das Datenregister. 
Das wird sofort vom UART ins Schiebergister übernommen. Nun ist das 
Datenregister leer, ein TX Interrupt wird generiert. Dort muss dein MSP 
ein nues Datenbyte reinschreiben. Nun dauert es ne Weile, bis das erste 
Byte übertragen ist, abhängig von der Baudrate. Dann wird das zweite 
Byte, welches im Puffer steht, wieder automatisch ins Schieberegister 
übernommen und gesendet. Nach der Übernahme kommt der nächste Interrupt.

Deine TX Interruptroutine muss also nur byteweise die Daten aus deinem 
Array lesen und ins TX Datenregister schreiben. Dann prüft sie noch, ob 
alle Daten gesendet wurden. Ist das der Fall, wird der TX-Interrupt 
wieder ausgeschaltet.

>Vielleicht bekomm ich dann die nächsten Schritte noch hin.

Mit etwas Geduld wird das schon.

MfG
Falk

Autor: Wolfgang (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Immer hübsch der Reihe nach kann anfangs sinnvoller sein.
Dann versuch ich mal zu sortieren.
a) Meine Interrupt-Routinen enthalten nur noch ein Flag, welches als 
globale volatile Variable deklariert wurde, damit es durch eine ISR 
veränderbar wird.
b) das Hauptprogramm läuft als Endlosschleife und fragt die beiden Flag 
„Timer_war_da“ und 'L' ab.
c) die Schoten der while-Schleifen wurden korrigiert
d)das Erscheinungsbild wurde teilweise verbessert
e)die Erläuterung zum Sendepuffer habe ich soweit verstanden, aber was 
ist der Vorteil gegenüber der Kontrolle mit
while (!(IFG2 & UTXIFG1));        // USART1 TX buffer ready
hier wird doch m.E. auch geprüft, ob der Sendepuffer leer ist.

Wäre das überarbeitete Programm im Ansatz möglich?
Für bessere Lösungen bin ich immer offen.
Wolfgang

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Anhang?

Autor: Wolfgang (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
fehlender Anhang

Autor: Falk Brunner (falk)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
@ Wolfgang (Gast)

>a) Meine Interrupt-Routinen enthalten nur noch ein Flag, welches als
>globale volatile Variable deklariert wurde, damit es durch eine ISR
>veränderbar wird.

Das volatile sagt dem Compiler, dass er keinerlei Optimierungen beim 
Zugriff auf diese Variablen machen darf.

>b) das Hauptprogramm läuft als Endlosschleife und fragt die beiden Flag
>„Timer_war_da“ und 'L' ab.

Richtig.

>c) die Schoten der while-Schleifen wurden korrigiert

;-)

>d)das Erscheinungsbild wurde teilweise verbessert

Du bewegst dich in die richtige Richtung. Aber es fehlen noch ein paar 
Schritte.

>e)die Erläuterung zum Sendepuffer habe ich soweit verstanden, aber was
>ist der Vorteil gegenüber der Kontrolle mit
>while (!(IFG2 & UTXIFG1));        // USART1 TX buffer ready
>hier wird doch m.E. auch geprüft, ob der Sendepuffer leer ist.

Ja, aber während der Prüfung kann die CPU NICHTS anderes machen, sie 
rennt wie wild in der Schleife rum! Bei 9600 Baud für ~ 1ms. Das ist 
eine Ewigkeit! Und das dann 512 mal hintereinander, (~530ms) und das 
dann noch 100 mal hintereinander! Macht schlappe 50 Sekunden! In denen 
die Routine nicht einmal verlassen wird!

>Wäre das überarbeitete Programm im Ansatz möglich?

Im Ansatz ja. Aber du denkst teilweis noch zu kompliziert.

>Für bessere Lösungen bin ich immer offen.

Siehe Anhang.

Das Ganze läuft erstmal noch ohne TX Interupt. Allerdings werden pro 
Aufruf nur 512 Bytes gelesen und zum PC gesendet. Wie oft kommt denn 
dein Timerinterrupt? Du hast ja leider nicht den vollständigen Quelltext 
reingestellt, da kann man das nicht erkennen. Die Übertragung von 512 
Byte bei 9600 Baud dauert 533ms. Wenn das Schreiben von einem Sektor 
AusgabeZW32(), das Lesen eines Sektors mit MB32_zuruecklesenSektor () 
plus die Übertragung (533ms) weniger Zeit braucht als ein Timerintervall 
dann läuft das Programm. Ich nehme mal an, der Timer klingelt 1 mal pro 
Sekunde, oder? Dann sollte es passen (wenn nicht das Lesen/Schreiben der 
SD-Karte tierisch lange dauert).


MfG
Falk

Autor: Andreas K. (a-k)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In einem Punkt möchte ich meinem obigen Ansatz etwas korrigieren. Im 
Timer-Interrupt den ADC auszulesen und in einen Puffer zu speichern, wie 
du ursprünglich gemacht hast, ist völlig ok. Der kritische Punkt kam 
dort, wo du diesen Puffer, wenn voll, noch innerhalb des Interrupts auf 
Karte gespeichert hast. Das ist erstens aus Zeitgründen nicht sinnvoll 
und führt zweitens zu Chaos, wenn der Interrupt grad das Auslesen der 
Karte unterbricht.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Andreas Kaiser (a-k)

>In einem Punkt möchte ich meinem obigen Ansatz etwas korrigieren. Im der
>Timer-Interrupt den ADC auszulesen und in einen Puffer zu speichern, wie
>du ursprünglich gemacht hast, ist völlig ok. Der kritische Punkt kam

Yep, die Version hab ich aber nicht mitbekommen. Bin erst bei B 
eingestiegen ;-)
Aber so ist es auch erstmal OK.

MFG
Falk

Autor: Falk Brunner (falk)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Kleine Anmerkung, in der Version _d darf der UART TX interrupt nicht 
aktiviert werden. Hab ich vergessen rauszumachen. Hier jetzt noch als 
Sahnehäubchen die Lösung mit Interrupt, damit läuft die Datenübertragung 
wirklich parallel zum Rest, innerhalb eines Timerintervalls muss nun nur 
noch einmal ein Sektor gelesen und geschrieben werden können. Die 
Baudrate ist vollkommen egal.

MfG
Falk

Autor: Wolfgang (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Ja, aber während der Prüfung kann die CPU NICHTS anderes machen, sie
>rennt wie wild in der Schleife rum! Bei 9600 Baud für ~ 1ms.
Kann ich zwar nicht richtig beurteilen, denke aber, es ist fast egal, ob 
der Sendepuffer einen interrupt auslöst, wenn 10? Bit für ein Byte 
gesendet wurden oder ob vor dem Sendevorgang geprüft wird, ob der 
Sendepuffer leer ist.

>noch innerhalb des Interrupts auf Karte gespeichert hast. Das ist erstens
>aus Zeitgründen nicht sinnvoll und führt zweitens zu Chaos, wenn der
>Interrupt grad das Auslesen der Karte unterbricht.
Wie in meinem ersten Beitrag schon geschrieben, hat bei mir auch nicht 
die Unterbrechung des Auslesens funktioniert.
Zur Lösung des Problems hatte ich deshalb im Forum um Hilfe gebeten.

Durch die Anzahl der Beiträge kann man leicht den Überblick verlieren.
Deshalb mal kurz die Beschreibung meines Datenspeichers:
µC: MSP430F1611
es werden je nach Programmierung die internen AD- Wandler ausgelesen 
bzw. über SPI oder I2C diverse Temperatur- oder Druckfühler abgefragt, 
in einem 512 Bytes großen [Feld] zwischengespeichert und wenn es voll 
ist, auf einer 32MB oder64MB Smart Media- Karte abgespeichert. Es müssen 
also bis zu 65000 Sektoren (bei 32MB) ausgelesen werden. Z.Zt. entnehme 
ich die Karte in gewissen Abständen dem Datenspeicher und spiele sie an 
der parallelen Schnittstelle in den PC ein.
Dadurch entsteht oftmals Datenverlust.
Je nach Anwendung werden 1, 2, 4, 8 Messwerte in frei wählbaren 
Abständen (zumeist 1s, 2s, 4s, 6s kann auch mal der ms Bereich sein) 
gesammelt. Auch GPS-Daten wurden gespeichert, um die Geschwindigkeit 
beim Skifahren zu ermitteln.
Zuletzt kam eine Flüssigkristallanzeige dazu.

Inzwischen hat sich das Programm tüchtig aufgebläht, sodass ich mit IAR 
Kickstart nicht mehr das volle Programm compilieren kann.
Deshalb habe ich mspgcc verwendet. Beim compilieren mit mspgcc 
funktioniert bei mir der I2C Bus nicht.

!! Wichtig !! kennt jemand dieses Problem und hat eine passende Lösung?

Teile des Sahnehäubchens „Datenspeicherkorrigiert_d_int.c „ werde ich in 
mein Programm in Ruhe einarbeiten und testen und zu gegebener Zeit 
berichten. Schon jetzt mal vielen Dank.
Wie schon oben gesagt: Ich bin für alle Vorschläge offen.
Wolfgang

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Wolfgang (Gast)

>Kann ich zwar nicht richtig beurteilen, denke aber, es ist fast egal, ob
>der Sendepuffer einen interrupt auslöst, wenn 10? Bit für ein Byte
>gesendet wurden oder ob vor dem Sendevorgang geprüft wird, ob der
>Sendepuffer leer ist.

Du hast das Problem noch nicht verstanden. Denk nochmal drüber nach!

>Zur Lösung des Problems hatte ich deshalb im Forum um Hilfe gebeten.

Die hast du ja reichlich bekommen, oder?

MfG
Falk

Autor: Wolfgang (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Die hast du ja reichlich bekommen, oder?
Da kann ich nicht meckern, kann nicht besser sein!!
MfG
Wolfgang

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.