www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Problem mit Projekt eines Empfangsprogramms für Besucher


Autor: Klas Meyer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Moin,

ich habe hier zusammen mit dem Vermieter im Haus ein Projekt laufen, 
welches auf Tastendruck festgelegte Texte, die auf Conrad Soundmodulen 
gespeichert sind abspielt. Dieses soll vorne an der Tür Gäste empfangen. 
Nach einigem hin und her habe ich nun auch endlich eine Möglichkeit 
gefunden, wie es zuverlässig läuft. Verwendet wurde dazu das myAVR MK1 
LPT Board mit Atmega8, sowie das 4 Kanal Relais Modul "myDigitalOut". 
Letzteres ist notwendig, da die Soundmodule eine sehr hohe 
Eingangsempfindlichkeit haben und somit jetzt über die Relais nur dann 
Strom erhalten, wenn sie auch wirklich an der Reihe sind.

Quelltext:
#define F_CPU 3686400

#include <avr/io.h>
#include <util/delay.h>

unsigned char tasterFrei;

// Funktionen

void initPorts (void)
{
  DDRB = 0xFF;                       // PORTB = Ausgang
  PORTB = 0x00;         // Ausgänge B auf low
  DDRC = 0xFF;         // PORTC = Ausgang
  PORTC = 0x00;         // Ausgänge C low
  DDRD = 0x00;                       // PORTD = Eingang
  PORTD = 0x04;                      // PORTD = PULL-UP  
}

void delayLong (unsigned char i)
{
  unsigned char j;
  for (j = 0; j < i; j++)
  {
    _delay_ms(1000);
  }
}
char alleAnsagen(char i)
{
  if (i == 1)
  {
    tasterFrei = 0;
    PORTC |= 0x01;
    _delay_ms(10);
    PORTB = 0x01;
    _delay_ms(100);
    PORTB = 0x00;
    delayLong(10);//dauer ansage nr.1 (in sekunden)
    PORTC &= ~0x01;
    tasterFrei = 1;
  }
else 
  {
    if (i == 2)
    {
      tasterFrei = 0;
      PORTC |= 0x02;
      _delay_ms(10);
      PORTB = 0x02;
      _delay_ms(100);
      PORTB = 0x00;
      delayLong(9);//dauer ansage nr.2
      PORTC &= ~0x02;      
      tasterFrei = 1;
    }
  else 
    {
      if (i == 3)
      {
        tasterFrei = 0;
        PORTC |= 0x04;
        _delay_ms(10);
        PORTB = 0x04;
        _delay_ms(100);
        PORTB = 0x00;
        delayLong(18);//dauer ansage nr.3
        PORTC &= ~0x04;
        delayLong(80); //letzte verzoegerung extra lang (in sek)
        tasterFrei = 1;
        i=1;
      }
    else 
      {
        tasterFrei = 1;
        i=1;
      }
    }
    return (i);
  }

}

// Haupt

main (void)
{
  initPorts();  
  unsigned char i;
  i=1;
  unsigned char fertig;
  fertig = 0;
  unsigned char tasterFrei;
  tasterFrei = 1;
  do 
  {
    if ((!(PIND&0x04)) && (tasterFrei == 1))
    {
      alleAnsagen(i);
      i++;
    }

  }
  while (fertig == 0);
}

Nun zu meinem Problem: Das System besteht aus 3 dieser Soundmodule (also 
3 Texte), die je nach Tastendruck in festgelegter Reihenfolge abgespielt 
werden. Nach Durchlauf ist das System für eine gewisse Zeit gesperrt und 
dann wieder einsatzbereit. Drückt jemand jetzt den Taster jedoch nur 1x 
oder 2x und geht danach weg, so soll sich das Programm nach einer 
gewissen Zeit zurück auf Anfang zurücksetzen. Leider sind meine 
Programmierkenntnisse nicht so ausgereift. Meine Vermutung ist es einen 
Timer zu setzen, der global über der gesamten Schleife läuft und nach 
einer festgelegten Zeit das System unabhängig von anderen Parametern 
zurücksetzt. Allerdings habe ich keine Ahnung wie ich es hinbekomme, 
dass der Timer sozusagen im Hintergrund mitläuft.

Ich hoffe, dass jemand mir hier helfen kann.


Gruß,
Klas Meyer

Autor: Kai S. (hugstuart)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,
mach dich mal im AVR-Tutorial mit Interrupts vertraut. Das ist dort sehr 
gut beschrieben, ich denke eine Erklärung hier ist dann gar nicht mehr 
nötig.

Autor: Klas Meyer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi,

vielen Dank für den Tipp, habe mich dort mal eingelesen, habe es auch 
grundsätzlich verstanden, nur scheints in der Anwendung bei mir nicht zu 
klappen. Habe aber auch lediglich Beispiele gefunden, die mir erlauben 
vor der Main Routine zu warten, nicht, wie ich möchte, die Hauptroutine 
zurücksetzen.

Ich vermute mal dass einfach meine Programmierkenntnisse zu gering sind 
für das Projekt, werde mir wohl einen versierteren C-Programmierer 
suchen müssen.

Gruß,
Klas

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klas Meyer schrieb:
> Hi,
>
> vielen Dank für den Tipp, habe mich dort mal eingelesen, habe es auch
> grundsätzlich verstanden, nur scheints in der Anwendung bei mir nicht zu
> klappen. Habe aber auch lediglich Beispiele gefunden, die mir erlauben
> vor der Main Routine zu warten, nicht, wie ich möchte, die Hauptroutine
> zurücksetzen.
>
> Ich vermute mal dass einfach meine Programmierkenntnisse zu gering sind
> für das Projekt, werde mir wohl einen versierteren C-Programmierer
> suchen müssen.

So schwer ist das nicht.
Du brauchst:
einen Timer, der in regelmässigen Zeitabständen einen Interrupt auslöst.
Diese Zeitabstände kennst du, sagen wir mal alle 100 Millisekunden.
Dann brauchst du weiters eine globale Variable. Diese wird deine Uhr.
In der Interruptroutine des Timers zählst du diese Variable um 1 hoch.
Soweit so gut.

Wenn du nun in der Hauptschleife diese Variable immer wieder abfrägst 
(die Variable volatile machen nicht vergessen) und diese Variable hat 
den Wert 10 erreicht, dann bedeutet das, das 1 Sekunde vergangen ist, 
denn 10 mal 100 Millisekunden sind 1 Sekunde. Und ganz wichtig: 1 
Sekunde, seit dem du das letzte mal diese Varíable auf 0 gesetzt hast.
Wenn du daher bei einem erkannten Tastendruck die Variable auf 0 setzt, 
dann kannst du in der weiteren Behandlung der Hauptschleife die Variable 
zb auf größer 100 abfragen und wenn der Fall eintritt, dann weißt du, 
dass seit mindestens 10 Sekunden kein weiterer Tastendruck erfolgt ist.

volatile unsigned char Ticks;    // hier zählt die ISR hoch

ISR(  .... )
{
  Ticks ++;
}

main (void)
{
  initPorts();  
  unsigned char i;
  unsigned char fertig;
  unsigned char tasterFrei;

  i=1;
  fertig = 0;
  tasterFrei = 1;

  // Timer initialisieren
   .....

  ///

  do 
  {
    if ((!(PIND&0x04)) && (tasterFrei == 1))
    {
      alleAnsagen(i);
      i++;
      Ticks = 0;
    }

    if( Ticks > 100 ) {
      i = 1;         // Grundzustand
      Ticks = 0;
    }

  }
  while (fertig == 0);
}

Du musst nur anfangen, deine geradlinige einfache Denkweise
"zuerst mach dieses, dann mach jenes, dann warte ein Weilchen und mach 
schlussendlich noch dieses" aufzugeben und in Ereignissen denken. In der 
Hauptschleife werden Ereignisse abgefragt
   ist eine Taste gedrückt
   ist eine Zeit abgelaufen
   ist ....

und dann musst du natürlich noch dafür sorgen, dass das Ereignis auch 
entsprechend eintreten kann.

Autor: Klas Meyer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Moin nochmal,

vielen Dank, der Quelltext hat mir schon sehr weiter geholfen, 
allerdings scheint es immer noch nicht korrekt zu sein, es lässt sich 
zwar ohne Fehler brennen, jedoch scheint er den Timer einfach zu 
ignorieren, liegt das im Ausführen innerhalb von "while(1)" ? Wenn ich 
dort allerdings nichts hineinschreibe würde er ja einfach warten bis der 
Timer am festgelegten Wert ankommt.

Mein derzeitiges Programm sieht jetzt so aus:

#define F_CPU 3686400

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

unsigned char tasterFrei;

// Funktionen

void initPorts (void)
{
  DDRB = 0xFF;                       // PORTB = Ausgang
  PORTB = 0x00;         // Ausgänge B auf low
  DDRC = 0xFF;         // PORTC = Ausgang
  PORTC = 0x00;         // Ausgänge C auf low
  DDRD = 0x00;                       // PORTD = Eingang
  PORTD = 0x04;                      // PORTD = PULL-UP  
}


void delayLong (unsigned char i)
{
  unsigned char j;
  for (j = 0; j < i; j++)
  {
    _delay_ms(1000);
  }
}
char alleAnsagen(char i)
{
  if (i == 1)
  {
    tasterFrei = 0;
    PORTC |= 0x01;
    _delay_ms(10);
    PORTB = 0x01;
    _delay_ms(100);
    PORTB = 0x00;
    delayLong(10);//dauer ansage nr.1 (in sekunden)
    PORTC &= ~0x01;
    tasterFrei = 1;
  }
else 
  {
    if (i == 2)
    {
      tasterFrei = 0;
      PORTC |= 0x02;
      _delay_ms(10);
      PORTB = 0x02;
      _delay_ms(100);
      PORTB = 0x00;
      delayLong(9);//dauer ansage nr.2
      PORTC &= ~0x02;
      tasterFrei = 1;
    }
  else 
    {
      if (i == 3)
      {
        tasterFrei = 0;
        PORTC |= 0x04;
        _delay_ms(10);
        PORTB = 0x04;
        _delay_ms(100);
        PORTB = 0x00;
        delayLong(18);//dauer ansage nr.3
        PORTC &= ~0x04;
        delayLong(80); //letzte verzoegerung extra lang (in sek)
        tasterFrei = 1;
        i=1;
      }
    else 
      {
        tasterFrei = 1;
        i=1;
      }
    }
    return (i);
  }

}


volatile unsigned char Ticks; //ISR zählt Variable "Ticks" in 1er Schritten hoch

ISR(TIMER0_OVF_vect)
{
Ticks ++;
}

// Haupt

main (void)
{
  initPorts();  
  unsigned char i;
  unsigned char fertig;
  unsigned char tasterFrei;
  
  i=1;
  fertig = 0;
  tasterFrei = 1;
  
  TCCR0 |= (1<<CS02)|(1<<CS00);    // ((3686400/1024)/256)*1000ms=71,11ms für 1 Overflow
  TIMSK |= (1<<TOIE0);
  sei();
  
  while(1)
  {
    
  do 
  {
    if ((!(PIND&0x04)) && (tasterFrei == 1))
    {
      alleAnsagen(i);
      i++;
    }
  
      if(Ticks>500){    //(71,11ms*500)/1000 = 35,55s für Reset durch Timer
      i=1;        //Grundzustand
      Ticks=0;      //Timer reset
      }
  }
  
  while (fertig == 0);
}

Ich bedanke mich schonmal im Voraus für die Antworten, ist wirklich sehr 
hilfreich hier.
Gruß,
Klas

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klas Meyer schrieb:

> Ich bedanke mich schonmal im Voraus für die Antworten,

Jetzt fängst du als allererstes einmal an, eine äussere Form in deinen 
Source Code hineinzubringen.

Nach einer { wird alles darunteretehende um 2 Zeichen eingerückt. Bis 
zur zugehörigen }. Die kommt wieder 2 Leerzeichen nach links und dann 
geht es direkt unter dieser } in derselben Spalte weiter.

Deine Kraut und Rüben Einrückerei durchblickt doch kein Mensch mehr.

Und dann siehst du dir an, ob alle Anweisungen tatsächlich in der 
Einrückstufe sind und du sie auch tatsächlich von den entsprechenden 
Blockanweisungen (if, while, do) abhängen von denen du sie abhängig 
haben willst.

Und nein: Äussere Form wird nicht nachträglich in ein Programm 
eingebaut, sondern schon während man entwickelt. Gerade konsequent 
durchgezogenes Einrückschema macht sehr oft den Unterschied zwischen 
"Ich finde einen Fehler leicht" und "Ich finde den Fehler einfach nicht"

Autor: der mechatroniker (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was mir spontan auffällt: in main() existiert eine lokale Variable 
tasterFrei, die natürlich die gleichnamige globale verdeckt. D.h. in 
main() wird die lokale mal abgefragt und mal auf 1 gesetzt (und nie 
wieder auf 0), in alleAnsagen() dagegen die globale auf 0 gesetzt. Ich 
bin mir zu 99,9% sicher, dass das nicht das ist, was du willst.

Kleiner Schönheitsfehler außer der Formatierung: alleAnsagen() macht 
nicht alle Ansagen, sondern bei jedem Aufruf nur eine abhängig vom 
Übergabeparameter. Das mag gewollt sein, aber dann benenn die doch bitte 
anständig.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
volatile unsigned char Ticks; //ISR zählt Variable "Ticks" in 1er Schritten hoch

...

      if(Ticks>500){    //(71,11ms*500)/1000 = 35,55s für Reset durch Timer

und dann überlegst du dir, wie wohl eine unsigned char Variable mit 8 
Bit jemals größer als 500 werden kann :-)

Autor: Klas Meyer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oh, 500 mit 8 bit ist selbstverständlich Mist :-), die Anmerkung mit der 
Variable tasterFrei ist auch durchaus berechtigt, finde es hingegen 
interessant das es überhaupt so funktioniert wie es momentan ist. 
Vermutlich lässt sich das lokale Setzen der Variable auf den Wert 1 auch 
einfach streichen, da sie ja sowieso in "alleAnsagen" was nun einfach 
"Ansage" heißt gesetzt wird auf den gewollten Wert.

Habe nun auch versucht die Anforderungen an die äußere Form zu erfüllen, 
ich denke es liegt einfach an meiner mangelnden Erfahrung und dem 
ständigen Verändern des Codes dass er immer unübersichtlicher wurde.

Ich vermute den Fehler darin, dass die letzte If-Bedingung mit in der 
while(1) steht, allerdings diese einfach rauszunehmen führt auch zu 
keinem Effekt, bzw. eher zu Fehlern des Compilers.

Aktueller Code:

#define F_CPU 3686400

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

unsigned char tasterFrei;

// Funktionen

void initPorts (void)
{
    DDRB = 0xFF;            // PORTB = Ausgang
    PORTB = 0x00;            // Ausgänge B auf low
    DDRC = 0xFF;            // PORTC = Ausgang
    PORTC = 0x00;            // Ausgänge C auf low
    DDRD = 0x00;            // PORTD = Eingang
    PORTD = 0x04;            // PORTD = PULL-UP  
}

void delayLong (unsigned char i)
{
    unsigned char j;
    for (j = 0; j < i; j++)
    {
        _delay_ms(1000);
    }
}

char Ansage(char i)
{
    if (i == 1)
    {
        tasterFrei = 0;
        PORTC |= 0x01;
        _delay_ms(10);
        PORTB = 0x01;
        _delay_ms(100);
        PORTB = 0x00;
        delayLong(10);      //dauer ansage nr.1 (in sekunden)
        PORTC &= ~0x01;
        tasterFrei = 1;
    }
    else 
    {
        if (i == 2)
        {
            tasterFrei = 0;
            PORTC |= 0x02;
            _delay_ms(10);
            PORTB = 0x02;
            _delay_ms(100);
            PORTB = 0x00;
            delayLong(9);      //dauer ansage nr.2
            PORTC &= ~0x02;
            tasterFrei = 1;
        }
        else 
        {
            if (i == 3)
            {
                tasterFrei = 0;
                PORTC |= 0x04;
                _delay_ms(10);
                PORTB = 0x04;
                _delay_ms(100);
                PORTB = 0x00;
                delayLong(18);      //dauer ansage nr.3
                PORTC &= ~0x04;
                delayLong(80);       //letzte verzoegerung extra lang (in sek)
                tasterFrei = 1;
                i=1;
            }
            else 
            {
                tasterFrei = 1;
                i=1;
            }
        }
          return (i);
    }
}


volatile unsigned char Ticks;       //ISR zählt Variable "Ticks" in 1er Schritten hoch

ISR(TIMER0_OVF_vect)
{
    Ticks ++;
}


main (void)
{
    initPorts();  
    unsigned char i;
    unsigned char fertig;
    unsigned char tasterFrei;
  
    i=1;
    fertig = 0;
    tasterFrei = 1;
  
    TCCR0 |= (1<<CS02)|(1<<CS00);          // ((3686400/1024)/256)*1000ms=71,11ms für 1 Overflow
    TIMSK |= (1<<TOIE0);
    sei();
  
    while(1)
    {    
        do 
        {
            if ((!(PIND&0x04)) && (tasterFrei == 1))
            {
                Ansage(i);
                i++;
                Ticks=0;
            }  
            
            if(Ticks>100)          //(71,11ms*500)/1000 = 35,55s für Reset durch Timer
            {    
                i=1;          //Grundzustand
                Ticks=0;      
            }
        }
    }
while (fertig == 0);
}

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klas Meyer schrieb:

> Habe nun auch versucht die Anforderungen an die äußere Form zu erfüllen,

In deiner main steht (Übrigens: der return type von main ist int. Du 
solltest Compiler Warnings ernster nehmen)

int main()
{
 ....

     while(1)
     {
         do
         {
           ....
         }
     }
  while (fertig == 0);
}


schau doch einfach mal in welcher Einrückstufe das untere while steht. 
und dann gehst du von der } aus solange die Spalte hoch, bis du die 
öffnende { findest. Sieh dir mal an, wie das alles ineinander 
verschachtelt ist. Du trickst dich hier auf Dauer selber aus. Wozu die 
ganzen verschachtelten Schleifen? Du brauchst genau 1 Endlosschleife und 
nicht mehr!
int main()
{
  ....

  while( 1 )
  {
     ...
     Programmlogik
     ...
  }
}



PS:

So etwas

  if( Bedingung1 )
  {
  }
  else
  {
    if( Bedingung2 )
    {
    }
    else
    {
      if( Bedingung3 )
      {
      }
    }
  }

kannst du guten Gewissens so schreiben

  if( Bedingung1 )
  {
  }

  else if( Bedingung2 )
  {
  }

  else if( Bedingung3 )
  {
  }

letzteres ist durch die kleineren Einrückungen übersichtlicher.
Und dann siehst du plötzlich auch, dass in deiner Funktion Ansage der
 return i;
nur dann gemacht wird, wenn die erste if Bedingung zutrifft (auch 
erkennbar daran, dass nach dem return noch eine } kommt, die nicht ganz 
links am linken Rand steht.

PS2: return ist kein Funktionsaufruf! Kein Grund da Klammern rund um den 
Ausdruck zu machen. Du schreibst ja auch nicht
     i = (5);


laut Simulator tickt dein Timer. Die ISR wird auch aufgerufen.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Klas Meyer schrieb:

> zwar ohne Fehler brennen, jedoch scheint er den Timer einfach zu
> ignorieren

woher weißt du das eigentlich?
Mach dir doch mal eine Ausgabe rein, die dir den Fall Timer abgelaufen 
anzeigen kann (LED brennen lassen oder sowas)

Mit 'scheint' kann man nicht arbeiten.
Also musst du dir was einfallen lassen, wie du deinem Programm auf die 
Finger sehen kannst während es arbeitet. Das kann sein 1 LED die an 
bestimmten Code-Stellen eingeschaltet/ausgeschaltet wird oder blinkt. 
Das kann eine Ausgabe auf ein LCD sein. Das könnte bei dir zb das 
Abspielen eines kleinen 'Pieep' Sounds sein, etc.
Denk dir was aus. Hauptsache du hast eine Rückmeldung vom Programm "Bin 
hier angekommen" oder "Variable hat jetzt den und den Wert" oder ....
Was immer du eben an Informationen benötigst um aus dem Zustand "es 
scheint so zu sein, dass ..." rauszukommen und in den Zustand "Ich weiß, 
dass .." überzugehen.

Autor: Andreas Schweigstill (Firma: Schweigstill IT) (schweigstill) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> Und nein: Äussere Form wird nicht nachträglich in ein Programm
> eingebaut, sondern schon während man entwickelt. Gerade konsequent
> durchgezogenes Einrückschema macht sehr oft den Unterschied zwischen
> "Ich finde einen Fehler leicht" und "Ich finde den Fehler einfach nicht"

Genau DAS ist ja gerade die Stärke von Python. Dort werden die 
Block-/Schachtelungsebenen ausschließlich durch Einrückungen 
dargestellt.

In der meisten anderen Sprachen, die spezielle Blockbegrenzungen 
besitzen, überliest man diese nämlich auch sehr schnell, wenn der 
Quelltext ansonsten ordentlich formatiert ist. Eine schöne äußere Form 
verleitet beim Lesen und Fehlersuchen zu ziemlicher Nachlässigkeit.

Gerade in C passiert ja auch leicht der folgende Fehler:
if (a > 100)
  a = 100;

wird "einmal schnell" ergänzt um eine Debug-Ausgabe:
if (a > 100)
  printf("Bereichsbegrenzung fuer a!\n");
  a = 100;

Oh, b muss ja auch noch angepasst werden:
if (a > 100)
  printf("Bereichsbegrenzung fuer a!\n");
  a = 100;
  b = 0;

Und plötzlich stellt man fest, dass a und b immer verändert werden, 
wohingegen die Debug-Ausgabe nur zu den richtigen Zeitpunkten erscheint.

Autor: Klas Meyer (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
vielen Dank für die Ratschläge und kaum zu glauben, es scheint 
tatsächlich nun zu funktionieren :-).

Jetzt nur noch die Zeiten anpassen und dann kann es in einen ersten 
Praxistest gehen, bin gespannt.

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.