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 ;)
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.
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.
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
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
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
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!
Sowas kommt dabei heraus wenn man weder versteht wie die _delay_ms Funktion arbeitet noch wie Interrupts funktionieren. So sad!
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
Johann L. schrieb: > Nitpick: Die Delay-Schleifen verwenden keine NOPs Leere Schleifen sind auch eine elegante Art, Rechenzeit zu vernichten... ;-)
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(); } }
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.