Forum: Projekte & Code Einfaches Multithreading für ATMEGAs


von Detlev T. (detlevt)


Angehängte Dateien:

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!

von Baldrian (Gast)


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!

von Detlev T. (detlevt)


Lesenswert?

Baldrian schrieb:
> Vermutlich habe ich den Witz nicht verstanden.

Mein Fehler, ich habe die Pointe vergessen. ;)

Das Multithreading erfolgt prioritätsgesteuert preemptiv, d.h. Threads 
mit höherer Priorität können andere Threads unterbrechen, wenn ihre Zeit 
gekommen ist.

von Peter II (Gast)


Lesenswert?

das ist doch ein normales Kooperatives Multitasking, aber dazu passt 
überhaupt nicht das _delay_ms(1500.0);

von Detlev T. (detlevt)


Lesenswert?

@Peter II
Die delay-Funktionen sollen doch nur symbolisch den Controller 
beschäftigen. Ich will damit zeigen, dass led1 auch dann ausgeführt 
wird, wenn led2 noch läuft ohne dass led2 aktiv die Kontrolle abgeben 
muss. Das ist eben gerade nicht kooperatives MT.

von EGS_TI (Gast)


Lesenswert?

Peter II schrieb:
> _delay_ms(1500.0)

hmmm

von EGS_TI (Gast)


Lesenswert?

Stichwort: Patterns for time triggered embedded systems.

von Baldrian (Gast)


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.

von Detlev T. (detlevt)


Lesenswert?

Baldrian schrieb:
> Muss denn jeder 3zeiler unter Lizenz gestellt werden?

In unserer UrhG-Welt, wo schon das Singen von "Happy Birthday" im 
Hinterzimmer einer Kneipe kostenpflichtig ist, leider ja.

von Falk B. (falk)


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.

von Detlev T. (detlevt)


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.

von c-hater (Gast)


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...

von Carl D. (jcw2)


Lesenswert?

Interessantes Papier aus dem Universitären Bereich, in dem sowas wie 
Protothreads nicht mal erwähnt wird. Dabei versucht man doch die 
Nachteile derer mit dem Weglassen ihrer Vorteile zu verbinden.

von Falk B. (falk)


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.

von Markus (Gast)


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

von Bernd K. (prof7bit)


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
}

: Bearbeitet durch User
von Detlev T. (detlevt)


Lesenswert?

Markus schrieb:
> Wenn ich es richtig sehe, kann der eine Thread
> durch den höher prioren Thread mit Hilfe eines Interrupt unterbrochen
> werden.

Du siehst das richtig.

Von dem Begriff "übliche Nörgler" distanziere ich mich aber. Das Feuer 
der Kritik härtet den Stahl der Argumente. Deshalb stelle ich solche 
Dinge hier ja vor.

von Falk B. (falk)


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?

von Falk B. (falk)


Lesenswert?

@ Bernd K. (prof7bit)

>noch so ein komisches eigenwilliges Dingenskirchen im Interrupt ohne
>erkennbaren Sinn, also komplizierter als vorher. Wo war also jetzt
>gleich nochmal der Vorteil?

Man kann sich akademisch beweihräuchern.

von Markus (Gast)


Lesenswert?

>Von dem Begriff "übliche Nörgler" distanziere ich mich aber. Das Feuer
>der Kritik härtet den Stahl der Argumente.

Du hast Recht und wir sollten auch bei der technischen Argumenten 
bleiben.

von Detlev T. (detlevt)


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.

von Markus (Gast)


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?

von matrixstorm (Gast)


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

von Detlev T. (detlevt)


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.

von Markus (Gast)


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.

von Markus (Gast)


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.

von Detlev T. (detlevt)


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.

: Bearbeitet durch User
von Markus (Gast)


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.

von ASM Superprofi (Gast)


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?

von ASM Superprofi (Gast)


Lesenswert?

Wer unbedingt mit Multithreading Experimente machen will, sollte 
wirklich einen ARM nehmen. Dort kann man durchaus glücklich werden. Beim 
AVR ist das einfach nur Murks.

von ASM Superprofi (Gast)


Lesenswert?


von Markus (Gast)


Angehängte Dateien:

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
}

Beitrag #4984861 wurde vom Autor gelöscht.
von Detlev T. (detlevt)


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.

von Michael L. (michaelx)


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, 
...)

: Bearbeitet durch User
von Markus (Gast)


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);

von Mampf F. (mampf) Benutzerseite


Lesenswert?

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

Finden? Steht das nicht im Datenblatt, wie man die einstellen muss?

von knudle (Gast)


Lesenswert?

hallo,
hier im Forum gibt es ein Artikel der die Herangehensweise zur 
Multithread Systemen beschreibt.
Zwar ist der Artikel für Arm jedoch sollte sich das auch auf dem Avr 
umsetzen lassen.

https://www.mikrocontroller.net/articles/Scheduler_mit_Erweiterung_zum_Mini-Betriebssystem


grüße

von Mampf F. (mampf) Benutzerseite


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?

von Detlev T. (detlevt)


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.

von Basti B. (basti195) Benutzerseite


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

: Bearbeitet durch User
von Falk B. (falk)


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

von Andreas M. (amesser)


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.

von Markus (Gast)


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 ;-)

von M. (Gast)


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.

von Ordner (Gast)


Lesenswert?

Detlev T. schrieb:

> In unserer UrhG-Welt, wo schon das Singen von "Happy Birthday" im
> Hinterzimmer einer Kneipe kostenpflichtig ist, leider ja.

Stimmt spätestens seit 31.12.2016 nicht mehr.

https://de.wikipedia.org/wiki/Happy_Birthday_to_You

von EGS T. (egs_ti)


Lesenswert?

Hahaha

von c-hater (Gast)


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.

von Markus (Gast)


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.

von Detlev T. (detlevt)


Angehängte Dateien:

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. ;-)

: Bearbeitet durch User
von Uwe M. (uwe_g675)


Angehängte Dateien:

Lesenswert?

hi,
die beste umsetzung dazu lautet "super simple tasker" und ist dann ein 
echtes event driven preemptive mini os. die idee ist echt gut und schon 
für alle möglichen controller portiert (google sst rtos !) worden. läuft 
bei mir in mehreren avr/arm projekten!

uwe

von Markus (Gast)


Lesenswert?

>die beste umsetzung dazu lautet "super simple tasker" und ist dann ein
>echtes event driven preemptive mini os.

Hallo Uwe,
hast Du einen Link auf den Source Code?

von Anti-Heiner (Gast)


Lesenswert?

> hast Du einen Link auf den Source Code?

https://state-machine.com/doc/Samek0607.zip

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.