Forum: Mikrocontroller und Digitale Elektronik Arduino: Handling parallel anfallender Aufgaben


von Robert M. (robert_m949)


Lesenswert?

Moin,
ich gebe zu, den Text zu schreiben war leichter, als einen 
aussagekräftigen Titel zu finden. Ich hoffe, ich habe nicht einfach nur 
nicht die richtigen Suchbegriffe im Kopf, um eine Lösung zu googlen :-/

Ich versuche mal, meine Problemstellung abstrakt zu beschreiben, um euch 
nicht unnötig mit fachlichen Nebensächlichkeiten zu belästigen. Bei 
Bedarf kann ich aber natürlich auch gern Details liefern.

Folgende Ausgangssituation: Über einen Arduino Mega steuere ich diverse 
Relais an, die dann u.A. Elektromagneten steuern. Der Anwendungsfall 
erfordert dabei regelmäßig Pausen, also z.B.

1. Releais 1 durchschalten (=> PIN X = HIGH)
2. Delay(500)
3. Relais 1 aus
4. Delay(10000)
5. Relais 2 durchschalten
6. Delay(500)
...


Stand jetzt passiert das in der loop Funktion. Die läuft dadurch recht 
lang, das störte bislang aber auch nicht.
In einer späteren Ausbaustufe soll noch eine Überwachung des Systems 
durch von Sensoren ausgelöste Interrupts implementiert werden.

Nun möchte ich in den Ablauf gerne per Netzwerk eingreifen können: Ich 
will per HTTP Anfrage ebenfalls Steuerbefehle absetzen (zB ebenfalls 
einen Magneten für 500ms anschalten). Diese sollen dann "direkt" 
durchgeführt werden. In der aktuellen loop Fkt (ein mal) auf neue 
Requests zu prüfen, ist also kein valider Ansatz.

Überlegt habe ich folgendes:

Variante 1
In der loop Fkt bei "jeder Gelegenheit" auf Requests prüfen und ggfs. 
ausführen. Also z.B. statt dem o.g. 10s Delay 10x jeweils nur 1s Delay 
und dann auf Requests prüfen. Requests dann an passender Stelle 
abarbeiten.
+ sehr einfach unzusetzen
- Fühlt sich gehackt an
- Verzögert die eigentliche "Geschäftslogik" trotzdem

Variante 2
Ich verändere die Architektur des Systems: Die Logik wird aus dem 
Arduino raus in eine eigene Anwendung verlagert, die dann auch von der 
UI angesprochen wird. Der Arduino nimmt per HTTP nur noch einzelne 
Befehle vom neuen Backend entgegen (Magnet X für n ms an), die mit jeder 
loop ausgelesen und umgesetzt werden können.
+ recht einfach umzusetzen
+ Wäre die bevorzugte Lösung, wenn meine Anforderungen anders nicht 
vernünftig abgebildet werden können
- erfordert weitere dauerhaft verfügbare Hardware für das neue Backend
- Logik und Umsetzung liegen auf zwei Systemen. Mein Bauchgefühl sagt, 
dass das Debugging unangenehmer wird

Variante 3
Ich nutze einen Timer, um regelmäßig nach neuen Requests zu gucken. Das 
wäre dann aber auch nicht viel anders als ein asynchron laufender 
Webserver, siehe Variante 4

Variante 4
Ein asynchroner Webserver, der die Requests entgegen nimmt und 
abarbeitet.
+ wenig Aufwand
- Wenn ein Magnet zB für 1s geschaltet wird, müsste dort ein 
entsprechendes Delay aufgerufen werden (geht das überhaupt?). Solange 
wäre dann die loop Fkt blockiert, ein zuvor aktiviertes Relais wäre dann 
ggfs. zu lange aktiv.

Variante 5
Ich führe eine Liste von "Jobs", also einem Tupel aus 
Ausführungszeitpunkt t und einem Task T (zB Magnet A anschalten, etc.).
loop() prüft, ob der nächste Job in der Vergangenheit liegt und führt 
ihn dann aus. Jobs werden erzeugt
1. in setup()
2. aus einem speziellen Task heraus: bislang hat die loop Methode die 
Logik ja immer wieder neu gestartet. Da das jetzt entfällt, lege ich als 
letzten Job einen Task an, der alle Jobs für den automatischen Ablauf 
erneut erzeugt.
3. durch einen (asynchronen) Webserver, der die HTTP Requests umsetzt.

+ Zumindest in der Theorie eine vollständige Lösung, klingt für mich am 
vielversprechendsten
- Aufwendigste Lösung. Insbesondere, da aus höheren Programmiersprachen 
gewohnter Komfort wie Listen mit dynamischer Länge oder automatischer 
Sortierung fehlt. Einen Semaphor oÄ bräuchte man auch, damit die Liste 
nicht vom Webserver und der loop Methode parallel manipuliert wird.

Vielleicht übersehe ich auch irgendwas und das geht viel leichter? Ich 
bin schließlich nicht der erste mit so einem Problem? Dann immer her mit 
den Ideen.

Beruflich komme ich aus der Java Ecke. Entgegen der herrschenden 
Vorurteile lösen wir da nicht jedes  Problem mit mehr RAM oder einer 
schnelleren CPU  - aber es ist zumindest immer eine valide Option ;)

 Gruß
 RoCMe

von Ein Kommentar (Gast)


Lesenswert?

Tja, das Arduino Konzept ist genial, solange du im Mainloop millis() 
abfragst und die Arbeit verteilst. Wenn das nicht mehr ausreicht, 
verursacht die Arduino Lib mehr Probleme, als sie löst.

Einen zusätzlichen Vorschlag hätten wir noch. Wenn die 
Echtzeitanforderungen nichts zu hoch sind - eine Linux-Platine. Da 
kannst du sogar einen der Java Webserver benutzen.

von Harry L. (mysth)


Lesenswert?


: Bearbeitet durch User
von amDelayErkenntManDenAnfänger (Gast)


Lesenswert?

Dein Suchbegriff ist nonblocking delay

von J. S. (jojos)


Lesenswert?

es gibt auch genug Arduino Cores mit eingebautem RTOS: die ESP haben 
FreeRTOS drin, die Mbed Cores haben RTX drin und sind z.B. im RP2040, 
Nano33 oder Portenta. Gut, letzterer ist etwas teuer.

von Hermann Kokoschka (Gast)


Lesenswert?

Sowas kann man auch prima mit einer "State-Maschine" innerhalb eines 
ausreichend-schnellen Timer-INT lösen.

von scheißdanixdannfeitdanix (Gast)


Lesenswert?

Schreib dir ne eigene Delay-Routine.
Sobald millis() einen anderen Wert hat als zB millis2 hat zählst du in 
deiner eigenen Delay-Routine alle Variablen rauf. Dann setzt du 
millis2=millis().
Gleichzeitig führst du bei jedem Durchlauf von deiner eigenen 
Delay-Routine alle wichtigen Funktionen weiter aus, die dann nicht 
blockiert werden. Fertig

Dafür brauchts keine Lib oder ein kompliziertes OS.

von EAF (Gast)


Lesenswert?

Robert M. schrieb:
> Vielleicht übersehe ich auch irgendwas und das geht viel leichter? Ich
> bin schließlich nicht der erste mit so einem Problem? Dann immer her mit
> den Ideen.

Ich verwende eine vereinfachte Form von dem Adam Dunkels seinen 
Protothreads.

von Uwe K. (ukhl)


Lesenswert?

Zur Anregung und selbst draufkommen: Benutze keine delay Anweisung. 
Tipps dazu hat Harry schon geliefert:

Beitrag "Re: Arduino: Handling parallel anfallender Aufgaben"

Ein Timer der jede Millisekunde eine Variable hochzählt, sollte zur 
Lösung führen. Dann hat man genug Freiraum in der Mainroutine für einen 
Web-Server.

von Falk B. (falk)


Lesenswert?

Siehe Multitasking.

von Falk B. (falk)


Lesenswert?


von Grummler (Gast)


Lesenswert?

Robert M. schrieb:

> Variante 1
> In der loop Fkt bei "jeder Gelegenheit" auf Requests prüfen
> und ggfs. ausführen. Also z.B. statt dem o.g. 10s Delay 10x
> jeweils nur 1s Delay und dann auf Requests prüfen. Requests
> dann an passender Stelle abarbeiten.
> + sehr einfach unzusetzen
> - Fühlt sich gehackt an

Tatsächlich?

Aber es fühlt sich für Dich nicht "gehackt" an, den Begriff
des Algorithmus (also einer Vorschrift, die nach endlich
vielen Schritten terminiert) zwanghaft auf einen permanent
laufenden Prozess (wie zum Beispiel das Steuern eines
Elektrokraftwerkes oder einer Produktionslinie) anzuwenden?


Wo finde ich übrigens an einer Turing-Maschine die I/O-Ports?

von Grummler (Gast)


Lesenswert?

Uwe K. schrieb:

> Zur Anregung und selbst draufkommen: Benutze keine delay Anweisung.

Ist zwar notwendig, aber noch nicht hinreichend.

Allgemeiner: Keine Warteschleifen benutzen -- also auch nicht
darauf warten, dass ein bestimmter Input-Port einen bestimmten
Wert annimmt.

von J. S. (jojos)


Lesenswert?

so kompliziert ist weiterer Task auch nicht:
1
void io_thread_fn() {
2
  // setup
3
  DigitalOut  led(p6);
4
5
  // loop
6
  while(true) {
7
    led = !led;
8
    ThisThread::sleep_for(100ms);
9
  }
10
}
11
12
Thread io_thread;
13
14
void setup() {
15
  io_thread.start(io_thread_fn);
16
}
17
18
void loop() {
19
  // tu irgendwas
20
}

Ein RPi Pico hat genug Ressourcen dafür.

: Bearbeitet durch User
von Ein Kommentar (Gast)


Lesenswert?

> Dafür brauchts keine Lib oder ein kompliziertes OS.

Dafür nicht, aber kommen beim Webserver immer mehr Anforderungen dazu? 
WLAN, Fehlermeldungen, Datenlogger....

Relay und 500ms sehen dagegen recht Problemlos aus.

Sieht eher so aus, als sollte man das Problem von der umgekehrten Seite 
anpacken. Lib oder OS für den Webserver auswählen, über USB nachdenken 
und eine Hardware auswählen, an die man auch ein Relais anschließen 
kann.

von noiasca (Gast)


Lesenswert?

"Blink without delay" wurde bereits genannt.
Lass mal 3 LEDs unabhängig voneinander blinken, dann klärt sich auch der 
Relais-Ablauf, der Webserver und das 500ms Gepulse von deinem Relais.

von Heinz (Gast)


Lesenswert?

ein sehr einfacher Weg:

loop()
{
  //lässt alle 10ms jeden Task 1x laufen
  Task1();
  Task2();
  Task3();
  delay(10);
}

In den einzelnen Tasks keine Delays nutzen (!!!) sondern 
Zustandsmaschinen und
Timervariablen hochzählen

Task1()
{
  //inkrementiert ca. alle 10ms
  timer1++;
  if ( timer1 == 100 )
  {
     timer1 = 0;
     //mache jede Sekunde irgendwas...
  }
}

Wenn du ein genaueres Timing willst, kannst du beim Eintritt in die
loop Funktion millis abfragen und am Ende auch. Dann weißt du, wie lange
ein Durchlauf gedauert hat und du kannst die Differenz zu 10ms gezielt 
warten.

Das ist sehr einfach und rudimentär und auch nicht sehr genau, reicht
zumindest bei meinen Projekten aber bisher immer aus.

von Falk B. (falk)


Lesenswert?

Heinz schrieb:
> Das ist sehr einfach und rudimentär und auch nicht sehr genau, reicht
> zumindest bei meinen Projekten aber bisher immer aus.

Tja, dann kannst auch du noch ein wenig lernen, wie man sehr einfach die 
berühmt-berüchtigte millis() Funktion benutzt und damit recht genaue 
Zeitintervalle hinkriegt, ganz ohne Schätzung und Handstände.

Beitrag "Re: mehrere Millis()"

von J. S. (jojos)


Lesenswert?

schrieb Heinz doch das man die millis für genaueres Timing benutzen 
kann.

Der Haken ist eher das es kooperatives Multitasking ist, alle müssen 
brav mitspielen. Wenn ein Display aber z.B. 50 ms für einen Refresh 
braucht kann da nicht ein Sensor zyklisch mit 10 ms gelesen werden.

Variante 4 mit AsyncWebserver ist elegant. Mit einer EventQueue kann man 
das asynchrone Konzept dann weiterführen, so wie es JavaScript auch 
macht.
1
Thread event_thread;
2
events::EventQueue queue;
3
DigitalOut relais(p10);
4
5
void setup() {
6
  event_thread.start(callback(&queue, &events::EventQueue::dispatch_forever));
7
8
  // webserver startet dies um ein Relais 2s einzuschalten
9
  relais = 1;
10
  queue.call_in(2s, [](){ 
11
        relais = 0;
12
      }
13
    );
14
}

Es können beliebige Funktionen in die Q geworfen werden, call_in() ruft 
die Funktion dann nach der angegebenen Zeit auf. Die Funktion selber 
läuft im event_thread, könnte aber auch in der loop() laufen. Q calls 
dürfen auch aus einer ISR erfolgen, damit kann man also ein printf aus 
einer ISR starten.
Das geht wie gesagt z.B. beim RPi Pico und sollte auch beim Pico 
W(ireless) funtkionieren, da hätte man dann auch den Webserver. Ich 
werde es ausprobieren.

von Robert M. (robert_m949)


Lesenswert?

Moin,

vielen Dank fur die schnellen und zahlreichen Antworten.

Ganz generell: Ich will aus Gründen beim Arduino bleiben, hätte ich 
vielleicht noch mal erklären sollen.

Die meisten Vorschläge (Analogie zu "3 LEDs unabhängig blinken lassen", 
State Machine, etc.) intepretiere ich zusammengefasst so, dass ich mir 
für jedes Relais / jeden Magneten merke, wann er das nächste mal seinen 
Zustand wechseln soll und dann in loop jeweils prüfe, ob etwas geändert 
werden muss.

Das hatte ich als "Variante 0" im Kopf, habs aber direkt wieder 
verworfen :D

Hintergrund: Eigentlich gibt es selten was zu tun, die meiste Zeit würde 
also unnötig geprüft, ob man nun endlich mal den Zustand ändern kann. Da 
klang für mich Neuling die Verwendung von delay als Mittel der Wahl. 
Energiesparen ist ja aktuell in und so ;)

Aber vermutlich ist das einfach ein Unterschied zur Entwicklung in 
"großen" Rechnern, in denen es inzwischen viele Möglichkeiten für 
Parallelisierung gibt.

Letztlich ist meine Variante 5 ja auch nur eine Variante dieses 
Vorschlags:

Statt bei jedem Durchlauf für jedes Relais zu prüfen, ob ein Status 
geändert werden muss, merke ich mir, wann das nächste mal überhaupt 
etwas geändert werden muss (und was). Damit entfallen unnötige 
Prüfungen, und ich kann länger idlen.

Wobei, vielleicht ist das auch mein Denkfehler: Ich habe das Delay 
bislang verstanden als "leg dich schlafen, ich weck dich, wenn du weiter 
machen sollst." Wenn da intern nichts anderes als ne Zählshleife 
durchläuft, bringt das auch keinerlei Einsparungen mehr.

von W.S. (Gast)


Lesenswert?

Robert M. schrieb:
> Folgende Ausgangssituation: Über einen Arduino Mega steuere ich diverse
> Relais an, die dann u.A. Elektromagneten steuern. Der Anwendungsfall
> erfordert dabei regelmäßig Pausen,...

Nein, das tut er nicht, sondern die verschiedenen Teil-Aufgaben sollen 
zu unterschiedlichen Zeiten erledigt werden.

Eigentlich ist es ganz einfach: programmiere nicht stur geradeaus, wie 
man es bei einem PAP oder dem Rumpelstilzchen gelernt hat (heute: back 
ich, morgen: brau ich, übermorgen: ...), sondern stelle deine 
Programmierweise um von geradeaus auf ereignisorientiert und schaffe dir 
'verzögerte Ereignisse' oder auf neudeutsch delayed events. Das ist 
einfach ein Mechanismus in der Firmware, dem man Aufträge geben kann zum 
Erzeugen eines Ereignisses nach einer vorgebbaren Zeitspanne.
Etwa so:
bool Add_Delayed_Event (dword millisec, dword aEvent);
Dazu brauchst du eine Systemuhr, also etwas, das eine Uhrzeit in der 
Firmware führt. Die muß nicht mit der tatsächlichen Uhrzeit 
übereinstimmen, aber sie muß die Zeit in sinnvollen Stücken zählen 
können, z.B. 1 ms oder 10 ms - und zwar unabhängig von deinen sonstigen 
Programmteilen. Das bei Arduino-Jüngern allseits beliebte 'millis' ist 
dazu ungeeignet.
Tja und in der Grundschleife in main() fragt man nur die normale 
Event-Warteschlange ab, ob da etwas vorhanden ist und wenn, holt man es 
ab und reagiert sinnvoll darauf.

Mal kurz ein Szenario:
Event 'evBitteLüften' kommt rein
also begin
       MacheFensterAuf();
       Add_Delayed_Event (500, evFensterSchließen);
     end
Dann kommt nach 500 ms der Event 'evFensterSchließen' rein und man kann 
darauf sinnvoll reagieren. In der Zwischenzeit kann man sich um andere 
Dinge kümmern oder in der Grundschleife kreiseln.

W.S.

von J. S. (jojos)


Lesenswert?

RPi Pico und Nano33 sind Arduinos, meine Beispiele sind das Arduino 
Framework, eben die mit Mbed core. Gibt es nur nicht für die kleinen AVR 
Arduinos.
Mein letztes Beispiel macht genau das was W.S. meint, aber in elegant 
und mit eingebauten Mitteln wenn man den richtigen Arduino hat.

: Bearbeitet durch User
von Manfred (Gast)


Lesenswert?

Robert M. schrieb:
> Die meisten Vorschläge (Analogie zu "3 LEDs unabhängig blinken lassen",
> State Machine, etc.) intepretiere ich zusammengefasst so, dass ich mir
> für jedes Relais / jeden Magneten merke, wann er das nächste mal seinen
> Zustand wechseln soll und dann in loop jeweils prüfe, ob etwas geändert
> werden muss.
>
> Das hatte ich als "Variante 0" im Kopf, habs aber direkt wieder
> verworfen :D

Genau so geht das aber nun mal.

Ich renne in der loop herum, bis millisec() größer Zeit(x) ist, setze 
Zeit(x) auf einen neueren Wert und springe dann zu einer Funktion "void 
machewas()".

> Hintergrund: Eigentlich gibt es selten was zu tun, die meiste Zeit würde
> also unnötig geprüft, ob man nun endlich mal den Zustand ändern kann.

Das ist nun mal der doofe Job des µC, mangels Gewerkschaft beklagt der 
sich aber nicht über seine stupide Beschäftigung.

> Da
> klang für mich Neuling die Verwendung von delay als Mittel der Wahl.

Delay ist doof, weil es den Programmablauf blockiert.

> Energiesparen ist ja aktuell in und so ;)

Unfug, ob in der loop herumrennen oder delay zu machen, ändert nichts am 
Stromverbrauch. Mal messen?

Und überhaupt, um welche Größenordnung reden wir denn? Mein aktuell 
vollendeter Aufbau mit ProMini (3V3, LED und Regler entfernt) und etwas 
Peripherie nimmt um die 5 mA aus dem LiIon-Akku, das sind um 20 
MILLIwatt. Hätte ich stattdessen einen Nano oder Uno, würde schon deren 
USB-Baustein die Stromaufnahme mindestens verdoppeln.

von EAF (Gast)


Angehängte Dateien:

Lesenswert?

Robert M. schrieb:
> Wenn da intern nichts anderes als ne Zählshleife
> durchläuft, bringt das auch keinerlei Einsparungen mehr.

Das hast du richtig erkannt!
1
#include <TaskMacro.h>
2
3
4
/**
5
 * Auf Tastendruck wird 5 mal geblinckt 
6
*/
7
8
9
10
11
const byte taster            = 4;   // Taster zwischen Pin und GND
12
const byte led               = 13;  // Led zwischen Pin und GND
13
const unsigned long interval = 500; // ms
14
const byte zyklen            = 5;   // Anzahl Blinker
15
16
bool blinkAnforderung        = false; // Merker für Blink Anforderung
17
18
Task blink() 
19
{
20
  static byte i = 0;  // Wiederholungszaehler
21
  taskBegin();
22
  while(1)
23
  {
24
    taskWaitFor(blinkAnforderung);
25
    for(i=0;i<zyklen;i++)
26
    {
27
      digitalWrite(led, HIGH); // leuchte an
28
      taskPause(interval);
29
      digitalWrite(led, LOW); // leuchte aus
30
      taskPause(interval);
31
    }
32
    blinkAnforderung = false ; // Anforderung konsumieren
33
  }
34
  taskEnd();
35
}
36
37
38
Task tastenAuswertung() 
39
{
40
  taskBegin();
41
  while(1)
42
  {
43
    taskWaitFor(!digitalRead(taster));
44
    blinkAnforderung = true;
45
  }  
46
  taskEnd();
47
}
48
49
50
Task showLoops()
51
{
52
  static unsigned long loops = 0;
53
  loops++;
54
  taskBegin();
55
  for(;;)
56
  {
57
    taskPause(1000);
58
    Serial.print("Loops pro Sekunde: ");
59
    Serial.println(loops);
60
    loops = 0;
61
  }
62
  taskEnd();
63
}
64
65
66
67
68
void setup()
69
{
70
  Serial.begin(9600);
71
  pinMode(led, OUTPUT);
72
  pinMode(taster,INPUT_PULLUP);
73
}
74
75
void loop()
76
{
77
  blink();
78
  tastenAuswertung();
79
  showLoops();
80
}

Wie schon gesagt, nach dem Adam Dunkels seinem Strickmuster.

von Thomas W. (Gast)


Lesenswert?

Robert M. schrieb:

> Wobei, vielleicht ist das auch mein Denkfehler: Ich habe das Delay
> bislang verstanden als "leg dich schlafen, ich weck dich, wenn du weiter
> machen sollst." Wenn da intern nichts anderes als ne Zählshleife
> durchläuft, bringt das auch keinerlei Einsparungen mehr.

Wenn man von einem "richtigen Computer" kommt sind die Einschraenkungen 
eines Mikrocontrollers schon gravierend: delay() [bei Arduino-Framework] 
ist im wesentlichen eine Zaehlschleife, eventuell von TimeTick 
unterbrochen.

"Ich will beim Arduino bleiben" solltest Du etwas besser spezifizieren: 
Wenn Du heute auf die Arduino-Webseite (www.arduino.cc) guckst, hast Du 
kleine Achtbitter (Arduino Uno oder Nano, AVR), M0-ICs (z.B. Arduino 
Nano RP2040, Arduino MKR 1XXX, M0-Kern[e]) oder gar den Portenta H7 
(enthaelt den STM32H747, das ist ein M7-Kern und ein M4-Kern).

Du merkst es gibt viele Moeglichkeiten: Ich (ich ich ich!!!!) finde den 
Arduino Zero als das beste wo es gibt wegen des InCircuit-Debuggers. Ist 
so wie ein StLink fuer STM32. Die geschenkte Lebenszeit ist jeden Cent 
wert.

Gruesse

Th.

von Robert M. (robert_m949)


Lesenswert?

Uiuiui, ihr seid Mal wieder so schnell, ich komme nicht hinterher :D

 @jojos: Sorry, wieder was gelernt :) Ich wusste wohl, dass es 
verschiedene Arsuinos gibt, aber diese Bezeichnungen sagten mir nichts. 
Hätte ich googlen sollen.

 @Manfred Das mit dem Energiesparen war nixht ganz ernst gemeint. Ich 
habe aber wirklich angenommen, dass ein Delay weniger Strom frisst => 
noch mal was gelernt.

Letztlich konnte ich mir einfach nicht vorstellen, dass das 
kontinuierliche Durchlaufen "State of the Art" ist.

Das aktuelle Setup besteht aus einem Arduino Mega (bequem, weil viele IO 
Pins). Das ist wohl nixh wichtig, wenn man Hardware Anpassungen für den 
Moment ausschließen will.

In der loop selbst handlen bekäme ich sicher hin. Aber vielleicht gibt 
es ja doch noch eine elegante Möglichkeit für "delayed events" auf 
dieser Plattform?

von Thomas W. (Gast)


Lesenswert?

Robert M. schrieb:
> Letztlich konnte ich mir einfach nicht vorstellen, dass das
> kontinuierliche Durchlaufen "State of the Art" ist.

Ein sleep() mit PowerDown gibt es noch (mit anderen Problemen und 
Benefits). Aber was eine CPU noch machen?

Je nach Input (also: z.B. Flanken an I/O-Ports, Timer) kannst Du 
Interrupts verwenden (praktisch als Input Deiner State-Maschine in Deine 
grosse Loop).

Oder z.B. kleine RTOS-System (z.B. FreeRTOS). Hat auch seinen Charme, 
sind stabil und braucht nur einen Timer-Interrupt.

Gruesse

Th.

von EAF (Gast)


Lesenswert?

Robert M. schrieb:
> Letztlich konnte ich mir einfach nicht vorstellen, dass das
> kontinuierliche Durchlaufen "State of the Art" ist.

Doch schon....
z.B. Bei einem Arduino Mega, bringt das schlafen nicht all zuviel.
Um eine evtl. vorhandene Batterie zu schonen sollte man ein anderes 
Board wählen.
Dann kann man über schlafen nachdenken.

Robert M. schrieb:
> Aber vielleicht gibt
> es ja doch noch eine elegante Möglichkeit für "delayed events" auf
> dieser Plattform?
Gibt es. Verschiedenste.
Solche Queues wollen allerdings auch angelegt und verwaltet werden.

Das von mir gezeigte Programm schafft auf einem UNO etwas über 72 
Durchläufe pro Millisekunde. Ja, alle 3 Tasks kommen ein mal pro 
Durchlauf an die Reihe.

von Robert M. (robert_m949)


Lesenswert?

Hallo EAF,
das sieht sehr spannend aus, werde mir das morgen Mal genauer ansehen. 
Beim drüber Lesen ist mir nur aufgefallen, dass du in der Loop Methode 
die drei Tasks jedes Mal neu ausführst. Müssten die nicht eigentlich nur 
einmal gestartet werden und dann "von selbst" laufen? Aber vielleicht 
ist es auch einfach nur schon zu spät für mich :)

von Grummler (Gast)


Lesenswert?

Robert M. schrieb:

> Hintergrund: Eigentlich gibt es selten was zu tun, die
> meiste Zeit würde also unnötig geprüft, ob man nun
> endlich mal den Zustand ändern kann. Da klang für mich
> Neuling die Verwendung von delay als Mittel der Wahl.

Hmm.
Eigentlich hätte ich "sleep()" für das Mittel der Wahl
gehalten. Weiss nur nicht, ob Arduino so etwas kennt...

von EAF (Gast)


Lesenswert?

Robert M. schrieb:
> Müssten die nicht eigentlich nur
> einmal gestartet werden und dann "von selbst" laufen?

Da es bei dieser einfachen Kooperativen Variante keinen Scheduler gibt, 
geht es nicht anders. Hier ist loop() der "Scheduler", welcher dafür 
sorgt, dass jeder mal dran kommt.

Habe auch noch eine OOP fähige Variante im Köcher, die eben diesen 
Automatismus implementiert hat.

von EAF (Gast)


Lesenswert?

Grummler schrieb:
> Eigentlich hätte ich "sleep()" für das Mittel der Wahl
> gehalten. Weiss nur nicht, ob Arduino so etwas kennt...

Jain!

Die µC können es.
Lohnt nur nicht wirklich.
Beim Mega ist noch ein ATmega16U2 drauf, und noch so Beiwerk, wie Opamp, 
Regler, Led

von Klaus S. (kseege)


Lesenswert?

Robert M. schrieb:
> In der loop selbst handlen bekäme ich sicher hin. Aber vielleicht gibt
> es ja doch noch eine elegante Möglichkeit für "delayed events" auf
> dieser Plattform?

Ohne Betriebssystem programmiert man völlig anders als mit OS.
"Delayed events" macht man mit "state machines"  Schrittschaltwerken  
Zustandsautomaten (verschiedene Wörter für ein- und dasselbe).

Also einfach ein switch-statement in das Programm loop() eingebaut und 
bei jeder Zustandsänderung wird weitergeschaltet. Mit verschachtelten 
Zustandsautomaten kann man beliebige Abläufe realisieren. Eine 
Zustandsänderung kann zum Beispiel die Änderung des Rückgabewerts der 
Funktion millis() sein. In den Beispielen gibt es "Blink ohne Delay". Da 
wird gezeigt, wie es geht.

Ein großer Vorteil beim Arbeiten mit Schrittschaltwerken ist die 
Tatsache, daß die Ausgabe aller Schrittvariablen genau zeigt, wo das 
Programm gerade steht. Hilft ungemein bei der Fehlersuche.

Gruß Klaus (der soundsovielte)

von W.S. (Gast)


Lesenswert?

Manfred schrieb:
> Ich renne in der loop herum, bis millisec() größer Zeit(x) ist,...

und was machst du, wenn 2 oder mehr Aktionen zu unterschiedlichen Zeiten 
getan werden müssen? Wenn also sich die Zeitspannen quasi überlappen?

Nein, das ist alles Geradeausprogrammierung - und die führt immer zu 
unsäglichen Komplikationen, wenn mehr als nur jeweils eines getan werden 
soll.

Die Lösung ist eben, einen zweiten Akteur zu haben, was in meinem 
Beispiel eben der Uhrtick ist, der ganz einfach neben dem Hauptprogramm 
läuft und zum rechten Zeitpunkt eine Mitteilung schickt "jetzt ist es 
Zeit, dies (oder das) zu tun".

Die nächste und weitaus aufgebohrtere Version wäre ein Multitasksystem, 
aber sowas eher fettes braucht man in einer durchschnittlichen 
Mikrocontroller-Anwendung zumeist nicht.

Nochwas: Dieses Proto-Zeugs von Adam Dunkel ist eine mittelgroße 
Katastrophe, denn es ist im Grunde nur eine Präprozessor-Akrobatik und 
dahinter steckt eine switch-case-Anweisung. Eigentlich nur eine 
Verkleidung, so daß man das C-Konstrukt dahinter nicht mehr erkennen 
kann. Dafür würde ich den Adam teeren und federn.

W.S.

von Εrnst B. (ernst)


Lesenswert?

Robert M. schrieb:
> @Manfred Das mit dem Energiesparen war nixht ganz ernst gemeint. Ich
> habe aber wirklich angenommen, dass ein Delay weniger Strom frisst =>
> noch mal was gelernt.

Wenn du willst, dass "delay(...)" Strom spart: Das kannst du leicht 
nachrüsten, ist extra im (AVR-)Arduino-Core vorgesehen.

"delay" ruft wiederholt "yield()" auf, während es wartet. "yield()" ist 
als Weak-Funktion definiert, d.H. extra zum Überschreiben aus deinem 
Code, und ist normalerweise leer.

Da kannst du irgendwelche Hintergrund-Sachen drin erledigen, oder auch 
einen passenden Stromspar-Sleepmode aktivieren. Du darfst nur nicht so 
tief schlafen, dass der "millis"-Timer die CPU nicht mehr wecken kann...

Vorsicht: Andere Cores können das anders handhaben, könnte mir z.B. 
vorstellen dass die ESP8266-Arduino-Cores das yield schon anderweitig 
belegt haben...

: Bearbeitet durch User
von Manfred (Gast)


Lesenswert?

W.S. schrieb:
> Manfred schrieb:
>> Ich renne in der loop herum, bis millisec() größer Zeit(x) ist,...
>
> und was machst du, wenn 2 oder mehr Aktionen zu unterschiedlichen Zeiten
> getan werden müssen? Wenn also sich die Zeitspannen quasi überlappen?

Ich verstehe nicht, was Du mir damit sagen willst. Für mehrere Dinge 
habe ich eben mehrere Zeiteinträge, deren Ablauf in der loop geprüft 
wird.

Da blinken zwei LEDs, alle paar Sekunden wird eine Spannung geholt und 
ins Display geschrieben, alle xx Sekunden auf die SD-Karte, die Taster 
werden abgefragt - immer im Kreis herum.

Wenn eine Zeit um ist, geht es in die Unterroutine, die den Befehl 
ausführt.  Während dessen Ausführung gibt es natürlich keine Abfrage der 
anderen, aber ich sehe keinen Weg, das zu verhindern - der µC ist nunmal 
ein seriell ablaufendes Gebilde.

Damit ergibt sich vielleicht ein Versatz der Zeiten, z.B. alle 15 
Sekunden können auch mal 15,1 Sekunden sein. Die absolute Folge läuft 
aber nicht weg, wenn man es geschickt macht.

von Grummler (Gast)


Lesenswert?

W.S. schrieb:

> Manfred schrieb:
>> Ich renne in der loop herum, bis millisec() größer
>> Zeit(x) ist,...
>
> und was machst du, wenn 2 oder mehr Aktionen zu
> unterschiedlichen Zeiten getan werden müssen?

Dann gibt es mehrere Timer-Abfragen mit "if".


> Wenn also sich die Zeitspannen quasi überlappen?

???

Die ZeitSPANNEN, in denen nix passiert, sind völlig egal.

Interessant sind nur die ZeitPUNKTE, zu denen sich irgend
etwas ändert.


> Nein, das ist alles Geradeausprogrammierung

Ich denke, Du hast Manfred missverstanden.

Und -- ja, innerhalb der SPS-Hauptschleife ist das ein
Geradeaus-Programm: Eine ellenlange Abfolge der Form
"Prüfe Bedingung; wenn erfüllt, dann gib dies und jenes
aus."

von W.S. (Gast)


Lesenswert?

Manfred schrieb:
> Ich verstehe nicht, was Du mir damit sagen willst.

Ich versuche mal, es dir zu erklären:
Wenn du nur einen einzigen Ablauf hast, dann kannst du dir in aller Ruhe 
die Zeit bis zur nächsten Aktion ausrechnen, also
- Aktion
- Warten
- nächste Aktion
usw.
Wenn du aber mehrere Abläufe hast, die nicht aneinander gekoppelt sind 
(was wiederum einen einzigen größeren Ablauf darstellen würde), dann 
hast du zwar berechenbare Wartezeiten innerhalb jedes dieser Abläufe, 
aber da die Abläufe nicht miteinander gekoppelt sind, gibt es auch keine 
vorher berechenbare Zeitspanne zwischen einer Aktion in einem Ablauf und 
einer anderen Aktion in einem anderen Ablauf. Du müßtest für jeden 
Ablauf eine separate Zeitsteuerung vorsehen oder diese Zeitsteuerung für 
alle Abläufe eben komplett als separate Funktion auskoppeln. Im 
einfachsten Fall ist es das, was ich vorgeschlagen habe, im andern 
Extrem ist es das RTOS, was den Abläufen (sprich Threads) die Rechenzeit 
häppchenweise zuteilt.

Nun klaro?

W.S.

von Grummler (Gast)


Lesenswert?

W.S. schrieb:

> Ich versuche mal, es dir zu erklären:
> Wenn du nur einen einzigen Ablauf hast, dann kannst du
> dir in aller Ruhe die Zeit bis zur nächsten Aktion
> ausrechnen,

Das geht auch, wenn man mehrere Abläufe hat: Bei jeder
Aktion eines bestimmten Ablaufes kann man ausrechnen,
zu welchem Zeitpunkt die nächste Aktion in diesem Ablauf
notwendig wird.


> Wenn du aber mehrere Abläufe hast, die nicht aneinander
> gekoppelt sind (was wiederum einen einzigen größeren
> Ablauf darstellen würde), dann hast du zwar berechenbare
> Wartezeiten innerhalb jedes dieser Abläufe,

Korrekt.
Genau das schrieb ich.

Naja, nicht ganz: Interessant ist nicht, wie lange man
warten muss , sondern interessant ist, zu welchem
Zeitpunkt in der Zukunft irgend eine Aktion notwendig
ist.


> aber da die Abläufe nicht miteinander gekoppelt sind, gibt
> es auch keine vorher berechenbare Zeitspanne zwischen einer
> Aktion in einem Ablauf und einer anderen Aktion in einem
> anderen Ablauf.

Das ist doch auch völlig unnötig: Da man jederzeit auf seine
Armbanduhr gucken kann, ist es niemals erforderlich zu sagen:
"Wir müssen jetzt 10 Sekunden warten" -- man kann das immer
umformen zu: "Jetzt ist es 27 Uhr 83:20 -- die nächste Aktion
ist also 27 Uhr 83:30 notwendig."

Und genau auf diese Bedingung(en) prüft man.


> Du müßtest für jeden Ablauf eine separate Zeitsteuerung
> vorsehen oder diese Zeitsteuerung für alle Abläufe eben
> komplett als separate Funktion auskoppeln.

Nein!

Man löst sich einfach von der Vorstellung, dass das WARTEN
das Wesentliche wäre, das eine genau bestimmte Zeitdauer lang
stattzufinden hätte, und versteht, dass die nächste AKTION
das Entscheidende ist, die zu einem bestimmten, in der Zukunft
liegenden ZEITPUNKT stattzufinden hat!


Und in der Hauptschleife prüft man dann einfach alle diese
Bedingungen für die Aktionen ab, und welche Bedingung erfüllt
ist, deren Aktion wird ausgeführt. Ganz einfach.

von W.S. (Gast)


Lesenswert?

Grummler schrieb:
> Und in der Hauptschleife prüft man dann einfach alle diese
> Bedingungen

Eben.
Etwa so:
if (Event_available())
  { E = Get_event();
    Dispatch_event(E);
  }

Womit wir wieder am Ausgangspunkt wären. Für die Vergleiche der 
Aktions-Zeitpunkte ist ein Vergleich mit der aktuellen Zeit (mit deiner 
Taschenuhr sozusagen) notwendig, weswegen eine Systemuhr in der Firmware 
benötigt wird. Folglich kann man all dieses Gucken auf die Taschenuhr 
und die Vergleiche mit den nächten Aktions-Zeitpunkten auch aus dem 
Grundprogramm herausnehmen und der Systemuhr als Zusatzaufgabe geben. 
Eben das, was ich bereits weiter oben geschrieben habe.

W.S.

von Harry L. (mysth)


Lesenswert?

Das artet mal wieder (leider typisch) in akademische Diskussionen aus.

Dem TO ist damit sicher nicht geholfen.

Der ist offensichtlich absoluter Anfänger in der Materie, und sollte zum 
Einstieg sich wirklich "Blink without Delays" als Erstes anschauen!

Danach den (wirklich guten) Artikel über Statemachines hier im Forum 
lesen. (und verstehen!)

Und DANN kann man über irgendein RTOS reden...

von Grummler (Gast)


Lesenswert?

W.S. schrieb:

> Eben.
> Etwa so:
> if (Event_available())
>   { E = Get_event();
>     Dispatch_event(E);
>   }

Naja...


> Für die Vergleiche der Aktions-Zeitpunkte ist ein Vergleich
> mit der aktuellen Zeit (mit deiner Taschenuhr sozusagen)
> notwendig, weswegen eine Systemuhr in der Firmware benötigt
> wird.

Beides korrekt.


> Folglich kann man all dieses Gucken auf die Taschenuhr
> und die Vergleiche mit den nächten Aktions-Zeitpunkten
> auch aus dem Grundprogramm herausnehmen und der Systemuhr
> als Zusatzaufgabe geben.

Man kann -- aber man muss nicht .


> Eben das, was ich bereits weiter oben geschrieben habe.

Nicht ganz.
Du schriebst von "Events" und "Warteschlange". Das
verträgt sich nur bedingt mit Echtzeitfähigkeit;
deswegen funktionieren klassische SPS auch nicht so.

von Grummler (Gast)


Lesenswert?

Harry L. schrieb:

> Das artet mal wieder (leider typisch) in akademische
> Diskussionen aus.

"Themenfremd" und "Selbstdarstellung" fehlt noch.

Tu' Dich bitte mit Peter im "Verein rechthaberischer
Oberlehrer" zusammen -- aber verschone dieses Forum
mit der Benotung fremder Beiträge, um die niemand
gebeten hat.


Wenn Du
Beitrag "Re: Arduino: Handling parallel anfallender Aufgaben"
akademisch findest, solltest Du Dich auf die Alternative
"Knete oder Buntstifte?" beschränken.


> Dem TO ist damit sicher nicht geholfen.

Das kann der TO sicher ganz gut allein beurteilen.

von Robert M. (robert_m949)


Lesenswert?

Moin,
TO hier :)

ja, die Diskussion hat mich zwischenzeitlich etwas abgehängt. Das liegt 
aber vermutlich weniger an der Thematik, da hoffe ich, zuminfest grob 
mitzukommen, und mehr an der Schlagzahl hier ;)

Ich bin sicher ziemlicher Einsteiger. Das Beispiel mit den LEDs kannte 
ich aber durchaus, hatte das zugrundeliegende Konzept wie erwähnt aber 
aus den falschen Gründen verworfen.
Den genannten Artikel zu State Machine habe ich nur überflogen, behaupte 
aber, dass mein Vorwissen dazu noch ausreicht :)

Also: Ne Nacht lang drüber geschlafen und zu dem vorläufigen Entschluss 
gekommen, es alleine zu machen. Vielleicht doch kurz grob die 
Fachlichkeit erklärt, dann wird es hoffentlich verständlicher:

Es geht um das Steuern einer (sehr kleinen) analogen Modellbahn mit ein 
paar geerbten Zügen. Ich habe ein paar Gleise, Weichen und Signale 
gekauft und ein Regal gebaut, auf dem sie hin- und herfahren können.

In einer ersten Ausbaustufe wurde das ganze ausschließlich manuell 
bedient.

Dann wurde automatisiert und der Arduino zog in die Schublade, in der 
schon die manuellen Schalter versteckt sind. Aktuell kann er einen Zug 
wählen, ihn auf ein Gleis auf der anderen Seite schicken, dort 
Rückwärtsfahrt anordnen und den Zug wieder in die Ausgangsposition 
bringen.

Signale und Weichen werden dabei über Magnete gestellt, die ich jeweils 
mit Relais bediene.

 Perspektivisch ist eine Idee, regelmäßig meinen Kalender zu prüfen und 
quasi als Erinnerung kurz vor einem Termin einen Zug zu starten.

Wichtig: Ja, heutzutage gibt es digitale Bahnen, die das alles schon 
können. Aber:
- das ist sehr teuer und lohnt für eine so kleine Bahn kaum
- Das "Flair" von Oppas Bahn würde ich gern behalten. Digital würde 
einiges fehlen, was den Betrieb damals gerade ausgemacht hat
- Wo bliebe die Herausforderung? Fertig kaufen kann jeder ^^

Also: Die Bahn bleibt analog, davon bringt ihr mich nicht ab;)

Als nächste Ausbaustufe nun die Idee, ein Webinterface anzubieten, über 
das man die Baun steuern kann. Und damit das oben beschriebene Problem, 
dass ich nicht mehr stumpf mit delay() in loop arbeiten kann.

Und jetzt zu meinem Lösungsansatz:

Signale und Weichen sind schon heute als Klassen implementiert. Aktuell 
setzt die Funktion zum "auf Fahrt stellen" eines Signals den 
zugeordneten PIN auf HIGH, wartet eine Zeit (delay!) und setzt den PIN 
wieder auf LOW. Diese Klassen werde ich jeweils als State Machine 
implementieren.

loop iteriert über alle Signale und Weichen und stößt jeweils einen 
Durchlauf der State Machine an. Außerdem wird hier auf HTTP Requests 
geprüft.

Und jetzt kommen wir zum eigentlich spannenden Teil ;) Dort erhaltene 
Befehle werden in einen (oder mehrere) Tasks übersetzt.

Ein Task ist zunächst relativ generisch (der Javajaner in mir denkt an 
eine abstrakte Klasse) und besitzt im Wesentlichen nur wenige 
Informationen:

- Ist er neu, oder wurde er schon gestartet?
- Gibt es andere Tasks, auf die gewartet werden muss, bevor dieser 
gestartet werden kann?

Entsprechend gibt es dann mehrere konkrete Tasks.

Ein Taskhandler (mal wieder eine State Machine ;) ) prüt in jeder loop, 
ob es Tasks gibt, die gestartet werden können, und gibt diese dann an 
das ausführende Element.

ZB kann ein Signal den Task "Auf Fahrt stellen" bekommen, markiert ihn 
als begonnen und stellt den Pin auf HIGH. Nacjlh einigen Durchläufen ist 
die Wartezeit durch, der Pin wird zurück auf LOW gesetzt und der Task 
als erledigt markiert.

Der Taskhandler findet in der nächsten Iteration den abgeschlossenen 
Task und entfernt ihn, wobeo ggfs blockierte Tasks frei werden.

 Eine komplette Fahrt wird dann zB abgebildet durch:

1. Für jede Weiche der gewünschten Fahrstraße ein Task zum Ausrichten in 
die gewünschte Richtung
2. Signal für den Zug auf Fahrt stellen. Vorbedingung: Darf erst nach 
Abschluss aller Tasks aus 1. gestartet werden.
3. Fahrstrom anschalten. darf erst nach 2 starten
4. n Sekunden (die erwartete Zeit fur eine Zugfahrt + x) warten. Darf 
erst nacu 3. gestartet werden.
5. Fahrstrom aus. Natürlich erst nach Abschluss von 4.
6. ...

Vermutlich ist das schon ziemlich over engineered, aber hey , ich hab 
Spaß an sowas :D
Ggfs. könnte man noch je Deadlock Erkennung bauen, eine maximale Zeit 
vorgeben, bis ein Task erledigt sein muss etc., aber das würde den 
Rahmen wohl endgültig sprengen :D

Ob die Hardware / Plattform und meine doch sehr begrenzten C++ 
Kenntnisse ausreichend sind, sehen wir dann frühestens nächste Woche. 
Aktuell bin ich im Urlaub und hab nur mein Smartphone. Das gesamte 
Konzept ist also bislang nichts weiter als Spinnerei in meiner nem Kopf 
- und jetzt hier niedergeschrieben, damit ich mich später noch an den 
Unsinn erinnern kann ;)

von Sebastian (Gast)


Lesenswert?

Was noch gar nicht erwähnt wurde (oder?) ist dass die Zeitdauer für die 
Behandlung eines Ereignisses klein genug bleiben muss so dass die 
Abläufe an anderer Stelle nicht aus dem Takt geraten.

Gerade wenn man mit Peripherie kommuniziert kann das aufwendig werden. 
Ich musste z.B. gerade bei der Aktualisierung der Uhrzeit auf einem 
Display das Zeichnen der Ziffern in mehrere Jalousien zerteilen die dann 
unter 1.5ms Dauer bleiben um keine Pakete auf dem CAN-Bus zu verpassen.

Leider sind solche Zeitzusagen gerade in Bibliotheken kaum bis gar nicht 
dokumentiert.

LG, Sebastian

von Robert M. (robert_m949)


Lesenswert?

Genau, das ist klar. Das Fachwort heißt "kooperatives multithreading", 
wenn ich hier richtig aufgepasst habe, uns bedingt,dass sich alle an die 
Spielregeln halten und die Loop nicht blockieren.

Dürfte hier aktuell nicht das große Problem sein, wenn der Magnet ein 
paar Mal länger an bleibt bringt es niemanden um, und HTTP Requests sind 
iA ja froh, wenn sie überhaupt Mal irgendwann bedient werden ;)

von Grummler (Gast)


Lesenswert?

Sebastian schrieb:

> Was noch gar nicht erwähnt wurde (oder?) ist dass die
> Zeitdauer für die Behandlung eines Ereignisses klein
> genug bleiben muss

Steuerungsfritzen "behandeln" keine "Ereignisse".

Sie prüfen Bedingungen und setzen Zustände und Ausgangs-
signale. (Zumindest war das früher so, in der guten alten
Zeit.)

von Grummler (Gast)


Lesenswert?

Robert M. schrieb:

> Das Fachwort heißt "kooperatives multithreading",

Nur in der PC-Welt.


> wenn ich hier richtig aufgepasst habe, uns bedingt, dass
> sich alle an die Spielregeln halten und die Loop nicht
> blockieren.

Nur in der PC-Welt.

Auf der SPS gibt es (soviel ich weiss) keine Ausdrucks-
mittel, die die Hauptschleife blockieren könnten. Das
geht technisch einfach nicht.


> Dürfte hier aktuell nicht das große Problem sein, wenn
> der Magnet ein paar Mal länger an bleibt bringt es
> niemanden um, und HTTP Requests sind iA ja froh, wenn
> sie überhaupt Mal irgendwann bedient werden ;)

Du bastelst lieber ein kompliziertes, unvollkommen
arbeitendes System selbst, als etwas wirklich Neues
zu lernen?

Na dann...

von J. S. (jojos)


Lesenswert?

Grummler schrieb:
> Nur in der PC-Welt.

das wurde hier doch nun reichlich demonstriert das es dem µC auch 
kooperatives und präemptives MT gibt.
Sogar kombiniert, EventQueue im Thread. Das kann man sogar mehrfach 
instanziieren um mehrere Queues mit unterschiedlichen Prioritäten zu 
bekommen.

Aber sicher sind Schrittketten ein Standard in SPS. Für SPS auf µC wurde 
hier mal OpenPLC genannt, ist da vielleicht auch einen Blick Wert. Habe 
ich noch nicht benutzt, aber wenn man damit Code für Logik generieren 
kann, dann hat das auch was.

: Bearbeitet durch User
von Harry L. (mysth)


Lesenswert?

Grummler schrieb:
> Robert M. schrieb:
>
>> Das Fachwort heißt "kooperatives multithreading",
>
> Nur in der PC-Welt.

Und erst recht auf einem µC.

Grummler schrieb:
>> wenn ich hier richtig aufgepasst habe, uns bedingt, dass
>> sich alle an die Spielregeln halten und die Loop nicht
>> blockieren.
>
> Nur in der PC-Welt

Und erst recht auf einem µC!

Grummler schrieb:
> Auf der SPS gibt es (soviel ich weiss) keine Ausdrucks-
> mittel, die die Hauptschleife blockieren könnten. Das
> geht technisch einfach nicht.

Und ob das geht!

Halte dich lieber etwas im Hintergrund, wenn du dich nicht vollständig 
blamieren willst!

: Bearbeitet durch User
von Grummler (Gast)


Lesenswert?

J. S. schrieb:

> Grummler schrieb:
>> Nur in der PC-Welt.
>
> das wurde hier doch nun reichlich demonstriert das
> es dem µC auch kooperatives und präemptives MT gibt.

Der µC von heute war der PC von vorgestern.

Der Begriff "Task" kommt von den Multi-User-Multi-Tasking-
Maschinen her; auf der SPS kommt man klassisch völlig
ohne diesen Begriff aus.
Man braucht ihn nicht -- und zwar selbst dann nicht, wenn
die SPS zahlreiche völlig unabhängige Abläufe steuern soll.

Das, was der TO versucht, hat er selbst m.E. schon völlig
korrekt benannt: Overengineering.


> Sogar kombiniert, EventQueue im Thread. Das kann man
> sogar mehrfach instanziieren um mehrere Queues mit
> unterschiedlichen Prioritäten zu bekommen.

Völlig anderes Thema.

Natürlich ist es u.U. nachteilig, wenn man GEZWUNGEN
ist, JEDE Aktion über Schrittketten auszudrücken.
FUP und AWL sind sicher nicht das Nonplusultra der
Ausdrucksmittel, das ist unstrittig.

Darüber sollte man aber m.E. erst diskutieren, wenn man
das Konzept der Schrittketten BEHERRSCHT...


> Aber sicher sind Schrittketten ein Standard in SPS.

Ich würde noch einen Schritt weiter gehen: Die
Schrittkette ist für den Programmablauf das, was für
die Daten das relationale Datenmodell ist.
Wer Schrittketten verstanden hat, sollte sich auch in
programmierbarer Logik schnell zu Hause fühlen.

von Grummler (Gast)


Lesenswert?

Harry L. schrieb:

> Grummler schrieb:
>> Robert M. schrieb:
>>
>>> Das Fachwort heißt "kooperatives multithreading",
>>
>> Nur in der PC-Welt.
>
> Und erst recht auf einem µC.

Darum ging es nicht: Der Begriff "Multitasking" ist
im Kontext klassischer Ablaufsteuerungen schlicht
überflüssig -- das ist eine Erfindung der "richtigen"
Informatiker.
Die sind nämlich stolz wie Bolle darauf, dass ihre
Blechtrottel inzwischen auch das können, was jede
beliebige Gatterschaltung schon immer konnte: Mehrere
unabhängige Dinge zur gleichen Zeit tun.

Oder würdest Du bei einer Relaissteuerung von "massiv
paralleler Hardware" sprechen? -- Und doch ist es so,
denn sie wertet alle booleschen Gleichungen parallel
in Hardware aus...


> Grummler schrieb:
>>> wenn ich hier richtig aufgepasst habe, uns bedingt, dass
>>> sich alle an die Spielregeln halten und die Loop nicht
>>> blockieren.
>>
>> Nur in der PC-Welt
>
> Und erst recht auf einem µC!

Darum ging es nicht: Im Kontext der Steuerungstechnik...
ach halt, warte. Ich wiederhole mich.


> Grummler schrieb:
>> Auf der SPS gibt es (soviel ich weiss) keine Ausdrucks-
>> mittel, die die Hauptschleife blockieren könnten. Das
>> geht technisch einfach nicht.
>
> Und ob das geht!

Mag sein.
Bei dem AWL/FUP-Kram, den ich gemacht habe, kam man damit
nicht in Berührung. Soll heißen: Normalerweise blockiert
nix.


> Halte dich lieber etwas im Hintergrund, wenn du dich
> nicht vollständig blamieren willst!

Sag' mal... tickst Du noch ganz sauber?

Könntest Du diese fortgesetzten persönlichen Anranzereien
mal bitte unterlassen?

Soweit ich weiss, ist dies hier ein DISKUSSIONSFORUM ,
und das, was ich hier tue, nennt man DISKUTIEREN .

Das muss Dir nicht gefallen, das solltest Du aber klaglos
hinnehmen. Ich muss Dich nämlich auch ertragen, und so
bemühe mich auch, Dich nicht grundlos (!) anzukacken.

von J. S. (jojos)


Lesenswert?

ich weiß nicht wie alt deine SPS sind, aber in aktuellen laufen Tasks. 
Und in den Tasks sind Programminstanzen. Es gibt langsame oder schnelle 
oder getriggerte Tasks.

Grummler schrieb:
> Der µC von heute war der PC von vorgestern.

und deshalb kann man da ohne zu jammern auch mehrere Threads laufen 
lassen. Kooperativ oder Präemptiv.

SPS können heute mehr als nur Relais klappern lassen, die machen 
mittlerweile auch Bildverarbeitung.

: Bearbeitet durch User
von Grummler (Gast)


Lesenswert?

J. S. schrieb:

> ich weiß nicht wie alt deine SPS sind,

Siemens S5/S7.

Codesys war noch kein echtes Thema; FUP/AWL war (aufgrund
der vorhandenen Codebasis) das Mittel der Wahl.


> aber in aktuellen laufen Tasks. Und in den Tasks sind
> Programminstanzen. Es gibt langsame oder schnelle oder
> getriggerte Tasks.

Mag sein.
Das bedeutet aber noch lange nicht, dass "Task" auf der
SPS dasselbe bedeutet wie "Task" unter Unix...

(Schließlich bedeutet "Liste" für Tcl auch nicht dasselbe
wie "Liste" für C++...)


> Grummler schrieb:
>> Der µC von heute war der PC von vorgestern.
>
> und deshalb kann man da ohne zu jammern auch mehrere
> Threads laufen lassen. Kooperativ oder Präemptiv.

Sicher KANN man. Habe ich nie bestritten.

Es gibt aber KEINE Äquivalenz der Form "Maschine soll
mehrere unabhängige Dinge tun" == "es MÜSSEN mehrere
Tasks gestartet werden".


> SPS können heute mehr als nur Relais klappern lassen,
> die machen mittlerweile auch Bildverarbeitung.

???

Bildverarbeitung kann ich auch auf einem FPGA machen --
genauso, wie der auch eine Maschine steuern könnte.
Ich verstehe das Argument nicht.

von J. S. (jojos)


Lesenswert?

Grummler schrieb:
>> Das Fachwort heißt "kooperatives multithreading",
>
> Nur in der PC-Welt.

Grummler schrieb:
> Sicher KANN man. Habe ich nie bestritten.

dann gibt es hier min. 2 Grummler.
Spielt euer Nullsummenspiel alleine weiter.

von EAF (Gast)


Lesenswert?

W.S. schrieb:
> Dieses Proto-Zeugs von Adam Dunkel ist eine mittelgroße
> Katastrophe,
Verlangt ja keiner, dass dir das schmeckt!
Mache einfache einen Bogen drum.

W.S. schrieb:
> denn es ist im Grunde nur eine Präprozessor-Akrobatik und
Ja, Präprozessor Arbeit.
Und?

W.S. schrieb:
> dahinter steckt eine switch-case-Anweisung.
Alternativ: Computed Goto


W.S. schrieb:
> Eigentlich nur eine
> Verkleidung, so daß man das C-Konstrukt dahinter nicht mehr erkennen
> kann.
Hier liegt dein Hauptirrtum!
In einem ordentlichen Multitasking System schreibt man eine Schleife, 
wenn man eine Schleife braucht.
Selbst wenn sie 14 Tage läuft, marschieren die anderen Tasks ungestört 
weiter.
Bei deinem Vorschlag muss man die Schleifen zerreißen, so dass die nicht 
mehr als solche klar zu erkennen sind. Da muss man im Kommentar 
schreiben: Dieses ist eine ausgerollte Schleife.

Das "zerreißen" erledigen bei den ProtoThreads die Makros.
Man schreibt eine Schleife, oder eine Wartezeit(taskPause), wann man sie 
braucht. Das macht die Anwendungslogik klarer.

W.S. schrieb:
> Dafür würde ich den Adam teeren und federn.
Wurdest du schon so oft geteert und gefedert, dass du das auf andere 
projizierst?


Auf den kleinen µC herrscht eher Speichermangel vor.
Die ProtoThreads benötigen keinen (permanenten) Stackframe für jede 
ihrer Tasks.
Der Scheduler kann schlicht gehalten werden.
Und TaskControll Strukturen braucht es auch nicht unbedingt.

Es ist eine schlanke Variante, welche einige Vorzüge ausgewachsener 
Multitasking Systeme mitbringt, aber längst nicht alle.
Sie muss auch nicht jedem schmecken....

von W.S. (Gast)


Lesenswert?

Grummler schrieb:
> Nicht ganz.
> Du schriebst von "Events" und "Warteschlange". Das
> verträgt sich nur bedingt mit Echtzeitfähigkeit;

Nun ja, eine solche Lösung ist darauf angewiesen, daß so eine Botschaft 
(aka Event) nicht nur in eine Warteschlange gestopft wird, sondern auch 
gelesen und darauf reagiert wird. Aber dafür ist das ganze Konstrukt 
einfach und billig zu haben und bedarf nur wenigen Lernens. Wenn man da 
sogleich nach einem RTOS ruft, dann wäre dort die Einarbeitung erheblich 
umfänglicher - und für viele eher überschaubare Anwendungen wäre sowas 
auch übertrieben.

Mit den genannten Warteschlangen scheint hier aber fast jeder Zweite ein 
mentales Problem zu haben. Nun, sowas trifft man weitaus häufiger an, 
als es einem auffällt - jedenfalls dann, wenn eine Firmware nicht 
trampelig geradeaus programmiert wurde. Ich denke da z.B. an sowas wie 
COM-Port-Treiber, die Fifos für die Sende- und Empfangs-Daten haben. 
Eben um das physische Sende- und Empfangs-Gehampel von den höheren 
Programmschichten zu entkoppeln. Genau so ist es auch mit einem Fifo 
(aka Warteschlange) bei den Benachrichtigungen (aka Events), die zwecks 
Entkopplung zwischen der generierenden Stelle in der Firmware und der 
reagierenden Stelle benötigt werden.

Ich entsinne mich dunkel, hier schon mal ein passendes Stück C-Quelle zu 
diesem Thema gepostet zu haben. Das Ganze ist wirklich nicht umfänglich 
und leicht zu verstehen und anzuwenden.


Harry L. schrieb:
> Das artet mal wieder (leider typisch) in akademische Diskussionen aus.
> Dem TO ist damit sicher nicht geholfen.
> ...
> Und DANN kann man über irgendein RTOS reden...

Nö, über irgendein RTOS will jedenfalls ICH hier nicht reden, denn 
sowas ist zum einen aufwendig und zum anderen hier herzlich übertrieben. 
Und dem TO ist (wenn er nicht nur lesen sondern auch verstehen kann) 
sehr wohl geholfen. Und akademische Diskussionen sind was ganz anderes.

W.S.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Dieses Proto-Zeugs von Adam Dunkel ist eine mittelgroße
> Katastrophe, denn es ist im Grunde nur eine Präprozessor-Akrobatik und
> dahinter steckt eine switch-case-Anweisung.

Es ist schon sehr elegant und zeigt schön, wie man mehrere 
Statemaschines quasi parallel ausführen kann. Niemand zwingt dich, es zu 
verwenden. Man kann natürlich die switch/case Statemaschines direkt 
hinschreiben. Wichtig ist nur, daß nirgends auf etwas gewartet, sondern 
die CPU-Zeit sofort zur Mainloop zurück gegeben wird. Gewartet wird 
immer nur indirekt, indem Flags oder Countdown-Zähler abgeprüft werden.
Man kann auch parallel einen Scheduler laufen lassen, dem man einfach 
Callbacks und die gewünschte Wartezeit übergibt.
Der Vorteil gegenüber einem RTOS ist, daß die Reihenfolge immer 
deterministisch ist und somit keine Task eine andere mittendrin 
unterbrechen kann und Datensalat oder Deadlocks fabriziert.

von Stefan F. (Gast)


Lesenswert?

Robert M. schrieb:
> Vermutlich ist das schon ziemlich over engineered

Das mag bei einer kleinen Anwendung zutreffen, aber es ist ein 
problemlos ausbaufähiges Konzept mit dem Anwendungen wachsen können ohne 
dass man sich verzettelt.

von Stefan F. (Gast)


Lesenswert?

Robert M. schrieb:
> Genau, das ist klar. Das Fachwort heißt "kooperatives multithreading",
> wenn ich hier richtig aufgepasst habe, uns bedingt,dass sich alle an die
> Spielregeln halten und die Loop nicht blockieren.

Kennen die alten Hasen von Windows 3.11. Beim Drucken stotterte die 
Musikwiedergabe (trotz DMA) und wenn man doof programmiert, bleibt der 
ganze Rechner hängen.

Falls sich jemand für die genannten Protothreads interessiert, ich habe 
sie in mehreren Anwendungen eingesetzt und einen deutschen Aufsatz dazu 
geschrieben:
http://stefanfrings.de/net_io/protosockets.html
Ich habe mich bemüht, nicht allzu sehr durchblicken zu lassen, dass ich 
die Protohtreads nicht mag.

Als Kontrast dazu habe ich dort beschrieben, wie ich Zustandsautomaten 
ohne Protothreads implementiere:
http://stefanfrings.de/multithreading_arduino/index.html

In dieser Diskussion wurde angesprochen, Ereignisse in Warteschlangen zu 
packen und dann abzuarbeiten. In vielen Anwendungen kann das sicher auch 
sehr hilfreich sein, durchschaubaren Code zu schreiben. Man kann das 
auch gut mit Zustandsautomaten kombinieren. Ist auf jeden Fall ein 
Thema, mit dem man sich früher oder später auseinander setzen sollte. 
Nicht nur bei Mikrocontrollern, sondern bei Softwareentwicklung 
allgemein.

von Klaus S. (kseege)


Lesenswert?

Manfred schrieb:
> Delay ist doof, weil es den Programmablauf blockiert.

Nein, Delay() ist dazu da, den Programmablauf zu blockieren, auch wenn 
User wie "amDelayErkenntManDenAnfänger (Gast)" das noch nicht erkannt 
haben:

Das braucht aus mindestens 2 Gründen:
1. Um schnell was zusammenzuschustern und Zeit zu sparen
2. Um ein größeres Programm kurzzeitig für eine Debugausgabe (die Zeit 
braucht) anzuhalten und keine Variablenänderung zu verpassen.

Just my 2 cents (Meinung!)

von Klaus S. (kseege)


Lesenswert?

Ein Großteil der Menschen bevorzugt wohl einen linearen Programmablauf, 
weil es so schön einfach ist. Zustandsautomaten sind nicht jedermanns 
Sache, daher wurde kooperatives Multitasking erfunden. Und um den dabei 
normalerweise notwendigen erhöhten RAM-Bedarf zu vermeiden, wurden die 
Protothreads erfunden. Alles nur "syntactic sugar", um die Komplexität 
in einen unteren Layer zu verbannen und sich das Leben leichter zu 
machen. Bezahlen muß man dafür mit dem Verlust der Schrittvariablen, die 
einem ständig signalisieren, wo das Programm gerade auf etwas wartet.

Auch präemptives (laut EAF: ordentliches) Multitasking wird ja außerhalb 
von Realtimeanwendungen nur dafür gebraucht, daß man bei langen 
Schleifen nicht an Andere denken muß und das "Auseinanderreißen" 
einsparen kann, also einfach Bequemlichkeit.

Just my 2 cents (Meinung!)

von EAF (Gast)


Lesenswert?

Klaus S. schrieb:
> Zustandsautomaten sind nicht jedermanns
> Sache, daher wurde kooperatives Multitasking erfunden.

Dass ist so dermaßen falsch, dass sicherlich noch nicht einmal das 
Gegenteil richtig ist.

Zustandsautomaten haben nichts mit Multitasking zu tun, ist somit auch 
kein Ersatz.

Multitasking hat nichts mit Zustandsautomaten zu tun, ist somit auch 
kein Ersatz.

Was allerdings richtig ist, dass die beiden gerne mal gemeinsam 
auftreten.


Multitasking/Multithreading sagt einfach nur, dass verschiede, logisch 
getrennte Handlungsabläufe/-stränge vorliegen. Welche quasi gleichzeitig 
abgearbeitet werden sollen.

Zustandsautomaten, gerade auch Schrittketten, beruhen darauf, dass die 
Dinge fein nacheinander, geordnet, abgearbeitet werden.

Das sind zwei völlig verschiedene Baustellen.
Das eine möchte ein (quasi) gleichzeitig erreichen, das andere ein 
nacheinander

Eine einzelne Schrittkette braucht kein Multitasking, oder blockadefreie 
Programmierung.
Das wird erst wichtig, wenn sich die Automaten rudeln.

von Chris (Gast)


Lesenswert?

Ich benutze sowas, damit lassens sich mit #define delay(n) TH_DELAY(n)
relativ einfach "arduino code" auf State-machine  bringen und von der
Blockierung wegkommen. Habe nur das wichtigste kopiert.
1
#define THREADS 10             
2
uint8_t th_seq[THREADS];      // seq values
3
uint64_t th_now[THREADS];     // delay values
4
uint8_t thseq;                  // current thread state, same as seq[thid]
5
uint8_t thid;                   // current thread
6
uint8_t  thseqtemp;             // temp counter
7
#define thnow th_now
8
9
#define CODE_ELAPSED(current,old,time)  (((current)-(old)) > ((time)*1000))                                               #define TH__BRK         if(thseq==thseqtemp++)                                                                        #define TH_YIELD()      } TH__BRK {                                                                                   #define TH_DELAY(n)     thnow[thid]=micros64(); } TH__BRK if(!CODE_ELAPSED(millis64() , thnow[thid] , n)) ; else {
10
#define TH_START(A)     thseq=th_seq[thid=A]; thseqtemp=0; { TH_YIELD() 
11
#define TH_END()        } if(++thseq>thseqtemp) thseq=0; seq[thid]=thseq;    
12
13
uint64_t micros64() {                                                                                                     static uint32_t low, high;                                                                                            uint32_t val = micros();                                                                                              if (val < low) high++;                                                                                                low = val;                                                                                                            return (uint64_t) high << 32 | low;                                                                               }

von Klaus S. (kseege)


Lesenswert?

EAF schrieb:
> Dass ist so dermaßen falsch, dass sicherlich noch nicht einmal das
> Gegenteil richtig ist.
> Zustandsautomaten haben nichts mit Multitasking zu tun, ist somit auch
> kein Ersatz.

Wenn man die Nase dicht am Problem hat, sieht man die Unterschiede und 
nicht die Gemeinsamkeiten. Dann sind alle Bäume verschieden. Tritt man 
einige Schritte zurück, sieht man die Gemeinsamkeiten, den Wald.

Deshalb vorweg: Es gibt grob 5 Möglichkeiten für Multitasking.

1. Hardware. Kombinatorische Logik, 1 CPU je Task, Quantenrechner

2. Kette von Zustandsautomaten. Das machen SPSen und kleine uC-Systeme 
(wie Arduino) und simulieren so das parallele Abarbeiten durch 
quasiparalleles.

3. Kooperatives Scheduling. Das zerlegt die Programme im Scheduler in 
die Pakete, die einen Zustandsautomaten bilden, jedes Programm einen und 
meistens a la RoundRobin einen nach dem Anderen. Das machen etliche der 
RealTime-Operatingsysteme für Mikrocontroller, kann man in der 
Wikipedia-Übersicht nachschauen.

4. Interruptgesteuerte Priorisierungshierarchien. Viele uCs (AVR8 
nicht!) haben im Interruptcontroller ein (auch einstellbares) 
Priorisierungsschema. Damit kann man sich selbst relativ einfach ein 
Multitaskingsystem bauen, allerdings eben nur prioritätsgesteuert.

5. Präemptive Betriebssysteme. Da starten je nach Auslegung ein oder 
mehrere Interrupts jeweils den Scheduler nach unterschiedlichen 
Algorithmen (z.B.RR,PRIO,MRS,EDF...)

Und da der TO nach "Handling parallel anfallender Aufgaben" gefragt 
hatte, sprechen wir hier genau darüber. Und nicht über einen einzelnen 
Zustandsautomaten, den Du sehr richtig beschrieben hast.

Sollte das meinem Text nicht zu entnehmen gewesen sein, habe ich mich 
wohl zu undeutlich ausgedrückt. Ich bitte dafür um Nachsicht. Mir ging 
es um die organisatorische Äquivalenz von Lösung 2 und Lösung 3. Einmal 
muß der Programmierer die Arbeit machen, im anderen Fall hat jemand 
Anderes vorgearbeitet, kooperatives Scheduling eingerichtet und der 
Programmierer kann ganz bequem linear programmieren. Ist das jetzt 
besser verständlich?

Gruß Klaus (der soundsovielte)

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.