Forum: Mikrocontroller und Digitale Elektronik msp430 mini multitasking system


von andi (Gast)


Lesenswert?

moin,

möchte ein einfaches Multitasking Programm für den msp430 schreiben, 
welches per Interrupt zwischen den Tasks wechselt. Es geht hier eher um 
erlernen von Dingen als um möglichst praktisch und effizient zu fahren. 
Ich weiss ungefähr was ich machen muss, nur die Assembler Kenntnisse 
sind kaum bis garnicht vorhanden.

Ich bin jetzt soweit, dass ich während ich Tasks a la
1
void tBlink ()
2
{
3
  while (1)
4
  {
5
    P1OUT ^= LEDrt;
6
    wait_ms(1000);
7
  }
8
}
 habe und eine "Stack Struktur"
1
struct stack
2
{
3
unsigned   short   reg[16];
4
}stack[5];
5
[/c
6
7
des weiteren habe ich ein Functionpointer-array dass mir die Adressen der Funktionen zeigt
8
[c]
9
typedef void   (* fnt_ptr) (void);      
10
fnt_ptr   task_list[5];

mit
1
  task_list[0] = tBlink;
2
  task_list[1] = tBlink2;
3
  task_list[2] = tPWM;

Ich bin jetzt soweit, dass ich zumindest den Task wechseln kann, aber 
eben immer am Anfang beginne, nicht dort wo ich rausgesprungen bin. Auch 
das Löschen des stacks bekomm ich nicht so ganz hin
1
  asm(" mov.w r0, reg_buff\n");      // Program Counter
2
  stack[task_nr].reg[0] = reg_buff;
3
  asm(" mov.w r1, reg_buff\n");      // Stack Pointer
4
  stack[task_nr].reg[1] = reg_buff;
5
  asm(" mov.w r2, reg_buff\n");      // Status Register
6
  stack[task_nr].reg[2] = reg_buff;
7
  asm(" mov.w r3, reg_buff\n");      // CG Register
8
  stack[task_nr].reg[3] = reg_buff;
9
  asm(" mov.w r4, reg_buff\n");      // GP Register
10
  stack[task_nr].reg[4] = reg_buff;
11
        //... usw bis r15
12
13
  /*
14
   * Stackeinträge löschen wegen überlauf undso
15
   */
16
17
  reg_buffer = (unsigned short) task_list[task_nr]; 
18
  reg_buff = stack[task_nr].reg[2];
19
  asm (" push.w reg_buff\n");
20
  asm (" reti\n");
Er speichert mir eben die Register die er gerade hat, nicht die die auf 
dem Stack liegen (denke ich mal), denn beim Debuggen springt er mir, 
wenn ich
1
reg_buff = stack[task_nr].reg[0];
2
asm (" push.w reg_buff\n");
schreibe, in die ISR zurück, was schwachsinn ist.

Mir fehlt also eine Funktion "read_stack()" die mir wirklich die auf dem 
Stack befindlichen PC und SR liest.

Und noch eine art "pop()" die mir PC und SR vom Stack entfernt.

Die Register SP, R4 bis R15 interargieren im Interrupt ja nicht wirklich 
mit dem Stack oder? hörte sich im Datenblatt mal so an.

Danke im Voraus für jede Hilfe :)

von c-hater (Gast)


Lesenswert?

andi schrieb:
>
> möchte ein einfaches Multitasking Programm für den msp430 schreiben,
> welches per Interrupt zwischen den Tasks wechselt. Es geht hier eher um
> erlernen von Dingen als um möglichst praktisch und effizient zu fahren.
> Ich weiss ungefähr was ich machen muss, nur die Assembler Kenntnisse
> sind kaum bis garnicht vorhanden.

Dann würde ich vorschlagen, daß du deinen Plan damit umzusetzen 
beginnst, daß du erstmal Assembler lernst. Erstens geht das damit viel 
einfacher umzusetzen als mit C und zweitens kannst du C ja 
offensichtlich auch nicht wirklich.

Und mit zwei Sprachen, von denen man nicht und die andere kaum 
beherrscht, kann man nunmal einfach nicht sinnvoll programmieren.

von andi (Gast)


Lesenswert?

und du kannst an 3 Zeilen C erkennen, ob man es beherrscht oder nicht? 
Respekt.

von rangi (Gast)


Lesenswert?

beim eintritt in die isr werden doch eh schon alle register gesichert. 
es genügt, die rücksprungadresse auf dem stack gegen die des 
fortzusetzenden threads auszutauschen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

rangi schrieb:
> es genügt, die rücksprungadresse auf dem stack gegen die des
> fortzusetzenden threads auszutauschen.

Nein, denn was soll Thread B mit den Registerinhalten von Thread A 
anfangen?

von Eric (Gast)


Lesenswert?

rangi schrieb:
> beim eintritt in die isr werden doch eh schon alle register
> gesichert.
> es genügt, die rücksprungadresse auf dem stack gegen die des
> fortzusetzenden threads auszutauschen.

Das genügt nicht, da dann ja der fortzustzenden Thread mit den 
Registerwerten des gerade unterbrochenen Threads weiterarbeiten würde. 
Also nicht nur PC sondern auch alle Register austauschen!

von rangi (Gast)


Lesenswert?

naja, also das war die quick n dirty variante. wenn in der isr eine 
funktion gerufen wird die in einem anderen modul liegt, müssen alle 
register gesichert werden. je nach hardware gibt es hier auch 
hw-unterstützung. demzufolge werden beim verlassen der isr alle register 
aus dem zustartenden thread wieder hergestellt.

von andi (Gast)


Lesenswert?

rangi schrieb:
> beim eintritt in die isr werden doch eh schon alle register
> gesichert.
> es genügt, die rücksprungadresse auf dem stack gegen die des
> fortzusetzenden threads auszutauschen.

Hm, kommt für mich aus dem Datenblatt nicht so rüber. Aber falls dem so 
währe, eventuell das der Fehler, warum ich in der ISR mit
1
asm (" pop r5");
2
asm (" mov.w r5, reg_buffer");

immer 0x0000 lese?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Tatsächlich ist das ganze noch etwas komplizierter - da jeder Thread 
bzw. Task einen eigenen Stack haben muss. Also muss beim Verlassen der 
ISR je nach zu aktivierendem Thread nicht nur dessen vollständiger 
Registersatz, sondern auch der zugehörige Stackpointer restauriert 
werden.

von Andi (Gast)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Tatsächlich ist das ganze noch etwas komplizierter - da jeder
> Thread bzw. Task einen eigenen Stack haben muss. Also muss beim
> Verlassen der ISR je nach zu aktivierendem Thread nicht nur dessen
> vollständiger Registersatz, sondern auch der zugehörige Stackpointer
> restauriert werden.

Ich hab des jetzt halt schnell in ein struct gespeichert, und die 
register mit mov.w schreiben funktioniert ja

Nur eben pop r5 oder r15 was auch immer liefert mit immer 0

von Andi (Gast)


Lesenswert?

Bezieungsweise 2mal pop gibt den PC

von andi (Gast)


Lesenswert?

wenn ich zB etwas mit
1
reg_buff = 0x1234;
2
asm (" push.w &reg_buff");
3
reg_buff = 0x0000;

auf den Stack lege kann ich es ohne Probleme mit
1
asm (" pop.w &reg_buff");

wieder runter holen, sodass reg_buff wieder 0x1234 ist

von rangi (Gast)


Lesenswert?

ich hab das mal für einen M16C und einen HC08 gemacht. Ich finde aber 
meinen damaligen code nicht mehr wieder.
1. für jeden thread, der gestartet werden soll muss ein eigener stack 
angelegt werden (array of char). die größe so nach gefühl fg.
2. dann muss jeder dieser stacks mit bestimmten werten vorbelegt werden. 
das guckst du dir am besten im listing und im debugging ab. dazu musst 
du eine isr schreiben und dort einen funktionsruf in ein anderes modul 
machen (oder optimierung aus). im breakpoint schaust du dir an, was 
alles auf den stack gesichert wurde. ganz oben sollte die 
rücksprungadresse sein, also dort, wo die abarbeitung unterbrochen 
wurde.
3. jetzt füllst du initial die stacks mit dem fake-kontext und als 
rückspungadresse die "main" des threads, der gestartet werden soll.
4. wenn der thread gestartet werden soll, also in einer isr (z.b. 
timer-isr) merkst du dir den aktuellen stackpointer und ersetzt ihn 
durch den stackpointer der in den stack deines threads zeigt. mehr muss 
man hier nicht sichern.
beim verlassen der isr (assembler: iret) wird jetzt nicht der kontext 
des zuvor laufenden programms (z.b. main) wiederhergestellt, sondern der 
kontext des zustartenden theads. zuletzt wird der pc vom stack geholt. 
das ist der zeiger auf die zu startende funktion wie unter 3. 
beschrieben. und schwups läuft den thread an.
5. bei zukünftigen isr-aufrufen wird dann der laufende kontext gesichert 
und nur durch austausch der stackpointer erfolgt der switch zum neuen.

von Ecki (Gast)


Lesenswert?

Schau mal hier als weitere Anregung, insbesondere die swap-Funktion in 
task.c:

http://sourceforge.net/p/mspgcc/historical/ci/bzr/mspgcc-examples/~/tree/tasker/

von andi (Gast)


Angehängte Dateien:

Lesenswert?

Ich hab die Tage nochmal dran gesessen, das ding zum laufen zu bringen.
Eigentlich habe ich fast nichts vom alten code behalten und alles neu 
gemacht. zB speichere ich meinen Stack nicht in einer Struktur sonder 
gebe jedem Task, so wie hier schon oft angedeutet wurde, einen eigenen 
Bereich des (Hardware)Stacks.

Da ja beim Interrupt nicht nur PC und SR, sondern auch diverse Variablen 
auf dem Stack landen, speichere ich einfach den Wert des SP nach dem 
Betreten der ISR. Um wieder in die Funktion zu springen muss ich ja 
eigentlich nur den SP auf diesen Wert setzen und ein "return;" 
durchführen a la
1
...
2
struct task
3
{
4
struct task                    // Task struct
5
{
6
  fnt_ptr     init_address;
7
  unsigned short  sp_return;
8
  unsigned short  sp;
9
  unsigned char    state;
10
  unsigned short  delay;
11
  unsigned char  first_call;
12
  unsigned short  stack_size;
13
}task[MAXTASKS];
14
...
15
t_idle ();
16
...
17
// --->interrupt (momentan Tastendruck)
18
task[x].sp_return = _get_SP_register ();
19
...
20
_set_SP_register (task[x].sp_return);
21
return;

Tasks starten ist auch kein Problem
1
  if (task[task_nr].first_call == 0)
2
  {
3
    task[task_nr].state = RUNNING;
4
    task[task_nr].first_call = 1;
5
6
    buff  = (unsigned short) task[task_nr].init_address;  // PC
7
    buff2 = 0x0008;                      // IEN SR
8
9
    asm (
10
        " push &buff\n"
11
        " push &buff2\n"
12
        " reti\n"
13
      );
14
  }

Einen Task restoren kann ich jedoch nur 1 mal, beim zweiten restore (den 
ersten restorten task kann ich noch abspeichern), lande ich nach dem 
return; immer wieder in der ISR in Zeile 171 
(_set_SP_register(task[x].sp) ).

ich habe pro Task immer 2 SP, einen .sp_return und einen .sp, letzterer 
Zeigt auf R15 der CPU Register. Ich reserviere für jeden
Task einen gewissen Speicher für seine Variable, momentan 16 unsigned 
short's beziehungsweise 32 Adressen:

Stack: --][PC][SR][v1][v2][x]...[x][x][r4][r5]...[r15][--
.......................^sp_return..................^sp...

Irgendjemand eine Idee woran das liege könnte?

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.