Einfaches Multithreading für ATMEGAs

OP #4982661
Lesenswert?

Hallo Leute,

das Aufteilen einer Firmware in mehrere weitgehend unabhängige Threads 
hat häufig Vorteile. Die meisten werden da zu RTOS-Lösungen greifen, 
auch wenn eigentlich gar keine echten Real-Time-Anforderungen bestehen, 
sondern nur bestimmte Teile regelmäßig aufgerufen werden müssen.

Eine sehr interessante, alternative Idee für solche Fälle habe ich in 
der Publikation "RIOS: A Lightweight Task Scheduler for Embedded 
Systems" by Bailey Miller et al. 
(https://www.ics.uci.edu/~givargis/pubs/C50.pdf) gefunden und noch 
einmal anders umgesetzt.

Kurz gesagt besteht jeder Thread aus jeweils einer Funktion, die 
periodisch aufgerufen wird. Es ist also kein RTOS. Bei jedem Aufruf 
muss die Funktion beendet werden und wird das nächste Mal wieder "von 
vorn" begonnen (Run to completition). Einen Thread schlafen legen, um 
auf ein externes Ereignis zu warten, geht also nicht. So etwas muss man 
mit einer State-Machine modellieren, was daher vom System explizit 
unterstützt wird.

Akzeptiert man diese Einschränkungen, bekommt man ein ein 
leistungsfähiges Multithreading mit sehr kleinem Footprint. Nur etwa 400 
Byte Flash sowie 10 Byte RAM pro (möglichem) Thread werden benötigt. 
Alle Threads verwenden einen(!) gemeinsamen Stack, was aus meiner Sicht 
ein großer Vorteil gegenüber RTOS Lösungen ist. Zusätzlich spart man 
sich die Zeiten für die dort notwendigen Kontextwechsel.

Ich habe meinen Sourcecode unter die LGPL-Lizenz gestellt, er kann also 
ziemlich frei verwendet und verändert/weiterentwickelt werden. Er sollte 
ohne Änderung auf allen neueren ATMEGAs lauffähig sein. "Neuer" meint 
die mit der Zusatzziffer 4 bzw 8 (z.B. atmega644 oder atmega328). Für 
ATTinys sollte es auch gehen, man braucht aber wohl eine Anpassung. 
XMEGAs sind wegen des Interruptcontrollers hier leider ganz raus. (Daran 
arbeite ich noch) Das Beispiel in main.c habe ich auf einem ATMEGA328P 
XPLAINED MINI getestet.

Viel Spaß am Gerät!
Angehängte Dateien:
Gast #4982673
Lesenswert?

Vermutlich habe ich den Witz nicht verstanden.

> Kurz gesagt besteht jeder Thread aus jeweils einer Funktion, die
> periodisch aufgerufen wird.

Dazu brauche ich kein wie immer auch geartetes RTOS, RIOS or whatever!
Gast #4982717
Lesenswert?

> Ich habe meinen Sourcecode unter die LGPL-Lizenz gestellt, ...

Muss denn jeder 3zeiler unter Lizenz gestellt werden?

Den Witz (s. o.) habe ich mittlerweile verstanden, lustig ist er aber 
nicht.
#4982835
Lesenswert?

@ Detlev T. (detlevt)

>Kurz gesagt besteht jeder Thread aus jeweils einer Funktion, die
>periodisch aufgerufen wird. Es ist also kein RTOS. Bei jedem Aufruf
>muss die Funktion beendet werden und wird das nächste Mal wieder "von
>vorn" begonnen (Run to completition).

Das ist schnödes kooperatives Multitasking. Das ist OK, aber man sollte 
es auch einfach so nennen.

>Akzeptiert man diese Einschränkungen, bekommt man ein ein
>leistungsfähiges Multithreading mit sehr kleinem Footprint. Nur etwa 400
>Byte Flash sowie 10 Byte RAM pro (möglichem) Thread werden benötigt.
>Alle Threads verwenden einen(!) gemeinsamen Stack, was aus meiner Sicht
>ein großer Vorteil gegenüber RTOS Lösungen ist. Zusätzlich spart man
>sich die Zeiten für die dort notwendigen Kontextwechsel.

AUA! Das läuft ALLES im Interrupt? Und dann noch mit _delay_ms() in den 
"Threads". Sorry, aber das ist leider nur ein "gutes" Beispiel, wie man 
es NICHT machen sollte! Eine praxisfremde, akademische Spielerei!

https://www.mikrocontroller.net/articles/Multitasking#Verbesserter_Ansatz_mit_Timer

Das ist zwar ein SEHR einfaches Beispiel, aber das macht es wenigstens 
richtig! Es wird KEINERLEI Zeit per _delay_ms() verheizt und alle 
Statemachines bekommen einen garantieren Takt, hier 1ms! Wenn das dein 
"Framework" nicht leisten kannst, schmeiß es weg.
OP #4983190
Lesenswert?

Falk B. schrieb:
> Das ist schnödes kooperatives Multitasking.

Nö, ist es nicht. Das kann man aber wohl nur verstehen, wenn man sich 
damit auseinander setzt.

> AUA! Das läuft ALLES im Interrupt? Und dann noch mit _delay_ms() in den
> "Threads". Sorry, aber das ist leider nur ein "gutes" Beispiel, wie man
> es NICHT machen sollte!

Wie ich schon oben geschrieben habe: Die _delay_ms() sollen hier nur 
stellvertretend für "sinnvolle" andere Tätigkeiten stehen, die 
Rechenzeiten benötigen.

Warum sollte "man es NICHT machen"? Die Erfahrung sagt, dass man die 
Ausführungszeit von Interruptroutinen so kurz wie möglich halten sollte. 
Erfahrungen sind wichtig und hilfreich. Man sollte aber dabei aber nie 
vergessen, warum "man" bestimmte Dinge "nicht macht". Denn dann 
verkommt Erfahrung zum Dogma und wird damit wertlos.

Denn warum sollen ISR kurz sein? Der Grund ist, dass Interrupts andere, 
bei ATMEGAs sogar alle anderen, Interrupts davon abhalten, ausgeführt zu 
werden und das sollte nicht länger als unbedingt nötig der Fall sein. 
Das trifft hier aber gerade nicht zu, da im Header der Routine ein 
"ISR_NOBLOCK" steht.

Dies ist also keine ISR im engeren Sinne, um zeitkritische Dinge zu tun. 
Vielmehr ist es eine (reentrante) Routine, die mit Hilfe der vorhandenen 
Hardware regelmäßig und mehrmals aufgerufen wird und dann ihrerseits 
ausführbereite Thread-Funktionen ausführt, so lange keine mit höherer 
Priorität schon läuft. Das ist zugegeben sehr unorthodox - und meiner 
Meinung nach gerade deshalb interessant.
Gast #4984384
Lesenswert?

Detlev T. schrieb:

> Warum sollte "man es NICHT machen"? Die Erfahrung sagt, dass man die
> Ausführungszeit von Interruptroutinen so kurz wie möglich halten sollte.

Unsinn. Was möglichst kurz sein muss, sind die Zeiten exclusiver 
Codeausführung. Es spielt überhaupt keine Rolle, ob da ISR-Code 
unnötigerweise exclusiv läuft oder main() unnötig lange Interruptsperren 
hat.

Entscheidend ist nicht ISR-Code vs. main()-Code, sondern Interrupts 
möglich oder nicht möglich. Die Zeiten für letzteres sollten so gering 
wie möglich sein. Nur ziemlich beschränkten C-only-Leuten kommt es so 
vor, als wäre das gleichbedeutend mit "Ausführungszeit von 
Interruptroutinen so kurz wie möglich". Es ist leider der Nachteil von 
C, den Programmierer von der Hardware isolieren zu wollen. Wenn der 
Programmierer das zuläßt, landet er halt bei so grundfalschen 
Einschätzungen...

Deine "Lösung" ist deswegen keine Lösung, weil es eben beim ATmega 
einfach das Problem nicht gibt, welches du damit zu lösen versuchst...
#4984423
Lesenswert?

@Detlev T. (detlevt)

>> Das ist schnödes kooperatives Multitasking.

>Nö, ist es nicht. Das kann man aber wohl nur verstehen, wenn man sich
>damit auseinander setzt.

Es IST Kooperativ! Denn deine Threads werden von außen NICHT 
unterbrochen, das hast du doch selber gesagt! Nur weil sie mit 
Prioritäten unterschiedlich oft aufgerufen werden, ändert rein gar 
nichts!

>Wie ich schon oben geschrieben habe: Die _delay_ms() sollen hier nur
>stellvertretend für "sinnvolle" andere Tätigkeiten stehen, die
>Rechenzeiten benötigen.

OK.

>Warum sollte "man es NICHT machen"?

Weil DAS kein sonderlich gutes Beispiel ist.

> Die Erfahrung sagt, dass man die
>Ausführungszeit von Interruptroutinen so kurz wie möglich halten sollte.

Das ist auch so.

>Das trifft hier aber gerade nicht zu, da im Header der Routine ein
>"ISR_NOBLOCK" steht.

OK, hab ich übersehen. Aber warum dann eine ISR? Dann kann man das 
genausogut in der Hauptschleife abarbeiten. Da muss man keine extra 
NOBLOCK ISR-Tricks ausspielen und man spart noch etwas CPU-Zeit, denn in 
der ISR muss in deinem Fall der AVR fast alle Register poppen und 
pushen. Die ISR bringt hier kaum Vorteile aber einige Nachteile.

>Hardware regelmäßig und mehrmals aufgerufen wird und dann ihrerseits
>ausführbereite Thread-Funktionen ausführt, so lange keine mit höherer
>Priorität schon läuft. Das ist zugegeben sehr unorthodox - und meiner
>Meinung nach gerade deshalb interessant.

Naja, es gaukelt aber Funktionalität vor, welches es so nicht gibt.
Gast #4984425
Lesenswert?

Hallo Detlev,

interessante Ausführung. Wenn ich es richtig sehe, kann der eine Thread 
durch den höher prioren Thread mit Hilfe eines Interrupt unterbrochen 
werden.
Damit ist es dann kein Kooperatives Multitasking und Du hast Recht und 
die üblichen Nörgler hier unrecht.

Gruß,
Markus
#4984431
Lesenswert?

Detlev T. schrieb:
> Einen Thread schlafen legen, um
> auf ein externes Ereignis zu warten, geht also nicht.

Das wäre doch gerade der Hauptvorteil von Threads: Zustände und 
Übergänge als normalen Programmfluss hinschreiben mit normalen 
Kontrollstrukturen, auf Ereignisse warten und danach genau dort 
weiterlaufen wo man gerade ist. Ohne das machen zu können, (oder 
zumindest so hinschreiben zu können als würde man es machen [Protothread 
Makros]) bringt das doch überhaupt keine Erleichterung.

> So etwas muss man
> mit einer State-Machine modellieren,

Ja, also doch state machines statt Threads wie eh und je. Und zusätzlich 
noch so ein komisches eigenwilliges Dingenskirchen im Interrupt ohne 
erkennbaren Sinn, also komplizierter als vorher. Wo war also jetzt 
gleich nochmal der Vorteil?

Warum nicht so:
1
while("my guitar gently weeps") {
2
    sleep_until_interrupt();
3
    foo();
4
    bar();
5
    baz();
6
}
#4984470
Lesenswert?

@ Markus (Gast)

>
>interessante Ausführung. Wenn ich es richtig sehe, kann der eine Thread
>durch den höher prioren Thread mit Hilfe eines Interrupt unterbrochen
>werden.

Nö.

>Damit ist es dann kein Kooperatives Multitasking und Du hast Recht und
>die üblichen Nörgler hier unrecht.

Welche üblichen Nörgler? Oder meinst du Leute, die unbequeme Tatsachen 
aussprechen?
OP #4984484
Lesenswert?

@Bernd K.
Dass dieses Konzepte nicht nur Vorteile hat, habe ich nie bestritten. 
Man könnte das noch weiter ausbauen, z.B. so:
1
switch(state){
2
  case 0: //Initialisierung
3
  case 1: if(!Bedingung1) return 1;
4
         //tue irgendwas
5
  case 2: if(!Bedingung2) return 2;
6
        //tue irgendwas  
7
}
Das würde die Verarbeitung unterbrechen, wenn Bedingung1 nicht erfüllt 
ist und im nächsten Aufruf an derselben Stelle das wieder prüfen.

Eine andere Möglichkeit wäre es, einen negativen Wert als Status zurück 
zu geben. Die ISR Schleife müsste den Status auswerten und Threads mit 
negativen Werten übergehen. Der Thread ist dann quasi schlafen gelegt 
bis er durch einen anderen wieder geweckt wird. Dies geschieht indem man 
den entsprechenden positiven Wert in den TCB schreibt:
1
switch(state){
2
  case 0: //Initialisierung
3
         if(!Bedingung1) return -1;
4
  case 1: //tue irgendwas
5
         if(!Bedingung2) return -2;
6
  case 2: //tue irgendwas  
7
}
Das habe ich im Prinzip schon vorgesehen. Deshalb ist der integer für 
den Status signed und die add-Funktion gibt einen Zeiger auf den TCB 
zurück, wo sonst ein boolscher Wert ja gereicht hätte.

Der Code wäre aber dadurch noch unübersichtlicher geworden und wie man 
sieht, überfordert der so manchen jetzt schon.
Gast #4984485
Lesenswert?

>Deine "Lösung" ist deswegen keine Lösung, weil es eben beim ATmega
>einfach das Problem nicht gibt, welches du damit zu lösen versuchst...

Ich überlege gerade, ob es für folgendes Problem ( auch auf dem Atmega ) 
passen könnte:
Bei der Sound-Ausgabe gibt es überlicherweise zwei ISR: eine schnelle, 
die einen Buffer mit z.b 22kHz ausgibt und eine langsame ISR welchen 
z.B. den Buffer vollständig mit einem Sinus füllt.
Kann man es mit dem hier vorgestellten Multithreading mit nur einem 
Timer machen?
Gast #4984504
Lesenswert?

Hallo.

Für alle die es interessiert, anbei der Link zu meiner AVR-Bibliothek 
welche den CPU Zustand abstrahiert und Umschalten ermöglicht.

https://github.com/baerwolf/avrlibs-baerwolf

Oft will man gar kein preemtives Multitasking weil gerade die Interrupts 
echtzeitfähig bleiben müssen (und ein regelmäßiger Timerinterrupt 
stört).
Wenn man nicht Teilautomaten dekomponieren und dann wieder mergen will - 
ist das genau das richtige.

Meine 93cX6 Programmer-Firmware 
(https://github.com/tinyusbboard/93cX6usbasp) uist übrigens auch Nutzer 
dieser Bibliothek.

MfG Stephan
OP #4984507
Lesenswert?

@Markus
Ein Sinus ist vielleicht ein schlechtes Beispiel, da der sich ja 
wiederholt. Ansonsten geht das natürlich, die Berechnung der Werte würde 
ich aber eher in den idle-loop schieben.

Nützlich ist dieses Prinzip überall da, wo periodisch gewisse Dinge zu 
tun sind: LED-Blinken, Eingangswerte pollen etc, die untereinander 
weitgehend unabhängig sind. Denn ist die Abhängigkeit stark, vertrödelt 
man wieder viel Zeit mit der Synchronisation.
Gast #4984515
Lesenswert?

>Ein Sinus ist vielleicht ein schlechtes Beispiel, da der sich ja
>wiederholt. Ansonsten geht das natürlich, die Berechnung der Werte würde
>ich aber eher in den idle-loop schieben.

Ich habe das Beispiel vielleicht didaktisch zu vereinfacht: Ich meinte 
eher eine  Kombination mehrerer DDS-Oszillatoren, die den Buffer in 
einer langsamen "update" Funktion füllen. Das Prinzip findet sich mehr 
oder weniger in der Arduino Mozzi Synth Lib.
Gast #4984561
Lesenswert?

Eigentlich wäre das System gut für das Soundproblem geeignet.
Möglicherweise wäre eine Performance-Untersuchung aber angebracht.
Die Scheduler-ISR hat doch einige Befehle, die Zeit brauchen und 
normalerweise sollte die ISRfür die Ausgabe der Samples aus dem Buffer 
möglichst kurz sein, damit sie das System nicht zu stark belastet, da 
sie ja mit 22kHz aufgerufen wird.
OP #4984758
Lesenswert?

Markus schrieb:
> Möglicherweise wäre eine Performance-Untersuchung aber angebracht.

Bei 20MHz hat man etwa 900 Takte zwischen zwei Ausgaben. Das würde schon 
gehen. Es ist aber natürlich noch ein Unterschied zwischen "geht" und 
"ist sinnvoll". Besser wäre es sicher, der Ausgabe einen eigenen 
Timer+ISR zu spendieren und letztere vielleicht sogar in Assembler zu 
realisieren. Für nur diese eine Aufgabe wäre ein multi threading 
System wohl allgemein nicht sinnvoll.

Aber nehmen wir einmal an, wir hätten neben der Soundausgabe noch 
folgende Aufgaben in absteigender Priorität regelmäßig zu erledigen:
* Software-Entprellung von Tasten
* Berechnung der nächsten Werte
* Auswertung von Tastendrücken
* Auswertung von Daten, die über USART reinkommen
* Ausgabe des aktuellen Status auf einem LC-Display
* Status-LED blinken lassen.

Da hat man gleich zwei Probleme. Erstens hat ein ATMEGA in der Regel gar 
nicht genug Timer, um jeder Aufgabe einen eigenen zuzuweisen. Zweitens 
gibt es dort nicht die Möglichkeit, die Priorität von Interrupts 
untereinander festzulegen.

Also macht man eine Mischung aus ISRs und einer Art kooperativen 
Multitasking in einem Superloop. Dabei verliert man die weitgehende 
Unabhängigkeit der einzelnen Aufgaben und der Code wird schwieriger zu 
warten, weil spätere Änderungen einem das Timing durcheinander bringen 
können. Da sehe ich schon Vorteile für so ein System.
Gast #4984803
Lesenswert?

>Bei 20MHz hat man etwa 900 Takte zwischen zwei Ausgaben. Das würde schon
>gehen. Es ist aber natürlich noch ein Unterschied zwischen "geht" und
>"ist sinnvoll". Besser wäre es sicher, der Ausgabe einen eigenen
>Timer+ISR zu spendieren und letztere vielleicht sogar in Assembler zu
>realisieren.

Da genau liegt das Problem: ein Timer mehr.
Man kann das Problem aber lösen, wenn man in den Scheduler in den 
PWM-Interrupt einbaut. Man muss nur die Last verteilen, indem der 
Scheduler nur alle N-Mal aufgerufen wird.
Gast #4984814
Lesenswert?

Interessant. Es wird wohl immer Leute geben, die versuchen, mit einem 
Schweizer Taschenmesser ein Haus zu bauen.

Wem das zu hoch ist:

Die AVRs wurden nicht für diese Aufgabe gemacht. Allein der verfügbare 
RAM sollte das schon zeigen. Der Befehlssatz ist auch sehr deutlich. Ich 
sehe da zumindest keine MMU-Funktionen. Ihr etwa?
Gast #4984834
Lesenswert?

So, jetzt habe ich das ganze Mal auf das Lieblingssystem der MC-Net User 
portiert:

Einen Arduino UNO ;-)

Die wesentliche Änderung: die Benutzung von Timer1 statt Timer0.

Das Ganze scheint zu funktionieren, allerdings blinkt die LED aus dem 
zweiten Thread alle 2 Sekunden, obwohl meine Definitionen folgende sind:

1
#define THREAD_MS_PER_TICK 15
2

3
void setup()   
4
{
5
  pinMode(LED_BUILTIN, OUTPUT);
6
  pinMode(LED2, OUTPUT);
7
  threadAdd(led1, NULL, 100/THREAD_MS_PER_TICK);
8
  threadAdd(led2, NULL, 1000/THREAD_MS_PER_TICK);
9
  threadStart();
10
}
Angehängte Dateien:
Beitrag #4984861 wurde von einem Moderator gelöscht.
OP #4985121
Lesenswert?

Markus schrieb:
> allerdings blinkt die LED aus dem
> zweiten Thread alle 2 Sekunden, obwohl meine Definitionen folgende sind:

Der Fehler liegt IMO in der Initialisierung des Timers. So läuft der 
Timer1 nicht im CTC mode. So müsste es richtig sein: TCCR1A bleibt auf 
null, TCCR1B muss auf 0x0D gesetzt werden.

Die Wahl von THREAD_MS_PER_TICK ist auch nicht gut, da 15 kein Teiler 
von 100 und 1000 ist. 10 wäre hier besser.
#4985916
Lesenswert?

c-hater schrieb:
> Es ist leider der Nachteil von
> C, den Programmierer von der Hardware isolieren zu wollen.

Und es ist leider der Nachteil von "Internet", dass jeder ahnungslose 
Hasser seinen Unfug posten kann.

Ignoraten und Hasser bitte ab hier nicht mehr weiterlesen ...

"Das Haupteinsatzgebiet von C liegt in der Systemprogrammierung 
einschließlich der Erstellung von Betriebssystemen und der 
Programmierung von eingebetteten Systemen. Der Grund liegt in der 
Kombination von erwünschten Charakteristiken wie Portabilität und 
Effizienz mit der Möglichkeit, Hardware direkt anzusprechen und dabei 
niedrige Anforderungen an die Laufzeitumgebung zu haben." (Wikipedia, 
...)
Gast #4986042
Lesenswert?

>Der Fehler liegt IMO in der Initialisierung des Timers.

Du hast Recht, man muss die richtigen Timer-Einstellungen finden.

> TCCR1A bleibt auf null, TCCR1B muss auf 0x0D gesetzt werden.

Die sind leider auch falsch. Ich habe mal ein wenig an meinen 
Einstellungen gedreht und konnte passendere Werte finden.

Die Funktion threadAdd wäre meiner Meinung eventuell einfacher wenn dort 
die wirkliche Zeit stehen würde:
1
threadAdd(threadFunc_t pointerToFunction, void * pArgumentFuerWas, uint16_t periodTicks_ms);
Persönliche Seite #4986133
Lesenswert?

knudle schrieb:
> hier im Forum gibt es ein Artikel

Sehr geil xD

> Warnung
> Es werden gute Kenntnisse in C und Assembler, sowie gute Kenntnisse der
> CPU benötigt. Des Weiteren ersetzt ein selbst geschriebenes OS
> keinesfalls ein herkömmliches OS (RTOS …), da diese zuverlässiger und
> effizienter laufen und mehr Funktionen haben.

Gibt es das FreeRTOS nicht für AVR auch?
OP #4986239
Lesenswert?

Markus schrieb:
> Die Funktion threadAdd wäre meiner Meinung eventuell einfacher wenn dort
> die wirkliche Zeit stehen würde:

So hätte das eine ganze Menge Nachteile:
* Die Berechnung der Ticks findet erst zur Laufzeit statt zur 
Compilezeit statt
* Die entsprechenden library-Routinen müssten mit eingebunden werden
* Ändert man den Type des Übergabeparameters nicht, beschränkt man die 
Periodendauer unnötigerweise.

Wer nur eine bessere Lesbarkeit habe will, kann ja das machen:
1
#define THREAD_ADD(x,y,z) thread_add((x), (y), (z)/THREAD_MS_PER_TICK)

knudle schrieb:
> hier im Forum gibt es ein Artikel der die Herangehensweise zur
> Multithread Systemen beschreibt.

Auf den ersten Blick habe ich keinen Quellcode finden können. Ist dieser 
Artikel zur Zeit nur eine Absichtserklärung?

Mampf F. schrieb:
> Gibt es das FreeRTOS nicht für AVR auch?

Naja, "Geben" ist sehr euphemistisch. Selbst bei der allerneuesten 
Version ist nur ein Port für den ATMEGA323 dabei. Wer kennt den denn 
(noch)? Also müsste man das System, das man ja noch gar nicht kennt, 
erst einmal vor dem Ausprobieren portieren. Für die ersten Schritte in 
Richtung RTOS ist FreeRTOS IMO daher nix.
Persönliche Seite #4986474
Lesenswert?

Serus,
Detlev T. schrieb:
> Auf den ersten Blick habe ich keinen Quellcode finden können. Ist dieser
> Artikel zur Zeit nur eine Absichtserklärung?

ich hab absichtlich keine Code zu dem Artikel veröffentlicht. Der 
Artikel ist im Rahmen eines privaten Projektes von mir entstanden.

Die Grund Idee hinter dem Artikel ist die Grundlegende Funktionsweise 
eines Scheduler aus der Sicht des IC's zu betrachten.
Und dabei für Interessenten zum Nachprogrammieren eine Art HowTo zu 
liefern.
Basti
#4998551
Lesenswert?

Nun ja, ich hab mir jetzt den Artikel mal genauer angeschaut und wie es 
scheint ist das Konzept doch schon clever. Man macht einen prääemtiven 
Scheduler nur durch verschachtelte Timer-Interrupts mit minimalem 
Aufwand. Damit kommen schnelle, kurze Tasks immer noch schnell dran und 
unterbrechen damit langsame, niederpriore Tasks. Da war ich wohl mit 
meinem Urteil ein wenig vorschnell . . .8-0
#4998588
Lesenswert?

Die Idee ist ganz nett, man sollte aber aufpassen, dass die Stack-Größe 
reicht. Im ungünstigsten Fall liegen der Reihe nach die Register und 
Stacks von der niedrigst Prioren bis zur höchst prioren Task im RAM. 
Nämlich genau dann wenn nach einander alle Tasken von der 
niedrigpriorsten  bis zur höchstpriorsten Task angestartet werden, aber 
keine in Ihrem Intervall fertig wird und dann noch ein (anderer) 
Interrupt kommt. Dann liegen
- je Task 32 + 1 Byte Register und
- der normale call stack jeder Task

auf dem Stack.

Bei 4 Tasken wären das dann mindestens 132 Byte nur für die Register, 
eher mehr. Ob man das wirklich als kleinen Speicherverbrauch bezeichnen 
will... Wenn dabei ein Stackoverflow auftritt wird das ganze sicher sehr 
spaßig zu debuggen sein. (Man könnte allerdings den SP beim Eintritt in 
die Routine gegen durch data/bss belegten Ram auf plausibilität prüfen.

Ich verwende dann lieber kooperatives Multitasking, indem ich einfach 
eine einfach verkettet Liste mit Funktionspointern befülle, die 
nacheinander ausgeführt werden. Resourcen sind dann von Anfang an klar 
und es wird kein unnötiger Platz für Stack verschwendet. Das Konzept 
einer Task an sich gibt es dann natürlich nicht und man braucht 
Statemachines, die man allerdings mittels anpassen der Funktionspointer 
auch implementieren kann.
Gast #4998625
Lesenswert?

Autor: Falk Brunner (falk)
>Das ist schnödes kooperatives Multitasking. Das ist OK, aber man sollte
>es auch einfach so nennen.
>AUA! Das läuft ALLES im Interrupt? Und dann noch mit _delay_ms() in den
>"Threads". Sorry, aber das ist leider nur ein "gutes" Beispiel, wie man
>es NICHT machen sollte! Eine praxisfremde, akademische Spielerei!

Autor: Falk Brunner (falk)
>Naja, es gaukelt aber Funktionalität vor, welches es so nicht gibt.

Autor: Falk Brunner (falk)
>Welche üblichen Nörgler? Oder meinst du Leute, die unbequeme Tatsachen
>aussprechen?

Autor: Falk Brunner (falk)
>Man kann sich akademisch beweihräuchern.

Autor: Falk Brunner (falk)
>Nun ja, ich hab mir jetzt den Artikel mal genauer angeschaut und wie es
>scheint ist das Konzept doch schon clever. Man macht einen prääemtiven
>Scheduler nur durch verschachtelte Timer-Interrupts mit minimalem
>Aufwand. Damit kommen schnelle, kurze Tasks immer noch schnell dran und
>unterbrechen damit langsame, niederpriore Tasks. Da war ich wohl mit
>meinem Urteil ein wenig vorschnell . . .8-0

Als Löwe gesprungen, als Bettvorleger gelandet ;-)
Gast #4998643
Lesenswert?

Detlev T. schrieb:
> das Aufteilen einer Firmware in mehrere weitgehend unabhängige Threads
> hat häufig Vorteile. Die meisten werden da zu RTOS-Lösungen greifen,
> auch wenn eigentlich gar keine echten Real-Time-Anforderungen bestehen,
> sondern nur bestimmte Teile regelmäßig aufgerufen werden müssen.

Man muß schon sehr aufpassen ob 1. der "Wasserkopf" benötigter 
Ressourcen und 2. der gesteigerte System-Komplexitätsgrad solcher 
Multitasking-Lösungen bei kleinen 8-Bit Controllern in einem 
angemessenen Verhältnis zum Nutzen stehen. Ich würde meinen, daß davon 
sehr viel weniger Apps profitieren als hier angenommen wird. Meiner 
Meinung nach sind alle anstehenden echtzeitbedürftigen Aufgaben viel 
besser ganz einfach und ganz wie beabsichtigt in zugehörigen Interrupts 
aufgehoben und die regelmäßige, gern auch prioritätsgesteuerte, 
zeitkritische oder einfach nur zeitlich zu strukturierende Abarbeitung 
diverser Funktionen kann sehr übersichtlich einem zentralen 
Timer-Interrupt überlassen bleiben der ggf. auch ein Funktions-Interface 
für das Hauptprogramm zur Verfügung stellt. Oft genug kann sogar auf 
letzteres "schlafend" verzichtet und die Funktionalität vollumfänglich 
in Interrupts realisiert werden.
Gast #5003563
Lesenswert?

M. schrieb:

> Meiner
> Meinung nach sind alle anstehenden echtzeitbedürftigen Aufgaben viel
> besser ganz einfach und ganz wie beabsichtigt in zugehörigen Interrupts
> aufgehoben und die regelmäßige, gern auch prioritätsgesteuerte,
> zeitkritische oder einfach nur zeitlich zu strukturierende Abarbeitung
> diverser Funktionen kann sehr übersichtlich einem zentralen
> Timer-Interrupt überlassen bleiben.

Sehr schön auf den Punkt gebracht. Wie ich (viel weiter oben) schon 
sagte: der Autor versucht hier ein Problem zu lösen, was de facto 
überhaupt nicht existiert.
Gast #5006773
Lesenswert?

> Wie ich (viel weiter oben) schon
> sagte: der Autor versucht hier ein Problem zu lösen, was de facto
> überhaupt nicht existiert.

Wie ich schon weiter oben dargelegt hatte, gibt es einen Anwendungsfall, 
bei dem das Prinzip durchaus nützlich ist.
OP #5010184
Lesenswert?

Hallo Leute,

leider war mein Labornetzteil ein Fall für die Gewährleistung, deshalb 
konnte ich die Version für XMEGAs nicht testen. Hier jetzt also die 
Version für XMEGAs. Der hier signifikante Unterschied zu den ATMEGAs ist 
der Interruptcontroller. Der muss überlistet werden und glauben, dass 
die ISR schon fertig ist, obwohl sie faktisch noch läuft. Nur dann ist 
ein nochmaliger Interrupt möglich.

Erreicht wird dies durch einen Call auf eine Stelle mit einem "reti" 
Befehl. Die ISR muss dafür im Gegenzug mit einem gewöhnlichen "ret" 
Befehl beendet werden. Ohne Assembler ist das leider nicht möglich. Die 
Sicherung der Register auf dem Stack muss daher auch "manuell" erfolgen. 
Bei der Auswahl, welche Register gesichert werden, bin ich den Angaben 
des GCC Compilers gefolgt.

Das Beispielprogramm wurde auf einem AL-XSLED Modul von Alvidi getestet, 
der mit einem xmega256a3 bestückt ist. Jeder andere XMEGA sollte aber 
auch funktionieren.

Das Ganze ist nach wie vor ein Proof of Concept. Was schon einmal in der 
Diskussion über die ATMEGA-Version geschrieben wurde, muss daher 
eigentlich nicht noch einmal wiederholt werden.

Ich habe inzwschen auch ein paar RTOS-Implementierungen gesehen, die auf 
diesem "Single-Stack"-Prinzip aufbauen. Ob die brauchbar sind, weiß ich 
allerdings nicht.

Viel Spaß am Gerät.

PS: Beide angehängte Dateien sind identisch. Ich bin hier mit der 
Vorschau-Funktion intellektuell überfordert gewesen. ;-)
Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren