Forum: Mikrocontroller und Digitale Elektronik Context Switch beim Cortex M


von Moritz (Gast)


Lesenswert?

Guten Abend,

ich versuche mich grade an einem RTOS für Cortex M (nur zum Spaß, ich 
weiß es gibt viele Fertige). Mein Problem ist zur Zeit der Kontext 
wechsel von einem Task zu einem anderen.
1
void Task1(void)
2
{
3
  int x = 123;
4
  while(1)
5
  {
6
    debug_printf("%d\n", x);
7
  }
8
}
9
10
void Task2(void)
11
{
12
  int x = 321;
13
  while(1)
14
  {
15
  }
16
}

Als erstes Läuft Task1 und dann wird gewechselt (im Systick Handler). 
Wenn Task1 dannach wieder laufen soll wird aber 321 statt 123 
ausgegeben, weshalb ich befürchte, dass die Stacks durcheinander kommen.
Ich könnte mir auch gut vorstellen, dass das mit dem Software Stacking 
zu tun hat, bei dem die Register R4 - R11 auf dem Stack gesichtert 
werden. Die anderen Register sichert die Hardware automatisch beim 
Eintritt in den Interrupt.
1
void SaveTask(void)
2
{
3
  CurrentTask->State = WAITING;
4
5
  msp = __get_MSP();
6
  __set_MSP(__get_PSP());
7
  __ISB();
8
9
  asm("push {r4}");
10
  asm("push {r5}");
11
  asm("push {r6}");
12
  asm("push {r7}");
13
  asm("push {r8}");
14
  asm("push {r9}");
15
  asm("push {r10}");
16
  asm("push {r11}");
17
18
  CurrentTask->StackPointer = __get_MSP();
19
20
  __set_MSP(msp);
21
  __ISB();
22
}
23
24
void LoadTask(Task_t *task)
25
{
26
  if(task->State == READY)
27
  {
28
    CurrentTask = task;
29
    CurrentTask->State = RUNNING;
30
31
    __set_PSP(CurrentTask->StackPointer);
32
    __ISB();
33
  }
34
  else if(task->State == WAITING)
35
  {
36
    CurrentTask = task;
37
    CurrentTask->State = RUNNING;
38
39
    msp = __get_MSP();
40
    __set_MSP(CurrentTask->StackPointer);
41
    __ISB();
42
43
    asm("pop {r11}");
44
    asm("pop {r10}");
45
    asm("pop {r9}");
46
    asm("pop {r8}");
47
    asm("pop {r7}");
48
    asm("pop {r6}");
49
    asm("pop {r5}");
50
    asm("pop {r4}");
51
52
    CurrentTask->StackPointer = __get_MSP();
53
54
    __set_MSP(msp);
55
    __ISB();
56
57
    __set_PSP(CurrentTask->StackPointer);
58
    __ISB();
59
  }
60
}

Die Stacks sind auch außreichen groß (128 Words nur für Daten) Der 
Zustand READY sagt nur aus, das der Task neu ist. WAITING bedeutet, das 
der Task schonmal aktiv war.

Vielleicht hat hier jemand Ahnung von RTOSs und kennt die Lösung. Per 
Debugging und Step by Step konnte ich leider nicht raus finden wie die 
Daten von Task2 in Task1 gelangen.

mfg Moritz

von Lothar (Gast)


Lesenswert?

Es ist besser / übersichtlicher den Switch in Assembler zu machen, alles 
im Systick Handler. Deine C Lösung könnte eventuell funktionieren, wenn 
Deine Funktionen nicht selbst stacken, versuch mal:

inline void SaveTask(void)
inline void LoadTask(Task_t *task)

Ansonsten in Assembler siehe hier:

http://stackoverflow.com/questions/25199802/switching-context-inside-an-isr-on-cortex-m

von Little B. (lil-b)


Lesenswert?

Ja, so muss ein Context Switch aussehen.

Mit entsprechendem Wissen über RTOSs und die Cortex-M-Prozessoren lässt 
sich hier sehr leicht verstehen, was du tust. Man merkt, dass du dir 
Gedanken gemacht hast, und es ist ein schönes Stück Software entstanden.

Auf den ersten Blick finde ich hier keinen Fehler. Mich würde aber 
interessieren, wie du deine Contexte initialisierst. Denn für 
"if(task->State == READY)" muss bereits was im Speicher vorhanden sein, 
das dann vom Interrupt Return geladen werden kann.
Dann wäre auch hilfreich zu wissen, wo genau die lokale variable x 
liegt. Stack oder Register?

Ich würde mich freuen, wenn du deinen kompletten code mit der community 
teilen würdest, es könnten vieleicht einige was davon lernen.

von eagle user (Gast)


Lesenswert?

Apropos Initialisierung: benutzen die Tasks überhaupt den PSP? Zu Anfang 
läuft ja alles auf dem MSP, also muss per CONTROL Register umgeschaltet 
werden.

von Moritz (Gast)


Lesenswert?

Moin,

also die Task werden so initialisiert:
1
Task_t *CreateTask(unsigned char *name, uint32_t stackSizeWords, uint8_t priority, void (*function)(void))
2
{
3
  Task_t *newTask = (Task_t *)malloc(sizeof(Task_t));
4
  if(newTask == NULL)
5
  {
6
    OutOfHeapCallback();
7
  }
8
9
  if(stackSizeWords < MIN_STACK_SIZE)
10
  {
11
    stackSizeWords = MIN_STACK_SIZE;
12
  }
13
14
  newTask->Function = function;
15
  newTask->Name = name;
16
  newTask->Next = NULL;
17
  newTask->Priority = priority;
18
  newTask->Stack = (uint32_t *)malloc(stackSizeWords * 4);
19
  if(newTask->Stack == NULL)
20
  {
21
    OutOfHeapCallback();
22
  }
23
24
  newTask->StackPointer = (((uint32_t)newTask->Stack) + ((stackSizeWords - 8) * 4));
25
  newTask->StackSizeWords = stackSizeWords;
26
  newTask->State = READY;
27
28
  *(newTask->Stack + (newTask->StackSizeWords - 1)) = 0x01000000;
29
  *(newTask->Stack + (newTask->StackSizeWords - 1) - 1) = (uint32_t)newTask->Function;
30
  *(newTask->Stack + (newTask->StackSizeWords - 1) - 2) = 0xFFFFFFFD;
31
  *(newTask->Stack + (newTask->StackSizeWords - 1) - 3) = 0x00;
32
  *(newTask->Stack + (newTask->StackSizeWords - 1) - 4) = 0x00;
33
  *(newTask->Stack + (newTask->StackSizeWords - 1) - 5) = 0x00;
34
  *(newTask->Stack + (newTask->StackSizeWords - 1) - 6) = 0x00;
35
  *(newTask->Stack + (newTask->StackSizeWords - 1) - 7) = 0x00;
36
37
  if(TaskChain == NULL)
38
  {
39
    TaskChain = newTask;
40
  }
41
  else
42
  {
43
    CMRTOS_Task_t *current = TaskChain;
44
    CMRTOS_Task_t *prev = NULL;
45
    while((current != NULL) && (current->Priority >= newTask->Priority))
46
    {
47
      prev = current;
48
      current = current->Next;
49
    }
50
51
    if(prev == NULL)
52
    {
53
      newTask->Next = current;
54
      TaskChain = newTask;
55
    }
56
    else
57
    {
58
      prev->Next = newTask;
59
      newTask->Next = current;
60
    }
61
  }
62
63
  return newTask;
64
}

Ich verwende eine sortierte Linked-List um die Task zu speichern. Hohe 
Prioritäten stehen am Anfang. TaskChain ist der Task mit der höchsten 
Priorität.

Ganz am Anfang gibts eine Init-Funktion für das RTOS. Bis jetzt wird da 
nur __set_CONTROL_(2) aufgerufen, was zum Process-Stack schaltet.

x ist eine lokale Variable und liegt auf dem Stack.

Wenns funktioniert lad ich das hier gerne hoch.

mfg, Moritz

von Moritz (Gast)


Lesenswert?

Also folgendermaßen funktioniert es anscheinend (Die Task interferieren 
nicht mehr):

-Alles im Systick-Handler
-Systick-Handler mit __attribute__((naked)) gekennzeichnet
-Keine Funtionsaufrufe im Handler (mit naked gibt das ein Hardfault)

Allerdings möchte ich gerne den Systick-Handler für etwas anderes 
verwenden (STM32-HAL usw.).

Also hab ich den Inhalt aus dem SysTick-Handler in den PendSV kopiert 
und trigger im Systick-Handler nur den PendSV (der auch mit 
__attribute__((naked)) gekennzeichnet ist)

Das führt allerdings auch schon zu einem Hardfault, weil der 
Systick-Handler erst beendet wird nachdem der PendSV fertig ist. 
(Systick hat Priorität 0 (hoch) und PendSV Priorität 15 (niedrig)). Wenn 
ich das naked attribute vom Systick wegnehme interferieren beide Task 
wieder.

Was soll ich tuen? Am besten wäre es wohl wenn sich der Systick erst 
komplett beendet und dann der PendSV beginnt. Aber das ist glaube ich 
beim Cortex M extra so, damit alle Interrupts schnell bearbeitet werden.

mfg, Moritz

von Moritz (Gast)


Angehängte Dateien:

Lesenswert?

So, jetzt funktioniert es richtig. Das Problem war meine Faulheit, die 
darin bestand den Simulator zu verwenden anstatt die echte Hardware. Wie 
versprochen einmal das gesamte "RTOS". Um zu verstehen wie ein RTOS 
funktioniert und als Grundlage für ein RTOS vielleicht ganz nützlich, 
für den richtigen Einsatz fehlen noch viel zu viele Sachen. Ich hab 
Crossworks verwendet (gcc) und als Hardware einen STM32F746NG. Sollte 
aber mit Cortex M3 und M4 funktionen (M0 und M1 bin ich mir nicht ganz 
sicher. Es wird die stm32f7xx.h mit in das RTOS eingebunden, müsste man 
vllt. anpassen)

Beispiel Programm könnte so aussehen:
1
#include "CMRTOS.h"
2
3
4
void HAL_SYSTICK_Callback(void)
5
{
6
  CMRTOS_IncrementCurrentTime();
7
}
8
9
void Task1(void)
10
{
11
  uint32_t x = 0;
12
  while(1)
13
  {
14
    x++;
15
    debug_printf("x: %d\n", x);
16
  }
17
}
18
19
20
void Task2(void)
21
{
22
  uint32_t y = 10000;
23
  while(1)
24
  {
25
    y--;
26
    debug_printf("y: %d\n", y);
27
  }
28
}
29
30
int main(void)
31
{
32
  CMRTOS_Init();
33
34
  CMRTOS_Task_t *task1 = CMRTOS_CreateTask("Task1", CMRTOS_MIN_STACK_SIZE + 32, 200, Task1);
35
  CMRTOS_Task_t *task2 = CMRTOS_CreateTask("Task2", CMRTOS_MIN_STACK_SIZE + 32, 200, Task2);
36
37
  while (1)
38
  {
39
  }
40
}

mfg Moritz

von olaf (Gast)


Lesenswert?

Ich hab mal eine Frage. Mit  __set_CONTROL(2) schaltet du ja die
Nutzung des psp frei. Brauchst du dafuer ein modifiziertes startup
damit der psp korrekt initialisiert ist?
Weil ueblicherweise nutzt man Embedded doch eigentlich nur den msp oder?

Olaf

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.