Forum: Mikrocontroller und Digitale Elektronik Delay mit Timer, Interrupt und Flags.


von Echt jetzt? (Gast)


Lesenswert?

Ich weiß gar nicht, ob ich meine Frage richtig beschrieben kann:
Ich habe eine mainloop, in der mit (volatile) flags gesetzt werden 
können. Bei gesetztem flag wird der Teil abgearbeitet.
1
while(1)
2
{
3
   if (flag1)
4
       {
5
        ...
6
       }
7
   if (flag2)
8
       {
9
        ...
10
       }
11
   if ("noflag")
12
       {
13
         sleep
14
       }
15
}

Wenn das Programm nun z.B. gerade in flag1 arbeitet, kommt ein 
Interrupt. Dort wird etwas gemacht und ein delay von 750 ms ist 
erforderlich (DS18B20).
Jetzt möchte ich prüfen, ob in der mainloop etwas zu tun ist. Wenn ja, 
dort weitermachen, wo vor dem Interrupt aktiv. Wenn nicht, sleep.
Alles gut bis hier.
Wie komme ich aber jetzt in den Programmablauf, wenn der delaytimer 
seinen Interrupt hat??? Es wird die timer-ISR gemacht und dann in die 
mainloop zurückgesprungen, wo das Programm vor dem timer-Interrupt war.
Ich möchte aber, daß an der Stelle nach dem timerdelay (750ms) 
weitergearbeitet wird.

von Stefan F. (Gast)


Lesenswert?

Bevor du dich da total verzettelst, finde einen anderen Weg ohne 
delay().

Lies das: http://stefanfrings.de/multithreading_arduino/index.html

von Falk B. (falk)


Lesenswert?

Echt jetzt? schrieb:
> Wenn das Programm nun z.B. gerade in flag1 arbeitet, kommt ein
> Interrupt. Dort wird etwas gemacht und ein delay von 750 ms ist
> erforderlich (DS18B20).

Schon falsch. Derartig lange Pausen haben in den meisten Programmen, 
erst recht in einem Interrupt NICHTS zu suchen! Siehe 
Multitasking.

> Jetzt möchte ich prüfen, ob in der mainloop etwas zu tun ist. Wenn ja,
> dort weitermachen, wo vor dem Interrupt aktiv. Wenn nicht, sleep.
> Alles gut bis hier.

Unfug.

von Echt jetzt? (Gast)


Lesenswert?

Verzetteln ist wohl das richtige Wort.
Delays machen eigentlich nur Sinn im Bereich von wenigen Mikrosekunden. 
Verstanden. Das weiß ich. Längere (und vor allem genaue Zeiten) müssen 
irgendwie über Timer gemacht werden.
Das im Link beschriebene Verfahren kenne ich im Prinzip auch schon von 
z.B. hier: 
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/a-classy-solution
Bei der Gelegenheit will ich, davon inspiriert, gleich von C auf "C mit 
Klassen" umsteigen, da ich da einige Vorteile sehe. Also wirklich nur 
ganz wenige Features von C++ nutzen.

Das Prinzip paßt aber auf meinen Anwendungsfall nicht bzw. glaube ich 
das. Da ich batteriebetrieben arbeiten will/muß, ist Stromsparen 
Pflicht. Strategie daher: Wenn nichts zu tun ist, soll der µC schlafen. 
Und das geht (glaube ich) nur, wenn ich mit Interrupts arbeite. Nur dann 
muß wirklich alles über die ISR laufen. Glaube ich.
Zum Thema Verzetteln: Ich habe schon an FSM gedacht und den 
"dynamischen" Teil dann in den ISR mit Funktionszeigern abzubilden 
gedacht. Aber das wird dann sehr schnell sehr komplex. Und bei 
Änderungen: Brain Overflow...
Auch das (zu recht) verhaßte goto kam mir schon in den Sinn. Aber auch 
dafür gilt das Gleiche.

Einen gewissen Teil kann ich mit den mir bekannten Mitteln auch lösen. 
Also z.B. den Fall, daß im o.g. Beispiel die 750 ms sleep sein soll, 
wenn nix anderes zu tun ist.

Aber den Fall z.B. bekomme ich theoretisch schon nicht gelöst: Das 
Programm  ist in der mainloop und macht gerade wirklich was. Jetzt kommt 
ein Timerinterrupt "Messe alle 10 Minuten die Temperatur". Die timer-ISR 
kann dann auch die Messung starten, kein Problem. Nur muß jetzt 750 ms 
gewartet werden, bis es mit der Temperaturauslesung weitergehen kann. In 
der Zeit soll in der mainloop weitergearbeitet werden. Und wenn der 
750ms-timer zuschlägt, soll erstmal mit der Temperaturmessung 
weitergemacht werden. Also andere Prioritäten. Da muß ich morgen mal 
weiterdenken, ob die Cortexe das mit ihren Prioritäten evtl. lösen 
können. Schent aber auch kompliziert zu sein.
Wenn in der mainloop nix los ist, kann der µC in den 750 ms schalfen. 
Der Fall ist ja kein Problem.

von Echt jetzt? (Gast)


Lesenswert?

Echt jetzt? schrieb:
> Wenn das Programm nun z.B. gerade in flag1 arbeitet, kommt ein
> Interrupt. Dort wird etwas gemacht und ein delay von 750 ms ist
> erforderlich (DS18B20).
Falk B. schrieb:
> Schon falsch. Derartig lange Pausen haben in den meisten Programmen,
> erst recht in einem Interrupt NICHTS zu suchen! Siehe
> Multitasking.

Ich habe auch nicht vor,750 ms im Interrupt zu warten. Wenn ich das 
wollte, hätte ich ja gar kein Problem.

Falk B. schrieb:
>> Jetzt möchte ich prüfen, ob in der mainloop etwas zu tun ist. Wenn ja,
>> dort weitermachen, wo vor dem Interrupt aktiv. Wenn nicht, sleep.
>> Alles gut bis hier.
>
> Unfug.

Nix Unfug. War im Eröffnungsthread nicht so gut beschrieben. Der zweite 
Post sollte besser erklären, was ich genau meine.

von Stefan F. (Gast)


Lesenswert?

Echt jetzt? schrieb:
> Wenn nichts zu tun ist, soll der µC schlafen.
> Und das geht (glaube ich) nur, wenn ich mit Interrupts arbeite. Nur dann
> muß wirklich alles über die ISR laufen.

Das sehe ich anders. Wenn er schläft, muss er über einen Interrupt 
aufgeweckt werden. Aber das bedeutet noch lange nicht, dass alles in der 
ISR passieren muss. Ganz im Gegenteil, die ISR kann sogar leer sein.

Irgendein Thread versetzt den µC in den Schlaf-Modus. Sobald ein 
Interrupt rein kommt, wird die leere ISR ausgeführt, und dann läuft das 
Programm genau an der Stelle weiter, wo es angehalten wurde.

von Wolfgang (Gast)


Lesenswert?

Echt jetzt? schrieb:
> Die timer-ISR kann dann auch die Messung starten, kein Problem. Nur muß
> jetzt 750 ms gewartet werden, bis es mit der Temperaturauslesung
> weitergehen kann.

Nein, das Programm muss nicht warten, sondern es kann 750ms etwas 
anderes machen und wenn die 750ms um sind, kann es die Daten vom Sensor 
abholen.

von Stefan F. (Gast)


Lesenswert?

Echt jetzt? schrieb:
> Jetzt kommt
> ein Timerinterrupt "Messe alle 10 Minuten die Temperatur". Die timer-ISR
> kann dann auch die Messung starten, kein Problem.

Das ist schon falsch. Der Timer soll höchstens ein Flag setzen: 
bitteMessen=1.

Einer der Threads ist dafür zuständig, zu messen. Dessen erster Zustand 
wartet einfach darauf, dass bitteMessen==1 ist. Dann startet er die 
Messung und wechselt er in den Zustand MESSEN. 750ms später wertet er 
das Messergebnis aus. Aber ohne delay!

Wenn du einen Millisekunden-Timer hast, brauchst du keinen weiteren 
10-Sekunden Timer. Denn die 10 Sekunden sind 10000ms Intervalle.

Zerlege dein Programm erst einmal in Threads. Alles was wartet, während 
etwas anderes ausgeführt wird, muss ein eigener Thread sein. Du brauchst 
mindestens zwei Threads.

von Stefan F. (Gast)


Lesenswert?

Thread 1:

Zustand: WARTE_10S
Bedingung: 10s Später
Reaktion: Messung starten
Nächster Zustand: MESSEN

Zustand: MESSEN
Bedingung: 750ms später
Reaktion: Messergebnis auswerten
Nächster Zustand: WARTE_10S

-------------------------------------------
Thread 2:

Zustand: FLAG_ABFRAGEN

Bedingung: flag1 ist gesetzt
Reaktion: was auch immer

Bedingung: flag2 ist gesetzt
Reaktion: was auch immer

Bedingung: flag3 ist gesetzt
Reaktion: was auch immer

Bedingung: kein Flag ist gesetzt und Thread1 ist nicht im Zustand MESSEN
Reaktion: schlafen legen

Nächster Zustand: FLAG_ABFRAGEN

------------------------------------------
Hauptschleife:

while (1)
{
   thread1();
   thread2();
}

von Theor (Gast)


Lesenswert?

Zunächst halte ich den Ansatz mit einer State-Maschine, wie Du ihn mit 
flag1, flag2, etc. andeutest für nützlich in diesem Zusammenhang.

Es gäbe eine Reihe Möglichkeiten, wovon ich nicht alle aber die zunächst 
naheliegendste(n) nennen will. (Wiel lange programmierst Du schon im 
Embedded-Bereich und wo soll es hingehen?)

1. Ich würde, notfalls mit grösserem Aufwand, vermeiden wollen, dass bei 
Ablauf des delays der Programmablauf in einem der Fälle flag1 etc. 
"hart" abgebrochen wird.
Dazu kann man die Fälle flag1 usw. feiner aufteilen. Soll heissen, im 
Extremfall bekommt jeder Anweisung seinen eigenen Zustand (flag1a, 
flag1b, usw.) Die Aufteilung muss aber nur so fein sein, dass jeder 
Unterzustand innerhalb der Zeit von 750ms erledigt werden kann, bzw. 
(siehe den Nachsatz) so schnell erledigt werden kann, dass nach den 
750ms noch Zeit für den Rest innerhalb dieses Zustandes ist.
Ob die Methode anwendbar ist, hängt davon ab, ob nach Ablauf der 750ms 
noch eine kleinere Verzögerung akzeptabel ist. Falls das absolut nicht 
geht, lässt sich die Methode nicht anwenden.
Analoges gilt für den Code, der bei flag2 ausgeführt wird.

D.h, falls während der Ababeitung von dem, von einem der Flags flag1a, 
flag1b etc. abhängigen, Code der Timer abläuft, würde in dem Interrupt 
ein weiteres Flag gesetzt und der Code zum weiterschalten des Zustandes 
in dem Code für flag1x würde dieses Flag prüfen bevor er entscheidet, ob 
er in den nächsten Zustand flag1y geht oder (vorerst) ganz aus der 
Zustandsmaschine aussteigt.

Das lässt sich noch variieren, indem man die Unterzustände feiner 
unterteilt, bis die Verzögerung nach den 750ms akzeptabel ist; falls sie 
es überhaupt ist.

2. Ich würde, falls 1. nicht anwendbar ist, dennoch das selbe Ziel 
verfolgen indem ich eine Variante anwende.
Dabei werden die Unterzustände wieder fein unterteilt. Und zwar so fein, 
dass jeder Schritt in einer Zeit deutlich unter den 750ms erledigt 
werden kann. Sagen wir, in 740ms (das hängt von den Befehlen, aber auch 
von der Taktfrequenz ab).
Der Witz ist, dass nun noch 10ms Zeit bleiben um in dem Code von flag1, 
flag2 usw. das Flag für den Ablauf der 750ms abzufragen.

3. Falls 1. und 2. nicht anwendbar sind, würde ich dennoch das selbe 
Ziel verfolgen.
Nun aber, in dem ich grundsätzlich verhindere, dass die Zustandsmaschine 
überhaupt weiter abgearbeitet wird, falls der erste Interrupt in dessen 
Folge dann das delay nötig ist, aufgetreten ist oder die Möglichkeit 
besteht das er auftritt.
Ob das geht, hängt davon ab, ob das auftreten des Interrupts irgendwie 
absehbar ist.

4. Falls 1., 2., und 3. nicht anwendbar sind gäbe es vielleicht noch 
weitere Möglichkeiten. Dazu müsste ich aber wissen, wozu das delay 
überhaupt eingefügt wird und was nun aus Deiner Sicht nötig macht, dass 
grundsätzlich und genau nach 750ms eine bestimmte Aktion erfolgt und was 
diese Aktion ist. Vielleicht geht es ja auch anders. Z.B. indem man das 
delay kürzer macht.

5. Falls die Aktion nach den 750ms nicht allzu lang oder komplex ist, 
könnte man sie auch innerhalb des Interrupts auslösen. Genau genommen, 
könnte man das auch machen, wenn die Aktion sehr lange dauert. Es gilt 
zwar die Daumenregel, dies zu unterlassen, aber das gilt nur für Fälle 
in denen das Verweilen im Interrupt unerwünschte Seiteneffekte hat. Die 
Daumenregel gilt strenggenommen nur für Anfänger, die noch nicht 
überblicken können, welche Folgen das hat.

6. Als letzer Ausweg bleibt noch, den Stack zu manipulieren, also die 
Rückkehradresse aus dem Interrupt. Dazu musst Du Maschinensprache 
verwenden. Auch nichts für Anfänger.

Aber Punkt 4. ist noch ein Hoffnungsschimmer.

von Stefan F. (Gast)


Lesenswert?

Wenn du auch während der Warteschleifen schlafen willst, könntest du 
auch den Ansatz fahren, dass am Ende der Hauptschleife immer in den 
sleep Modus gewechselt wird und ein Timer jede Millisekunde den Schlaf 
unterbricht.
1
void loop()
2
{
3
    thread1();
4
    thread2();
5
    sleep();
6
}

Dadurch guckt er quasi im Millisekunden-Intervall nach, ob es etwas zu 
tun gibt. Wenn nicht, legt er sich wieder hin. Um weiter Strom zu 
sparen, kann man auch mit größeren Intervallen arbeiten.

von Theor (Gast)


Lesenswert?

@ Echt jetzt?

Das Wort "delay" ist hier etwas unglücklich gewählt, da man darunter 
gemeinhin ein busy-waiting versteht. Das aber ist wohl nicht Deine 
Absicht, denn Du sprichst ja davon, einen Timer zu verwenden, der nach 
der "Wartezeit" einen Interrupt auslöst.

OK.

Während ich meinen Beitrag schrieb hast Du noch etwas ergänzt.

Wie ich dem Datenblatt des DS18B20 entnehme, ist es nicht zwingend 
erforderlich, das Ergebnis nach 750ms abzuholen.
Vielmehr finde ich die Angabe, dass dies die Maximalzeit für eine 
Messung ist,
D.h. du kannst lediglich kein Ergebnis zu einer früheren Zeit 
erwarten. Später geht immer. Also: ob Du das Ergenmos nach 1s oder 1a 
abholst spielt keine Rolle.

Das dürfte das ganze Problem hinfällig machen (es sei denn es gibt noch 
Bedingungen von denen Du bisher nichts gesagt hast).

Du holst die Temperatur einfach in einem der Fälle Deiner 
Zustandsmaschine, sobald sie da ist.

Temperaturmessungen in so kurzen Intervallen sind im allgemeinen auch 
nicht notwendig. Aber das mag in Deinem Fall nicht zutreffen.

von Falk B. (falk)


Lesenswert?

Echt jetzt? schrieb:

> Verzetteln ist wohl das richtige Wort.

In der Tat.

> Das Prinzip paßt aber auf meinen Anwendungsfall nicht bzw. glaube ich
> das.

Du irrst dich.

> Da ich batteriebetrieben arbeiten will/muß, ist Stromsparen
> Pflicht.

Sicher.

> Strategie daher: Wenn nichts zu tun ist, soll der µC schlafen.

Gut.

> Und das geht (glaube ich) nur, wenn ich mit Interrupts arbeite.

Ja, aber.

> Nur dann
> muß wirklich alles über die ISR laufen. Glaube ich.

Naja.

> Zum Thema Verzetteln: Ich habe schon an FSM gedacht und den
> "dynamischen" Teil dann in den ISR mit Funktionszeigern abzubilden
> gedacht.

Mach es nicht unnötig kompliziert. Funktionszeiger sind nur eine 
Möglichkeit, eine FSM umzusetzen.

> Aber das wird dann sehr schnell sehr komplex. Und bei
> Änderungen: Brain Overflow...

Falsch. Eben WENN man eine FSM verstanden hat und diese nicht mit Zuviel 
akademischem SchickiMicki umsetzt, wird es deutlich EINFACHER!

> Auch das (zu recht) verhaßte goto kam mir schon in den Sinn. Aber auch
> dafür gilt das Gleiche.

Käse^3.

> Einen gewissen Teil kann ich mit den mir bekannten Mitteln auch lösen.
> Also z.B. den Fall, daß im o.g. Beispiel die 750 ms sleep sein soll,
> wenn nix anderes zu tun ist.

Trivial. Und auch sehr stromsparend umsetzbar.

> Aber den Fall z.B. bekomme ich theoretisch schon nicht gelöst:

Weil du das Grundkonzept nicht verstanden hast. Siehe Multitasking. 
Lesen, Nachdenken, Verstehen. Ggf. mehrfach.

 Das
> Programm  ist in der mainloop und macht gerade wirklich was. Jetzt kommt
> ein Timerinterrupt "Messe alle 10 Minuten die Temperatur". Die timer-ISR
> kann dann auch die Messung starten,

Schon falsch. Sie kann, in den meisten Fällen sollte sie das aber nicht. 
Sie Setzt nur Flags, welche der FSM dann die Anweisung geben, die 
Messung zu starten.

> kein Problem. Nur muß jetzt 750 ms
> gewartet werden, bis es mit der Temperaturauslesung weitergehen kann.

Ja, aber WIE! Nicht mit einem delay irgendwie, weder in der ISR noch in 
der Hauptschleife. Sondern in einem State WAIT einder FSM. Dort wird 
hochgezählt, bis dein 750ms um sind.

> In
> der Zeit soll in der mainloop weitergearbeitet werden.

Kann man, wenn man die FSMs passend aufteilt.

> Und wenn der
> 750ms-timer zuschlägt, soll erstmal mit der Temperaturmessung
> weitergemacht werden. Also andere Prioritäten.

Nö. Dann wird die Temperaturmessung, genauer, das Auslesen der 
Ergebnisse AUCH bearbeitet.

> Da muß ich morgen mal
> weiterdenken, ob die Cortexe das mit ihren Prioritäten evtl. lösen
> können. Schent aber auch kompliziert zu sein.

Nö, dein Problem ist das noch nicht Verstehen des Grundkonzepts. Siehe 
oben.

> Wenn in der mainloop nix los ist, kann der µC in den 750 ms schalfen.

Das tut er so oder so REGELMÄßIG!

von Lutz (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Bevor du dich da total verzettelst, finde einen anderen Weg ohne
> delay().
>
> Lies das: http://stefanfrings.de/multithreading_arduino/index.html

Ist zwar schon etwas älter, aber noch aktuell und Du liest hier ja mit:
Möchtest Du das noch mal überarbeiten?
1
void thread_rot()
2
{
3
    static enum {AUS, EIN} zustand = AUS;
4
    static unsigned int intervalle = 0;
5
6
    intervalle = intervalle + 1;
7
    switch (zustand)
8
    {      
9
        case AUS:
10
            if (intervalle = 10)                 // Bedingung: 100 ms später
11
            //            ^^^
12
            {
13
                digitalWrite(LED_ROT, HIGH);
14
                intervalle = 0;  
15
                zustand = EIN;
16
            }          
17
            break;
18
19
        case EIN: 
20
            if (intervalle = 10) 
21
            //            ^^^
22
            {
23
                digitalWrite(LED_ROT, LOW); 
24
                intervalle = 0;  
25
                zustand = AUS; 
26
            }          
27
            break;
28
    }
29
}

von Stefan F. (Gast)


Lesenswert?

Lutz schrieb:
> Möchtest Du das noch mal überarbeiten?

ja gerne doch! Watt'n Schiet.

von Lutz (Gast)


Lesenswert?

Das nenn ich mal flott!!!

von Stefan F. (Gast)


Lesenswert?

War Zufall.

Da habe ich doch glatt das > falsch geschrieben.

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.