Forum: Compiler & IDEs genaues Delay in gcc


von lögel (Gast)


Lesenswert?

Hallo,
ich suche nach einer Möglichkeit, ein Ausgangspin möglichst genau im 
Bereich von 1ms bis 5s möglichst genau zu schalten.

Mit dem Oszilloskop habe ich schon herausgefunden, dass ein _delay_ms(1) 
in einer for-Schleife (250 mal) zu einem genaueren Ergebnis führt als 
z.B. _delay_ms(250)

Aber ganz genau ist es auch nocht nicht. Gibt es auch noch andere 
Varianten? Wenn es möglich ist, sollte man den Ausgang auch vorzeitig 
abschalten können. Der µC sollte also nicht vollständig ruhen.

: Verschoben durch Admin
von P. S. (Gast)


Lesenswert?

Schoen waere, wenn Mikrocontroller Timer haetten.

von Oliver (Gast)


Lesenswert?

>Mit dem Oszilloskop habe ich schon herausgefunden, dass ein _delay_ms(1)
>in einer for-Schleife (250 mal) zu einem genaueren Ergebnis führt als
>z.B. _delay_ms(250)

Läuft der Prozessor mit dem internem Oszillaotor, oder an einem Quarz?

Oliver

von lögel (Gast)


Lesenswert?

Ich benutze den AT90CAN128 und er läuft mit einem 16 MHZ-Quarz. Ich habe 
schon im Datenblatt nachgeschaut, ob man die Frequenz erhöhen kann, aber 
bei 16 MHz ist leider Schluss....

von Albrecht H. (alieninside)


Lesenswert?

lögel schrieb:
> ...
> Aber ganz genau ist es auch nocht nicht. Gibt es auch noch andere
> Varianten? Wenn es möglich ist, sollte man den Ausgang auch vorzeitig
> abschalten können. Der µC sollte also nicht vollständig ruhen.

Jo, Timer halt, (ich find inzwischen die 16Bit-PWM ziemlich universell 
für alles mögliche), oder eben Taktzyklen zählen Peter Fleury hat in 
seiner bekannten LCD-Routine dazu ein Beispiel mit Inlineassembler.

von Michael U. (amiga)


Lesenswert?

Hallo,

lögel schrieb:
> Ich benutze den AT90CAN128 und er läuft mit einem 16 MHZ-Quarz. Ich habe
> schon im Datenblatt nachgeschaut, ob man die Frequenz erhöhen kann, aber
> bei 16 MHz ist leider Schluss....

Und warum nimmst Du keinen Timer? Selbst bei 1ms sind es 16000 Takte, 
die er irgendwas sinnvolles machen kann, bis der nächste IRQ zuschlägt.

1ms Timer initalisieren, gewünschte Zeit in passend große Variable 
schreiben,
in der IRQ Variable um 1 runterzählen, wenn 0 Timer stoppen und Flag für 
main setzen.
Vorzeitig stoppen: Timer anhalten.

Gruß aus Berlin
Michael

von lögel (Gast)


Lesenswert?

@Michael U.: hört sich schon ganz gut an! habe aber noch nie mit timern 
programmiert. hast du auch ein stückchen code-beispiel?

von Falk B. (falk)


Lesenswert?

Siehe Timer und Interrupt.

MFG
Falk

von lögel (Gast)


Lesenswert?

Ich habe jetzt in der main() folgendes:
1
TCCR0A = (1<<CS01);
2
TIMSK0 |= (1<<TOIE0);
3
sei();

Leider tut das Proigramm dann nichts mehr. Auch nicht das, was vor dem 
Codestücken in main() steht. Wenn ich die zweite Zeile auskommentiere 
geht wieder alles... aber nicht der timer.

von Klaus W. (mfgkw)


Lesenswert?

Dann muß man natürlich auch die Timerfunktion hinschreiben, also
was beim Eintreffen getan werden soll.
Sonst macht er dann gleich einen Reset.
Siehe Tutorial?

von lögel (Gast)


Lesenswert?

ich habe natürlich auch
1
ISR (TIMER0_OVF_vect)
2
{
3
  ....
4
}

als timerfunktion....

von Karl H. (kbuchegg)


Lesenswert?

Dann würde ich vorschlagen du zeigst alles auf einmal und nicht nur in 
homöpathischen Dosen

von Oliver (Gast)


Lesenswert?

Und warum sollte ein Timer genauer sein als _delay_ms(), so lange 
lezteres nicht durch Interrupts unterbrochen wird? Ob der Prozessor 
jetzt die Taktzyklen durch einen Hardwarezähler oder durch Abarbeiten 
genau abgezählter Befehle abzählt, die Genauigkeit sollte die selbe 
bleiben.

Oder addieren sich bei _delay_ms() Rundunsgfehler auf, so daß das der 
Fehler am Ende größer als +/- eine Auflösungseinheit wird (wie groß auch 
immer diese ist)?

Oliver

von Falk B. (falk)


Lesenswert?

@  Oliver (Gast)

>Und warum sollte ein Timer genauer sein als _delay_ms(), so lange
>lezteres nicht durch Interrupts unterbrochen wird? Ob der Prozessor
>jetzt die Taktzyklen durch einen Hardwarezähler oder durch Abarbeiten
>genau abgezählter Befehle abzählt, die Genauigkeit sollte die selbe
>bleiben.

Nöö, der Timer läuft parallel zur CPU. Damit kann dies alles mögliche 
veranstalten, die Zeitbasis bleibt genau. Wenn man alles mit der CPU und 
_delay_ms() machen würde, müsste man für jede Aktion deren 
Ausführungszeit mit reinrechnen. Das heisst dann Takte im ASM Code 
zählen. Nicht sehr praktikabel. Vor allem wenn dann mal ne Änderung 
kommt.

>Oder addieren sich bei _delay_ms() Rundunsgfehler auf,

Das auch.

MFG
Falk

von Oliver (Gast)


Lesenswert?

>Wenn man alles mit der CPU und
>_delay_ms() machen würde, müsste man für jede Aktion deren
>Ausführungszeit mit reinrechnen. Das heisst dann Takte im ASM Code
>zählen.

Ähem, genau das erwarte ich von den _delay-Funktionen. Die sollten ja 
nun so gebaut sein, daß da eine der gewünschten Delayzeit entsprechende 
Anzahl Taktzyklen verbraten wird. Nicht mehr und nicht weniger. Und ich 
hoffe doch, daß dafür der Jörg Wunsch (oder wer auch immer die 
geschrieben hat) ganz genau Takte im ASM-Code gezählt hat.

Olver

von Falk B. (falk)


Lesenswert?

@  Oliver (Gast)

>>_delay_ms() machen würde, müsste man für jede Aktion deren
>>Ausführungszeit mit reinrechnen. Das heisst dann Takte im ASM Code
>>zählen.

>Ähem, genau das erwarte ich von den _delay-Funktionen. Die sollten ja
>nun so gebaut sein, daß da eine der gewünschten Delayzeit entsprechende
>Anzahl Taktzyklen verbraten wird. Nicht mehr und nicht weniger.

Das tut sie auch. Aber was machst du, wenn du 100ms warten willst, dann 
irgendwelche Aufgeban bearbeiten willst, und dann wieder EXAKT 100ms 
später das gleiche machen willst?

> Und ich
>hoffe doch, daß dafür der Jörg Wunsch (oder wer auch immer die
>geschrieben hat) ganz genau Takte im ASM-Code gezählt hat.

Das ist nicht das Problem, das Problem ist die undefineirte Zeit aus 
Warteschleife + Aktion danach.

MFG
Falk

von Karl H. (kbuchegg)


Lesenswert?

Das ist die eine Sache.
Die andere Sache ist, dass du deine Anwendung 'reagierend halten' 
willst. Wenn der µC in er delay Schleife steckt, steht (ausser ISR) 
alles andere. Der Schlüssel zu einer Anwendung, die scheinbar mehrere 
Dinge gleichzeitig abarbeitet liegt praktisch immer in der 
Nichtverwendung von _delay_xx und des Ersetzens durch einen Timer.
Von daher ist das Erlernen dieser Technik einer der wichtigen 
Grundpfeiler in der µC Programmierung.

von Oliver (Gast)


Lesenswert?

Jungs, ihr habt alle Recht, aber das war doch alles gar nicht die Frage.

Das Ausgangsproblem lautet: Ein (völlig ungestörtes) _delay_ms() ist zu 
ungenau, wie bekomme ich genauere Zeiten erzeugt?

Und da bringt ein Timer überhaupt nichts, da der genauso wie _delay_ms() 
die verstrichene Zeit aus der Anzahl der Prozessortakte bestimmt.

Oliver

von Falk B. (falk)


Lesenswert?

@Oliver (Gast)

>Jungs, ihr habt alle Recht, aber das war doch alles gar nicht die Frage.

Und ob.

>Das Ausgangsproblem lautet: Ein (völlig ungestörtes) _delay_ms() ist zu
>ungenau,

Ohne das Makro im Detail zu kennen wage ich zu behaupten, dass es auf 
+/-3 Takte genau ist.

> wie bekomme ich genauere Zeiten erzeugt?

Mit einem Timer, wurde nun bis zum Erbrechen diskutiert.

>Und da bringt ein Timer überhaupt nichts, da der genauso wie _delay_ms()
>die verstrichene Zeit aus der Anzahl der Prozessortakte bestimmt.

Du hast es nicht verstanden. Lies den Thread noch einmal. In Ruhe. Und 
nimm dann einen Timer. Denn dort ist die Abweichung dann nur noch durch 
den Quarz bestimmt, und der Fehler liegt irgendwo bei +/-100ppm und 
weniger. Oder hast du den internen RC-Oszillator verwendet (siehe [[AVR 
Fuses]]). Dann ist klar, warum deine Zeiten ungenau sind.

MFG
Falk

von Randy N. (huskynet)


Lesenswert?

> Mit dem Oszilloskop habe ich schon herausgefunden, dass ein _delay_ms(1)
> in einer for-Schleife (250 mal) zu einem genaueren Ergebnis führt als
> z.B. _delay_ms(250)

Ja, steht auch so ähnlich in dem Kommentar über der Funktion:
1
   The maximal possible delay is 262.14 ms / F_CPU in MHz.
2
3
   When the user request delay which exceed the maximum possible one,
4
   _delay_ms() provides a decreased resolution functionality. In this
5
   mode _delay_ms() will work with a resolution of 1/10 ms, providing
6
   delays up to 6.5535 seconds (independent from CPU frequency).  The
7
   user will not be informed about decreased resolution.
8
 */
9
void
10
_delay_ms(double __ms)
11
{

> Aber ganz genau ist es auch nocht nicht. Gibt es auch noch andere
> Varianten?

Naja, wenn es wirklich 100% genau sein muss gibt es eigentlich nur die 
Möglichkeit, die ASM-Befehle selbst zu schreiben und die Ausführungszeit 
zusammenzurechnen. Da lässt sich dann auch eine Prüfung einbauen, die 
bei bestimmten Bedingungen das ganze abbricht. Der IAR-Compiler hat 
dafür sogar eine Intrinsic-Funktion __delay_cycles(...), die exakt die 
angegebene Anzahl an Takten wartet - der Compiler erstellt dann ein 
Stückchen ASM-Code, das eben genau so viele Takte benötigt, aber so kurz 
wie möglich implementiert ist. Von dem Compiler gibts glaub ich ne 
kostenlose Version, die nur 4KB Code erzeugt, aber dir so ein 
ASM-Codestück compilieren kann. Eine Möglichkeit zum Abbrechen ist darin 
aber nicht vorgesehen.

Die Alternative wären dann halt die schon genannten Timer. Entweder 
prüfst du in einer Schleife, ob der Timer abgelaufen ist, oder lässt 
einen Interrupt durchführen. Das ist denke ich auch hinreichend genau 
(aber ein paar Takte Abweichung gibt es trotzdem, da z.B. die 
Interruptfunktion ja erst aufgerufen werden muss), aber "sauberer" als 
die Takte-Warten-Methode.

Mehr fällt mir nicht ein. Ich hoffe ich konnte helfen.

Viele Grüße
Randy

von Oliver (Gast)


Lesenswert?

>Und nimm dann einen Timer. Denn dort ist die Abweichung dann nur noch durch
>den Quarz bestimmt, und der Fehler liegt irgendwo bei +/-100ppm und
>weniger.

Und wieso wird bei der Abarbeitung einer der delay-Zeit entsprechenden 
Anzahl von Assemblerbefehlen der Fehler größer? Ob ich jetzt einen Timer 
oder ein Programm die selbe Anzahl Zyklen abzählen lasse, ändert an der 
dafür benötigten Zeit überhaupt nichts.

Und ja, der Fehler bei Benutzung eines externen Quarzes sollte irgendwo 
bei +/-100ppm oder weniger liegen. UNd zwar sowohl bei Nutzeng eines 
Timers, als auch bei _delay_ms(), solange das nicht durch Interrupts 
unterbrochen wird.

Da der TO aber eine Abweichung bei 250ms delay-Zeit mit einen Oszi 
messen kann, liegt diese wohl eher im Bereich einiger %. Also ist da was 
ganz anderes faul.

Oliver

von (prx) A. K. (prx)


Lesenswert?

Oliver schrieb:

> Und wieso wird bei der Abarbeitung einer der delay-Zeit entsprechenden
> Anzahl von Assemblerbefehlen der Fehler größer? Ob ich jetzt einen Timer
> oder ein Programm die selbe Anzahl Zyklen abzählen lasse, ändert an der
> dafür benötigten Zeit überhaupt nichts.

Wenn du Interrupts laufen hast, dann schon. Und wenn du die Optimierung 
durch den Compiler vergisst, dann erst recht.

> Da der TO aber eine Abweichung bei 250ms delay-Zeit mit einen Oszi
> messen kann, liegt diese wohl eher im Bereich einiger %. Also ist da was
> ganz anderes faul.

Ich hatte schon einmal mit einem Elektronik-Profi zu tun, der sich bei 
meinem selbstgebauten und ziemlich schräg aussehenden Netzteil über eine 
Netzfrequenz von 70Hz wunderte. Und stinksauer auf mich war, als ich 
(Schüler ~18) daraufhin auf seinem Oszi den Steller für die 
Zeitablenkung auf "kalibriert" drehte ;-).

von Rolf M. (rmagnus)


Lesenswert?

Wie wäre es, wenn man den Timer einfach per output-compare den Pin 
hardwaremäßig toggeln läßt? Damit bekommt man es taktzyklengenau hin. 
Für die langen Delays, wo der Timer zwischendrin mehrfach überläuft, muß 
man eben etwas mehr nachenken, aber da geht es zur Not taktzyklengenau, 
ohne daß das den Prozessor merklich auslasten würde.

von timer (Gast)


Lesenswert?

dann lasst den TE  seine for() mit delay nutzen ..

spätestens wenn der CAN128 mal eine tätigkeit oder eine echte funktion 
bekommt wird er darauf kommen das ein timer doch verdammt sinnvoll ist .
denn einen pin toggeln is nunmal keine große aufgabe

und einen µC damit zu beschäftigen 250ms oder bis 5sekunden in einer 
for() rumzurennen is nich sinnvoll

was ist wenn vom UART was kommt  oder vom CAN ?
oder die zeit die man brauch um etwas zu berechnen  oder um etwas aufs 
LCD zu schreiben

willst du jedesmal die ASM befehle suchen + takte zählen ?

von Falk B. (falk)


Lesenswert?

Lernen durch Schmerz. Anders geht es meist nicht :-0

von Oliver (Gast)


Lesenswert?

Jungs, ihr habt immer noch recht. Selbstvertändlich macht man in einem 
realen Programm so etwas mit einem Timer.

Und trotzdem bleibe ich dabei:

Wenn ein nacktes, durch nichts unterbrochenes _delay_ms(250) keine 
250ms, sondern eine (mit einem Oszilloskop messbare !!!)  Abweichung 
davon ergibt, wird es auch bei Nutzung eines Timers keinen Deut genauer. 
Da stimmt schlicht und einfach was mit dem Takt nicht.

Oliver

von (prx) A. K. (prx)


Lesenswert?

Allerdings liegt lögels letztes Lebenszeichen bereits 3 Tage zurück und 
abgesehen von der allgemeinen aber nicht quantifizierten Aussage über 
Abweichungen und ein paar sinnlosen Programmfetzen liegt keine 
Information vor.

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


Lesenswert?

_delay_ms(250) übersetzt bis zu F_CPU von 1.048E6 Hz in eine direkte
Schleife mit 4 Takten pro Schleife, also maximal Fehler von -3 Takten,
plus der (nicht konstante) Compileraufwand, das Doppelregister dafür
ggf. vorher freizuräumen.

Bei F_CPU > 1.048E6 Hz übersetzt es in 2500 Schleifen _delay_ms(0.1),
d. h. der Fehler von maximal -3 Takten wird ggf. maximal 7500 Takte
hoch, was bei reichlich 1 MHz etwa 7 ms wären (allerdings nur für
"pathologische" F_CPU-Fälle, reine MHz-Fälle gehen glatt in den
100 µs auf), außerdem kommen noch 6 * 2500 Takte (<= 15 ms, je nach
F_CPU) für die äußere Schleife hinzu.  Das macht also worst case
(F_CPU = 1.1E6) 263 ms statt 250 ms, das kann man mit dem Oszilloskop
schon sehen.  (Bei F_CPU = 3.6864E6 sind es aber schon nur noch
253.6 ms statt 250.0 ms.)

_delay_ms() war nie dafür konzipiert worden, überhaupt derartig lange
Verzögerungen anzubieten, sondern delays benutzt man typisch dort,
wo es einem entweder nicht auf genaues Timing ankommt, oder wo die
Zeiten so kurz sind, dass sich der Overhead für einen Timer nicht
lohnt.

Mit einem Patch, den Eric Weddington gern auch in den AVR-GCC hinein
bekommen will, kann der Compiler eine eingebaute Funktion
__delay_cycles anbieten, bei der er in der Tat in der Lage ist,
zyklengenaue Verzögerungen für praktisch beliebig kleine oder große
Werte zu erzeugen.  Damit könnte man auch _delay_us/_delay_ms anders
implementieren.  Im Moment enthält der Patch dafür allerdings kein
Präprozessorsymbol, an Hand dessen man das Feature "on the fly"
erkennen könnte; bliebe noch die Möglichkeit, das zur configure-
Zeit der Bibliothek zu erkennen und den installierten Header
util/delay.h entsprechend anzupassen.

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.