mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Multitasking sehr einfach für Mega128


Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen.

Ich versuche mich gerade an einem einfachen Multitasking System.

Daszu habe ich zwei Funktionen, die in einer Endlosschleife sind und 
eine LED an unterschiedlichen Pins unterschiedlich schnell blinken 
lassen sollen.

Aufgerufen wird am anfang keine Funktion, sondern es wird nur ein Timer 
gestartet. Der soll dann immer zwischen den Funktionen hin und 
herschalten.

Ich habe mir gedacht, dass sobald die Interrupt-Routine aufgerufen wird, 
befindet sich ja die Rücksprungadresse ganz oben auf den stack....um 
jetzt zu eine Funktion zu springen, müsste man doch die Rücksprung 
adresse verändern so das sie auf die erste Funktion zeigt.
Beim verlassen des Interrupts müsste doch dann durch den befehl Reti zur 
ersten Funktion gesprungen werden.
Der Timer dürfte aber dann weiterlaufen, beim nächsten interrupt wird 
die erste funktion unterbrocken, rücksprungadresse auf die zweite 
funktion gesetzt usw..
Natürlich müssten alle register gesichert werden vorher.

Aber ich bekomm es irgendwie nich, ich benutze CodeVision und weiss 
irgendwie nicht wie ich die Rücksprungadresse manipulieren kann.

Hat jemand eine Idee?

Autor: sechsminuszwei (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du willst dir das Leben schwer machen ? Der Standardansatz ist ein Timer 
Interrupt und eine Statusmaschine im Main. Diesen Ansatz durch 
Multitasking zu ersetzen macht erst Sinn wenn die Statusmaschine komplex 
und unuebersichtlich geworden ist. Falls du wirklich einen Kernel selbst 
schreiben willst, solltest du dir mal das ASM Manual des Prozessors 
anschauen. Welche Befehle was machen und so.

Autor: Simon K. (simon) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Vorallem solltest du zuerst im richtigen Forum posten.

Autor: Guile Lampert (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also ein relativ einfacher Ansatz ist, einen Timer aufzuziehen.
Jeder Task bekommt ein festes Intervall in dem er läuft.
z.B. alle 20ms.
In der Timer ISR reduziert man dann jedesmal den Counter für alle Tasks. 
Sobald ein Counter 0 ist, wird ein Flag (running flag)  gesetzt.
In der main lässt Du eine Endlosschleife laufen, die jede Funktion 
auführt deren running flag auf true steht.

Das ist mal gaaanz einfach beschrieben wie man das im Prinzip machen 
kann. Wenn Du echtes Multitasking haben willst, musst Du bei jedem 
Taskwechsel sämtliche Register etc. retten.

Ich empfehle freeRTOS um mal zu gucken wie sowas funktioniert.

Gruss
Guile

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
hi,
danke für die schnellen antworten.
Bin ich denn hier nicht im richtigen Forum??

also ich habe mir das so gedacht, das jeder Task eine bestimmte anzahl 
von Ticks (Timer Interrupts bekommt) somit kann man regulieren ob man 
ein task länger ausgeführt (höhere Priorität) oder weniger lange 
(niedrige Prio).
Jeder Task bekommt seinen Eigenen Stackpointer bereich, in dem er seine 
Variablen sichern kann, die grosse des bereiches wird bestimmt durch 
eine minimale Anzahl von Registern (also R0 bis r31) und die Größe der 
lokalen Variablen.
Alle Daten zu einem Task sind in einer Ringliste die aus einem 
entsprechenden Struct besteht gespeichert. Dort ist auch jedes mal ein 
pointer zum nächsten Struct für den nächsten Task gespeichert, ein ring 
halt...
Wird nun ein Timerinterrupt ausgelöst, sollen zunächste alle Register 
gesichert werden, danach wird überprüft ob der aktuelle Task (ein 
globaler Pointer zeigt auf das Struct von diesem Task) gestartet wurde 
und ob die anzahl der Ticks schon auf null steht. Is dies nicht der Fall 
wird die Tick variable decrementiert und nix geschieht weiter...
Ist sie allerdings null ist die zeit für den Task abgelaufen, und der 
nächste Task soll gestarten bzw. fortgesetzt werden...dazu wird der 
aktuelle Taskpointer auf den Next Pointer vom aktuellen Task gesetzt 
(Weil Ringliste)..
Und jetzt haperts bei mir. Denn der nächste Stackbereich muss 
eingestellt werden, für den nächsten Task, und da habe ich Probleme, ich 
weiss nicht genau wie.....
hmm vielleicht kann mir ja jetzt irgendwie jemand ein tip geben??

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Hirbel Ha (leo)

>also ich habe mir das so gedacht, das jeder Task eine bestimmte anzahl
>von Ticks (Timer Interrupts bekommt) somit kann man regulieren ob man
>ein task länger ausgeführt (höhere Priorität) oder weniger lange
>(niedrige Prio).
>Jeder Task bekommt seinen Eigenen Stackpointer bereich, in dem er seine
>Variablen sichern kann, die grosse des bereiches wird bestimmt durch
>eine minimale Anzahl von Registern (also R0 bis r31) und die Größe der
>lokalen Variablen.

Ob das auf einem kleinen uC sinnvoll ist? Warum nicht einfach 
kooperatives Multitasking, jeder Task ist einmal pro Timerinterupt dran. 
Das alles als Endloschleife im main(). Siehe dein eigenes Betreff!

MG
Falk

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
höö?

naja okay, aber wie bitte schön soll das funktionieren?

im main sieht es dann so aus?

void main(void)
{
...

while (1)
{
task1();
task2();
}

Task1 und 2 haben jeweils auch eine endlosschleife, bei start würden wir 
also in task1 gehen und da bleiben, wird ein interrupt ausgelöst, 
springen wir aus task1 in die interrupt routine...danach würden wir 
wieder zur endlosschleife in task1 springen und so weiter, man käme nie 
zu task2, wenn wir nicht die rücksprungadresse manipulieren würden. Und 
genau um dieses manipulieren geht es mir.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Hirbel Ha (leo)

>naja okay, aber wie bitte schön soll das funktionieren?

Das geht wunderbar. Im Prinzip wie hier dargestellt, auch wenn das Thema 
ein anderes ist.

Sleep Mode

>Task1 und 2 haben jeweils auch eine endlosschleife, bei start würden wir

Falscher Ansatz. Die Task sind kooperativ! D.h. sie werden aufgerufen, 
prüfen ob was zu machen ist, machen was, und werden wieder beendet.

>zu task2, wenn wir nicht die rücksprungadresse manipulieren würden. Und

Lass den Quark. Das ist bestenfalls was für grosse CPUs.

>genau um dieses manipulieren geht es mir.

Falscher Ansatz.

MFG
Falk

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sorry du verstehst mich nicht...

hab mich ein bissl gelesen und es ist auf einem avr kein problem.
das was du meinst,hat ja absolut nix mit multitask bzw. scheinbarer 
parallelität zu tun, das ist einfach nur sequentiell und das ist was, 
was ich zwar auch immer gemacht habe, aber das brauche ich nicht.

Also das "Verbiegen" des Stacks ist schon notwendig.

Aber trotzdem danke.

Autor: Düsentrieb (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>rücksprungadresse manipulieren

push & pop sind deine freunde :-)

Autor: Guile Lampert (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lad Dir doch einfach freeRTOS runter und guck Dir den portierten Code 
für den ATmega323 an!
Was da gemacht wird, kannst Du auch nehmen um Deine Register etc. zu 
sichern bevor ein Taskwechsel durchgeführt wird.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ja aber, ein falsches mal "poppen" oder "pushen" und alles is dahin ;)
ausserdem gibts in Codevision ein Hardware und Software Stack...das is 
alles nich so einfach..

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> Daszu habe ich zwei Funktionen, die in einer Endlosschleife sind und
> eine LED an unterschiedlichen Pins unterschiedlich schnell blinken
> lassen sollen.

Um viele LEDs unabhängig blinken zu lassen, ist ein Scheduler ideal:

Beitrag "Wartezeiten effektiv (Scheduler)"


Peter

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@peda
das mit den LEDs sollte nur ein test sein...das man LEDs mit einem timer 
unterschiedlich schnell blinken lassen kann, das is mir klar..

in wirklichkeit sind später mein task's viel komplexer...z.b. sensoren 
oder motoren steuerung..

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>hab mich ein bissl gelesen und es ist auf einem avr kein problem.

Bei jedem Taskwechsel alle 32 Register im SRAM zu sichern und 
wiederherzustellen, kann schneller zum Problem werden, als Dir lieb ist. 
Sowohl zeitlich gesehen, als auch vom Speicherplatz her (8 Tasks --> 256 
Byte = schon 1/4 des SRAMs eines ATmega8).

>das was du meinst,hat ja absolut nix mit multitask bzw. scheinbarer
>parallelität zu tun,

Offensichtlich kennst Du den Unterschied zwischen kooperativem und 
preemptiven Multitasking nicht.

>das ist einfach nur sequentiell

Nein.

>und das ist was,
>was ich zwar auch immer gemacht habe, aber das brauche ich nicht.

Welche Aufgabe kannst Du denn mit der herkömmlichen Methode (siehe Falks 
Erklärungen) nicht lösen?

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  Hirbel Ha (leo)

>in wirklichkeit sind später mein task's viel komplexer...z.b. sensoren
>oder motoren steuerung..

Was umsomehr für kooperatives Multitasking spricht. Ohne Stackgefummel.
Siehe

Multitasking

MfG
Falk

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
es wird ein atmega128 benutzt mit 5 tasks

das ziel ist es das die ergebnisse der task, quasi alle gleichzeit zur 
verfügung stehen sollen..

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Hirbel Ha (leo)

>es wird ein atmega128 benutzt mit 5 tasks

>das ziel ist es das die ergebnisse der task, quasi alle gleichzeit zur
>verfügung stehen sollen..

Ja und? Deine Stackfummelei bringt dich diesem Ziel nicht wirklich 
näher.

MFG
Falk

Autor: StinkyWinky (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es ist doch so: Wenn Task1 durch den Timer-IRQ unterbrochen wird, legt 
er  die Rücksprungadresse in den Stack in seinem Stackmemory-Bereich ab. 
Nun ermittelt der Timer-IRQ, welcher Task als nächstes dran ist. Nehmen 
wir an, Task3. Der Stack wird umgeschaltet auf den Stackmemory-Bereich 
des Tasks3. Da Task3 ja vor einiger Zeit bereits unterbrochen worden 
ist, liegt ja die Rücksprungadresse immer noch auf seinem Stack (von 
Task3). Also braucht es nur noch ein RTS plus allenfalls vorher noch 
IRQ-Flags aufräumen.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
hmmm also ich glaube, das was ich will is multi-threading...oder 
präemptives mutltitasking?
also ich will das ein timer in einem intervall die funktionen 
unterbricht und hin und her schaltet

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> @peda
> das mit den LEDs sollte nur ein test sein...das man LEDs mit einem timer
> unterschiedlich schnell blinken lassen kann, das is mir klar..
>
> in wirklichkeit sind später mein task's viel komplexer...z.b. sensoren
> oder motoren steuerung..

Mag ja alles sein.
Trotzdem ist der Ansatz

> Task1 und 2 haben jeweils auch eine endlosschleife

auf einem µC nicht unbedingt der beste.

Wenn du kein OS drunter liegen hast, dann braucht ein Task
nun mal keine Endlosschleife, bzw eine Warteschleife.
Genau das ist ja der Dreh beim kooperativen Multitasking:
Kein Task darf die CPU längere Zeit blockieren sondern ist
verpflichtet die CPU wieder abzugeben, damit der nächste Task
drann kommen kann.

Wie Falk schon sagte:
In der Hauptschleife in main() (der einzigen Endlosschleife die
im Programm ist), werden zyklisch alle Tasks aufgerufen. Jeder
Task prüft ob etwas zu tun ist und wenn ja, dann macht er es.
Hat er nichts zu tun (weil er zb. auf das Ablaufen einer Zeit
wartet), dann darf er nicht in eine Schleife gehen, sondern
kommt sofort zurück.

Im Grunde macht PeDa's Scheduler (wenn ich das richtig im Kopf
habe) auch nicht anders. Nur halt organisierter.

Kooperatives Multitasking hat den Vorteil, dass es schön einfach
ist. Der Nachteil ist, dass sich alle Tasks an die Regeln halten
müssen.

Dem gegenüber steht preemptive Multitasking, dass so funktioniert
wie du dir das im Moment vorstellst. Der Nachteil vom kooperativen
Multitasking wird dadurch behoben, dass der Scheduler einem
Task die CPU mit Gewalt entziehen kann und nicht auf seine
Kooperationsbereitschaft angewiesen ist.
Aber dann wird alles komplizierter. Mit dem Kontextswitch ist
dann nämlich noch lange nicht getan. Dann brauchst du auch
Semaphoren und sontiges Zeugs um sichere Kommunikation zwischen
den Tasks zu erreichen. Bei kooperativem Multitasking ist das
alles wesentlich weniger aufwändig, weil u.A. ein Task sich
auch darauf verlassen kann, dass er nicht unterbrochen wird,
bis er die Kontrolle wieder freiwillig abgibt. Und es macht
nun mal einen Unterschied, ob ich die 2 Byte eines int in einem
Rutsch in den Speicher schreiben kann oder ob ich damit rechnen
muss, dass mir der Scheduler die CPU nach dem Schreiben des
ersten Bytes unter dem Arsch wegzieht und der momentane Zustand
des Speichers nicht stimmt (auf den dann aber wiederrum ein
anderer Task angewiesen ist).

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  Hirbel Ha (leo)

>hmmm also ich glaube, das was ich will is multi-threading...oder
>präemptives mutltitasking?
>also ich will das ein timer in einem intervall die funktionen
>unterbricht und hin und her schaltet

Du legst dich schon vorher fest, WIE ein Problem gelöst werden soll, 
ohne erstmal die Alternativen zu suchen und zu bewerten. Keine gute 
Idee.

MFG
Falk

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
nein @Falk,
versteh mich nicht falsch, ich bin dankbar über jede alternative..aber 
ich habe meine vorgaben und es sollte so ablaufen, das der timer die cpu 
mit gewalt einer funktion entreisst und einer anderen funktion zuweisst, 
so wie hier schon beschrieben.
Also die Funktion selber kann nichts machen ausser vorsichhinlaufen, die 
steuerung muss jemand anderes übernehmen

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ Hirbel Ha (leo)

>versteh mich nicht falsch, ich bin dankbar über jede alternative..aber
>ich habe meine vorgaben und es sollte so ablaufen, das der timer die cpu
>mit gewalt einer funktion entreisst und einer anderen funktion zuweisst,
>so wie hier schon beschrieben.

Kann man machen. Will ich dir auch nicht ausreden. Mach mal und berichte 
von deinen Erfahrungen.

MfG
Falk

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ja mach ich gerne, nur da wäre ich wieder bei meiner anfänglichen Frage 
:)

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das ist doch genau was preemptive Multitasking macht.
Das braucht man nicht selbst zu programmieren, das gibt es fix und 
fertig.
AvrX oder FreeRTOS.
Wenn man das selber macht, braucht man nur VIEL länger und funktioniert 
nicht so sicher.
Ich habe AvrX eingesetzt am Atmega32 und es funktioniert wunderbar.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> nein @Falk,
> versteh mich nicht falsch, ich bin dankbar über jede alternative..aber
> ich habe meine vorgaben und es sollte so ablaufen, das der timer die cpu
> mit gewalt einer funktion entreisst und einer anderen funktion zuweisst,
> so wie hier schon beschrieben.

Dann würde ich mal vorschlagen, du studierst wie andere das
machen. Im Web gibt es einige Multitaksing Systeme zum download.

Und soviel ich weiss, ist der innerste Kern des Schedulers
(nicht ohne Grund) sehr oft in Assembler geschrieben.

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Und ums nochmal zu sagen:

Beim kooperativen Multitasking gibts innerhalb der Tasks grundsätzlich 
keine Warteschleifen (heißt: Controller zigtausendmal nop ausführen 
lassen) oder gar Endlosschleifen.  Die sind strikt verboten.  Die 
einzige Endlosschleife im gesamten Programm ist die Mainloop, in der 
alle Tasks z. B. alle 10 ms zyklisch aufgerufen werden.  Für die 
Erzeugung der 10 ms-Ticks ist ein Timer zuständig.  Haben alle Tasks 
ihre Arbeit erledigt, kann man den µC stromsparend schlafen legen, bis 
die aktuellen 10 ms vergangen sind.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ja ich kenne diese ganzen projekte...
ich habe mir ein eigenes konzept zusammengestellt und teilweise 
funktioniert das schon, aber an einigen stellen komm ich nicht weiter, 
aus dem einen grund das codevision irgendwie komisch mit stacks umgeht 
und asm auch anders behandelt wird als in winavr...aber ich werds weiter 
versuchen :)

Autor: sechsminuszwei (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zwei Anmerkungen.
Es gibt auch bei praemtivem multitasking keine Warteschleifen in einer 
Task. Wenn die Task auf einen Wait auflaeuft wechselt der Task.
Zweitens. Das Konzept sollte unabhaengig von den Tools sein. Und wenn 
ein Tool das Konzept versaut, war's wohl das Falsche.

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>ja ich kenne diese ganzen projekte...

Dann ist ja gut :-)

>ich habe mir ein eigenes konzept zusammengestellt und teilweise
>funktioniert das schon, aber an einigen stellen komm ich nicht weiter,
>aus dem einen grund das codevision irgendwie komisch mit stacks umgeht
>und asm auch anders behandelt wird als in winavr...

Das ist ja nur ein compilerbedingtes Detailproblem. Du kannst es sicher 
schnell lösen.

Auf die Gefahr, dass Du schon genervt bist, noch ein paar Worte zu 
Deinem Vorhaben. Zunächst kann man sagen, dass die Taskwechselei mit 
Registersicherung, -wiederherstellung und Stackverwaltung (die "context 
switches") noch einigermaßen easy zum Laufen zu bringen ist.  Aber damit 
ist es nicht getan.  Deine Prozesse müssen nämlich auch untereinander 
kommunizieren, d. h. es muss Speicherbereiche geben, auf die mehrere 
Prozesse zugreifen dürfen.  Und dies ist der Punkt, an dem die Sache 
anfängt, teuflisch knifflig zu werden, weil jeder Prozess ja 
unvorhersehbar zu jedem Zeitpunkt, also auch mitten in einer Berechnung 
unterbrochen werden kann, und wegen der Reservierung von Ressourcen. 
Stichworte dazu: Race Conditions, Deadlocks, Semaphore, Mutexe, Critical 
Sections, Message Passing, Scheduling, Thread Synchronisation.  Und 
glaub nicht, dass Du davon verschont bleiben wirst, weil Du Dich ja mit 
einem "ganz einfachen" System begnügen willst.  Allgemein kann man 
sagen, dass das Schreiben eines funktionierenden präemptiven 
Multitasking-Betriebssystems eine enorm anspruchsvolle Aufgabe ist.  Ein 
auf kooperativem Multitasking basierendes System ist viel einfacher zu 
verstehen und (vom Anwendungsentwickler) zu beherrschen, weil hier viele 
typische Probleme von Haus aus nicht auftreten.  An µC-Anwendungen 
("messen, steuern, regeln") stellt man auch eher die Forderung, dass 
bestimmte Aktionen zu bestimmten Zeiten ausgeführt werden, als dass eine 
vom Benutzer initiierte Berechnung, z. B. die Simulation der Bewegung 
einer Rakete, so schnell wie möglich abgearbeitet ist.  Bei letzterer 
Klasse von Problemen sind präemptive Betriebssysteme sinnvoll und 
vorteilhaft.

So, wenn ich Dir jetzt die gute Laune verdorben haben sollte, tuts mir 
leid ;-)  Schau, wie weit Du kommst, und auf jeden Fall viel Spaß und 
Erfolg!

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@AVRFan..

Jeder Task hat seinen eigenen Speicherbereich im SRAM, auf den kann und 
wird nur er zugreifen...darin werden alle laufzeit variablen für den 
task abgelegt...wenn ein switch erfolgt...die größe errechnet sich aus 
den normalen registern die gesichert werden + die anzahl der lokal 
benutzen Variablen dieses Tasks.
Natürlich greifen sie evtl. auf I.O. oder andere system register zu, 
aber das muss dann ebenfalls gesichter werden, wo es nötig ist...

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> auf den kann und wird nur er zugreifen

Das haben schon viele gesagt :-)
Um dann tagelang den Bug zu suchen, warum Variablen plötzlich
und unerwartet ihre Werte ändern.


Darf ich fragen was das wird, wenn du ein Multitaskingsystem
aufbaust, bei dem die Tasks nicht miteinander reden?

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
redet winamp etwa mit word wärend du ein text schreibst und dabei musik 
hörst?
Die verständigen sich mit der cpu usw. aber doch nicht untereinander? 
warum auch?
Genaus ist es bei mir auch.
Z.b. soll sensoren daten erfassen (an eigenen IO pins), speichern diese 
Global..
ein anderer Task sendet diese globalen Daten z.b. übers UART...

Natürlich könnte man sagen sie verständigen sich indirekt über eben 
diese globalen Var....aber das wär übertrieben..

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
lach... Du bist ein Knaller :-)

>Natürlich könnte man sagen sie verständigen sich indirekt über eben
>diese globalen Var....

Genau darum geht es!!!

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:

> Natürlich könnte man sagen sie verständigen sich indirekt über eben
> diese globalen Var....aber das wär übertrieben..

Lass mich raten:
Du hast noch nie wirklich Multitasking-Programmierung betrieben.
Oder?

Wobei: Wenn man exakt sein will, dann gibt es tatsächlich nur
einen Task und wir reden eigentlich von Multithreading
Programmierung.
Ist im Kontext eines µC aber meist ein und dasselbe.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
häää??

aber wo ist denn das Problem eurer meinung nach??
Beide dürfen natürlich die Globalen Speicher nicht verändern, wenn er 
einer aber nur liest und der andere diese verändert, so wie bei meinem 
beispiel, das ist das doch kein problem...
Das ein Task die überlebenswichtigen Variablen vom anderen Task nicht 
verändern DARF, das versteht sich doch von selber..

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
was soll das denn jetzt @Karl heinz??

natürlich nicht, sonst hätte ich diesen thread garnicht erst eröffnet...
Wenn es für dich so einfach ist, dann kannst du mir ja helfen und mir 
meine anfangsaufgabe ganz leicht zusammenstellen:

Zwei tasks, beide bestehen aus einer endlosschleifen...beider lassen 
eine LED unterschiedlich schnell blinken...
Eine Timer soll zwischen beiden hin und her schalten...
ERGEBNISS: beide LED's blinken mit einer unterschiedlich 
geschwindigkeit..scheinbar laufen zwei Funktionen parallel...

mehr will ich erstmal garnicht...

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> aber wo ist denn das Problem eurer meinung nach??
> Beide dürfen natürlich die Globalen Speicher nicht verändern, wenn er
> einer aber nur liest und der andere diese verändert, so wie bei meinem
> beispiel, das ist das doch kein problem...

Du bist naiv.

Nehmen wir mal an, da gibt es eine globale int Variable (16 Bit,
also 2 Byte)

int Global;

Jetzt schreibt der eine Task auf diese Variable

  Global = 20768;

Dein Prozessor kann das aber a priori nicht atomar machen.
Er muss 2 Schreibzugriffe machen.

Und jetzt kommt dein Scheduler daher und macht einen Taskwechsel
nachdem das erste Byte geschrieben wurde aber noch bevor das
2. Byte geschrieben wurde.

Was denkst du, wird ein zweiter Task, der zufällig genau jetzt
die Kontrolle vom System kriegt aus dieser halb geschriebenen
Variablen auslesen?

> Das ein Task die überlebenswichtigen Variablen vom anderen Task nicht
> verändern DARF, das versteht sich doch von selber..

Du vergisst, dass wir alle Menschen sind und dass wir alle
Fehler in Programme einbauen.
Das gemeine bei solchen Fehlern ist, dass sie Stunden-, Tage-,
Wochen-, ja Jahrelang nicht auffallen, bis sie dann eines Tages
zuschlagen, wenn sie den größtmöglichen Schaden anrichten können.

Autor: sechsminuszwei (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Jeder Task hat seinen eigenen Speicherbereich im SRAM, auf den kann und
>wird nur er zugreifen...darin werden alle laufzeit variablen für den
>task abgelegt.

Das ist eine sehr vereinfachte Version, dann sind alle Task voneinander 
komplett unabhaengig. Es wird etwas schwieriger, wenn die Task 
untereinander kommunizieren, resp gemeinsame Daten haben.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> was soll das denn jetzt @Karl heinz??
>
> natürlich nicht, sonst hätte ich diesen thread garnicht erst eröffnet...
> Wenn es für dich so einfach ist, dann kannst du mir ja helfen und mir
> meine anfangsaufgabe ganz leicht zusammenstellen:
>
> Zwei tasks, beide bestehen aus einer endlosschleifen...beider lassen
> eine LED unterschiedlich schnell blinken...
> Eine Timer soll zwischen beiden hin und her schalten...
> ERGEBNISS: beide LED's blinken mit einer unterschiedlich
> geschwindigkeit..scheinbar laufen zwei Funktionen parallel...
>
> mehr will ich erstmal garnicht...

Genau sowas und noch viel mehr würde ich überhaupt nicht mit
einem premptive Multitasking System lösen, sondern mit
kooperativem Multitasking. So wie es unzählige Poster vor
und nach mir ebenfalls beschrieben haben.

Ist viiiiieeeeel einfacher, wesentlich weniger fehleranfällig
und wäre schon längst fertig.

Im Moment machst du aus deiner Übungsaufgabe sehr viel mehr
als dazu notwendig ist: Du konstruierst ein preemptive Multitasking
System, ohne zu wissen worauf du dich da im Endeffekt einlässt.
Noch schlimmer: Anstatt ein fertiges System zu verwenden, baust
du das auch noch selber auf. Fazit: Viel Zeit in etwas investiert,
was du geprüft und getestet auch für lau haben kannst.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
okay @karl-heinz
das ist korrekt, obwohl es nur ein beispiel war und für meine anwendung 
nicht notwendig...aber das wäre mit einem kurzen ausschalten des 
interrupts gelöst...
aber bevor ich mich hier noch um kopf un kragen rede, werd ich einfach 
mal machen :)

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> Beide dürfen natürlich die Globalen Speicher nicht verändern, wenn er
> einer aber nur liest und der andere diese verändert, so wie bei meinem
> beispiel, das ist das doch kein problem...

Ganz genau das ist ein ganz großes Problem!

In der Regel bestehen Daten nicht nur aus einem Byte, sondern einem 
Datensatz. Und wenn der dann halb neue und halb alte Daten enthält, 
entsteht Chaos.

Du hast z.B. ne Funktion, die nen Pixel gemalt haben will, der besteht 
aus X,Y,R,G,B.
Greift sich nun die Malfunktion diesen Datensatz, während er noch nicht 
komplett geschrieben wurde, siehst Du nen Pixel am falschen Platz in der 
falschen Farbe.

Die Lösung ist nun, Du schreibst als letztes ein Flag, was sagt, nun ist 
der Pixel gültig, mal ihn.

So und nun das ganze umgekehrt, die Malfunktion hat sich noch nicht 
alles geholt und Du schreibst schon die nächsten Daten rein -> wieder 
Chaos.

Also noch ein Flag nehmen, wo die Malfunktion sagt, ich habe fertig.

Das Ganze geht aber beliebig weiter, genau wie in dem Lied: "Wenn der 
Topf aber nun ein Loch hat ...".


Ich hab mal in der Elektronik nen Artikel gelesen, wo es darum ging.
Angefangen hat er in etwa: Mainloop, Interrupts und globale Variablen 
sind Pfui-Bäh.
Und dann wurden Lösungen vorgestellt, die ein Problem lösten, dafür aber 
2 neue aufmachten.
Am Ende war das ganze so komplex, daß außer erheblichen Speicher- und 
CPU-Mehrverbrauch keinerlei Vorteile zu sehen waren.


Peter

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Aber seis drum:

Timer aufsetzen, der einen 10ms Sekunden Takt vorgibt.
Weniger ist bei blinkenden Leds nicht notwendig, da sowieso
kein Mensch auf 10ms genau Zeiten schätzen kann.

Für jede Led gibt es eine Zählvariable, die die Zeit
angibt, die die Led noch in ihrem bisherigen Zustand
verbringen soll. Nach Ablauf der Zeit soll die Led
umgeschaltet werden.
#define PORT_LED1  PORTA
#define PORT_LED2  PORTA
#define LED1       ( 1 << PA2 )
#define LED2       ( 1 << PA3 )

uint16_t TimeForLed1;   // in wievielen 10ms Einheiten muss Led1 geschaltet werden
uint16_t TimeForLed2;   // in wievielen 10ms Einheiten muss Led2 geschaltet werden

volatile uint16_t Led1TimePreset;
volatile uint16_t Led2TimePreset;

volatile uint8_t TimerTick;

ISR( .... )    // Timer Interrupt alle 10ms
{
  TimerTick = 1;
}

void HandleLed1()
{
  if( TimeForLed1 == 0 ) {     // Zeit abgelaufen?
    TimeForLed1 = Led1TimePreset;
    // Led 1 umschalten;
    PORT_LED1 ^= LED1;
  }
  else
    TimeForLed1--;               // 10 ms für LED1 runterzählen
}

void HandleLed2()
{
  if( TimeForLed2 == 0 ) {  // Zeit abgelaufen?
    TimeForLed2 = Led2TimePreset;
    // Led 2 umschalten;
    PORT_LED2 ^= LED2;
  }
  else
    TimeForLed2--;            // 10 ms für LED2 runterzählen
}

int main()
{
   // Timer initialisieren und Interrupt freigeben

  ....

  Led1Preset = 1200;  // Led 1 blinkt alle 12 Sekunden
  Led2Preset =  250;  // Led 2 blinkt alle 2.5 Sekunden

  sei();

  while( 1 ) {
    if( TimerTick == 1 ) {
      TimerTick = 0;

      HandleLed1();
      HandleLed2();
    }
  }
}

Welchen Interrupt du nimmst um die 10 ms zu erreichen:
Würde ich mal einen Timer mit CTC Modus nehmen.

Autor: sechsminuszwei (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Na so ist auch nicht ganz. Multitasking macht dann Sinn wen die 
Statusmaschine im Main schwer wartbar wird. Mach mal in 5 Jahren eine 
Aenderung in einer verschachtelten Statusmaschine, die 200 Zustaende 
hat. Dann ist die Multitaskingloesung immer noch uebersichtlicher. Die 
Antwortzeit des Systems wird durch den Overhead des Multitaskuing 
natuerlich laenger, etwas Flash ist weg, und etwas RAM.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@ karl heinz:
genau darauf habe ich gewartet....in der uni würde es heissen Thema 
verfehlt, neu machen...
Du hast nich verstanden worauf ich hinaus will, das mit den LED's sollte 
nur ein simples beispiel eine context-switches sein...das man das 
ergebniss auch mit zählern erreicht ist mir völlig klar...aber darum 
geht es doch garnich

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sechsminuszwei wrote:
> Na so ist auch nicht ganz. Multitasking macht dann Sinn wen die
> Statusmaschine im Main schwer wartbar wird. Mach mal in 5 Jahren eine
> Aenderung in einer verschachtelten Statusmaschine, die 200 Zustaende
> hat. Dann ist die Multitaskingloesung immer noch uebersichtlicher. Die
> Antwortzeit des Systems wird durch den Overhead des Multitaskuing
> natuerlich laenger, etwas Flash ist weg, und etwas RAM.

Ist ja alles richtig.
Nur setzte ich mich dann nicht hin und programmiere ein
Multitasking System von Grund auf. Da hätte ich dann 2
Baustellen wo eine alleine auch schon völlig in der
Komplexität ausreicht.

Und wenn jetzt das Argument kommt: Ja, aber irgendwann muss
man das doch auch lernen wie man ein derartiges System schreibt.
Schon richtig. Aber dann hol ich mir aus dem Web ein fertiges
System im Source Code und studiere das erst mal 1 oder 2 Wochen,
bis ich zumindest im Überblick verstanden habe, was da so alles
abgeht und was da noch alles notwendig ist. Task Wechsel ist
sicherlich nicht unwichtig. Aber nur mit Taskwechsel alleine
hat man noch kein MT-System.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> @ karl heinz:
> genau darauf habe ich gewartet....in der uni würde es heissen Thema
> verfehlt, neu machen...
> Du hast nich verstanden worauf ich hinaus will, das mit den LED's sollte
> nur ein simples beispiel eine context-switches sein...das man das
> ergebniss auch mit zählern erreicht ist mir völlig klar...aber darum
> geht es doch garnich

Mooooooooment.

Du wolltest wissen, wie ich dein Problem ohne einen Multitasking
Kern lösen würde.
Und genau so würde ich es lösen.
Und die Tatsache, dass ich in 10 Minuten eine Lösung fertig
habe und du noch immer kein Multitasking System hoch hast,
spricht ja wohl Bände.

Worum gehts hier eigentlich:
Gehts darum, dass ein Problem gelöst werden muss
Oder gehts darum, unbedingt ein Multitasking System hochzuziehen,
mit dem man dann sein eigentliches Problem lösen kann.

Weiter oben hats glaub ich schon mal wer gesagt: Du bist so
versteift darauf, daß ein MT System Teil deiner Lösung ist, das
du keinen Gedanken darauf verschwendest ob du ein MT System
überhaupt brauchst bzw. ob du ohne nicht bessér drann wärst.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nein, also die Aufgabe ist ein MT System.
Wobei es als Multithreading bezeichnet wird, wie schon gesagt wurde is 
das bei µC eh das gleiche...
In desem MT System wollte ich nur als Beispiel diese LED's zum laufen 
bringen, so das man sieht es funktioniert...anstelle die LEDFunktionen 
können dann später ganz andere aufgaben damit gelöst werden...

aber danke das du das in 10 min für mich so aufgebaut hast :)
jetzt nur noch als MT System...nein scherz, das ist ja meine aufgabe.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hirbel Ha wrote:
> Nein, also die Aufgabe ist ein MT System.

Dann sieht die Sache anders aus.
Wenn deine Aufgabenstellung dezidiert lautet ein MT System
zu bauen, dann hast du keine andere Wahl.
(Ausser du schaffst es deinem Auftraggeber diese Idee
wieder auszureden)

> aber danke das du das in 10 min für mich so aufgebaut hast :)
> jetzt nur noch als MT System...nein scherz, das ist ja meine aufgabe.

Ich kann dir nur wieder den Tip geben:
Hol dir FreeRTOS
http://de.wikipedia.org/wiki/FreeRTOS
http://www.freertos.org/
und studiere, wie dort die kritischen Dinge gelöst wurden.

Sowas schreibt man nicht mal eben in 10 min :-)

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
okay, ich werd mir das mal angucken, aber ich hab auch schon etwas 
vereinfachte systeme gesehen...

naja hab ja ca. noch 8 wochen zeit dafür :)

Autor: Jens (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Guten Abend,

also als Assemblerprogrammierer wundere ich mich schon was hier für ein 
Aufstand um das echte Multitasking gemacht wird. OK, die Sicherung u.U. 
aller Register braucht eine gewisse Raumzeit, doch so man die hat und 
mit ein paar Gedanken vorher, nämlich um die möglichst exklusive Nutzung 
jeder Ressource durch einzelne Tasks und ein einfaches (Interrupt-)Flag 
für unterbrechungsloses Arbeiten in datentechnisch kritischen 
Situationen, einen globalen Speicherbereich und Hardware-Timer und sind 
alle Probleme zumindest bei so einem kleinen Mikrocontroller im 
Handstreich erledigt. Denn es ist ja nicht so, daß ein zu allem 
befähigter Universalbolide a la RTOS unbedingt erforderlich wäre- die 
spezielle Aufgabe erlaubt immer auch spezielle Lösungen. Echtes 
Multitasking hat ganz klare Vorteile, wenn es um die Übersichtlichkeit 
von Singlecorecode geht!
Jens

Autor: hellseher (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
wie wärs damit ?
/*****************************************************
This program was produced by the
CodeWizardAVR V1.24.8 Standard
Automatic Program Generator
© Copyright 1998-2006 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com

Project : MEGA128_MT_0
Version : 00.
Date    : 14.12.2007
Author  : Winfried Jaeckel
Company : Deviltronic
Comments: alle variablen innerhalb der Task müssen "static" definiert !!!


Chip type           : ATmega32L
Program type        : Application
Clock frequency     : 11,059200 MHz
Memory model        : Small
External SRAM size  : 0
Data Stack size     : 512
*****************************************************/

#include <mega32.h>
// Declare functions here                                                           
//_________________________________________________


void main(void);
interrupt [TIM0_OVF] void timer0_ovf_isr(void);
void task(int nr);
void task0();
void task1();
void task2();
void task3();
void task4();
void task5();

//Funktionsdefinitionen
//__________________________________________________    
void task5()
{
 static char a,b,c;
 static int e,f,g;
}
                    
void task4()
{
 static char a,b,c;
 static int e,f,g;
}
                    
void task3()
{
 static char a,b,c;
 static int e,f,g;
}
                    
void task2()
{
 static char a,b,c;
 static int e,f,g;
}
                    
void task1()
{
 static char a,b,c;
 static int e,f,g;
 for (e=0 to 250);
  PORTB.1=!PINB.1
}
                    

void task0()
{ 
 static char a,b,c;
 static int e,f,g;
 for (e=0 to 50);
 PORTB.0=!PINB.0
}
                    

void task(int nr)
{ 
while nr==0;
{
task0()
};
while nr==1;
{
task1()
};
while nr==2;
{
task2()
};
while nr==3;
{
task3()
};
while nr==4;
{
task4()
};
while nr==5;
{
task5()
};

}
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
// Place your code here
tasknr++
if (task>taskmax)  tasknr=taskmin
}

// Declare your global variables here
Int tasknr=0, taskmin=0, taskmax=1;
void main(void)
{
// Declare your local variables here

// Input/Output Ports initialization
// Port A initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTA=0x00;
DDRA=0x00;

// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTB=0x00;
DDRB=0x00;

// Port C initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTC=0x00;
DDRC=0x00;

// Port D initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In 
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T 
PORTD=0x00;
DDRD=0x00;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 11059,200 kHz
// Mode: Normal top=FFh
// OC0 output: Disconnected
TCCR0=0x01;
TCNT0=0x00;
OCR0=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: Timer 1 Stopped
// Mode: Normal top=FFFFh
// OC1A output: Discon.
// OC1B output: Discon.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: Off
// Input Capture Interrupt: Off
// Compare A Match Interrupt: Off
// Compare B Match Interrupt: Off
TCCR1A=0x00;
TCCR1B=0x00;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer 2 Stopped
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// INT2: Off
MCUCR=0x00;
MCUCSR=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x01;

// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;

// Global enable interrupts
#asm("sei")

while (1)
      {
      // Place your code here
        task(tasknr)
      };
}

Autor: hellseher (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
taskmin und taskmax entsprechend der definerten Anzahl der Task setzen. 
alle variablen innerhalb der Tasks static definieren!!!!

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sucht mal mit google nach 'protothreads', das ist bestimmt das richtige 
für euch.

Autor: hellseher (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sorry 2 banale fehler drin

1.
richtig ist in tasks die IF-Funktion hat diese syntax

void task1()
{
 static char a,b,c;
 static int e,f,g;
 for (e=0; e<250;e++){};
  PORTB.1=!PINB.1;
}


void task0()
{
 static char a,b,c;
 static int e,f,g;
 for (e=0 (e=0; e<50;e++){};
 PORTB.0=!PINB.0;
}


2. die timer IR muss wie folgt aussehen


// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
// Place your code here
tasknr++
if (task>taskmax)  tasknr=taskmin
// die folgenden ASM anweisungen löschen die rücksprungadresse vom stack 
und bewart ihn vorm overflow
#asm
          pop
          pop
#endasm

goto main() //verhindert Rückrung,springt immer nach main
}

Autor: hellseher (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
auweiha freitagabend sollte ich sowas lassen ?-((

Autor: Unbekannter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also, sobald die Zustände etwas komplexer werden und viele kurze Delays 
im Millisekundenbereich bis zu einer Sekunde etc. vorkommen, ist ein 
einfacher Multitasking-Kernel wirklich die sehr viel einfacherer Lösung.

Und kompliziert ist es auch nicht.

Im Timer-Interrupt einfach ein Register auf den Stack pushen, dann das 
Status-Register in dieses Register einlesen und auf den Stack pushen, 
danach die restlichen Register pushen, und dann den Stackpointer auf den 
nächsten Stackframe setzen. Dann geht das ganze rückwärts mit pops, und 
am Schluss mit reti rausspringen und, oh Wunder, ein perfekter 
Kontextwechsel.

Dazu noch eine Initialisierungsrouting die den Stack-Frame entsprechend 
vorbereitet mit der Einsprungsadresse eines Tasks und korrektem 
Status-Register auf dem Stack und dann einfach mit ret den Task starten.

Dazu noch etwas Verwaltungskram, z.B. eine zirkuläre, lineare Liste um 
die Tasks nacheinander abzuarbeiten.

Alternativ oder kombiniert noch ein yield() das einen Kontextwechsel 
forciert.

Thema Overhead: Je nach Implementation benötigt man etwa 150 Taktzyklen 
für einen kompletten Kontextwechsel. Macht auf einem AVR pro MHz Takt 
etwa 7000 Kontextwechsel. Das sollte für die meisten Anwendungen 
reichen.

Thema Synchronisierung: Mutexe, Semaphoren etc. kann man alles machen, 
ist aber in etwa 99,99% der Fälle schlichtweg nicht nötig. Die 
einfachste Synchronisation ist cli() und sei(). Wer's feiner haben will, 
setzt oder löscht das Interrupt-Mask-Bit des Tick-Timers.

Thema RAM-Verbrauch: Der Registersatz mit Stackpointer und 
Status-Register sind auf dem AVR gerade mal 35 Bytes. Kleine Tasks sind 
mit rund 64 Bytes RAM  problemlos möglich. Wenn ein Task mal 300 Bytes 
RAM benötigt, ist er schon richtig fett. Tasks die nie parallel laufen, 
können abwechselnd den selben RAM-Bereich nutzen.


Ein Anfänger, der so etwas das erste mal programmiert, dürfte in etwa 4 
Stunden locker fertig sein. Hat man so etwas schon mal gemacht, geht das 
deutlich schneller. Ist ja nur etwas Tipp-Arbeit um die 33 push und pops 
zu tippen.

Autor: Unbekannter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> sucht mal mit google nach 'protothreads', das ist bestimmt das
> richtige für euch.

Protothreads (oder Spielvarianten davon) ist für kleine Sachen ganz 
praktisch. Sobald es aber kompliziert wird mit verschachtelten 
Bedingungen und Timings, wird man wahnsinnig, da man keine 
"Warte-Funktionen" verwenden kann.

So etwas wie das hier, geht in protothreads nicht:


void wait_for_something()
{
  ticks_t start = systime();

  while ( !some_condition() && systime()-start < milliseconds(50) )
    yield();
}

void task_a()
{
  bla();
  wait_for_something();
  blub();
}

void task_b()
{
  foo();
  wait_for_something();
  blub();
}


Bei protothreads wird man zum Wahnsinningen, weil man jeden Müll als 
statische Variable deklarieren muss und anfängt bescheurte Makros zu 
schreiben um die protothreads-Beschränkungen zu umgehen.

Protothreads für kleine Mikrocontroller und/oder einfache (primitive) 
Tasks ganz nützlich, ansonsten wird es schnell zum Fallstrick.

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Also, sobald die Zustände etwas komplexer werden und viele kurze Delays
>im Millisekundenbereich bis zu einer Sekunde etc. vorkommen, ist ein
>einfacher Multitasking-Kernel wirklich die sehr viel einfacherer Lösung.

Ich seh das Argument mit den kurzen Wartezeiten irgendwie nicht. WARUM 
sollen die sich einfacher realisieren lassen?

Zum Rest: Ein Programm mit 5 Tasks und einem Timer-Tickintervall von 1 
ms würde dann also nach dem Schema

Task 1 rechnet genau 1 ms
Task 2 rechnet genau 1 ms
Task 3 rechnet genau 1 ms
Task 4 rechnet genau 1 ms
Task 5 rechnet genau 1 ms
Task 1 rechnet genau 1 ms
Task 2 rechnet genau 1 ms
Task 3 rechnet genau 1 ms
Task 4 rechnet genau 1 ms
Task 5 rechnet genau 1 ms
Task 1 rechnet genau 1 ms
Task 2 rechnet genau 1 ms
Task 3 rechnet genau 1 ms
........
........

ablaufen?  Alle Tasks dürfen gleich lange rechnen.  Ist die Zeit für 
einen Task rum, entzieht der Scheduler ihm die Kontrolle und führt einen 
context switch zum nächsten Task aus.

Hab ich das richtig verstanden?

Autor: sechsvorzwei (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jetzt lasst das Preemtive mal weg. Kooperative ist viel einfacher. Auch 
wenn jeder Task auf einen Wait mit Timer laeuft. Beim Preemptiven MT 
muss man alle Register wechseln, beim kooperative nicht zwangslaeufig. 
Die grosse Vereinfachung liegt bei den Variablen. Variablen zwischen 
Tasks benoetigen beim Kooperativen MT keine Semaphore. Bleiben nur noch 
die Interrupt variablen. Sinnvollerweise weist man einer 
Interruptresource einen Task zu und  muss so nur noch schauen, das der 
Task und der zugewiesene Interrupt miteinander klarkommen. Semaphoren 
sind ein zusaetzlicher Sonderaufwand der beim Preemptiven MT hinzukommt. 
Sie sind wesentlich mehr als nur Read-Modify-Write Strukturen. Hinter 
jeder Semaphore kommt noch eine Warteschlange, wo sich die Task die 
darauf warten eintragen. Das Schluesselwort dabei ist dynamische 
Variablen, Listen, beziehungsweise ein Memorymanager fuer dynamisches 
Memory, welcher auch eine Resource darstellt, da der nur einmal 
exitiert. Der Memortymanager muss daher direkt dem MT kernel unterstellt 
sein.
Ich fuer mich tue einiges, um dynamisches Memory vermeiden zu koennen. 
So ganz nebenbei. Preemptives MT ist ein Zweihaender, der eigentlich auf 
einem 8bitter Overkill ist.

Autor: Wolfram Quehl (quehl)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
das MT ist keine Aufgabe, sondern ein Hilfsmittel. Darum muß erstmal in 
Erfahrung gebracht werden, wozu das Hilfsmittel gebraucht wird (Lehrer 
fragen ). Ich kauf mir auch keinen Rasenmäher, wenn ich nicht weiß, was 
ich damit machen könnte. Erst kommt die Aufgabe, Rasen muß gekürzt 
werden und dann kommen erst die Überlegungen, wie mache ich das.

Daß das MT ein schlechtes Hilfsmittel ist, sieht man an Win. Nur fehler 
und Bugbereinigung, solange es Win gibt. Beim linearen System DOS gibt 
es keine Fehler.

Die einzige Anwendung, die ich mir für das MT vorstellen kann, ist eine 
Regelmäßigkeit der Aufgabenabarbeitung. Ein Motor kann nicht solange 
stehen bleiben, bis andere Tasks ihre Arbeit erledigt haben. Dann und 
nur dann ist eine Unterbrechung erforderlich. Wenn eine Temperatur 
gemessen werden soll, kann man auch nicht warten, weil die Temp. sich in 
der Zwischenzeit verändert. Dein Beispiel 1. daten erfassen und 2. 
Erfaßte daten über Uart ist das schlechteste Beispiel, was Dir 
eingefallen ist, diese sind nämlich voneinander abhängig und können 
daher nur umständlich wie oben geschrieben verarbeitet werden. Solch 
eine Aufgabe muß 1 Task sein. Beim Kontextswitch müssen ggf. auch die 
Peripherie Register geswitched werden. Und Statusregister. Und selbst da 
muß man noch aufpassen, daß man nicht 2 Tasks einbaut, die beide den 
gleichen Uart verwenden. Dann gibt es auch Mist. Insofern sind die Tasks 
nicht ganz so unabhängig, wie Du Dir das vorstellst. Schwierig wird es, 
wenn man mehrere Aufgaben hat, die einer Regelmäßigkeit der Ausführung 
bedürfen. Da muß man sehen, ob diese zeitlich zueinander passen oder 
nicht. Wenn das nicht paßt, muß man mehrere CPUs verwenden. Um aber auf 
eine Aufgabe zeitlich noch reagieren zu können, sollten diese Aufgaben 
so schnell wie möglich erledigt werden. Und dafür muß dann auch eine 
geeignete Programmiersprache gewählt werden und das kann nur Assembler 
sein. Auch in der Hinsicht, daß andere Programmiersprachen 
Einschränkungen haben. Ich kenne Codevision nicht, aber wenn die 
Stackmanipulation mit Push und Pop da nicht möglich ist, kann man nur 
auf Assembler umsteigen.

Bei Freertos habe ich weder eine ASM Datei noch eine .hex datei 
gefunden. Auch Downloads allgemein habe ich nicht gefunden. Wenn das 
hier öfter mal angesprochen wird, wäre es doch mal schön, einen direkten 
Link zum Download anzugeben.

Mit Ausnahme der o.g. speziellen Aufgaben ist die bessere Programmierung 
wie meine Vorschreiber hier angegeben haben.

Wenn MT so übersichtlich wäre, wo kommen dann die ganzen Fehler bei Win 
her?

mfg

Autor: Klaus Falser (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das kannst Du doch nicht erst meinen !

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So leute ich habs geschafft..
Vier Tasks, alle haben ihren eigenen Hardware-und Softwarestack, alle 
sind gleichberechtigt, alle Task bestehen aus einer endlosschleife und 
toggeln eine LED.
Alle Tasks sind in einer Ringliste initialisiert.
Ein Timer macht alle 2 ms einen Kontext-Switch und springt zum task der 
als nächstes in der Ringliste steht...

Ergebnis:
4 LEDs die unterschiedlich schnell blinken, wobei jede einzelne eine 
konstante geschwindigkeit hat (wird aber noch mit dem oszi überprüft).
Läuft seit 20 min stabil, ich glaub das bleibt auch so :)

Autor: sechs ueber drei (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Wolfram Quehl (quehl) wrote
>Wenn MT so übersichtlich wäre, wo kommen dann die ganzen Fehler bei Win
her?

Naja, einen Realtimekernel mit einige kBytes mit Windows zu vergleichein 
ist unpassend. Zumal Windows kein Realtime System ist. Auch wenn es 
preemptive ist. Einen RTK verwendet man, wenn man verschiedene logiche 
Ablaeufe die irgendwie gekoppelt sind auf einem Geraet integrieren muss. 
Dabei steht eine definierte Antwortzeit im Vordergrund. Sie muss nicht 
minimal sein, sondern definiert. Da scheitert Windows. Der Unterschied 
zwischen RTK zur komplexen Zustandsmaschine ist die Uebersicht und die 
Wartbarkeit. Muss man einen der Ablaeufe anpassen, so bekommt man bei 
einer Zustandsmaschine mit 200+ Zustaenden an die Grenzen. Bis das 
ge-debugt ist und die Fehler gefunden worden sind ... ist man mit einem 
RTK schneller. Der wurde ja laenger und von mehreren Leuten geprueft. 
Richtig, wenn man fuer ein Projekt einen RTK selbst schreibt gewinnt man 
nichts, man kann auf nichts Geprueftem aufbauen. Ich halte es fuer 
vernuenftig von Hirbel Ha, sich das mal im Rahmen eines Projektes 
anzuschauen. Dann aber mit dem kooperativen Ansatz zuerst.

Autor: AVRFan (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>4 LEDs die unterschiedlich schnell blinken, wobei jede einzelne eine
>konstante geschwindigkeit hat (wird aber noch mit dem oszi überprüft).
>Läuft seit 20 min stabil, ich glaub das bleibt auch so :)

Klasse :-) Doch ich sagte es schon: Die Context Switches stellen kein 
Problem dar.  Das kann man auf die Reihe kriegen.  Der schwierige Teil 
ist die inter process communication.  In Deinem LED-Programm findet 
keine statt.  Damit das anders wird, musst Du nur einen Prozess haben, 
der irgendwelche Daten schreibt, die von einem anderen Prozess gelesen 
werden.

So, nun will ich Dir aber keine weiteren Besserwissereien mehr 
zumuten... ;-)

Gute Nacht.

Autor: Hirbel Ha (leo)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
also das sind echt schon ganz schöne besserwissereien die du mir hier 
zumutest :)

Habe inzwischen ein paar funktionen hinzugefügt...z.b. kann ein task 
einen anderen task deaktivieren (dieser befindet sich dann noch zwar in 
der ringliste, wird aber beim switch übergangen)...das gleiche kann man 
für eine bestimmte anzahl an timerticks machen (also wie lange ein task 
nicht geswitcht wird)...
sooo das wars erstmal
und ob diese kontext-switch anweisungen so einfach sind....naja, ich 
glaube nicht, für dich vielleicht weil du ein avr-asm-genie bist ;)

Autor: Error (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Irgendwie fällt mir zu fehlerhaftem Multitasking der
Therac-25 ein...
http://de.wikipedia.org/wiki/Therac-25

Autor: Klaus Falser (kfalser)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Error
.. und mir fällt dabei der Hammer ein, der auch nicht daran schuld ist, 
wenn man sich selbst auf die Finger haut.

Autor: gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
so mal ne allgemeine Frage:
Also ich versuche grade, selber auch sowas zu implementieren (auf nem 
68k allerdings).
Kooperatives Multitasking ist kein Problem, das habe ich schon gemacht. 
Ging auch ganz gut. Ich will mich aber mal darin versuchen, ein 
präemptives zu machen. Das klappte bis jetzt so halbwegs:
Also ich hab einen Timer aufgesetzt. Der läuft alle 10 ms über. Im 
Timerinterrupt muss ich dann den Kontext wechseln, das ist mir auch noch 
klar. Nun muss ich ja aber für jeden Task einen eigenen Stackbereich 
haben (ist kein Problem, auf meinem Board sind 1 MB SRAM).
Ich habe es schon mal fertig gebracht, im Interrupt-Programm den Stack 
zu manipulieren, sodass der Prozessr nachher an einem anderen Task 
weiterrechnet. Mein Problem ist jetzt:
Wie lege ich die verschiedenen Stack am gescheitesten an? Und wie starte 
ich einen zusätzlichen Task?

Ich will hier nicht wieder so eine Diskussion starten "och Multitasking 
ist eh unnötig". Ich weiss, dass ich es nicht brauche. Aber ich will es 
mal versuchen, selber zu programmieren. Einfach dass ich es mal gemacht 
habe ;)
Kennt jemand ne gute Internetseite zu dem Thema? Insbesondere was den 
68k anbelangt? (also Multitasking auf dem 68k versteht sich).

Grüsse

Autor: ... (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hijack (gast),

Ein praemtiver Taskwechsel wechselt den Stack. Stack meint in diesem 
Fall natuerlich den Stack fuer die Funktionsaufrufe. Hardwareinterrupts 
sind natuerlich von den Tasks unabhaengig und bleiben auf dem Hardware 
Stack.

Autor: NochEinGast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
also ich hab das so gelöst, das man sich zu vorher ein kopf machen muss, 
wieviel threads überhaupt maximal erstellt werden können, bin mir jetzt 
nicht ganz sicher (weil ist schon ein weilchen her) aber die anzahl 
wärend der laufzeit zu bestimmen, ist nicht möglich oder nur mit sehr 
viel aufwand...irgendwo gabs da ein problem

also am anfang jeden thread einen speicherbereich zuordnen und fertig is 
:)

noch was zur notwendigkeit g.
es ist für die meisten anwendungen die hier besprochen werden nicht 
nötig, bzw. lohnt der aufwand nicht, es gibt aber anwendungen, die 
daraus profitieren und wo diese technik eigentlich unverzichtbar ist, da 
es keine wartezeiten mehr gibt wo die CPU nichts macht und wo kostbare 
zeit verschwendet wird und somit das ganze system viel viel langsamer 
ist...(so wars bei mir).

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
gast wrote:
> Also ich versuche grade, selber auch sowas zu implementieren (auf nem
> 68k allerdings).

Dann ist es natürlich ne saugute Idee, nen 68k-Frage hinter nem 
AVR-Thread zu verstecken.


> Wie lege ich die verschiedenen Stack am gescheitesten an? Und wie starte
> ich einen zusätzlichen Task?

Da wird Dir wohl nichts weiter übrig bleiben, als erstmal tief in den 
Eingeweiden Deines ungenannten C-Compilers zu wühlen, welche Stacks er 
anlegt, wie er Parameter übergibt, wie er Speicher verwaltet usw.


> Kennt jemand ne gute Internetseite zu dem Thema? Insbesondere was den
> 68k anbelangt? (also Multitasking auf dem 68k versteht sich).

Die einen AVR-Thread lesen, höchstwarscheinlich nicht.


Peter

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Peter Dannegger

Guten Morgen! Hast Du schon einmal einen 68K in der Hand gehabt oder 
stänkern wir heute wieder?

@gast
Bei soviel RAM kannst Du doch großzügig verteilen, zumal beim 68K dafür 
ein separater Userstack zur Verfügung steht und die Anzahl der möglichen 
Tasks auch endlich ist.
Aufpassen sollte man allerdings bei lokalen Feldern, die auf dem Stack 
gelegt werden.

Autor: gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke @peter dannegger,
genau das meinte ich - solche Diskussionen wollte ich grade eben nicht 
hervorrufen. Wenn du den 68k nicht kennst (und da du ja sowieso nichts 
davon hältst, Multitaskig in einem Bastelprojekt einzusetzen) solltest 
du vielleicht nicht unbedingt auf die Frage antworten. Dein Post ist 
reiner Offtopic und gehört demnach ins entsprechende Forum (siehe 
http://www.mikrocontroller.net/forum/offtopic).

@NochEinGast:
Danke erstmal. Du hast also so ein System implementiert?
Wie viele Threads laufen denn bei dir max. ? (so nur aus Interesse).
Wie sorge ich denn dafür, dass jeder Task seine Daten auf dem eigenen 
Stack ablegt, aber wenn ein Interrupt auftritt (Timer-Tick) wird die 
Rücksprungadresse auf dem Systemstack gespeichert? Das ist mir noch 
etwas unklar.

Grüsse

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
gast wrote:
> Danke @peter dannegger,
> genau das meinte ich - solche Diskussionen wollte ich grade eben _nicht_
> hervorrufen. Wenn du den 68k nicht kennst

Zumindest weiß ich, daß er sich vom AVR unterscheidet, daher der 
ernstgemeinte Hinweis. Es ist mir doch völlig wurst, ob Du nen 
AVR-Thread hijackst, Du wirst dann eben weniger Antworten zum 68k 
kriegen, als mit nem 68k-Thread.

Und das man für ein MT erstmal wissen muß, welchen Compiler Du benutzt, 
ist keinesfalls offtopic. Ein MT aufm AVR für den GCC geschrieben läuft 
nicht mit dem IAR oder Codevision und umgekehrt.


Peter

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Ein MT aufm AVR für den GCC geschrieben läuft
>nicht mit dem IAR oder Codevision und umgekehrt.

Wer hätte das gedacht?

@gast
Was mit Usermode und Supervisormode los ist, solltest Du schon wissen. 
Demzufolge sollten Deine Tasks im Usermode laufen. Die Timerticks 
greifen sich per Interrupt dann den Supervisorstack; das ist ja das 
Schöne am 68K.

Autor: Michael Appelt (micha54)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

also irgendwie gehen hier einige Dinge durcheinander.

Sowohl preemptives als auch kooperatives Multitasking benutzen 
Endlosschleifen in den Tasks. Allerdings ist richtig, daß beim 
kooperativen Multitaksing die Prozesse freiwillig die Kontrolle abgeben.

void task1() {
  while(1) {
    PORTA |= 1<<LED1;
    wait(100);
    PORTA &= ~(1<<LED1);
    wait(900);
  }
}


void task2() {
  while(1) {
    PORTA |= 1<<LED2;
    wait(333);
    PORTA &= ~(1<<LED2);
    wait(333);
  }
}

int main() {
  create(task1, ....);
  create(task2, ....);
  // mach irgendwas sinnvolles oder while (1);
}

wait übergibt die Kontrolle an andere Task oder loopt ggf. vor sich hin

Preemptiv wird die Sache, wenn ein Interrupt eine Task aufwecken kann, 
was dann voraussetzt, dass alle 32 regs und sreg im Stack gesichert sein 
müssen, beim kooperativen könnte der laufende Prozess kontrolliert nur 
die kritischen register retten.

das Beispiel

int main() {
  while(1) {
    task1();
    task(2);
  }
}

sind 2 Statusmaschinen!!!

Gruss,
Michael

Autor: gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Was mit Usermode und Supervisormode los ist, solltest Du schon wissen.
> Demzufolge sollten Deine Tasks im Usermode laufen. Die Timerticks
> greifen sich per Interrupt dann den Supervisorstack; das ist ja das
> Schöne am 68K.

Danke, das ist ein guter Tipp. Auf die Idee bin ich gar noch nicht 
gekommen! Natürlich kenne ich die beiden Modi, aber bisher (und ohne 
Betriebssystem) habe ich natürlich immer im Supervisor Mode gearbeitet. 
Den Usermode habe ich dabei komplett vergessen.... Danke für den 
Hinweis.

@peter
> ob Du nen AVR-Thread hijackst

Ja sorry.
Übrigens wollte ich dich auch nicht angreifen mit meine vorhergehenden 
Post.
Es ist einfach so, dass ich das Gefühl hatte, die Leute in dem Thread 
hier haben ne Ahnung von der Materie. Wieso sollte ich also nen neuen 
Thread aufmachen, wenn hier schon einer ist, wo es um dasselbe geht?
Grundsätzlich erstmal ist mir der 68k egal. Wenn ich nämlich weiss wie 
das Multitasking auf nem AVR oder 8051 oder irgendwas anderem genau 
funktioniert, dann kann ich das auch im 68k implementieren.

Grüsse

Autor: gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Falls es jemanden interessiert:
habe jetzt den Context-Wechsel fertiggebracht! *jubel, freu*.
Es ist zwar noch eine quick-and-dirty Lösung, denn im Moment sichere ich 
den Context der Tasks an fixe Adressen. Später soll dann natürlich 
dynamisch entschieden werden, an welcher Adresse der Context gespeichert 
wird, da muss ich mir noch was einfallen lassen...
Auf jeden Fall klappt es jetzt (habe mal den Code in nem Simulator 
laufen lassen, wo ich Schritt für Schritt anchvollziehen konnte, was die 
CPU macht). Context Switch läuft jetzt einwandfrei, dank dem Tipp vom 
anderen Gast hier.... (User und Supervisor Mode).

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.