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!
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!
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.
das ist doch ein normales Kooperatives Multitasking, aber dazu passt überhaupt nicht das _delay_ms(1500.0);
@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.
> 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.
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.
@ 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.
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.
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...
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.
@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.
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
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
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.
@ 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?
@ 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 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.
@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.
>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?
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
@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.
>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.
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.
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
>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.
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?
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.
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.
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.
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
>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); |
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?
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
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?
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.
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
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
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.
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 ;-)
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.
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
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.
> 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.
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
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
>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?
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.