www.mikrocontroller.net

Forum: Compiler & IDEs Selbstgemachte delay-Routine geht nicht


Autor: Spurius (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Spiritus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Rufus Τ. Firefly (rufus) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

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

Autor: Spurius (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Christian Zietz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In der Zeile

  TCNT2 = (256-6);

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

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Spurius (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Spurius (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Würdest du mir dann vielleicht verraten wo mein Fehler ist?

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Spurius (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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)
  }
}

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Spurius (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Jörg Wunsch (dl8dtl) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

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.