Forum: Mikrocontroller und Digitale Elektronik Delay Funktion umgehen


von Stefan M. (ronaldonho)


Lesenswert?

Hallo,

ich arbeite mit dem AvrStudio und einem µC und ich möchte gerne die 
Delay Funktion umgehen, dazu habe ich einen Timer erstellt. Dieser Timer 
ist so eingestellt das er für einen takt 1ms braucht.

Ich möchte jetzt das einer meiner while Schleifen den Programmfluss mit 
dem Timer für 100ms unterbricht oder verzögert.

Mein Problem ist jetzt, das ich nicht weiß, wie ich mit dem Timer auf 
diesen bestimmten Zeitpunkt warte. Wie programmiere ich das am besten?

Danke

von Cyblord -. (cyblord)


Lesenswert?

Stefan M. schrieb:
> Ich möchte jetzt das einer meiner while Schleifen den Programmfluss mit
> dem Timer für 100ms unterbricht oder verzögert.
>
> Mein Problem ist jetzt, das ich nicht weiß, wie ich mit dem Timer auf
> diesen bestimmten Zeitpunkt warte. Wie programmiere ich das am besten?

Der Timer generiert einen Interrupt. z.B. alle 1 ms.
In der ISR wird gezählt und bei 100ms eine Variable auf 1 gesetzt.

Im Hauptprogramm wird auf diese Variable gewartet (und dann auf 0 
gesetzt).

von Wolfgang (Gast)


Lesenswert?

Stefan M. schrieb:
> Mein Problem ist jetzt, das ich nicht weiß, wie ich mit dem Timer auf
> diesen bestimmten Zeitpunkt warte. Wie programmiere ich das am besten?

Mit einer FSM. Der Timer inkrementiert bei jedem Tick einen Zähler und 
den fragst du in der FSM ab. Wenn der Zählerstand deines Tick-Zählers 
sich seit Start der Wartezeit um 100 erhöht hat, sind die 100ms um und 
es Zeit, etwas zu unternehmen.

von 50c (Gast)


Lesenswert?

Stefan M. schrieb:
> Ich möchte jetzt das einer meiner while Schleifen den Programmfluss mit
> dem Timer für 100ms unterbricht oder verzögert.

Cyblord -. schrieb:
> Im Hauptprogramm wird auf diese Variable gewartet

...dann stellt sich aber insgesamt die Frage, wo jetzt der Unterschied 
zu einem Delay ist...

Das Ganze macht doch nur Sinn, wenn der MC auch noch etwas anderes macht

von Stefan M. (ronaldonho)


Lesenswert?

Cyblord -. schrieb:
>
> Der Timer generiert einen Interrupt. z.B. alle 1 ms.
> In der ISR wird gezählt und bei 100ms eine Variable auf 1 gesetzt.
>
> Im Hauptprogramm wird auf diese Variable gewartet (und dann auf 0
> gesetzt).

Ja genau, ich habe zum Beispiel dieses Programm von der Seite 
https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Warteschleifen_.28delay.h.29

    while( 1 )                  // Endlosschleife
    {
        PORTB ^= ( 1 << PB0 );  // Toggle PB0 z.&nbsp;B. angeschlossene 
LED
        delay_ms(1000);       // Eine Sekunde warten...
    }

Jetzt möchte ich gerne dieses Delay mit meiner Globalen Counter Variable 
welches im ISR zählt ersetzen. Aber wie sage ich dem Programm das er in 
der while-Schleife auf diese Variable warten soll?

if (counter == 100)
    { counter = 0;  }

Das hatte bei mir nicht funktioniert

von Cyblord -. (cyblord)


Lesenswert?

50c schrieb:
> ...dann stellt sich aber insgesamt die Frage, wo jetzt der Unterschied
> zu einem Delay ist...

Der Unterschied ist, dass du ein präzises Timing hast.
Wenn du Messungen mit Zeitbezug hast, reicht ein Delay nicht.

Dann musst du auch nicht mit einem while warten. Die Main Schleife kann 
durchgehend laufen, und je nach Zähler/Gate Variable verschiedene Dinge 
tun.

Aber die Grundlage ist eben ein Timer, der in der ISR zählt, und das 
Ergebnis dem Main Programm zugänglich macht.
Das ganze kann man dann noch beliebig generalisieren, mit Timer 
Strukturen und Callbacks und sich ein ganzes Posix Timer Sammelsurium 
zusammenprogrammieren. Wenn man das will.

Meist reicht aber ein Zuschnitt auf die jeweilige konkrete Anwendung 
aus.

> Das Ganze macht doch nur Sinn, wenn der MC auch noch etwas anderes macht

Ja, das schließt sich nicht aus.

: Bearbeitet durch User
von Cyblord -. (cyblord)


Lesenswert?

Stefan M. schrieb:
> Das hatte bei mir nicht funktioniert

Mach die Variable mal volatile.

Und "hat nicht funktioniert" ist ne üble Fehlerbeschreibung. Warum 
sollte es nicht funktionieren? Also Debugge es ordentlich.

von HildeK (Gast)


Lesenswert?

Stefan M. schrieb:
> ich möchte gerne die Delay Funktion umgehen,
...
> Ich möchte jetzt das einer meiner while Schleifen den Programmfluss mit
> dem Timer für 100ms unterbricht oder verzögert.

Den Timer zu nehmen anstatt mit Delays zu arbeiten ist meist sinnvoll.

Wenn du aber nur unterbrichst und eine bestimmte Zeit wartest und im 
Programm derweil nichts anderes tun willst, dann ist Delay genau so gut 
und einfacher zu programmieren.
Außer du willst zum Strom sparen den µC in den Schlaf schicken und nach 
der Zeit wieder wecken; dann hat der Timer wieder die Nase vorn.

von MaWin (Gast)


Lesenswert?

Cyblord -. schrieb:
> Der Unterschied ist, dass du ein präzises Timing hast.
> Wenn du Messungen mit Zeitbezug hast, reicht ein Delay nicht.

Humbug.

delay ist so genau wie der Prozessortakt, alsp ggf. quartzgenau.

Man möchte kein delay, weil es den Programmablauf blockiert, der uC also 
nichts anderes machen kann (nicht Mal Interrupts bearbeiten, das wurde 
die delay-Zeit verlängern).

Stefans while wird zu
1
int start=millis();
2
while(1) // bzw. loop()
3
{
4
  if(millis()>=start+1000)
5
  {
6
    PORTB ^= ( 1 << PB0 ); 
7
    start+=1000; // keine Akkumulation von Fehlern
8
  }
9
  // else anderes bearbeiten wenn nötig
10
}
Arduino-millis ggf. durch eigenes ersetzen.

von Cyblord -. (cyblord)


Lesenswert?

MaWin schrieb:
> delay ist so genau wie der Prozessortakt, alsp ggf. quartzgenau.

Ja Bubi, aber nach dem Delay kommt ja noch Code. Der auch Zeit braucht.
Wenn du also eine Messung alle 100ms machen willst, bringt dich ein 
100ms delay nicht weiter.
Ein 100ms "Timer-Delay" aber eben schon.

von Stefan F. (Gast)


Lesenswert?

Stefan M. schrieb:
> Wie programmiere ich das am besten?

Ich gehe davon aus dass deine Timer ISR eine uint32_t Variable im ms 
Intervall hochzählt. Ansonsten musst den Code entsprechend anpassen. 
Nennen wir sie "millis".
1
void delay(uint32_t milliseconds)
2
{
3
    uint32_t start=millis;
4
    while (millis-start < milliseconds)
5
    {
6
      // waste time
7
    }
8
}
9
10
while(1) // Endlosschleife
11
{
12
    PORTB ^= ( 1 << PB0 ); // Toggle LED an PB0
13
    delay(1000);
14
}

Wichtig ist, dass du eine Subtraktion und unsigned Integer benutzt 
werden. Dann liefert die Rechnung sogar ein korrektes Ergebnis, wenn der 
Zähler zwischendurch einmal überläuft.

Also nicht
1
while (millis < start+milliseconds)
das würde beim Überlauf nicht funktionieren.

von Stefan F. (Gast)


Lesenswert?

MaWin schrieb:
> if(millis()>=start+1000)

Das ist genau die falsche Methode!

von Cyblord -. (cyblord)


Lesenswert?

Stefan ⛄ F. schrieb:
> MaWin schrieb:
>> if(millis()>=start+1000)
>
> Das ist genau die falsche Methode!

Ja, MaWin zeigt eindrucksvoll dass er nichts kann.

von Stefan F. (Gast)


Lesenswert?

Erklärung, warum es falsch ist:

Der maximale Wert einer uint32 Variable ist 4294967295. Danach kommt 
wieder die 0. Mal angenommen, wir sind kurz vor diesem Überlauf und 
warten dann 100ms, dann ergibt die Bedingung beim ersten 
Schleifendurchlauf:

> if(4294967291 >= 4294967291+1000)

das entspricht:

> if(4294967291 >= 995)

Und das ist sofort wahr, schon bevor überhaupt auch nur eine 
Millisekunde gewartet wurde.

Mit einer Subtraktion funktioniert es hingegen.

von Ralf (Gast)


Lesenswert?

Hallo,

ich empfehle einen etwas anderen Ansatz. Erstelle dir eine globale 
Variable. Diese Variable dekrementierst du im Timer-Interrupt, solange 
sie nicht null ist(!)
In deinem Programm setzt du die Variable nun auf 100, was 100 ms 
entspricht. Du kannst nun in deinem Programm darauf warten, dass die 
Variable den Wert null hat oder alternativ etwas anderes machen, solange 
sie eben nicht null ist. Wenn du mehrere Variablen anlegst (bzw. ein 
Array), dann hast du einen Satz Softwaretimer, die flexibel einsetzbar 
sind.
Das Dekrementieren hat den Vorteil, dass man bei Ablauf der Zeit immer 
bei null landet, das wäre bei einer inkrementierten Variablen m.E. 
schwieriger zu handhaben.

Ein Programm könnte dann ungefähr so aussehen:
1
#define NUM_SOFTTMR 3
2
volatile uint16_t SoftTmr[NUM_SOFTTMR];
3
4
void Timer_ISR(void) {
5
  uint8_t I;
6
  for(I = 0; I < NUM_SOFTTMR; I++) {
7
    if(SoftTmr[I] > 0)
8
      SoftTmr[I]--;
9
  }
10
}
11
12
void main(void) {
13
  //Initialisierung Timer, etc.
14
15
  SoftTmr[0] = 100;
16
  SoftTmr[1] = 250;
17
  SoftTmr[2] = 10000;
18
19
  while(SoftTmr[2] > 0) {
20
    if(SoftTmr[0] = 0) {
21
      SoftTmr[0] = 100;
22
      LED0 = ~LED0;
23
    }
24
    if(SoftTmr[1] = 0) {
25
      SoftTmr[1] = 250;
26
      LED1 = ~LED1;
27
    }
28
  }
29
  while(1);  //Programmende
30
}
Das ist ein generisches Beispiel, es sollte das Prinzip aber klar machen 
(ich hoffe da hat sich jetzt kein Fehler eingeschlichen). Wichtig ist, 
dass die Timervariable(n) als 'volatile' markiert sind, sonst wird evtl 
nicht der richtige Wert herangezogen (ich kenne AVRs nicht, aber da gilt 
das vermutlich auch).
Zu beachten ist auch, dass (unabhängig von 
Dekrementieren/Inkrementieren) die Toleranz +0/-1 betragen kann, d.h. 
wenn du mindestens 100ms benötigst, dann musst du 101 als Wert 
verwenden. Das ist bei "schnellen" Softwaretimern nicht so kritisch, 
aber bei bspw. 10ms Auflösung kann das schon ins Gewicht fallen.

Gruß Ralf

von Dyson (Gast)


Lesenswert?

1
#define INTERVAL 100
2
volatile unsigned char timerflag = 0;
3
4
ISR(1ms-Timer)
5
{
6
  timerflag = 1;
7
}
8
__attribute__((OS_main)) int main(void)
9
{
10
  unsigned char interval = INTERVAL;
11
  //Init alles noetige...
12
13
  while(1)
14
  {
15
    //tu was mit voller Geschwindigkeit
16
17
    if(timerflag)
18
    {
19
      timerflag = 0;
20
      //tu was alle 1ms
21
      if(interval)interval--;
22
      else
23
      {
24
         interval = INTERVAL;
25
         //tu was alle 100ms...
26
      }
27
  }
28
}

von EAF (Gast)


Lesenswert?

MaWin schrieb:
> if(millis()>=start+1000)
Das führt ins Versagen!

von W.S. (Gast)


Lesenswert?

Stefan M. schrieb:
> if (counter == 100)
>     { counter = 0;  }

if (counter >= 100) ...

HildeK schrieb:
> Den Timer zu nehmen anstatt mit Delays zu arbeiten ist meist sinnvoll.

Das sehen ich SO nicht. Wenn überhaupt ein Timer, dann sollte man sich 
auch ein paar Gedanken machen über einen klügeren Grundentwurf seiner 
Firmware. Dann sollte der Timer auch gleich in einer firmwareinternen 
Uhr verwendet werden und diese Uhr sollte Delay-Funktionalität im ms und 
s Bereich erledigen.
Also wenn man schon ein dickeres Kaliber zum Schießen auf Spatzen 
erwägt, dann bitte richtig.

W.S.

von Stefan F. (Gast)


Lesenswert?

W.S. schrieb:
> Wenn überhaupt ein Timer, dann sollte man sich auch ein
> paar Gedanken machen über einen klügeren Grundentwurf seiner
> Firmware

Ich denke, die delay_ms() durch eine eigene zu ersetzen, ist der erste 
Schritt in die Richtung. Man soll ja nicht zu viel auf einmal ändern, 
sonst verliert man schnell den Durchblick.

von Cyblord -. (cyblord)


Lesenswert?

W.S. schrieb:
> Also wenn man schon ein dickeres Kaliber zum Schießen auf Spatzen
> erwägt, dann bitte richtig.

Nö. Immer so dick wie gerade benötigt.

von W.S. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ich denke, die delay_ms() durch eine eigene zu ersetzen, ist der erste
> Schritt in die Richtung.

Das sehe ich so nicht. Der erste Schritt ist das gründliche Überdenken 
des Grundgerüstes. Und überhaupt: was soll das Ersetzen einer 
Delay-Funktion durch eine andere Delay-Funktion bringen? Den Stolz, auch 
mal was eigenes geschrieben zu haben und nicht nur per copy&paste gelebt 
zu haben? Oder?

W.S.

von Axel R. (axlr)


Lesenswert?

er soll doch gerade statt der delay-Funktion, in der er blöd rumwartet, 
etwas sinnvolles machen können und nur, wenn die Intervallzeit 
abgelaufen ist, eben das vorgesehene "tun". Sonst kann man ja 
"delay_ms()" auch stehen lassen. Gibt es ja schon.
Also eher einen Zustandsautomaten aufbauen oder eben kurz nachsehen, ob 
zeit schon um ist und was wichtigeres abarbeiten... keine Ahnung, wie 
man das richtig ausdrückt.

von Axel R. (axlr)


Lesenswert?

Dyson schrieb:

>     if(timerflag)
>     {
>       timerflag = 0;
>       //tu was alle 1ms
>       if(interval)interval--;
>       else
>       {
>          interval = INTERVAL;
>          //tu was alle 100ms...
>       }
>   }
> [/c]
ja sowas hier in der Art
(hatte sich überschnitten)

von ohje (Gast)


Lesenswert?

Cyblord -. schrieb:
> Nö. Immer so dick wie gerade benötigt.

Ein Sleepmodi kann Faktor 100 oder mehr beim Stromverbrauch ausmachen. 
Selbst ein schöndes Idle bringt schon viel. Ein nicht zu 
unterschätzender Faktor, speziell wenn man auf Batterie läuft, aber 
generell auch dann, wenn man z.B. auf der Platine Temperaturen messen 
will.

Da heißt dann 1% CPU-Last statt 100% 99% weniger Stromverbrauch oder 
Wärme.

Die Grundstruktur schreibt man sich einmal und das wars dann.

Vom Vorteil, dass man in der 99% Zeit noch etwas anstellen kann, kommt 
noch dazu.

Warten durch Verbrennen von Strom und CPU-Zyklen wäre in etwa so, wie 
wenn man beim Auto das Gaspead weglässt, den Motor immer auf Vollgas 
laufen lässt, und die Geschwindigkeit nur durch Bremsen anpasst.

von Cyblord -. (cyblord)


Lesenswert?

ohje schrieb:
> Warten durch Verbrennen von Strom und CPU-Zyklen wäre in etwa so, wie
> wenn man beim Auto das Gaspead weglässt, den Motor immer auf Vollgas
> laufen lässt, und die Geschwindigkeit nur durch Bremsen anpasst.

Jaja, nur implementieren die wenigstens normale Wartefunktionen zusammen 
mit Sleep Modes. Du kannst dir dann auch gerne ausrechnen ob die 1 
Stunde mehr an Implementierungszeit, nicht mehr Strom gekostet hat, als 
dein Sleep Mode im ganzen Leben spart.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> Und überhaupt: was soll das Ersetzen einer
> Delay-Funktion durch eine andere Delay-Funktion bringen?

Das AVR Libc delay_ms() verlängert sich bei jedem Interrupt um dessen 
Laufzeit.
Das Arduin delay() tut das nicht.

Das ist ein Unterschied!

von HildeK (Gast)


Lesenswert?

W.S. schrieb:
> HildeK schrieb:
>> Den Timer zu nehmen anstatt mit Delays zu arbeiten ist meist sinnvoll.
>
> Das sehen ich SO nicht.

Ich meinte, ich hätte erklärt, wann man ein einfaches Delay 
(_delay_ms(nn)) nehmen kann und wann ein Timer sinnvoller ist. Ein Delay 
ist halt blockierend; macht nichts, wenn ich es mir leisten kann. Oft 
jedoch nicht.
Und den Timer kann man auch blockierend verwenden, wenn man will:
while (!timer_expired());

Der TO wollte den Programmablauf unterbrechen bzw. verzögern; da ist das 
einfache Delay durchaus sinnvoll, einfach und ausreichend und der 
Aufwand, das mit einem Timer lösen zu wollen höchstens lehrreich.

von Ralf (Gast)


Lesenswert?

Aber wenn er von Anfang an den Timer verwendet (in welcher der hier 
angesprochenen Varianten auch immer), dann hat er den Vorteil dass er 
beim ersten Programm, bei dem er häufig und auf verschiedene Dinge 
warten muss immer noch was anderes machen kann. Denn das erste was 
passieren wird ist, dass das Programm vollgestopft mit Delays und extrem 
träge ist. Ich finde, der Zustand eines "kontrollierten" Nichtstuns ist 
vorzuziehen anstatt an verschiedenen (und später unüberschaubaren) 
Stellen Zeit zu verdröseln. Zustandmaschine und ähnliches wurden ja 
dafür schon genannt. Das könnte er sich ja als nächstes Lernziel setzen.

von LostInMusic (Gast)


Lesenswert?

>... das mit einem Timer lösen zu wollen höchstens lehrreich.

Nicht "höchstens lehrreich", sondern "höchst lehrreich".

>Zustandmaschine

Meint hier eigentlich das, was man "kooperatives Multitasking" nennt.

von Dyson (Gast)


Lesenswert?

Habe ich irgendwas verpasst? Bisher war es hier immer so, dass man für 
ein  Delay gekreuzigt wurde. Demnächst kommt bestimmt noch LED ohne 
Vorwiederstand[sic!] und Taster ohne Entprellung.

von EAF (Gast)


Lesenswert?

Dyson schrieb:
> Bisher war es hier immer so, dass man für
> ein  Delay gekreuzigt wurde.

Nöö....
Gegen ein notwendiges Delay ist nicht zu sagen.
Wenn es zu einer Kugel am Bein entwickelt, wird es zu Recht verurteilt.

Die Medaille hat also 2 Seiten.

von Falk B. (falk)


Lesenswert?

Siehe Multitasking.

von Noch ein Kommentar (Gast)


Lesenswert?

Multitasking!

Ja, das Problem ist nicht das delay(). Das Problem ist, du willst 
während der Wartezeit etwas anderes machen. Ohne Multitasking 
Betriebssystem wird das aufwendig.

Übrigens - auch MC-Programme, die für Multitasking konzipiert sind, 
benutzen gelegentlich ein delay(). Z.B. beim initialisieren eines 
Displays. Hier lohnt sich der Aufwand ohne delay() nicht.

von Falk B. (falk)


Lesenswert?

Noch ein Kommentar schrieb:
> Multitasking!
>
> Ja, das Problem ist nicht das delay(). Das Problem ist, du willst
> während der Wartezeit etwas anderes machen. Ohne Multitasking
> Betriebssystem wird das aufwendig.

Quark. Lies den Artikel.

von EAF (Gast)


Lesenswert?

Noch ein Kommentar schrieb:
> Ja, das Problem ist nicht das delay(). Das Problem ist, du willst
> während der Wartezeit etwas anderes machen. Ohne Multitasking
> Betriebssystem wird das aufwendig.

Das ist nicht ganz richtig.
Selbst die AVR Arduino Welt erlaubt ein Leben neben delay()
Also Nebenläufigkeit, ohne RTOS oder vergleichbares.

Wenn Arduino das schon kann, dann bekommt man das auf jedem µC hin.

von Cyblord -. (cyblord)


Lesenswert?

Noch ein Kommentar schrieb:
> Ja, das Problem ist nicht das delay(). Das Problem ist, du willst
> während der Wartezeit etwas anderes machen. Ohne Multitasking
> Betriebssystem wird das aufwendig.

Das ist ja nun mal echt Unsinn.

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.