Hallo Forum,
ich würde gerne einen Preemtiven Scheduler im Mega8 Implementieren.
Basis soll ein Interrupt vom Timer2 mit 20kHz sein.
Jetzt ist mein Problem, dass ich dem Programmcounter ja irgendwie klar
machen soll, wo anders weiterzu machen, als er es nach dem reti
eigentlich tun würde. Ich müsste also auf den Stack die Aderesse einer
anderen Routine legen.
1.) Wie komme ich an die Adresse einer Routine dran? Geht das?
2.) Wie kann ich in C etwas am Stack ändern?
Im Datenblatt steht, dass der Stack im "general data SRAM" liegt. In ASM
würde ich also eines der Pointerregister Paare r26 bis r31 auf den Ort
des Stackpointers richten und einen store Befel bemühen. Aber wie kann
ich in C gezielt auf eine bestimmte Stelle im RAM zugreifen?
SP kann ich scheinbar lesen, dann hätte ich schon mal die aktuelle
Position. Kann ich dann einen 16Bit Pointer deklarieren im Sinne
"uint16_t* Prg_Cnt = ..." und dann über "*Prg_Cnt = Zieladresse;" gehen?
viele Grüße,
Jasson
Mir ist schon bewusst, dass ich mir deutliche Gedanken zum den
Sprungadressschiebereien machen muss ;-)
>ich würde gerne einen Preemtiven Scheduler im Mega8 Implementieren.>Basis soll ein Interrupt vom Timer2 mit 20kHz sein.
Sehr lustig. Nimm 1kHz. Sonst wird die ganze Rechenzeit
vom Scheduler verheizt.
Schau mal nach FreeRTOS oder in die Codesammlung.
Da gibt es Versuche für Scheduler mit ATMega.
Habe gerade auch mal gegoogelt, du findest z.B. den Sheduler hier:
Beitrag "NeuesOS für AVR Mikrocontroller"
Hier wird der Stackpointer gesetzt.
SP = (unsigned int)(current->sp - 32);
Register werden per ASM auf den Stack kopiert.
mfg Andreas
Andreas B. schrieb:> Register werden per ASM auf den Stack kopiert.
Auf den Thread-eigenen Stack. Das tun die Threads bei einfachen Systemen
selber, da sie implizit bei Synchronisationsmechanismen(wait, timer,
signal-send, semaphore-take, semaphore-give) den Scheduler aufrufen, wie
einen Funktionsaufruf. Die aufgerufene Funktion braucht zufällig alle
Prozessorregister pfeif
so kann man das auch in C noch relativ gut beherrschen.
Ich würde das liebevoll die "Schwarzes-Loch"-Methode nennen. Die Threads
bekommen nur mit, dass sie irgendeine Funktion aufgerufen haben. Mehr
nicht.
Der Trick dabei ist: nur der Scheduler muss "über sich selbst" eine
andere Rücksprungadresse ablegen. Und das tut er, indem er zuerst den
Stackpointer des Threads sichert, aus dem er aufgerufen wurde, dann den
neuesten/wichtigsten Job ermittelt, den Stackpointer mit dem
gespeicherten stackpointer des gesicherten wichtigen threads
zurückschreibt und wie eine Funktion endet.
Die gesicherten Register des nun neuen threads werden zurückkopiert und
der neue wichtigste thread oder was auch immer gerade anliegt rennt
weiter.
mfg mf
PS: das geht auch mit Timerdiensten. Der Timer-Interrupt hat _am Ende
der ISR_ auch einen Scheduleraufruf.
Wenn das AVR Studio ausreichend konform ist, sollte
uint16_t (*Func_Ptr_1)(übergabe) = NULL;
Func_Ptr_1 = &My_Func;
gehen.
1.)
Ich habe es leider nicht geschafft, aus den Beispielen (Links)
rauszufiltern, wie ich die Adresse dann wirklich auf den Stack kriege.
Wenn ich es richtig verstanden habe, dient
SP = (unsigned int)(current->sp - 32);
dazu, den SP wegen dem Sichern von Registern den SP zu verschieben.(?)
Mein Idee:
Func_Ptr_1=&My_Func;// dem F-Pointer eine Adress zuweisen
4
Stack_Ptr=(SP+-(mussichnochdrübernachdenken));// Ort auf dem Stack für den F-Pointer berechnen
5
*Stack_Ptr=Func_Ptr_1;// Funktionsadresse auf den Stack mogeln
6
reti;
-- Mir ist natürlich klar, das der obige Code nicht in der Reihenfolge
im Scheduler bzw. dessen ISR steht. Es ging mehr um die Idee :-)
2.)
Zudem habe ich ein weiteres Gedankliches Problem:
Wenn der Stack im SRAM liegt, hat der ja auch nur eine Breite von 8 Bit,
wie kann man dann damit den ganzen Programmraum vom Mega8 adressieren?
Trotz meiner Fragen an dieser Stelle ein Danke für die präzisen
Antworten!!
mfg,
Jasson
Jasson JFK schrieb:> Wenn das AVR Studio ausreichend konform ist, sollte>> uint16_t (*Func_Ptr_1)(übergabe) = NULL;> Func_Ptr_1 = &My_Func;
Das geht. ggf. mit entsprechenden Casts.
> 2.)> Zudem habe ich ein weiteres Gedankliches Problem:> Wenn der Stack im SRAM liegt, hat der ja auch nur eine Breite von 8 Bit,> wie kann man dann damit den ganzen Programmraum vom Mega8 adressieren?
Ich glaube du hast gerade ein Durcheinander, der ADRESSbus ist 16bit,
also 65kByte können adressiert werden, aber der DATENbus ist 8bit.
1
uint16_t*Stack_Ptr=NULL;//
mit dieser Zeile sagst du aber dem Compiler bereits das du 2 Bytes
schreiben willst, ob der Controller das kann oder nicht kann dir völlig
egal sein, wenn er es nicht kann macht der Compiler einfach zwei
Schritte.
Vom Prinzip her funktioniert also dein Code schon.
1
Stack_Ptr--;
Ich würde den Stack dekrementieren, kann mich aber auch irren. Auch hier
schnallt der Compiler das selbst. Da er ja den Typ kennt wird
automatisch 2 abgezählt und nicht eins. Dies wird ja auch oft bei Arrays
eingesetzt.
mfg Andreas
Vielen Dank!!!
wenn ich Ergebnisse habe, werd ich mich noch mal melden. Wird aber
sicher etwas dauern, weil "mein" Scheduler nach dem Earliest Deadline
First arbeitet und sagt, dass bald Klausuren sind und der anliegende
Task über Scheduling im AVR nicht "mal eben" gemacht ist.
Ich denke, ich habe mich verständlich ausgedrückt :-)
Hallo,
i´m back.
Ich habe jetzt in der Scheduler Richtung mal ganz klein angefangen mit
folgender Idee:
Es läuft ein Timer2 Cmp Match Interrupt, an dessen Ende ich dann zu
"function1" springen will, um die LED an PORTC 4 anzumachen.
Erstmal geht es mir nur darum, dort anzukommen.
Hier der einzige Code mit dem ich es bisher geschaft habe, dass ich
wenigstens im Sumulator in function1 komme:
Stack_Ptr = SP;//assign current SP position to my own Stackpointer
15
Stack_Ptr++;
16
*Stack_Ptr = pt_function1;// store function adress to adress pointed to by my own Stackpointer;
17
18
asm volatile("ret");//goto function
19
asm volatile("nop");
20
asm volatile("nop");
21
}
22
23
void function1(){
24
25
PORTC = 0b00110000;
26
}
27
28
int main(void)
29
{
30
TCCR2 = 0b00001001;
31
OCR2 = 30;
32
TIMSK = 0b10000000;//input cp int enable, int on match A enable
33
TIFR |= 0b10000000;
34
35
DDRC = 0b00110000;
36
37
38
39
pt_function1 = &function1;//assign function adress to pointer
40
sei();
41
main2:
42
43
goto main2;
44
return 0;
45
}
Ich habe mir auch das ASM File angesehen und bringe hier nur den
wichtigen Ausschnitt:
1
Stack_Ptr = SP;//assign current SP position to my own Stackpointer
2
5e: ed b7 in r30, 0x3d ; 61
3
60: fe b7 in r31, 0x3e ; 62
4
Stack_Ptr++;
5
62: 32 96 adiw r30, 0x02 ; 2
6
64: f0 93 64 00 sts 0x0064, r31
7
68: e0 93 63 00 sts 0x0063, r30
8
6c: 32 97 sbiw r30, 0x02 ; 2
9
*Stack_Ptr = pt_function1;// store function adress to adress pointed to by my own Stackpointer;
10
6e: 80 91 61 00 lds r24, 0x0061
11
72: 90 91 62 00 lds r25, 0x0062
12
76: 93 83 std Z+3, r25 ; 0x03
13
78: 82 83 std Z+2, r24 ; 0x02
Meiner Meinung nach in diesem Zustand Murx, weil ich so wie ich das sehe
den Stack_Ptr in einen Bereich lege, wo der SP schon war. Aber wie
gesagt, das ist die einzige Variante, mit der die Simulation in die
function1 kommt. Die Realität tut dieses schon nicht mehr. (ich hab auch
geprüft, dass die LED und der PIN überhaupt ok sind^^)
Ich habe auch schon verschiedene Sachen ausprobiert, aber stochere etwas
im Dunkeln.
Zum Beispiel hab ich noch nicht wirklich genau rausbekommen, wie ret(i)
funktioniert. Es heißt die sind pre-inkrementiv und SP <- SP + 2.
Ok, ABER macht er
1.)
SP++;
"hole ersten Teil der Adresse"
SP++;
"hole zweiten Teil der Adresse"
ODER
2.)
SP += 2.
...
...
(fehlt mir die Vorstellung)
Hallo,
Kann es sein, wenn ich per Hand eine Adresse auf den Stack mogeln will,
dass ich das High und Low Byte in der falschen Reihenfolge drauflade,
wenn ich über *Stack_Ptr = (unsigned int) pt_function1; gehe?
( da der Stack ja von oben nach unten wächst und ich noch keine 100%
genaue Angabe finden konnte, wann welcher Teil von Adressen geschrieben
bzw. gelesen wird bei call, rcall, ret, reti)
Ich verfolge im Moment dieese Idee (Erklärung siehe danach):
1
Stack_Ptr = (unsigned int *)(SP - 1);//assign current SP position to my own Stackpointer
2
*Stack_Ptr = (unsigned int) pt_function1;// store function adress to adress pointed to by my own Stackpointer;
3
SP -= 2;
Die SP Register zeigen ja immer auf die aktuell schreibbare Adresse.
Also auf EIN freies Byte. Da ich aber für eine Funktionsadresse 2 Byte
brauche, mache ich zunächst wie in der ersten Zeile (SP - 1).
Da bei einer ret(i) Anweisung der "preincrement schheme" verfolgt wird,
mache passe ich den SP in der letzten Zeile mit SP -= 2; an.
Soweit die Idee.
Jasson JFK schrieb:> @Stefan, ich habe ne E-Mail bekommen, dass du was geschrieben hast. Aber> das system scheint es nicht angenommen zu haben.
Nein, ich hatte den Beitrag nur gleich wieder gelöscht. Ich hatte dir ja
schon auf roboternetz.de was zu deinem Ansatz geschrieben. Wenn das
nicht dazu geeignet war, dich von deinem Irrweg abzubringen, dann der
gelöschte Beitrag auch nicht.
Hallo zusammen,
ich bin der Sache etwas näher gekommen. Bis jetzt konnte ich immerhin
die prinzipielle Frage klären, dass es möglich ist, am Ende einer ISR
erstmal wo anders hinzuspringen und von da aus gezielt wieder zurück in
die ISR.
Mir ging es also erstmal nur um die prinzipielle Machbarkeit.
Prinzip:
In der ISR wird die Adresse in ein Low- und ein High Byte zerlegt, die
dann jeweils mit Inlineassembler auf den Stack gepushed werden. Das es
gerade Register 24 ist, habe ich der .lss Datei entnommen.
Zurück komme ich dann über einen relativ Sprung nach "Label", wobei der
Simulator den Zeiger dann auf die "NOP" Anweisuung setzt.
Jasson JFK schrieb:> ich bin der Sache etwas näher gekommen. Bis jetzt konnte ich immerhin> die prinzipielle Frage klären, dass es möglich ist, am Ende einer ISR> erstmal wo anders hinzuspringen und von da aus gezielt wieder zurück in> die ISR.
Wenn du wieder in die ISR zurück willst, warum rufst du die Funktion
dann nicht einfach auf? Das ist doch alles nichts weiter, als eine
möglichst komplizierte Nachahmung von:
Jasson JFK schrieb:> Bis jetzt konnte ich immerhin> die prinzipielle Frage klären, dass es möglich ist, am Ende einer ISR> erstmal wo anders hinzuspringen und von da aus gezielt wieder zurück in> die ISR.
Wow ;)
Man kann prizipiell auch mit einer Rohrzange eine Schraube festziehen
(nur mal so als Beispiel), das hilft dir auf dem Weg zu einem
preemptiven Multitasking ähnlich viel weiter.
Oliver
Jasson JFK schrieb:> plakative Äußerungen helfen immer weiter...
Was genau willst du denn nun eigentlich machen? Ich dachte ja zuerst, es
ginge um richtiges preemptives Multitasking, aber jetzt sieht es ja eher
so aus, als wolltest du nur mal eine Funktion "dazwischen schieben".
Wenn es das "Dazwischenschieben" ist, dann reicht ein simpler
Funktionsaufruf, und wenn es preemptives Multitasking ist, ist dein
Ansatz ein Irrweg und du musst einen richtigen Kontext-Wechsel
implementieren. So oder so ist das, was du da gerade veranstaltest,
Zeitverschwendung.
next step...
Ich habe die Sache etwas erweitert:
Es gibt
- eine function1, um PORTC auf 0b00010000 zu schalten,
- eine function2, um PORTC auf 0b00100000 zu schalten,
also einen Wechselblinker zu realisieren, um sehen, ob das ganze
überhaupt lebt.
Es gibt eine globale 16 Bit Variable "counter", die in der ISR
inkrementiert und
- bei == 3000 den Sprung zu function1 veranlasst
- bei == 6000 "counter" nullt und den Sprung zu function1 veranlasst
Dieses wird in einer ISR lokalen non-static Variablen "jump_flag"
gespeichert. Von deren Anhängigkeit, werden die Working Register
gesichert. Da dieses nur gemacht wird, wenn die jump flag variable
gesetzt ist, kann etwas CPU Zeit gesichert werden.
Außerdem, und das ist sicher das wichtigste geht es nun nicht mehr in
die ISR zurück, sondern von function1 und 2 direkt wieder in main. Also
muss sich um das retten und restaurieren der Working- und des SREG
Registers gekümmert werden.
Das habe ich in Inlinefuntionen gemacht - der Lesbarkeit wegen.
Im Moment ist es ja so, dass ich weiß, dass die Funktionen fertig sind,
bevor die ISR wieder zuschlägt.
Also werde ich mir als nächstes ein Szenario mit einer "time_waste"
Funtion aufbauen um zu sehen, was passiert oder wie ich es dann
hinkriege, wenn die ISR kommt, während das Programm in der "time_waste"
funktion ist und/oder eine Funktion noch nicht fertig ist. Aber bitte
nicht vorsagen, habe gerade Spaß am knobeln^^
Wenn du sinnvoll knobeln willst, dann leg in deinen Funktionen noch ein
paar lokale Variable an, auch ein paar statische, und ruf mal aus einer
der Funktionen noch eine weitere auf. Spasseshalber solltest du da auch
ein paar Funktionen der avrlibc nutzen - wie das in einem richtigen
Programm halt alles so gemacht wird.
Dann lass da mal deinen Interrupt reinsemmeln...
Alles vorher hat mit pre-emtive Multitasking nix zu tun.
FF - Viel Vergnügen
Oliver
So,
ich bin etwas unbeweglich heute Abend Nachmittag und habe einfach drauf
los versucht:
Ich habe den Wechselblinker etwas verändert. In der ISR habe ich die
Vergleichswerte für counter halbiert und in
function1 und
function2
jeweils einen static cnt eingesetzt.
das Wechselblinken geht wie vorher.
Außerdem eine Reihe von Funktionen mit jeweils einer static Variablen.
Die Funktionen sind alle gleich.
Sie bekommen eine Variable per call by value übergeben, schreiben sie in
die static Variable, und rufen die nächste Funktion auf. Die das Selbe
macht. Im Grunde bilde ich damit reentrancy nach. Am Ende angekommen
wird der wert um 1 erhöht und zurückgegeben und die Kette geht wieder
zurück.
Außerdem gebe ich den Wert in jeder Funktion über UART aus. im
Hyperterminal geht es immer von 0 bis 255 und läuft dann über.
Dazu sei allerdings gesagt, dass der Compiler immer brav r24 verwendet.
Im Gunde also gehupft wie gesprungen, wie viele dieser Funktionen ich
einbaue.
Was ich auch versucht habe, ist mit einer Reihe Inlineassembler r31 zu
laden und den Wert dann einmal per "mov Rd,Rr" durch alle W-Register zu
schleifen. Hat auch geklappt.
Wie gesagt, bin heute nicht ganz drauf. Daher waren das jetzt nur
quantitative Stichproben, habe mir noch keine qualitativen Gedanken
gemacht.