Forum: Mikrocontroller und Digitale Elektronik _delay_ms() und Interrupts


von Mirko (mirkomikro)


Lesenswert?

Guten Morgen,

Leider finde ich zu dieser Frage nix passendes, weswegen ich mal auf 
euer Schwarmwissen zurückgreifen möchte.

Die Funktion _delay_ms() blockiert ja bekanntermaßen z.B. Interrupts.
Die kleinste Einheit der Funktion liegt bei _delay_us(1).
Wäre es denn nicht "einfach" möglich, mit Hilfe einer for-Schleife eine 
interrupt-fähige delay-Funktion zu erstellen, die eben x-mal 1us wartet? 
Oder wäre das immernoch zu "langsam"?

Vielen Dank für die Erleuchtung ;)

von Jens M. (schuchkleisser)


Lesenswert?

Man könnte auch einfach auf diesen Busyloop-Kram verzichten und 
"nebenbei" laufende Timer/Counter nutzen, dann kann man die Zeit mit 
Interrupts, HMI, Schnittstellen, ADC, DAC und anderem verplempern.

von Obelix X. (obelix)


Lesenswert?

Welcher MC-Architektur?

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Mirko schrieb:
> Die Funktion _delay_ms() blockiert ja bekanntermaßen z.B. Interrupts.
Du verwendest vermutlich die AVR-Toolchain. Dann blockiert diese 
Funktion nur dann Interrupts, wenn sie ungeschickterweise in einem 
Interrupt verwendet wird. Und zwar blockiert dann nicht die _delay_ms() 
Funktion andere Interrupts, sondern die Interruptfunktion, in der sie 
ausgefufen wird tut das.

Also: _delay_ms() blockiert keine Interrupts. Und deshalb kann sie 
unterbrochen werden und wird um die Zeit des dazwischen gekommenen 
Interrupts verlängert.

> eine interrupt-fähige delay-Funktion
Interrupts müssen so kurz wie möglich sein. In Interrupts wird nur 
festgestellt, dass irgendwas "zwischendurch" passiert ist und später mal 
bearbeitet werden muss. Wer in Interrupts Delays einbaut, sollte 
unbedingt seine Softwarearchitektur überarbeiten.

von Wastl (hartundweichware)


Lesenswert?

Lothar M. schrieb:
> Wer in Interrupts Delays einbaut

.... gehört geteert und gefedert.

von Mirko (mirkomikro)


Lesenswert?

Jens M. schrieb:
> Man könnte auch einfach auf diesen Busyloop-Kram verzichten und
> "nebenbei" laufende Timer/Counter nutzen, dann kann man die Zeit mit
> Interrupts, HMI, Schnittstellen, ADC, DAC und anderem verplempern.

Beitrag "Versuch einer Millis Funktion für AVR"

Check ;)
Es ging mir einfach nur um ein Verständnisproblem, was sich jetzt 
herausstellt, dass es gut war diese Frage gestellt zu haben.


Lothar M. schrieb:
> Du verwendest vermutlich die AVR-Toolchain.

Richtig.

> Also: _delay_ms() blockiert keine Interrupts.

Ich habe es fälschlichweise immer so verstanden, dass delay immer die 
definierte Zeit "blockiert" und Interrupts nur zwischen den Befehlen 
"aktiviert" werden können und somit erst "nach" dem delay frühestens 
unterbricht.

: Bearbeitet durch User
von Jens M. (schuchkleisser)


Lesenswert?

Mirko schrieb:
> Ich habe es fälschlichweise immer so verstanden, dass delay immer die
> definierte Zeit "blockiert"

Delay blockiert die CPU, weil es eine Busyloop ist, also einfach "fahre 
1000000mal im Kreis". In der Zeit kannst du (also du, im Sinne des 
Programmierers, dessen Programm abläuft) nichts anderes ausführen 
lassen.
Außer Interrupts.

Mirko schrieb:
> Interrupts nur zwischen den Befehlen
> "aktiviert" werden können und somit erst "nach" dem delay frühestens
> unterbricht.

Ints können nach jedem Befehl eingreifen, sofern nicht abgeschaltet.
Aber "Befehl" meint hier "Maschinenbefehl", und nicht C-Zeile.
So ein Delay für eine Millisekunde braucht bei 16MHz zwar nur wenige 
Maschinencodezeilen, führt diese aber etliche hunderttausend mal aus, 
und jedes Mal gibt's eine Gelegenheit einen Int dazwischen zu stecken.
Währenddessen ist die CPU aber mit dem Int beschäftigt und kann den 
Zähler nicht runterzählen, d.h. die Zeit des Delay pausiert quasi und 
wird nach dem Beenden des Int erst fortgesetzt. Bis der nächste Int 
kommt...
Daher ist es für komplexere Programme und/oder längere Delays 
sinnvoller, diese nicht mit einem Busyloop zu verzögern, sondern einen 
Timer zu setzen und diesen bei Gelegenheit abzufragen.
Wobei "Timer" hier einen Zähler meint, der z.B. in einem Interrupt 
runtergezählt wird.
Also "einfach" 1000 reinladen und nachsehen ob "ungleich 0", das wartet 
dann z.B. eine Sekunde (wenn der Millisekunden-Systemtick-Interrupt 
diesen Zähler runterzählt), kann aber trotzdem nebenbei UART bedienen, 
Tasten abfragen, ADC bedienen usw. usf.

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Mirko schrieb:
> Interrupts nur zwischen den Befehlen "aktiviert" werden können
Das ist korrekt. Allerdings sind mit diesen "Befehlen" einzelne 
Maschinenbefehle gemeint. Weil ein _delay_ms(1) aber letztlich aus 
zigtausend NOP (**N**o-**OP**eration) besteht, kann ein Interrupt dieses 
_delay_ms(1) zigtausend mal nach jedem NOP unterbrechen. Bei 8MHz 
Taktfrequenz werden für 1ms einfach "nutzlos" 8000 NOP Maschinenbefehle 
samt ein paar Schleifenbefehlen ausgeführt.

Ein Tipp: lass doch einfach den Compiler mal nebenher ein Assemblerfile 
als Output generieren und suche dort drin die _delay_ms() Funktion. 
Versuche zu verstehen, was der Compiler aus dieser _delay_ms() Funktion 
gemacht hat.

: Bearbeitet durch Moderator
von Falk B. (falk)


Lesenswert?

Mirko schrieb:
> Guten Morgen,
>
> Leider finde ich zu dieser Frage nix passendes, weswegen ich mal auf
> euer Schwarmwissen zurückgreifen möchte.
>
> Die Funktion _delay_ms() blockiert ja bekanntermaßen z.B. Interrupts.

Falsch.

> Die kleinste Einheit der Funktion liegt bei _delay_us(1).

Falsch die 2.

> Vielen Dank für die Erleuchtung ;)

RTFM!

von Cyblord -. (cyblord)


Lesenswert?

Sowas kommt dabei heraus wenn man weder versteht wie die _delay_ms 
Funktion arbeitet noch wie Interrupts funktionieren. So sad!

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Mirko schrieb:
> Die Funktion _delay_ms() blockiert ja bekanntermaßen z.B. Interrupts.

Die <util/delay.h> Funktionen der AVR-LibC blockieren Interrupts NICHT. 
Allerdings sagt die Dokumentation nichts dazu.  Lediglich dass 
__builtin_avr_delay_cycles() verwendet wird falls unterstützt, was schon 
seit weit über 10 Jahren der Fall ist.

Und __builtin_avr_delay_cycles wiederum blockiert keine Interrupts:
1
> Built-in Function: void __builtin_avr_delay_cycles (uint32_t ticks)
2
>    Delay execution for ticks cycles. Note that this built-in does not
3
>    take into account the effect of interrupts that might increase
4
>    delay time. ticks must be a compile-time integer constant; delays
5
>    with a variable number of cycles are not supported.

https://gcc.gnu.org/onlinedocs/gcc/AVR-Built-in-Functions.html#index-_005f_005fbuiltin_005favr_005fdelay_005fcycles

Lothar M. schrieb:
> Allerdings sind mit diesen "Befehlen" einzelne Maschinenbefehle
> gemeint. Weil ein _delay_ms(1) aber letztlich aus zigtausend
> NOP (**N**o-**OP**eration) besteht,

Nitpick: Die Delay-Schleifen verwenden keine NOPs, sondern andere 
Instruktionen. Beispiel mit F_CPU = 1MHz:
1
#include <util/delay.h>
2
3
void wait (void)
4
{
5
    _delay_ms (1000);
6
}

wird zu:
1
wait:
2
  ldi r24,lo8(199999)   ;  11  [c=4 l=7]  *delay_cycles_3
3
  ldi r25,hi8(199999)
4
  ldi r18,hlo8(199999)
5
1:
6
  subi r24,1
7
  sbci r25,0
8
  sbci r18,0
9
  brne 1b
10
  rjmp .     ;  6  [c=4 l=1]  *nopv/1
11
  nop        ;  7  [c=4 l=1]  *nopv/0
12
/* epilogue start */
13
  ret     ;  14  [c=0 l=1]  return

Nur am Ende wird ein einziges NOP verwendet um auf die exakte Anzahl von 
Ticks zu kommen ("RJMP ." entspricht 2 NOPs).  Die innere Schleife 
dauert ein Vielfaches von 5 Ticks, so dass nach der Schleife noch 0...4 
Ticks zu vertrödeln sind.

: Bearbeitet durch User
von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Johann L. schrieb:
> Nitpick: Die Delay-Schleifen verwenden keine NOPs
Leere Schleifen sind auch eine elegante Art, Rechenzeit zu vernichten... 
;-)

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Mirko schrieb:
> euer Schwarmwissen

Warum fühle ich mich da durch beleidigt?

von Rolf (rolf22)


Lesenswert?

Jens M. schrieb:
> Daher ist es für komplexere Programme und/oder längere Delays
> sinnvoller, diese nicht mit einem Busyloop zu verzögern, sondern einen
> Timer zu setzen und diesen bei Gelegenheit abzufragen.
> Wobei "Timer" hier einen Zähler meint, der z.B. in einem Interrupt
> runtergezählt wird.

Kann man machen. Manchmal aber hat man aber während des Wartens gar 
nichts Zeitaufwendiges/Wichtiges zu tun. Dann führt dass "gelegentliche" 
Abfragen zu einem nutzlos Laufen im Kreis, und da kann der 
Energieverbrauch im Batteriebetrieb sehr störend sein. Besser ist es 
dann, während des Wartens in den Sleep-Modus zu gehen. Dafür kann man 
sich MySleepDelay(int delay) schreiben.

Falls man keinen Timer hat, der die gewünschte lange Zeit schafft, kann 
man einen Multiplikator nutzen, IM PRINZIP so:

uint timerFactor, timerHelper;
bool delayElapsed;

void initTimer(int delay)
{ timerHelper = 0; delayElapsed = false;
  // Hier Timer-Hardware und 'timerFactor' anhand von 'delay' 
initialisieren
}

void TimerInt()
{ if (timerHelper++ == timerFactor) delayElapsed = true; }

void MySleepDelay(int delay)
{ initTimer(delay);  while (!delayElapsed) { sleep(); } }

von Axel S. (a-za-z0-9)


Lesenswert?

Arduino F. schrieb:
> Mirko schrieb:
>> euer Schwarmwissen
>
> Warum fühle ich mich da durch beleidigt?

Weil Mirko den Begriff falsch verwendet? Mirko meinte wohl "ich frage 
einfach viele Leute auf einmal, da wird dann schon einer dabei sein, der 
die Antwort weiß". Aber das ist nicht die Bedeutung des Begriffes.

https://de.wikipedia.org/wiki/Kollektive_Intelligenz bedeutet, daß ein 
Schwarm (Vogel-, Fisch-, Bienen- etc) als Ganzes ein intelligentes 
Verhalten zeigen kann, obwohl jedes Einzelwesen des Schwarms das nicht 
kann. Schwarmintelligenz ist ein Beispiel für Emergenz.

Ein Internetforum ist gerade kein passendes Beispiel. Denn eine 
Antwort (bzw. oft viele, einander auch widersprechede Antworten) bekommt 
man immer nur von einem Individuum. Nicht vom Forum als Ganzen.

von Andras H. (andras_h)


Lesenswert?

Mirko schrieb:
> Die Funktion _delay_ms() blockiert ja bekanntermaßen z.B. Interrupts.
> Die kleinste Einheit der Funktion liegt bei _delay_us(1).
> Wäre es denn nicht "einfach" möglich, mit Hilfe einer for-Schleife eine
> interrupt-fähige delay-Funktion zu erstellen, die eben x-mal 1us wartet?
> Oder wäre das immernoch zu "langsam"?

Prinzipiell soll man Delay funktionen nicht benutzen. Es gibt aber 
einige Fälle wo man leider nicht anders machen kann. Aber normaler weise 
gilt es, delay nie aufrufen.

Angenommen ich muss länger als 100ms warten. Wenn ich das in den Main 
context mache, dann läuft mein Programm 100ms nicht mehr. Die Interrupts 
werden da noch kommen, aber da soll eigentlich nur wirklich das laufen 
was nötig ist. Sprich die Applikationslogik ist nicht da, also alles 
wird 100ms lang blockiert. Wenn man einen OS hat, dann könnte man Tasks 
erstellen. Da könnte man durch den OS den Task dann 100ms lang schlafen 
lassen. Das geht. Macht man aber auch nicht, weil das später beim 
Entwicklung Probleme macht. Zum Beispiel weil man viel schwieriger 
herausbekommt wo der Task gerade ist und wieso man nix macht.
Was macht man an stelle dann? State machine bauen. Eigentlich immer. Und 
über all.
Angenommen man will zwischen 1ms und 100ms warten. Das geht auch genau 
wie oben. Wobei hier sage ich, dass zum Beispiele die Zeitspannen so 
sind, dass man soetwas eventuell von außen nicht merkt (Mensch). Leider 
merken aber die HW präferieren so etwas schon. Also ich rate davon auch 
ab.
Angenommen man will unter 1ms warten. Tcha hier wird es schwierig. Wenn 
da jetzt ein Interrupt dazwischen kommt, dann ist die 1ms nicht mehr 1ms 
sondern vielleicht 2ms. Bei 1us ist dann auch peinlich wenn man manchmal 
dann 100us hat. Und dann wird die Kommunikation mit ein Sensor nicht 
mehr tun. Man könnte hier den Interrupt sperren, ist aber nicht so gut, 
weil dann die Periferien die durch Interrupt etwas schnell mitteilen 
wollen dann warten müssen. State machine geht hier auch nicht immer, 
weil wer hat denn schon ein System wo man einen Task mit 100us 
Cykluszeit laufen lassen kann? Üblich sind 1ms und 10ms. Also hier gilt, 
versuchen HW Periferien zu nutzen damit man nicht unter 1ms warten muss.

Ich sage der 1-wire protocoll ist ein guter Beispiel. Da gibt es 
Wartezeiten zwischen 10us bis zu 480us. 480us sind ja 0,5ms. Das ist 
genau so eine Zeitspanne wo ich mein SW nicht komplett blockieren will. 
Aber da man meist 1ms Cykluszeiten hat, und das kann man auch gut 
halten, kann man aber leider auch nicht durch ein State Machine gehen. 
Die 10us würde durch delay gehen, aber da muss man unbedint die 
Interrupts sperren, sonst wird es nix. Aber hier könnte man eine Auge 
zudrücken wenn man will. Aber wenn man sich keine Probleme einräumen 
möchte, dann soll man lieber über DMA oder USART die Signale generieren 
lassen.

Fazit: Finger weg von delay. Nimm lieder State machine und oder HW 
periferien.

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.