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.
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.
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.
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.
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.
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.
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.
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();
}
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.
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
voidloop()
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.
@ 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.
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!