Forum: Compiler & IDEs Selbstgemachte delay-Routine geht nicht


von Spurius (Gast)


Lesenswert?

Hallo,
ich habe mit folgender selbstgemachter delay-Routine das Problem, dass
sie nicht in gewünschter Weise verzögert:

void waitms(int16_t time)
{
 int16_t takte, zaehler1, zaehler2;
 for(zaehler2=0;zaehler2==time;zaehler2++)
 {
  for(zaehler1=0;zaehler1==16000;zaehler1++)
   {
    ++takte;
   }
 }
}

Die Überlegung dahinter ist folgende:

1 Takt = 1/16000000 = 0,000000062s.

16000 = 0,001s / Dauer eines Takts.

Und dann wird die innere Schleife solange wiederholt, wie Millisekunden
verzögert werden soll.

Wisst ihr, warum es nicht funktioniert?

ATMega8 @ 16MHz, WinAVR

Gruß
Martin

von Spiritus (Gast)


Lesenswert?

Dann schau dir mal das erzeugte Assembler-Listing an. Dir wird schnell
klar, warum die Schleife länger dauert als 1ms. 1ms wären es, wenn du
z. B. genau 16000 NOPs hintereinander ausführst. Aber so hast du ja in
der Schleife einen Integer hängen ('takte', 2 Bytes), dessen
Inkrementierung ja schon mehr als ein Takt braucht. Benutze besser
einen Timer.

von Rolf Magnus (Gast)


Lesenswert?

> Hallo,
> ich habe mit folgender selbstgemachter delay-Routine das Problem,
> dass sie nicht in gewünschter Weise verzögert:

Was macht sie denn stattdessen? Ich würde vom Compiler erwarten, daß er
den gesamten Funktionsinhalt wegoptimiert. Falls das der Fall sein
sollte: takte volatile machen.

Abgesehen davon gilt natürlich das, was Spiritus gesagt hat. Deine
Schleife braucht nicht genau einen Takt pro Durchlauf, sondern
wesentlich mehr. Außer dem Inkrementieren (welches schon länger
braucht), muß bei jedem Durchlauf noch der Schleifenzähler ebenfalls
inkrementiert und sein Wert überprüft werden, dann kommt noch der
bedingte Sprung.

Deine Abbruchbedingungen sind auch falsch. Die äußere Schleife läuft so
lange, wie zaehler2 den gleichen Wert wie time hat. Außer bei time==0
hat er das am Anfang nie, also wird deine Schleife nie ausgeführt.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

.

   for (zaehler2 = 0; zaehler2 == time; zaehler2++)


Diese Zeile (und die andere mit dem gleichen Fehler) solltest Du Dir
nochmal genauer ansehen. Der zweite Ausdruck der for-Anweisung ist hier
das Problem.
Die Schleife wird solange durchgeführt, wie der zweite Ausdruck in der
for-Anweisung zutrifft.

Da steht "zaehler2 == time".
Der Ausdruck trifft also höchstens einmal zu, nämlich, wenn die
Funktion mit time = 0 aufgerufen wird. Ist das nicht der Fall, wird die
Schleife kein einziges Mal durchlaufen.

Abhilfe:

== durch < ersetzen.


Literaturhinweis:
Kernighan & Ritchie, Programmieren in C, Zweite Auflage, Hanser Verlag

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Last not least: don't roll your own.  Nimm die Routinen aus
<avr/delay.h>, die hat dir jemand passend zurechtgeschneidert.

von Spurius (Gast)


Lesenswert?

Hallo,
aber selbergemacht muss das ja auch gehen! Ich habs jetzt mit dem
Timer2 versucht, allerdings scheint er nichtmal die Interrupt-routine
zu erreichen :(

volatile int16_t waitms_;

void waitms(int16_t time)
{
 waitms_ = 0;
 while(waitms_ <= time)
  {
  TCCR0 = (0<<WGM21) | (0<<WGM20) |(0<<COM21) | (0<<COM20) | (1<<CS22)
| (0<<CS21) | (0<<CS20);
  TIMSK = (1<<TOIE2) | (0<<OCIE2);
  TCNT2 = (256-6);
  }
}

SIGNAL (SIG_OVERFLOW2)
{
 waitms_ ++;
}

Gruß
Martin

von Christian Zietz (Gast)


Lesenswert?

In der Zeile

  TCNT2 = (256-6);

setzt Du den Wert des Timercounters ja auch andauernd neu, so wird der
nie weit zählen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> aber selbergemacht muss das ja auch gehen!

Klar, aber dafür sollte man seinen Compiler schon recht gut kennen.
Nicht umsonst nutzen die vorgefertigten Routinen lieber inline asm,
damit ihre Zyklenzahl vorherbestimmt bleibt.

von Spurius (Gast)


Lesenswert?

Also dss ich TCNT2 jedesmal neusetze, sit Absicht, da ein
Schleifendurchlauf 1ms dauern soll, und dies soll erreicht werden,
indem der Timer 250-mal erhöht wird.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Das funktioniert so trotzdem nicht, wenn du dir das mal auf der Zunge
zergehen lässt.  Der Timer wird bei deinem Hase- und Igel-Spiel
einfach niemals sein Ziel erreichen und waitms_ jemals erhöhen können.

von Spurius (Gast)


Lesenswert?

Würdest du mir dann vielleicht verraten wo mein Fehler ist?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Du setzt doch den Timerwert laufen auf 254 zurück, in einer
knallharten CPU-Schleife, die praktisch immer abgearbeitet wird.
Damit kann der arme Timer gegen dich ankämpfen wie er will, das wird
in jedem Falle so ausgehen wie der Kampf des Ritters Don Quixotte
(sp?) gegen die Windmühlenflügel.  Kaum hat der Timer um eins erhöht,
boing, knallt ihm deine Schleife wieder eine 254 drauf.  Der kommt nie
dazu, jemals von 255 nach 0 überzulaufen (erst da triggert ja der
Interrupt).

Ich würde den Zähler in der ISR einen Wert herunterzählen lassen und
dann beim Erreichen von 0 ein globales Flag setzen.  Die Funktion
waitms() wartet dann nur noch auf das Erreichen dieses Flags (das
natürlich als volatile deklariert sein muss).

von Spurius (Gast)


Lesenswert?

Hallo,
ich hoffe ich bringe dich nicht zur Verzwiflung, aber es klappt immer
noch nicht :/

volatile int16_t waitms_, time;

void waitms(int16_t time_waitms)
{
 time = time_waitms;     time wird das routinen-argument übergeben
 waitms_ = 0;
 TCCR0 = (0<<WGM21) | (0<<WGM20) |(0<<COM21) | (0<<COM20) | (1<<CS22) |
(0<<CS21) | (0<<CS20);
 TIMSK = (1<<TOIE2) | (0<<OCIE2);
 TCNT2 = (255-250);   der timer wird einmalig vorgeladen, um in die ISR
zu gelangen
 while (waitms_ <  time)  endlosschleife solange das argument nicht
erfüllt ist
  {
  }
}

SIGNAL (SIG_OVERFLOW2)
{
 waitms_ ++;               variable wird inkrementiert
 if(waitms_ < time)
  {
   TCNT2 = (255-250);      timer wird vorgeladen
   char *test;
   test = "hallo!!!";
   send_string(test);      (überprüfung)
  }
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Uah, nee, was soll denn ein send_string() in einer timer-ISR?
Das bringt dir doch das Timing völlig aus dem Ruder.

Ansonsten, falls du damit ausdrücken willst (,,geht immer noch
nicht'' -- was passiert denn genau?), dass die ISR nie aufgerufen
wird, hast du denn global die Interrupts freigegeben?  Und hier noch:

TIMSK = (1<<TOIE2) | (0<<OCIE2);

Du gibst den output compare Interrupt frei, hast du denn auch einen
Handler dafür?  Andernfalls generiert dir der erste output compare
(auf irgendeinem Wert wird OCR2 ja wohl stehen, also irgendwann
schiesst der) einen Software-Reset.

von Spurius (Gast)


Lesenswert?

Hallo,
damit TIMSK = (1<<TOIE2) | (0<<OCIE2); enable ich doch den Overflow
Interrupt und nicht den output compare. Und mit "sei" sind Interrupts
global freigegeben.
Und ja, ich habe das Gefühl dass die ISR nicht aufgerufen wird.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

> damit TIMSK = (1<<TOIE2) | (0<<OCIE2); enable ich doch den Overflow
> Interrupt und nicht den output compare.

Ach, sorry, den etwas aufwändigen NOP mit (0<<OCIE2) habe ich
verkannt.

> Und mit "sei" sind Interrupts global freigegeben.

OK, das war im Zitat nirgends zu sehen.

Ich wollte dir gerade schreiben, dass ich den Fehler jetzt auch nicht
sehe, aber umm, es fällt mir wie Schuppen aus den Haaren:

TCCR0 = (0<<WGM21) | (0<<WGM20) |(0<<COM21) | (0<<COM20) | (1<<CS22) |
(0<<CS21) | (0<<CS20);

Du startest den Timer 0, willst aber einen Interrupt für Timer 2
haben?!

Übrigens, ich persönlich hielte:

TCCR2 = (1<<CS22);

ggf. noch von einem Kommentar begleitet (damit man nicht erst ins
Datenblatt gucken muss) für deutlich besser lesbar.

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.