Grundlagen ( Parrallele code Abarbeitung) Anfängerfrage

Hallo
Ich habe immer ein grundsätzliches Problem bei meiner Prorammierung. Muss aber auch sagen, das ich kein Informatiker bin, sondern mir mein wissen nur selber angeeignet habe.

Mein Problem:
kontinuirliche code abarbeiteitung (evtl. auch Zeitkritisch) und zusätzlich sollen auch noch andere Aktionen ausgeführt werden, bzw. auch Interrupts reagiert werden.

Beispiel:
LED Blinken 150ms an und 150ms aus.

Zwischendrinn:
wird ein taster abgefragt und wenn der HIGH ist sollen 10 anderen LEDS im 1Sec. takt nacheinander angehen.

Nun ist es ja so, das wenn dieser 10 LED code gestartet wird, das 150ms Blinken der ersten LED unterbrochen wird.
Wie kann man soetwas realisieren?

Ich weiß, das dies zu Grundlagen gehört, aber auf dieses Problem bin ich schon immer wieder mal gestoßen.
Aber immer versucht andere Wege zu finden, weil ich nicht wusste wie

Danke

Paralell geht gar nichts. Es geht nur eines nach dem anderen.

Beispiel
LED Blinken 150ms an und 150ms aus.

Da hast Du ca 149mS zeit zwischen einem LED umschalten und dem nächsten um anderes zu machen zB Tastenabfrage.

zum realisieren:
Kein delay() verwenden sondern millis().

siehe http://playground.arduino.cc/Learning/BlinkWithoutDelayDe

Grüße Uwe

  • oder preemptives MT, wonach ich auch schon gefragt habe - da soll's doch was geben...?

Ist aber nichts für einen Anfänger!

neop13:
Wie kann man soetwas realisieren?

Die dort genannte "zentrale Prozessverwaltung" ist in dem Fall Deine loop-Funktion, die die einzelnen "Prozesse" (hier: "Funktionen") nacheinander aufruft, und wenn der letzte Prozess ausgeführt wurde, kommt wieder der erste Prozess dran (sogenanntes "Round-Robin-Scheduling" in der loop-Funktion),

Im Sinne der strukturierten Programmierung bietet sich dabei eine Einteilung Deiner Prozesse nach dem EVA-Prinzip an:
E - Eingabe
V - Verarbeitung
A - Ausgabe

Das wesentliche dabei ist, jeden Prozess (jede Funktion) "kooperativ" zu programmieren, also nicht-blockierend.

Typische loop-Funktion:

void loop()
{
  eingabe();
  verarbeitung();
  ausgabe();
}

Mit nicht-blockierend ist gemeint, dass ein Prozess niemals auf irgendetwas wartet, bis es passiert, sondern die Logik jedes "kooperativen" Prozesses muss sein:

Prüfe, ob genau jetzt etwas zu tun ist:
Wenn ja, tue es jetzt und tue es schnell!
Wenn nein, beende die Funktion sofort.

Da ein Mikrocontroller mit 16 MHz Taktfrequenz 16 Befehle in einer einzigen Mikrosekunde ausführen kann, bzw. 16000 Befehle in einer Millisekunde (tausendstel Sekunde), kannst Du in einer einzigen tausendstel Sekunde schon ganz schön viele Dinge "fast gleichzeitig" ausführen. Aber nur dann, wenn Deine Funktionen "kooperativ" programmiert sind.

Super erklärt!

Verstehen des "blink_without_delay-Codes" wird Dich, neop13 definitiv weiterbringen.

Gruß Chris

viel einfacher (weil man sich dann um delay etc keine Gedanken machen muss), wäre ntl preemptives (Zeitscheiben-) Multitasking.
Insbesondere, weil dann auch threads möglich sind, die insgesamt mehrere Sekunden oder meinetwegen sogar Stunden für 1 Durchlauf brauchen. Diese werden dann pro Durchlauf einfach "scheibchenweise" ausgeführt, und zwischendurch kämen trotzdem alle anderen Threads regelmäßig auch mal dran.
Das wäre dann der erste Schritt (wohlgemerkt: der erste!) zu einem Echtzeit-Multitasking-System.

Der Aufbau ist ntl. API- und Firmware-abhängig, in verschiedenen Sprachen, die ich kenne und teilw auch nutze, ist es aber extrem simpel (gpp C, Java, RobotC, NXC,...) - ein Thread wird gestartet, as ob man eine Funktion aufruft, und Firmware / Compiler kümmern sich automatisch um den Rest, damit er quasi im Hintergrund automatisch seine Runden dreht und seine Rechenzeit automatisch mit Parallel-Tasks teilt.

Genau so etwas suche ich auch für Sketch z.Zt.

Der Aufbau ist ntl. API- und Firmware-abhängig, in verschiedenen Sprachen, die ich kenne und teilw auch nutze, ist es aber extrem simpel (gpp C, Java, RobotC, NXC,...) - ein Thread wird gestartet,

Es ist extrem Simpel weil die Sprache und das Betriebsystem und die Hardware das vorsieht. Bei Arduino haben wir kein Betriebssystem, die Sprache sieht das nicht vor und auch die Fähigkeiten des Kontrollers sind beschränkt. Ußerdem muß man schon gut in Informatik sein um sowas zu programmieren. Zeitscheiben den einzelnen programmen zu geben heißt daß man alle daten und den Zustand des Programms zwischenspeichern muß und in der nächsten Zeitscheibe wieder von dort starten muß. Es gibt aber Programmteile die man NICHT Unterbrechen kann (zB Senden oder Empfangen von Daten, setup von externen Bausteinen)

Grüße Uwe

stimmt ntl mit dem Sichern (habe ich ja auch schon geschrieben: Stack, Heap, Register) und mit dem Betriebssystem (Linux, Java, OSEK oder was auch immer).

Trotzdem gibt es doch angeblich ein freeRTOS und andere MT-OS...? Der Mega müsste das doch locker können?

(Schaffte ja sogar schon ein Lego RCX vor 15 Jahren mit 8-bit Renessas H8/300 CPU und Java, Forth, Lua, Interactive C, NQC, RCX-G, Robolab, Simulink, Labview...)
Auch der RCX (und die Nachfolger NXT, EV3) können per Parallel-Tasks auch senden und empfangen (seriell IR, USB, BT, rs485, WiFi, i2c, xBee, UART-TTL). Nur beim Zwischendurch-Schreiben von Daten auf den Flash-Speicher wurden Tasks z.T. angehalten, soweit die Programme auf dem Flash ausgeführt werden (gilt nicht für EV3).

HaWe:
viel einfacher (weil man sich dann um delay etc keine Gedanken machen muss), wäre ntl preemptives (Zeitscheiben-) Multitasking.

Preemptives Multitasking verbrät allerdings bei den Taskwechseln einen erheblichen Anteil der Performance, die ein Controller oder Prozessor bietet. Machst Du zu häufige Taskwechsel, geht fast die gesamte Performance für Taskwechsel drauf und für die eigentlichen Tasks bleibt kaum Rechenzeit übrig. Und machst Du nur seltene Taskwechsel, können die einzelnen Tasks nur mit erheblicher Zeitverzögerung reagieren.

Nicht umsonst hat sich Preemptives Multitasking eigentlich nur auf Multiprozessor-Betriebssystemen wirklich durchgesetzt.

Auf Einprozessorsystemen ist preemptives Multitasking nur eine unnötige Bremse und kann von der Performance her mit einem gut gemachten kooperativen Multitasking nicht mithalten. Es gibt kein preemtives Multitasking ohne Rechenzeitverbrauch. Und je preemptiver das System sein soll, desto mehr Rechenzeit geht nur für die Aufrechterhaltung des Multitaskings drauf.

Das Problem gibt es beim kooperativen Multitasking nicht. Anbei mal ein kleines Codebeispiel für so etwas ähnliches wie es der Thread-Starter nachgefragt hat:

define TASTERPIN 5
#define LED 13
#define BLINKTIME 250

void setup() {
  Serial.begin(9600);
  Serial.println("Kooperatives Multitasking by 'jurs' for German Arduino Forum");
  Serial.println("Verbinde Pin-5 mit GND zum Starten der Lauflicht-Demo...");
  pinMode(TASTERPIN, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
}

void blinken(unsigned long time)
// Funktion: Ständiges Blinken im Rhythmus von BLINKTIME
{
  static unsigned long lastSwitchTime=0;
  unsigned long timeSinceSwitching=time-lastSwitchTime;
  if (timeSinceSwitching>=BLINKTIME)
  {
    digitalWrite(LED,!digitalRead(LED)); // change LED state 
    lastSwitchTime+=BLINKTIME;
  }
}

boolean laufTaskRunning=false; // Task läuft oder läuft nicht
unsigned long laufTaskStart=0; // Startzeit des Tasks

byte virtualLEDS=0; // 8 Bits 1/0 simulieren 8 LEDs ein/aus

void eingabe(unsigned long time)
// Funktion: Taster abfragen, bei gedrücktem Taster den LaufTask starten
{
  static unsigned long lastRunTime=0;
  static byte lastButtonState=0;
  if (time-lastRunTime>5)
  {
    byte buttonState=!digitalRead(TASTERPIN);
    if (buttonState && !lastButtonState)
    {
      laufTaskRunning=true;
      laufTaskStart=time;
    }
  }
}

void verarbeitung(unsigned long time)
// laufender Task schaltet 8 virtuelle LEDs nacheinander an und wieder aus
{
  unsigned long laufTaskRunningSince=time-laufTaskStart;
  virtualLEDS=0;
  for (int i=0;i<8;i++)
  {
    if (laufTaskRunningSince>i*1000 && laufTaskRunningSince<=(i+8)*1000)
      bitSet(virtualLEDS,i);
  }
}

void ausgabe(unsigned long time)
{
  static byte lastLED=0;
  if (virtualLEDS!=lastLED) // Änderungen der LEDs anzeigen
  {
    for (int i=0;i<8;i++)
      Serial.print(bitRead(virtualLEDS,i));
    Serial.println();
    lastLED=virtualLEDS;  
  }
  if (time-laufTaskStart>16000) // Task beenden, wenn erledigt
  {
    laufTaskRunning=false;
    Serial.println("Task beendet...");
  }
}  



void loop() 
{
  unsigned long now=millis();
  blinken(now);
  eingabe(now);
  if (laufTaskRunning) verarbeitung(now);
  if (laufTaskRunning) ausgabe(now);
}

Das Programm läßt mit der loop als Round-Robin-Scheduler

  • die Board-LED ständig im vorgegebenen Rhythmus blinken
  • fragt einen Taster an Pin-5 ab, der einen zusätzlichen Task starten kann
  • wenn der zusätzliche Task läuft, werden seine Daten verarbeitet und ausgegeben

Die Aufgabe des zusätzlichen Tasks ist eine LED-Simulation aus 8 LEDs, bei der im Sekundentakt eine weitere LED angeschaltet wird bis alle LEDs leuchten, danach werden die LEDs in derselben Reihenfolge im Sekundentakt abgeschaltet bis alle aus sind. Danach beendet der Task sich selbst und stellt sich auf "laufTaskRunning=false". Solange bis der eingabe-Task den zusätzlichen Task wieder startet.

Race-Konditions, dass Tasks etwa um dieselbe Hardware konkurrieren, lassen sich im kooperativen Multitasking auch sehr leicht auflösen. Was soll zum Beispiel das Programm machen, wenn der Taster ein zweites mal gedrückt wird, während der Zusatztask noch abgearbeitet wird? In diesem Demoprogramm wird dann einfach der Zusatztask neu gestartet und beginnt bei einem Tastendruck während der laufenden Sequenz neu mit der Wiedergabe der Sequenz. Man könnte aber genau so gut weitere Tastendrücke verwerfen und ignorieren, solange der Zusatztask läuft. Oder man könnte weitere Tastendrücken während einer laufenden Sequenz in einem FIFO-Puffer zwischenpuffern, so dass die Sequenz nach der vollständigen Abarbeitung gleich nochmal abgearbeit wird.

Auf Einprozessor-Systemen läßt sich die Hardware jedenfalls durch kooperatives Multitasking viel besser bis zum letzten Takt ausnutzen als durch preemptives Multitasking, bei dem viele wertvolle Rechentakte für das Sichern und Wiederherstellen der Ausführungsumgebung bei der Taskumschaltung draufgehen. Keine Taskumschaltung = maximale Rechenleistung auf Einprozessorsystemen.

Erschwerend kommt beim preemptiven Multitasking noch dazu: Das verbrät nicht nur Rechenzeit, sondern auch zusätzlichen RAM-Speicher. Für jeden Task muss ja bei der Taskumgebung der Prozessorzustand mit allen seinen Registern gesichert werden, um genau diesen Zustand später wieder rückspeichern zu können, wenn der Task wieder an der Reihe ist, ausgeführt zu werden. Und das ist auf 8-Bit Atmegas echt hart, von beispielsweise 2 KB RAM für jeden Thread z.B. 128 Bytes zu verlieren, die für die Sicherung des Controllerzustands beim Umschalten der Tasks benötigt werden.

Im übrigen gibt es ja stets noch das preemtive Multitasking des kleinen Mannes: Interrupts!

Die Atmega-Controller kennen Timer-Interrupts und Hardware-Interrupts, und was neben der normalen kooperativen Programmausführung keinerlei Aufschub duldet und "in Echtzeit" abgearbeitet werden soll, das läßt man dann eben in Timer-Interrupts oder Hardware-Interrupts laufen.

so schlimm ist das mit dem cpu-Verbraten für Taskwechsel nicht, und wer nur 1 Task braucht, verliert ja nichts.
Aber wenn ich 20 Tasks bräuchte, stünde ich mit den Arduinos voll auf dem Schlauch.

Guck dir zur Performance mal die Benchmark-Tests an, insbesondere zum NXT (ARM7) mit nxt-OSEK, einem Echzeit-Betriebssystem mit preemptivem Multithreading, bei dem man sogar die Thread-Prioritäten einzeln festlegen kann. Es werden hier echte Executables erzeugt - und OSEK ist bereits millionenfach bewährt im Automobilbau, wo Echtzeitverhalten lebens- und überlebens-wichtig ist!!

Hier siehst du, dass es deutlich schneller ist als alle VM-basierten Sprachen - das "preemptive" spielt also performance-mäßig nicht die Erste Geige, wie oft von Nicht-Kennern der Materie fälschlich behauptet... :wink:

Aber auch Java/leJOS mit seinem JIT-Compiler für den EV3 (ARM9) kann hier absolut mithalten, vergleich das mal mit EV3-gpp C für Linux-Executables - ebenfalls mit preemptivem MT !!

Aber die zusätzlichen Möglichkeiten, die preemptives MT bietet, sind nahezu grenzenlos, versuch dich einfach mal dran!
Ich "arbeite" schon seit 15 Jahren damit! :slight_smile:
Kooperatives MT hingegen ist viel zu restriktiv - das macht überhaupt keinen Sinn für anspruchsvolle MT-Projekte mit Einzeltasks, die viel Rechenzeit brauchen (deutlich mehr als jeweils 1 Scheduler-Umlauf) und wo ggf. mal ein task vorzeitig einem anderen, wichtigeren, zwingend vorzeitig weichen muss !
Und, wie gesagt, selbst der Uralt-Vorläufer RCX mit 8-bit Renessas MCU konnte bereits preemptives MT auf den vielfältigsten Plattformen!

Aber zurück zum TOP:
wie sieht es denn jetzt aus mit einem free RTOS für Arduinos? Wer kennt's , wer hat Erfahrung damit?
Das wäre doch die Lösung des Problems...?!

HaWe
Wir haben hier einen 8 Bit Controller mit 16MHz Takt und 2 KByte RAM ohne Betriebssystem in der Hand nicht ein 32 Bit CPU System mit ettlichen Mbyte an RAM und ROM.
Was andere Systeme können ist wenig Aussagekräftig für Arduino.
Grüße Uwe

haaalloooo !

Ich führte bereits mehrfach den 15 Jahre alten RCX mit 8-bit- Renessas H8/300 µC als Gegenbeispiel an! 8)
(ganz zu schweigen von Sketch für den Due-ARM µC im Gegensatz zum Mega etc...)

und, wie bereits gefragt -

wie sieht es denn jetzt aus mit einem free RTOS für Arduinos? Wer kennt's , wer hat Erfahrung damit?
Das wäre doch die Lösung des Problems...?!

Wie man einen Timer als Scheduler verwenden kann wird hier ab Seite 19 gezeigt:

Die Sache ist halt auch dass man für viele einfache Sachen einfach keine komplette Kontext-Umschaltung braucht. Ein paar simple Dinge ein regelmäßigem Abstand zu erledigen geht auch anders. Kompliziert wird es dann bei gemeinsamen Zugriff auf die gleichen Ressourcen.

ja, mit den einfachen Sachen gebe ich dir Recht, da braucht man wirklich kein preemptives MT (nicht unbedingt, es macht aber vieles trotzdem einfacher).

aber ich rede ja von komplizierteren, aufwändigeren, Echtzeit-MT-Anwendungen, ohne mich jetzt nochmal wiederholen zu wollen.
Den Link schau ich mir gleich mal an -
Erfahrung hat hier damit aber keiner...? Evtl. mit eigenen Beispiel-Projekten / Anwendungen?

hallo
ich habe "Anfängerfrage" geschrieben, weil es denke ich nicht nur ich in meiner Anfangszeit das Problem hatte.
@ Chris. mit solchen sachen wie dem millis () bzw dem bsp. Code blink_without_delay habe ich dann soetwas auch gelöäst, allerdings kommen dadurch immer etwas komplizierte abfragen zustande um etwas einfaches (eine zeitdifferenz) zu erstellen.

gebe ich dir völlig Recht!

das mit Seite 19 ist nicht das, was ich eigentlich meinte - das ist in der Tat viel zu kompliziert.
Was der du wschl eher meintest (und ich jedenfalls auch), ist eher sowas mit
start (taskname)
und
stop (taskname)

Das ist leistungsfähig - und Anfänger-tauglich! (pthread für Linux ist sogar sehr ähnlich!):

// Multitasking-Demo:
// 3 Motoren an die Motorausgänge A,B,C anschließen !
// die Motoren werden automatisch angesteuert,
// die Encoderwerte werden simultan angezeigt!

#define CLEOL  DRAW_OPT_CLEAR_EOL

task  DisplayValues() {
  while(true) {
    TextOut(0,56, "Enc.A:", CLEOL); NumOut(42,56, MotorRotationCount(OUT_A));
    TextOut(0,48, "Enc.B:", CLEOL); NumOut(42,48, MotorRotationCount(OUT_B));
    TextOut(0,40, "Enc.C:", CLEOL); NumOut(42,40, MotorRotationCount(OUT_C));
    Wait(10);
  }
}


task MotorControl() {
  int speed;
 
  while(true) {
    speed=Random(201) - 100; // ergibt eine Zufallszahl für die Motorleistung  zwischen -100 und +100
    OnFwd(OUT_A,speed);
  
    Wait(100);

    speed=Random(201) - 100;
    OnFwd(OUT_B,speed);

    Wait(200);

    speed=Random(201) - 100;
    OnFwd(OUT_C,speed);
   
    Wait( 200+ (Random(2800)) ); // ergibt eine Zufallszahl für die Aktionsdauer von 200 - 3000 ms
  }
}


task main() {
  start DisplayValues;
  start MotorControl;

  while(true);

}

Hier verbraucht der Motor-Task teilw über 3 Sekunden (!!) Zeit! Trotzdem wird die Anzeige alle 10ms aktualisiert!
(Ist ntl nur eine Demo für die grundsätzliche Funktionsweise aus einem Tutorial!)

sowas bräuchten wir hier auch für die Arduinos!

Ich weiß du meinst. Und es mag einfacher Quellcode sein, aber was da gemacht wird ist für viele Sachen viel zu aufwendig und unnötig.

Nur weil einem das abnimmt das Timing per Hand zu programmieren ist es nicht unbedingt besser.

Leichtgewichtige Threads gibt es aber auch für den Arduino (ist allerdings völlig Platform unabhängig):
http://dunkels.com/adam/pt/
Wurde dir schon mal vorgeschlagen

dann muss man leider die Arduinos für diese anspruchsvolleren Aufgaben streichen - d.h. dann also wohl doch wieder Lego oder Raspberry Pi... :frowning:

das adam pt Dings ist übrigens wieder nicht preemptiv - darum geht es aber doch, wenn man nicht den ganzen komplizierten Timer-Overhead programmieren will und auch sehr lange rechnende Einzel-Tasks zu verwalten hat!

Ich glaube dir geht es zum Teil auch viel darum, dass du was hast dass dir das Scheduling abnimmt, die ganze Sache abstrahiert und den Code übersichtlicher macht.
Ich erachte einen Timer so zu programmieren, dass er regelmäßig einen Interrupt auslöst als nicht kompliziert (PWM z.B. ist was anderes). Sowas gehört einfach dazu wenn man 8-Bit µC programmiert. Für Tasks mit stark unterschiedlichen Laufzeiten ist das natürlich wieder nichts, da eine aufgerufene Funktion erst wieder in die ISR zurückkehren muss um die Interrupts wieder freizugeben. Aber um in etwas längeren Abständen relativ kurze Dinge zu erledigen ist es eine Option.

Es gibt natürlich Sachen mit sehr strengen Echtzeitanforderungen, die präemptives Multitasking erfordern, aber man kann auch mehr mit kooperativem Multitasking erschlagen als du implizierst.