Forum: Mikrocontroller und Digitale Elektronik Schleife mit ATmega16 in C


von Gast (Gast)


Lesenswert?

Hallo,

versuche mich gerade auf einem kleinen Testboard, worauf ein ATmega16
steckt.

Habe am PortB 4LEDs (PB0 bis PB3) und 4 Taster (PB4 bis PB7).

Funktioniert soweit auch, dass ich mit den Tastern etwas an den LEDs
herumspielen kann.

Nun wollte ich eine kleine Schleife bauen, wo mir LED1 aufleuchtet, und
dann die logische 1 rüber zur LED4 wandert (Lauflicht). Dies
funktionierte ebenfalls noch.

Diese Funktion wurde dann erweitert, dass ich die Anzahl der
Wiederholungen einstellen kann. Und hier beginnt das Problem:
Er macht zwar den Durchlauf so oft wie ich ihm sage, aber beim letzten
Durchlauf leuchtet LED1, dann 2, dann 3, und die 4. kommt nicht.

Kann mir einer helfen?

Hier der Programmauszug:

unsigned wert = 1;                   // Startwert -> LED1!
unsigned schleife = 0;               // Startwert für Schleife (f.
Wiederholungen benötigt)
unsigned wiederholungen = 5;         // Anzahl der Wiederholungen
int zaehl()
{
  _delay_ms(20);               // Zeitverzögerung
  PORTB = wert;         // Aktuellen Wert an Port B ausgeben
  wert = wert << 1;            // "wert" um 1 verschieben
  if bit_is_set (PORTB,LED4)   // Ende für Zähler ist LED4
  {                 // Wenn Ende erreicht:
    wert = 1;       // Zähler zurücksetzen
    schleife++;       // Schleife um 1 erhöhen
    if (schleife < wiederholungen)  // Schleife mit Anzahl d.
Wiederholungen vergleichen
    zaehl();      // Schleife kleiner als Wied. -> gehe zu zahel
    else        // Ansonsten:
    {
      schleife = 0;    // Schleife zurücksetzen
      main();      // Fertig: Zurück zum Hauptprogramm
    }
  }
  else                 // Wenn Ende noch nicht erreicht:
  zaehl();         // Beginne von vorne
}

von Karl H. (kbuchegg)


Lesenswert?

Du hast da einige rekursive Aufrufe in Deiner
Funktion drinnen. Rekursionen sind zwar nicht unbedingt
falsch, ich bin mir aber sicher, dass du damit noch
nicht zurecht kommst.

(Rekursion: Wenn eine Funktion sich selbhst entweder
direkt oder indirekt aufruft. In Deinem Fall ist dies
die Funktion 'Zaehl' die selbst wieder die Funktion
'Zaehl' aufruft, welche ihrerseits die Funktion 'Zaehl'
aufruft, usw.)

Warum verwendest Du nicht ganz einfach Zaehl-Schleifen:

   int i;

   for( i = 0; i < Anzahl; i++ ) {
     /* mach irgendwas */
   }

Das ist eine Schleife, die genau 'Anzahl'-mal ausgefuehrt
wird.
Mit dem was du da geschrieben hast (ich habs zwar nicht
analysiert, bin mir aber ziemlich sicher), hast du dich
selbst ins Bein geschossen. Viel zu kompliziert.

von Stefan K. (_sk_)


Lesenswert?

Du rufst main() aus Deiner Funktion auf?
Das ist kein "zurück zum Hauptprogramm, sondern main() wird aus Deiner
Funktion heraus aufgerufen! Das ist ein rekursiver Aufruf von main().

Genauso mit zaehl():
Weisst Du, was Du mit rekursiven Aufrufen machst? Jeder Aufruf von
zaehl() oder main() braucht Platz auf dem Stack - irgendwann läuft der
über und Dein Programm hängt.

Eine Funktion sollte sich NIEMALS selber aufrufen - solange Du nicht
genau weisst, was Du damit machst.


int zaehl(uint8_t wiederholungen)
{
  uint8_t schleife = 0;
  uint8_t wert = 1;

  while (schleife < wiederholungen){
    PORTB = wert;
    wert = wert << 1;
    if bit_is_set (PORTB,LED4){
      wert = 1;
      schleife++;
    }
    _delay_ms(20);
  }
  PORTB = 0;  // ganz am Schluss: alle LEDs aus
}


void main(void){
  zaehl(5);
  while(1);
}


Gruß, Stefan

von Karl H. (kbuchegg)


Lesenswert?

Ich hab noch mal etwas genauer durch den Code geschaut.
Man, du hast dich da aber ordentlich verfranst.
Ein Buch ueber Grundlagen der C-Programmierung waere
da mal angebracht.

Grundsaetzlich:
Teile dir die Arbeit auf.
Das was du vorhast, ist zunaechst mal viel zu schwierig.
Also muss man sich mal was einfacheres suchen. Eine einzelne
Lauflichtkette, zb.

void EinDurchlauf()
{
  int i;
  int Ausgabe = 1;

  for( i = 0; i < 4; ++i ) {
    PORTB = Ausgabe;
    Ausgabe = Ausgabe << 1;
    _delay_ms(20);
  }
}

Diese Funktion sollte eigentlich selbsterklaerend sein.
Eine 1 wird an Port B an 4 Pins durchgeschoben. Klar?

Wenn ja. Dann machen wir daraus Deine 'kompliziertere'
Aufgabenstellung: Eine Funktion, die unter Zuhilfenahme
obiger Funktion, das ganze Geschiebe eine bestimmte Anzahl
mal macht:

void Wiederhole( int WieOft )
{
  int i;

  for( i = 0; i < WieOft; i++ )
    EinDurchlauf();
}

und aufgerufen wird das ganze dann:

int main()
{
  int Anzahl;

  /* hier kommt der Teil, der feststellt wieoft
     das Lauflicht laufen soll. Dieser Teil soll
     eine Zahl in Anzahl hinterlassen */

  ....

  /* Da wir jetzt wissen, wieviele Durchlauefe notwendig
     sind, rufen wir ganz einfach Wiederhole mit dieser
     Anzahl auf */

  Wiederhole( Anzahl );

  /* und den Mikro Schlafen schicken (Eine Endlosschleife
     in der er nichts tut
  */
  while( 1 ) {
  }
}

Alternativ koennte man die Programmstruktur auch umstricken, so
dass der µC immer wieder (Hinweis: das wird eine Schleife)
feststellt ob eine Taste gedrückt wurde und wenn ja diese
Taste in eine Zahl verwandelt und Wiederhole mit dieser
Zahl aufruft:

int main()
{
  int Anzahl;

  while( 1 ) {             /* mache immer wieder */

    /* Tasten abfragen: Wenn keine Taste gedrueckt ist,
       soll Anzahl den Wert 0, haben, ansonsten steht die
       gewuenschte Anzahl an Wiederholgungen in Anzahl
    */

    ....

    /* Die Aktion jetzt ausfuehren */
    if( Anzahl > 0 )
      Wiederhole( Anzahl );
  }
}

In Deinem Code 'springst' Du zuviel herum. Zumindest denkst
du das, in Wirklichkeit passiert da ganz was anderes.
Wie gesagt: Grundlagenliteratur ueber Programmieren in C
kaufen und lesen.

von Stefan K. (_sk_)


Lesenswert?

oh Mann ... da haben wir ja wieder klassisch doppelt gemoppelt ...

Gruß Stefan

von Gast (Gast)


Lesenswert?

Vielen Dank mal für die Antworten.

Fange leider neu mit C an und daher noch diese Fehler. Aber nun weis
ich wenigstens worauf ich achten sollte, bzw. was nicht gemacht werden
darf.

Die Lösung mit der for-Schleife ist mir irgendwie gar nicht in den Sinn
gekommen, obwohl ich diese auch schon ab und zu verwendet hab. Mit
dieser klappt es jetzt aber problemlos.

Code sieht so aus:

int wert;     // Variable für Ausgabe
int schleife;    // Variable für Schleife
unsigned wiederholungen = 5;    // Anzahl der Wiederholungen

int zaehl()
{
  for (schleife = 0; schleife < wiederholungen; schleife++)
  {
    for ( wert = 1; wert < 16; wert = wert << 1)
    {
      PORTB = wert;  // Aktuellen Wert ausgeben
      _delay_ms(20);  // Zeitverzögerung
    }
  }
}

Nur dieses "wert < 16" stört mich hier noch... Wie kann ich da die
Variable LED4 mit herein bringen? Es ist die letzte LED die noch
leuchten soll, LED4 ist an PB3, PB4 ist bereits ein Taster.
Habe schon etwas herumprobiert, aber da ging dann überhaupt nichts
mehr, nur wenn ich wert kleiner 16 reinschreibe.

Werde jetzt noch Stefans Variante mal ausprobieren, da das mit der
Übergabe einer Zahl für die Anzahl der Wiederholungen ja auch nicht
schlecht währe...

Vielen Dank schonmal :-)
Schön das man hier Hilfe bekommt!

von Stefan K. (_sk_)


Lesenswert?

> Schön das man hier Hilfe bekommt!

Schön, dass es hier noch Leute gibt, die selber lernen wollen und nicht
nur cut & paste machen!

Zeig mal, wie LED4 definiert ist.

> Habe schon etwas herumprobiert, aber da ging dann überhaupt nichts
> mehr, nur wenn ich wert kleiner 16 reinschreibe.

Was hast Du da genau gemacht? Sinnvoll sind ja nur die Werte 2, 4, 8,
16, 32, 64 128. Aber Zwischenwerte sollten vom Prinzip auch
funktionieren.

Schreib die Variablen-Deklarationen besser in die Funktion mit rein,
das hat den Vorteil, dass Speicherplatz gespart wird (gut, bei Deinem
Programm wohl noch nicht das Hauptproblem).

Gruß, Stefan

von Gast (Gast)


Lesenswert?

Okay.

Hier der Auszug mit den Defines:

// ### Die Defines ###

#define TST1 PB4
#define TST2 PB5
#define TST3 PB6
#define TST4 PB7

#define LED1 PB0
#define LED2 PB1
#define LED3 PB2
#define LED4 PB3


>> Habe schon etwas herumprobiert, aber da ging dann überhaupt nichts
>> mehr, nur wenn ich wert kleiner 16 reinschreibe.

>Was hast Du da genau gemacht? Sinnvoll sind ja nur die Werte 2, 4, 8,
>16, 32, 64 128. Aber Zwischenwerte sollten vom Prinzip auch
>funktionieren.

Naja, anstatt eines Wertes habe ich die Variable LED4 benutzt. Habs
versucht mit LED4 == 1, wo ja überprüft wird ob LED4 (PB3) auf logisch
1 ist.


>Schreib die Variablen-Deklarationen besser in die Funktion mit rein,
>das hat den Vorteil, dass Speicherplatz gespart wird (gut, bei Deinem
>Programm wohl noch nicht das Hauptproblem).

Werde ich machen, danke für den Hinweis :-)

von Stefan K. (_sk_)


Lesenswert?

Du benutzt gcc, ja?

LED4 ist keine Variable. Die Zeile
#define LED4 PB3
macht nur einen Textersatz. LED4 wird also (aus Sicht des Compilers) in
Deinem Quelltext durch PB3 ersetzt. PB3 findet sich in den
I/O-Definitionen Deines ATmega:

/* PORTB */
#define PB7     7
#define PB6     6
#define PB5     5
#define PB4     4
#define PB3     3
#define PB2     2
#define PB1     1
#define PB0     0

Das heisst: wieder wird der Text PB3 ersetzt - und zwar durch 3.

Statt if(LED4 == 1)
sieht der Compiler also
if(3 == 1)
und das wird natürlich nie richtig/WAHR/ausgeführt oder was auch
immer.

Gruß, Stefan

von Gast (Gast)


Lesenswert?

Achja, natürlich.

Danke für die Erklärung, echt super :-)

Ja, benutze GCC (WinAVR).

Mein Problem währe dann wohl gelöst.

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.