Forum: Mikrocontroller und Digitale Elektronik AVR AtMega8 Preemtive Scheduler


von Jasson J. (jasson)


Lesenswert?

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

von holger (Gast)


Lesenswert?

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

von Andreas B. (andreasb)


Lesenswert?

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

von Achim M. (minifloat)


Lesenswert?

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.

von Jasson J. (jasson)


Lesenswert?

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:
1
uint16_t *Stack_Ptr = NULL;// 
2
uint16_t (*Func_Ptr_1)(übergabe) = NULL;// Funktionspointer deklarieren
3
Func_Ptr_1 = &My_Func;// dem F-Pointer eine Adress zuweisen
4
Stack_Ptr = (SP +- (muss ich noch drüber nachdenken));// 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

von Andreas B. (andreasb)


Lesenswert?

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

von Jasson J. (jasson)


Lesenswert?

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

von Jasson J. (jasson)


Lesenswert?

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:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <stdint.h>
4
5
uint8_t cnt = 0;
6
// http://www.newty.de/fpt/fpt.html#defi
7
void (*pt_function1)() = 0;//http://www.alenck.de/AVR_Tutorial_4_CVAVR/05_Syntax%20der%20C-Programmierung/CVAVR_05.pdf S.64
8
uint16_t *Stack_Ptr = 0;
9
10
SIGNAL (TIMER2_COMP_vect)//TIMER1_COMPA_vect) 
11
{
12
PORTC = 0b00100000;
13
14
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)

von Jasson J. (jasson)


Lesenswert?

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.

von Jasson J. (jasson)


Lesenswert?

@Stefan, ich habe ne E-Mail bekommen, dass du was geschrieben hast. Aber 
das system scheint es nicht angenommen zu haben.

von Stefan E. (sternst)


Lesenswert?

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.

von Jasson J. (jasson)


Lesenswert?

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.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <stdint.h>
4
5
uint8_t cnt = 0;
6
// http://www.newty.de/fpt/fpt.html#defi
7
void (*pt_function1)() = 0;//http://www.alenck.de/AVR_Tutorial_4_CVAVR/05_Syntax%20der%20C-Programmierung/CVAVR_05.pdf S.64
8
uint16_t *Stack_Ptr = 0;
9
10
volatile uint16_t tmp_ptr = 0;
11
volatile uint8_t low_ptr, high_ptr;
12
13
SIGNAL (TIMER2_COMP_vect)//TIMER1_COMPA_vect) 
14
{
15
16
PORTC = 0b00100000;
17
18
tmp_ptr = (unsigned int) pt_function1;
19
low_ptr = tmp_ptr; // get lowbyte
20
asm volatile("push r24");// push low byte of adress to the stack
21
tmp_ptr -= low_ptr;//clear low byte of the adress
22
high_ptr = 0;
23
24
//*****************************************************
25
// obtaining high byte of the adress and push it to stack
26
if (tmp_ptr >= 32786){
27
  high_ptr++;
28
  high_ptr << 1;
29
  tmp_ptr -= 32786;
30
}
31
if (tmp_ptr > 163845){
32
  high_ptr++;
33
  high_ptr << 1;
34
  tmp_ptr -= 163845;
35
}
36
if (tmp_ptr >= 8192){
37
  high_ptr++;
38
  high_ptr << 1;
39
  tmp_ptr -= 8192;
40
}
41
if (tmp_ptr >= 4096){
42
  high_ptr++;
43
  high_ptr << 1;
44
  tmp_ptr -= 4096;
45
}
46
if (tmp_ptr >= 2048){
47
  high_ptr++;
48
  high_ptr << 1;
49
  tmp_ptr -= 2048;
50
}
51
if (tmp_ptr >= 1024){
52
  high_ptr++;
53
  high_ptr << 1;
54
  tmp_ptr -= 1024;
55
}
56
if (tmp_ptr >= 512){
57
  high_ptr++;
58
  high_ptr << 1;
59
  tmp_ptr -= 512;
60
}
61
if (tmp_ptr >= 256){
62
  high_ptr++;
63
  high_ptr << 1;
64
}
65
asm volatile("push r24");
66
//*****************************************************
67
asm volatile("ret");//goto function
68
asm volatile("label:");
69
asm volatile("nop");
70
}
71
72
void function1(){
73
PORTC = 0b00110000;
74
asm volatile("RJMP label");// relativsprung zu "label";
75
}
76
77
int main(void)
78
{
79
TCCR2 = 0b00001100;// CTC Mode, prescaler 128
80
OCR2 = 100;//compare match value
81
TIMSK = 0b10000000;//timer2 match interrupt enable
82
TIFR |= 0b10000000;
83
84
DDRC = 0b00110000;// set PORTC PIN 5 and 6 to output
85
pt_function1 = &function1;//assign function adress to pointer
86
sei();// enable interrupts
87
88
main2:
89
PORTC = 0b00100000;
90
asm volatile("nop");
91
asm volatile("nop");
92
asm volatile("nop");
93
asm volatile("nop");
94
asm volatile("nop");
95
asm volatile("nop");
96
asm volatile("nop");
97
asm volatile("nop");
98
goto main2;
99
return 0;
100
}

von Stefan E. (sternst)


Lesenswert?

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:
1
ISR (TIMER2_COMP_vect) {
2
3
    function1();
4
}
5
   
6
void function1 (void) {
7
8
    PORTC = 0b00110000;
9
}

von Oliver (Gast)


Lesenswert?

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

von Jasson J. (jasson)


Lesenswert?

plakative Äußerungen helfen immer weiter...

von Stefan E. (sternst)


Lesenswert?

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.

von Jasson J. (jasson)


Angehängte Dateien:

Lesenswert?

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

von Oliver (Gast)


Lesenswert?

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

von Jasson J. (jasson)


Angehängte Dateien:

Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.