Forum: Mikrocontroller und Digitale Elektronik Cortex-M(0) - reentrant Funktion aus SysTick-Handler anspringen


von Tom W. (nericoh)


Lesenswert?

Hallo Zusammen!

Ich möchte folgendes auf der Cortex-M(0)-Architektur umsetzen:

Eine Funktion soll aus dem SysTick-Handler heraus angesprungen werden. 
Allerdings möchte ich dies nicht innerhalb des Interrupt-Kontextes tun, 
da die anzuspringende Funktion potentiell so lange läuft, dass 
währenddessen erneut der SysTick-IRQ kommt und erneut diese Funktion 
aufruft.

Durch einen anderen Beitrag bin ich bereits darauf gestoßen (worden), 
dass ich das dadurch erreichen kann, dass ich innerhalb der ISR durch 
Manipulation von Stack etc. dafür sorgen kann, dass nach Ende der ISR 
nicht wieder in die Main, sondern zu myFunktion gesprungen wird. Um mein 
Anliegen etwas anschaulicher zu machen, hier ein Beispielcode:
1
int main()
2
{
3
  while(FOREVER)
4
  {
5
    /* tue irgendwas */
6
  }
7
}
8
9
void SysTick_Handler()
10
{  
11
  NVIC_ClearPendingIRQ(SysTick_IRQn);
12
  SysTick->LOAD = SysTick_LOAD; 
13
14
  /*Hier soll der Inline-ASM stehen,
15
    der bewirkt, dass im Anschluss direkt 
16
    'myFunction' ausgeführt wird */  
17
}
18
19
void myFunction(void)
20
{
21
  /*Hier kann etwas passieren,
22
    was potientiell so lange dauert,
23
    dass diese Funktion erneut nach
24
    dem SysTick-Handler aufgerufen wird */
25
}

Dem Cortex-M0 Technical Reference Manual habe ich entnommen, dass R14/LR 
die Return-Adresse für Funktionsaufrufe, Exceptions etc. enthält. Mein 
erster Ansatz ist daher, einfach die Adresse von myFunction ins LR zu 
schieben. Dazu einige konkrete Fragen:

1. geht mein Ansatz in die richtige Richtung?

2. ganz so einfach wird es wohl vermutlich nicht sein - was ist außerdem 
noch zu beachten (pushen/popen der anderen Register, Modifikation des 
SPs, Modifikation des PCs, ...)?

3. für den Fall, dass es überhaupt Sinn macht, LR zu modifizieren: 
welche Instructions des Cortex-M0 eignen sich hierzu? LDR und STR 
scheiden wohl aus, da diese offenbar nur auf R0-R7 anwendbar sind ("Rt 
and Rn must only specify R0-R7."). Da ich in ASM wenig bis keine 
Erfahrung habe, muss ich diese banale Frage leider stellen :\

Ich danke schonmal für jeden konstruktiven Hinweis!

Tom

: Bearbeitet durch User
von Momo (Gast)


Lesenswert?

1
int main()
2
{
3
  while(FOREVER)
4
  {
5
    if(flag_SysTick) 
6
        {
7
          flag_SysTick = false; 
8
          call_my_mega_function(); 
9
         }
10
11
12
}
13
14
}
15
16
void SysTick_Handler()
17
{  
18
  NVIC_ClearPendingIRQ(SysTick_IRQn);
19
  SysTick->LOAD = SysTick_LOAD; 
20
  flag_SysTick = true; 
21
}

von Tom W. (nericoh)


Lesenswert?

Na darauf bin ich selber auch schon gekommen :D
Ist für meinen Fall aber ungeeignet, da:

- je nachdem, was noch so in main passiert, ich nicht garantieren kann, 
dass meine Funktion ausreichend oft ausgeführt wird

- die Funktion sich nicht selber unterbrechen kann (was sie aber können 
soll)

- mich das im Verständnis von ASM und Cortex-M kein Bischen 
weiterbringt...

: Bearbeitet durch User
von Marvin M (Gast)


Lesenswert?

Öhm - die Funktion braucht immer mehr Zeit, als zwischen zwei 
Systick-Ereignissen verbleibt?
Warum dann überhaupt den Aufruf per Systick...?

Anderer Ansatz:
Im Systick zählst du bei jedem Durchlauf eine Variable um 1 hoch 
(volatile nicht vergessen...)
Im Mainloop fragst du die Variable ab. Ist sie >0 wird die Funktion 
ausgeführt und die Variable um 1 vermindert.
So werden keine Funktionsaufrufe vergessen. Ändert aber nichts am 
Problem, dass irgendwann auch diese Variable überläuft.

von W.S. (Gast)


Lesenswert?

besser:

char aFlag;

void SysTick_Handler()
{ NVIC_ClearPendingIRQ(SysTick_IRQn);
  SysTick->LOAD = SysTick_LOAD;
  ++aFlag;
}


... main(..)
{ char oldflag;
  oldflaf = aFlag = 0;
...

immerzu:
  if (aFlag!=oldflag)
  {...}
  ...
  goto immerzu;


W.S.

von holger (Gast)


Lesenswert?

>Eine Funktion soll aus dem SysTick-Handler heraus angesprungen werden.
>Allerdings möchte ich dies nicht innerhalb des Interrupt-Kontextes tun,
>da die anzuspringende Funktion potentiell so lange läuft, dass
>währenddessen erneut der SysTick-IRQ kommt und erneut diese Funktion
>aufruft.

Dann vergiss die Scheisse und löse es anders.
Nimm einen eigenen Timer für deinen Müll.
Den kannst du ja nach belieben deaktivieren.

von Momo (Gast)


Lesenswert?

Wenn dein Durchlauf der Main länger als die Zeit zwischen zwei SysTick 
Events braucht hast du so oder so ein Problem. Denn, angenommen, deine 
Funktion wird aufgerufen zur hälfte abgearbeitet, dann erneut aufgerufen 
usw usw... Dann kommt deine Main ja gar nicht mehr drann.

Was auch immer es werden soll, ich empfehle als Literatur, damit du den 
M0 und alle andren Cortex verstehst:

http://www.amazon.de/Definitive-Guide-ARM-Cortex-M0/dp/0123854776/ref=sr_1_1?ie=UTF8&qid=1416432495&sr=8-1&keywords=cortex+m0

von Tom W. (nericoh)


Lesenswert?

Mir ist bewusst, dass euch auf Anhieb net einleuchten mag, wieso das 
Konstrukt, was ich zu bauen versuche, sinnvoll sein könnte.

Ich wäre wahnsinnig dankbar, wenn wir uns trotzdem auf die eigentliche 
Frage konzentrieren könnten anstatt darüber zu lamentieren, wie unsinnig 
das Anliegen ist.

Geht doch bitte einfach mal davon aus, dass ich schon weiß, warum ich 
das tue.

Falls ihr dann net ruhig schlafen könnt, weil ihr befürchtet, dass 
irgendwo auf der Welt ein embedded-System abstürzt oder so, betrachtet 
es alternativ einfach als rein akademische Frage, die ich mir stelle, um 
mir ein Verständnis von LR,SP,PC beim Cortex-M0 und ASM im Allgemeinen 
zu erarbeiten.

Tom

von Momo (Gast)


Lesenswert?

In dem Buch findest du sämtliche Erklärungen zum M0, auch SP, ISR, PUSH, 
POP usw.
Außerdem hat dir doch schon jemand einen Tipp gegeben. Vergiss den 
popeligen SysTick und nimm nen Timer. Dann brauchst auch net mit Inline 
Asembler rumbasteln.

Gruß,

von Tom W. (nericoh)


Lesenswert?

Ob ich nun den SysTick oder nen anderen Timer nehme - an meiner 
Ausgangsfrage ändert das doch nix? Ich würde (unabhängig davon, ob es 
sinnvoll ist oder nicht) einfach gerne verstehen, wie es prinzipiell 
ginge. Ich bitte daher nur noch um Antworten, die meine 
Ursprungs(verständnis)frage addressieren und nicht darauf abzielen, die 
Frage in Frage zu stellen ;)

: Bearbeitet durch User
von holger (Gast)


Lesenswert?

>Geht doch bitte einfach mal davon aus, dass ich schon weiß, warum ich
>das tue.

Nimm FreeRTOS. Du wirst hier sicher niemanden finden
der dir den ganzen Quark für dein selbstgemachtes
und völlig unsinniges Problem erklären wird.

Stackmanipulation? Grossen Eimer holen und vollkotzen.

von Tom W. (nericoh)


Lesenswert?

FreeRTOS ist für mein Vorhaben viel zu mächtig. Aber in der Tat benötige 
ich das, was ich da beschreibe, für so etwas ähnliches wie einen 
Scheduler. Für Interessierte siehe dort:

Beitrag "Reentrant IR auf Cortex-M3"

von Momo (Gast)


Lesenswert?

Hab das Buch von Yiu nur für den M3 da, aber: Chapter 3 Cortex_m3 Basic 
/ Stack memory Operations page 35

Aber wie gesagt, das ist zum Verstehen sicherlich Sinnvoll, Lösen wird 
man Problem aber anders.

Programmier seit Jahren (beruflich) uC. Inline Assembler hab ich noch 
nie benötigt. Wäre aber mal witzig im Code-Review was die dazu sagen :)

von (prx) A. K. (prx)


Lesenswert?

Es gibt bei den Cortexen die Möglichkeit, in einem Interrupt einen 
zweiten Interrupt zu auszulösen, den man sinvollerweise mit niedrigster 
Priorität konfiguriert und deshalb keinem Hardware-Interrupt im Weg 
steht. Das ist der PendSV Interrupt.

In einem RTOS wird der üblicherweise verwendet, wenn ein Task-Switch 
fällig wird. Aber möglicherweise löst er auch dein Problem.

von Link Register (Gast)


Lesenswert?

Tom W. schrieb:
> dass ich innerhalb der ISR durch
> Manipulation von Stack etc. dafür sorgen kann, dass nach Ende der ISR
> nicht wieder in die Main, sondern zu myFunktion gesprungen wird

hhmmmm, beim Cortex geht das über das LR.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/CHDBIBGJ.html

von Tom W. (nericoh)


Lesenswert?

Üblicherweise benötige ich auch kein ASM.

Da du schon CodeReview ansprichst: es ist sogar so, dass das ganze für 
ein Safety-System zum Einsatz kommt. Eben aus Sicherheitsgründen 
benötigen wir einen eigenen "Scheduler", der Laufzeitverletzungen (aus 
eigener Kraft; net mit Hilfe eines WDs oder so!) feststellen kann etc. 
Und dazu muss er in einem festen Zeitraster aufgerufen werden und sich 
notfalls auch selber unterbrechen können.

Dieses Prinzip ist schon seit Jahren so im Einsatz - nur eben noch net 
auf dem Cortex-M.

von Momo (Gast)


Lesenswert?

Nur aus Neugierde, gibt es für sowas nicht speziell die Cortex R reihe? 
M's waren meiner Meinung nach nie für Sicherheitskritische Anwendungen 
gedacht?

von Gerd E. (robberknight)


Lesenswert?

Tom W. schrieb:
> Ich bitte daher nur noch um Antworten, die meine
> Ursprungs(verständnis)frage addressieren und nicht darauf abzielen, die
> Frage in Frage zu stellen ;)

Sorry, mit der Einstellung bekommst Du keine sinnvollen Antworten, Du 
verprellst nur potentielle Antworter.

Das Ganze hat den Hintergrund daß sich sehr viele Fragesteller auf einen 
Lösungsweg "eingeschossen" haben, der aber nicht wirklich zielführend 
für das eigentliche Problem ist. Sie wollen jetzt nur noch zu diesem 
Irrweg etwas hören und ignorieren, wenn ihnen alle sagen daß das nix 
wird.

Daher: erkläre das GANZE Problem, nicht nur einen kleinen Teil davon 
wie Du es bisher getan hast. Dann kann man Dir helfen.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Tom W. schrieb:
> Eine Funktion soll aus dem SysTick-Handler heraus angesprungen werden.
> Allerdings möchte ich dies nicht innerhalb des Interrupt-Kontextes tun,
> da die anzuspringende Funktion potentiell so lange läuft, dass
> währenddessen erneut der SysTick-IRQ kommt und erneut diese Funktion
> aufruft.

 Ich habe mal so etwas ähnliches mit MEGA gemacht, habe einfach die
 myFunction als ISR deklariert und dann im SysTick Interrupt per
 Software ausgelöst (Wenn zwei Eingänge gleichzeitig gesetzt waren).
 Nur gibts beim MEGA keine nested Interrupts, also beim Eintritt in
 die myFunction Global Interrupt Flag gesetzt, damit diese auch
 unterbrochen werden könnte. ARM hat nested Interrupts, da muss man
 so etwas nicht machen.
 Falls myFunction nicht wirklich reentrant ist, einfach ein Flag beim
 Eintritt setzen, bzw. prufen. Wenn schon gesetzt, einfach wieder raus.
 Wenn nicht, Flag setzen und myFunction abarbeiten. Wenn fertig, Flag
 wieder zurücksetzen.

: Bearbeitet durch User
von Call (Gast)


Lesenswert?

Marc Vesely schrieb:
> Ich habe mal so etwas ähnliches mit MEGA gemacht, habe einfach die
>  myFunction als ISR deklariert und dann im SysTick Interrupt per
>  Software ausgelöst

Damit wird die erste ISR nicht terminiert, sondern nur unterbrochen. Das 
kommt einem stinknormalem Funktionsaufruf gleich.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Call schrieb:
> Damit wird die erste ISR nicht terminiert, sondern nur unterbrochen. Das
> kommt einem stinknormalem Funktionsaufruf gleich.

 Nein.
 Es gibt so etwas wie Prioritäten.
 myFunction kriegt eben niedrigere Priorität.

von Martin L. (maveric00)


Lesenswert?

Hallo,

Tom W. schrieb:
> Üblicherweise benötige ich auch kein ASM.
>
> Da du schon CodeReview ansprichst: es ist sogar so, dass das ganze für
> ein Safety-System zum Einsatz kommt. Eben aus Sicherheitsgründen
> benötigen wir einen eigenen "Scheduler", der Laufzeitverletzungen (aus
> eigener Kraft; net mit Hilfe eines WDs oder so!) feststellen kann etc.
> Und dazu muss er in einem festen Zeitraster aufgerufen werden und sich
> notfalls auch selber unterbrechen können.
>
> Dieses Prinzip ist schon seit Jahren so im Einsatz - nur eben noch net
> auf dem Cortex-M.

Nun, dann musst Du Dir sogar dringend etwas anderes überlegen, da ich 
keine Sicherheitsnorm für E/E bzw. PE/E-Systeme kenne, die diese Art der 
Code-Verhuzelung gut heißen würden. Das ihr das so schon seit Jahren 
macht, zählt für künftige Projekte genau gar nichts (es gilt der Stand 
der Technik zum Zeitpunkt des In-Verkehr-Bringens).

Wenn es darum geht, Laufzeitverletzungen festzustellen, dann ist die 
gängige Methode, an verschiedenen Checkpoints eine Routine aufzurufen, 
die z.B. einen Zähler hochzählt (oder Flags setzt). Ein Hoch-priorer 
Task (oder Interrupt) überprüft dann regelmäßig, ob alles abgearbeitet 
wurde und leitet Gegenmaßnahmen ein, wenn dies nicht der Fall ist.

Dazu ist aber keine reentrante Mega-Funktion notwendig.

Im Pseudo-Code:
1
int FLAGS ;
2
boolean Execute_Important = TRUE ;
3
boolean Execute_Medium = TRUE ;
4
boolean Execute_DontCare = TRUE ;
5
6
void Checkpoint (int Flag)
7
{
8
  FLAGS = FLAGS | Flag ;
9
}
10
11
void MegaFunktion () // Niedrige Priorität Mega-Interrupt-Routine
12
{
13
 Checkpoint (ENTRY) ;
14
 // Arbeite alles kritische ab
15
 Checkpoint (IMPORTANT) ;
16
 if (Execute_Important) {
17
   // Arbeite alles wichtige ab
18
 } ;
19
 Checkpoint (MEDIUM) {
20
 if (Execute_Medium) {
21
   // Arbeite alles mittelwichtige ab
22
 } ;
23
 Checkpoint (DONT_CARE) ;
24
 if (Execute_DontCare) {
25
   // Arbeite die uninteressanten Sachen ab
26
 } ;
27
 Checkpoint (EXIT) ;
28
}
29
30
void RuntimeControl () // Hohe Priorität Prüfroutine, 
31
                       // mit niedrigerer Frequenz, 
32
                       // aber noch innerhalb 
33
                       //Prozess-Sicherheitszeit
34
{
35
 if ((FLAGS&EXIT)!=0) {
36
   // Alles in Ordnung, wieder nächste Stufe freischalten
37
   if (!Execute_IMPORTANT) {
38
      Execute_IMPORTANT = TRUE ;
39
   } else if (!Execute_MEDIUM) {
40
      Execute_MEDIUM = TRUE ;
41
   } else if (!Execute_DontCare) {
42
      Execute_DontCare = TRUE ;
43
   } ;
44
   FLAGS = 0 ;
45
   return ;
46
 } ;
47
 if ((FLAGS&IMPORTANT)==0) {
48
    // Zeit, Panik zu bekommen - noch nicht einmal die
49
    // kritischen Elemente werden abgearbeitet
50
    System_PANIC () ; // stellt sicheren Zustand her
51
    return ;
52
 } ;
53
 if ((FLAGS&MEDIUM)==0) {
54
    // Kritisches wurde abgearbeitet, wichtiges nicht
55
    // also Lauflänge auf Kritisches begrenzen. Wenn dies durchlaeuft 
56
    // wird es oben wieder verlaengert. Eventuell hier noch ersatz-
57
    // Maßnahmen einleiten...
58
    Execute_IMPORTANT = FALSE ;
59
    Execute_MEDIUM = FALSE ;
60
    Execute_DontCare = FALSE ;
61
    return ;
62
 } ;
63
 if ((FLAGS&DONTCARE)==0) {
64
    // Wichtiges wurde abgearbeitet, Mittleres nicht
65
    // also Lauflänge auf wichtiges begrenzen. Wenn dies durchlaeuft 
66
    // wird es oben wieder verlaengert. Eventuell hier noch ersatz-
67
    // Maßnahmen einleiten...
68
    Execute_MEDIUM = FALSE ;
69
    Execute_DontCare = FALSE ;
70
    return ;
71
 } ;
72
 if ((FLAGS&EXIT)==0) {
73
    // Es wurde alles bis auf DontCare abgearbeitet
74
    // also Lauflänge begrenzen. Wenn dies durchlaeuft 
75
    // wird es oben wieder verlaengert. Eventuell hier noch ersatz-
76
    // Maßnahmen einleiten...
77
    Execute_DontCare = FALSE ;
78
    return ;
79
 } ;
80
}

Damit können beide Routinen so gestaltet werden, dass sie nicht 
Re-Entrant sind, sondern ggf. durch einen pending Interrupt direkt 
wieder aufgerufen werden.

Allerdings muss man natürlich die Aufrufhäufigkeiten so wählen, dass 
sichergestellt wird, dass die kritischen Elemente oft genug aufgerufen 
werden. Dabei ist zu beachten, dass zumindest einmal die maximal 
mögliche Lauflänge der Mega-Funktion verstreichen kann, bevor das 
Abtastraster wieder eingehalten wird.

Ist dies nicht akzeptabel, dann muss die Mega-Funktion aufgeteilt 
werden, und über verschiedene Timer die einzelnen Funktionen mit 
unterschiedlich hoher Priorität aufgerufen werden (die Dont-Care-Anteile 
würden dann durch die kritischen Anteile unterbrochen werden können). 
Alternativ kann man natürlich auch einen Scheduler eines Pre-Emtiven 
Multitaskings implementieren (mit Prioritätssteuerung). Das ist aber in 
einem Sicherheitsumfeld ein erheblicher Aufwand.

Da Du schreibst, dass ihr auf den Cortex umgezogen seid: könnte es sein, 
dass der bisher verwendete Prozessor keine Interrupt-Priorisierung 
kannte? Dann sind Re-Entrant-Funktionen ein (einfacher, aber trotzdem 
nicht mehr zeitgemäßer) Weg, ein festes Zeitraster für die kritischen 
Anteile einzuhalten. Mit dem Cortex ist das jedenfalls auf keinen Fall 
notwendig (und wie gesagt, normativ praktisch nicht zu argumentieren, 
warum man das gemacht hat).

Schöne Grüße,
Martin

von Tom W. (nericoh)


Lesenswert?

Momo schrieb:
> Nur aus Neugierde, gibt es für sowas nicht speziell die Cortex R reihe?
> M's waren meiner Meinung nach nie für Sicherheitskritische Anwendungen
> gedacht?

-> Die Cortex-R-Reihe ist für unsere Zwecke deutlich zu mächtig. Die Ms 
ersetzen bei uns im Wesentlichen 8-Bitter und ältere ARMs (ARM7TDMI, 
...).


Martin L. schrieb:
> Nun, dann musst Du Dir sogar dringend etwas anderes überlegen, da ich
> keine Sicherheitsnorm für E/E bzw. PE/E-Systeme kenne, die diese Art der
> Code-Verhuzelung gut heißen würden. Das ihr das so schon seit Jahren
> macht, zählt für künftige Projekte genau gar nichts (es gilt der Stand
> der Technik zum Zeitpunkt des In-Verkehr-Bringens).

-> Mein Argument war nicht, dass es ja deswegen gut sein muss, weil wir 
es seit Jahren so machen. Ich wollte damit lediglich sagen, dass es 
"betriebsbewährt" und auch von den relevanten Zertifizierern anstandslos 
so aktzeptiert wurde und auch nach wie vor wird - es ist durchaus Stand 
der Technik.

Martin L. schrieb:
> Ist dies nicht akzeptabel, dann muss die Mega-Funktion aufgeteilt
> werden, und über verschiedene Timer die einzelnen Funktionen mit
> unterschiedlich hoher Priorität aufgerufen werden (die Dont-Care-Anteile
> würden dann durch die kritischen Anteile unterbrochen werden können).
> Alternativ kann man natürlich auch einen Scheduler eines Pre-Emtiven
> Multitaskings implementieren (mit Prioritätssteuerung). Das ist aber in
> einem Sicherheitsumfeld ein erheblicher Aufwand.

-> eben einen solchen Scheduler versuche ich damit zu implementieren. 
Das Konzept ist wohl durchdacht und auch bezgl. Safety von allen Seiten 
durchleuchtet, abgesegnet, zertifiziert...

Martin L. schrieb:
> Da Du schreibst, dass ihr auf den Cortex umgezogen seid: könnte es sein,
> dass der bisher verwendete Prozessor keine Interrupt-Priorisierung
> kannte? Dann sind Re-Entrant-Funktionen ein (einfacher, aber trotzdem
> nicht mehr zeitgemäßer) Weg, ein festes Zeitraster für die kritischen
> Anteile einzuhalten. Mit dem Cortex ist das jedenfalls auf keinen Fall
> notwendig (und wie gesagt, normativ praktisch nicht zu argumentieren,
> warum man das gemacht hat).

-> Bisher läuft dieser Scheduler auf einem ARM7TDMI, der zwar nativ 
keine (oder nur 2?) IR-Prios kennt. Allerdings ist vom Chiphersteller 
ein entsprechender IR-Controller beigefügt, durch den wir hier mit 
mehreren IR-Prios arbeiten können.

A. K. schrieb:
> Es gibt bei den Cortexen die Möglichkeit, in einem Interrupt einen
> zweiten Interrupt zu auszulösen, den man sinvollerweise mit niedrigster
> Priorität konfiguriert und deshalb keinem Hardware-Interrupt im Weg
> steht. Das ist der PendSV Interrupt.
>
> In einem RTOS wird der üblicherweise verwendet, wenn ein Task-Switch
> fällig wird. Aber möglicherweise löst er auch dein Problem.

-> Bei einem Cortex-M3 habe ich das auch genau so gelöst. Jedoch 
benötige ich mindestens 4 Prioritäts-Ebenen und das gibt mein Cortex-M0 
leider nicht her :(

Grundsätzlich: ich habe viel darüber nachgedacht, ob sich das 
Gesamtproblem (der preemptive Scheduler) auch anders lösen lässt. Und 
natürlich gibt es etliche Wege zum Ziel (z.B. könnte ich einfach das 
Prinzip des Taskswitch eines RTOS nachbauen und würde dann nur eine 
IR-Ebene benötigen. Dann muss ich aber einen vollständigen 
Kontextwechsel für die Taskswitches implementieren. Das ist für das, was 
mein "Scheduler" leisten soll, deutlich übers Ziel hinausgeschossen). 
Der Lösungweg, den ich mit meiner Ausgangsfrage anstrebe, scheint mir 
aber derjenige zu sein, der insgesamt am schlankesten ist. Alle 
"Unschönheiten", die das potentiell mit sich bringt, sind mir voll 
bewusst und wurden konzeptionell berücksichtigt.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Tom W. schrieb:
> Jedoch
> benötige ich mindestens 4 Prioritäts-Ebenen und das gibt mein Cortex-M0
> leider nicht her :(
 Und welche wären das ?
 Wo genau in deinem Schnipsel oder in deiner Frage sind diese 4 Ebenen
 definiert ?

Tom W. schrieb:
> -> Bei einem Cortex-M3 habe ich das auch genau so gelöst.
 Ja, sicher.

von Martin L. (maveric00)


Lesenswert?

Hallo,

Tom W. schrieb:
> Martin L. schrieb:
>> Nun, dann musst Du Dir sogar dringend etwas anderes überlegen, da ich
>> keine Sicherheitsnorm für E/E bzw. PE/E-Systeme kenne, die diese Art der
>> Code-Verhuzelung gut heißen würden. Das ihr das so schon seit Jahren
>> macht, zählt für künftige Projekte genau gar nichts (es gilt der Stand
>> der Technik zum Zeitpunkt des In-Verkehr-Bringens).
>
> -> Mein Argument war nicht, dass es ja deswegen gut sein muss, weil wir
> es seit Jahren so machen. Ich wollte damit lediglich sagen, dass es
> "betriebsbewährt" und auch von den relevanten Zertifizierern anstandslos
> so aktzeptiert wurde und auch nach wie vor wird - es ist durchaus Stand
> der Technik.

Kannst Du kurz etwas zur Branche und möglicherweise zum maximalen 
Integritäts-Level sagen, damit ich einschätzen kann, wie gut das System 
sein muss bzw. mich Schlau machen kann, wieso dies von den Zertifizieren 
akzeptiert wurde?

Hintergrund ist, dass Du mit der Re-Entrant-Methode (also eine 
sicherheitsrelevante Funktion, die gelegentlich nicht zu Ende läuft, da 
sie zu viel Zeit braucht und dann einfach von Vorne anfängt zu laufen) 
zumindest gegen einige Vorgaben der IEC 61508 und auch der ISO 26262 
verstößt, wenn Du nicht auf dem niedrigsten Integritäts-Level arbeitest 
(SIL1/ASIL A). Spontan fällt mir die vorgeschriebene 
Interrupt-Priorisierung, die (durch das nicht gesteuerte Teil-Abarbeiten 
der Mega-Funktion) dynamische Konfiguration der Architektur, das anlegen 
von dynamischen Elementen (Stack-Verbrauch, der nicht vorhersehbar ist) 
und weitere Anforderungen ein.

Und auch auf der niedrigsten Integritäts-Ebene sind Manipulationen des 
Stack-Pointers der Art, wie Du sie vorhast, auf Grund der 
Unübersichtlichkeit, der aufgeweichten Determiniertheit des 
Programmflusses und der Unvorhersehbarkeit des Stack-Verbrauchs und 
damit der wesentlich erschwerten Validierbarkeit extrem ungern gesehen 
(sprich: wenn es eine andere Methode gibt, die besser ist, wähle diese).

> -> eben einen solchen Scheduler versuche ich damit zu implementieren.
> Das Konzept ist wohl durchdacht und auch bezgl. Safety von allen Seiten
> durchleuchtet, abgesegnet, zertifiziert...

Dann habe ich Deine Methode wohl noch nicht ganz verstanden; in der 
Regel arbeitet der Scheduler nämlich mit einem eigenen Stack, die Tasks 
werden entsprechend der Priorität umgeschaltet. Dazu gehört dann auch 
das Setzen der Rücksprungadresse - allerdings im Rahmen eines 
vollständigen Context-Switches, also nicht einfach das feste Setzen 
einer Prozedur-Adresse, die dann durchläuft oder auch nicht. LR ist hier 
auch nicht das entscheidende (da in einem Interrupt hier eine der 
EXC_RETURN-Werte steht, und nicht die Adresse, wohin zurückgesprungen 
wird (die ist im Exception-Stack-Frame). In der Regel werden 
verschiedene Stacks für die Tasks verwaltet, so dass ein Context-Switch 
im Prinzip nur auf ein Ändern des Stackpointers herausläuft.

In dem Zusammenhang musst Du dann natürlich auch überlegen, wie nach 
Abschluss Deiner "Interrupt-Funktion" der normale Programmablauf wieder 
aufgenommen werden soll. Da ist es ebenfalls hilfreich, wenn Du echte 
Tasks verwendest (und den einen dann auf "wiederstarten" stellst).

> -> Bei einem Cortex-M3 habe ich das auch genau so gelöst. Jedoch
> benötige ich mindestens 4 Prioritäts-Ebenen und das gibt mein Cortex-M0
> leider nicht her :(

Welchen M0 hast Du? Laut ARMv6-M-Architektur-Manual gehört zu einem M0 
immer ein NVIC, der 32 auf mindestens 4 Ebenen (ST z.B. 192 Ebenen) 
priorisierte Interrupts verwalten kann.

> Grundsätzlich: ich habe viel darüber nachgedacht, ob sich das
> Gesamtproblem (der preemptive Scheduler) auch anders lösen lässt. Und
> natürlich gibt es etliche Wege zum Ziel (z.B. könnte ich einfach das
> Prinzip des Taskswitch eines RTOS nachbauen und würde dann nur eine
> IR-Ebene benötigen. Dann muss ich aber einen vollständigen
> Kontextwechsel für die Taskswitches implementieren. Das ist für das, was
> mein "Scheduler" leisten soll, deutlich übers Ziel hinausgeschossen).

Bei Entwicklung von sicherheitsrelevanter Software gilt die Grundregel: 
So klar wie möglich, was nicht unbedingt so klein oder so schlank wie 
möglich bedeutet. Wenn die Struktur des vollständigen Task-Switchers 
(deutlich) klarer ist, ist er auch leichter zu implementieren und zu 
validieren.

Am Schluss bleibt bei Deinem Konzept aber - egal wie Du es löst - das 
Problem, dass Deine Mega-Funktion nicht jedesmal vollständig durchläuft. 
Und das spricht nicht für ein gutes Konzept, denn entweder es werden 
sicherheitsrelevante Aufgaben nicht jedesmal ausgeführt (was in der 
Regel nicht vertretbar ist, besonders, wenn es nicht-deterministisch 
erfolgt), oder aber sicherheitsrelevante Aufgaben und 
nicht-sicherheitsrelevante Aufgaben sind vermischt, was auch den 
normativen Ansprüchen an eine Software-Architektur widersprechen würde. 
Im letzten Fall würde ein Aufteilen der Mega-Funktion auf verschiedene 
Tasks mehr als Sinn machen.

Schöne Grüße,
Martin

von Tom W. (nericoh)


Lesenswert?

Martin L. schrieb:
> Kannst Du kurz etwas zur Branche und möglicherweise zum maximalen
> Integritäts-Level sagen, damit ich einschätzen kann, wie gut das System
> sein muss bzw. mich Schlau machen kann, wieso dies von den Zertifizieren
> akzeptiert wurde?

Wir reden hier über Systeme bis zu SIL3 nach 61508 oder auch PLe nach 
13849.

Der entscheidende Punkt ist, dass ich das Laufzeitverhalten des Systems 
unter allen denkbaren und auch nocht nicht angedachten Rahmenbedingen 
effektiv überwachen kann, wenn ich einen "Scheduler" (das Wort ist fast 
zu hoch gegriffen für das, was das Ding letztlich tut) habe, der in 
einem festen Zeitraster aufgerufen wird und der die "Tasks" (de facto 
sind es stinknormale Funktionsaufrufe) startet. Bleibt eine "Task" 
hängen oder braucht (inklusive aller Unterbrechungen durch höherpriore 
"Tasks") länger als ihre maximal zulässige Laufzeit, wird der 
"Scheduler" das bei seinem nächsten (dann verschachtelten) Aufruf 
feststellen und eine entsprechende Reaktion einleiten.

Dieser Ansatz bietet mit einem sehr simplen Mechanismus annähernd die 
Mächtigkeit eines RTOS, kann aber gleichzeitig für alle 
Rahmenbedingungen garantieren, das das Gerät niemals seine Laufzeit 
verletzt (bzw. dies erkannt würde und sicher abgeschaltet würde).

Einzige Krux ist eben, dass man es (wie auch immer) bewerkstelligen 
muss, dass der "Scheduler" mit den mehrfach beschriebenen 
Rahmenbedinungen (festes zeitliches Raster, reentrant) aufgerufen wird.

von Tom W. (nericoh)


Lesenswert?

Martin L. schrieb:
> Damit können beide Routinen so gestaltet werden, dass sie nicht
> Re-Entrant sind, sondern ggf. durch einen pending Interrupt direkt
> wieder aufgerufen werden.
>
> Allerdings muss man natürlich die Aufrufhäufigkeiten so wählen, dass
> sichergestellt wird, dass die kritischen Elemente oft genug aufgerufen
> werden. Dabei ist zu beachten, dass zumindest einmal die maximal
> mögliche Lauflänge der Mega-Funktion verstreichen kann, bevor das
> Abtastraster wieder eingehalten wird.
>
> Ist dies nicht akzeptabel, dann muss die Mega-Funktion aufgeteilt
> werden, und über verschiedene Timer die einzelnen Funktionen mit
> unterschiedlich hoher Priorität aufgerufen werden (die Dont-Care-Anteile
> würden dann durch die kritischen Anteile unterbrochen werden können).
> Alternativ kann man natürlich auch einen Scheduler eines Pre-Emtiven
> Multitaskings implementieren (mit Prioritätssteuerung). Das ist aber in
> einem Sicherheitsumfeld ein erheblicher Aufwand.
>
> Da Du schreibst, dass ihr auf den Cortex umgezogen seid: könnte es sein,
> dass der bisher verwendete Prozessor keine Interrupt-Priorisierung
> kannte? Dann sind Re-Entrant-Funktionen ein (einfacher, aber trotzdem
> nicht mehr zeitgemäßer) Weg, ein festes Zeitraster für die kritischen
> Anteile einzuhalten. Mit dem Cortex ist das jedenfalls auf keinen Fall
> notwendig (und wie gesagt, normativ praktisch nicht zu argumentieren,
> warum man das gemacht hat).

Erstmal danke an Martin, dass er sich die Mühe eines konstruktiven 
Vorschlages gemacht hat!

Ich sehe an der Variante aber folgendes Problem: es ist nicht 
sichergestellt, dass die "wichtigen" Sachen immer innerhalb einer 
definierten Zykluszeit garantiert bearbeitet werden.

Beispielsweise habe ich einige Dinge, die alle 250us passieren müssen, 
aber nur sehr wenig Rechenzeit beanspruchen. Weiterhin gibt es Dinge, 
die alle 1ms passieren und etwas mehr Rechenzeit erfordern. Und es gibt 
Dinge, die nur alle 50ms passieren müssen, dafür potentiell auch einige 
ms Laufzeit beanspruchen.

Ich sehe weiterhin lediglich die Möglichkeiten, es auf die von mir 
angedachte Weise anzugehen oder eben 3 unterschiedliche Timer-IRs mit 
abgestufgen Prios zu verwenden.
Für genau dieses System funktioniert das - wenn ich aber ein anderes 
System baue, was andere Aufgaben mit anderen zeitlichen Anforderungen 
mit sich bringt, benötige ich u.U. weitere Timer, muss aber in jedem 
Fall die Timer umkonfigurieren.
Das ist der Grund, warum ich es so allgemeingültig wie möglich zu lösen 
versuche.

von Martin L. (maveric00)


Lesenswert?

Hallo,

der Scheduler selber muss nicht re-entrant sein, und Du benötigst auch 
nur einen Timer-Interrupt. Wenn Du dann noch zusätzlich (bei SIL 3 
ziemlich sicher) keine dynamische Task-Verwaltung hast, sondern nur eine 
fixe Anzahl von Tasks hast, und du genug Speicher für die Stacks der 
Tasks hast, dann kann der Scheduler auch sehr schlank ausgelegt sein (da 
er z.B. nicht gleich-priore Tasks verwalten muss):

Du legst entsprechend der Anzahl der Tasks Stacks im Speicher an, in die 
Du jeweils als Initialisierung einen Exception-Stackframe schreibst 
(siehe Referenzmanual zur Exception-Init). In diesem steht als PC die 
Adresse deiner Task-Prozedur. Dabei sollte die Reihenfolge der Stacks so 
angeordnet sein, dass die niedrigprioren nicht die hochprioren 
überschreiben können, wenn sie überlaufen.

Dann kanst Du Deine jeweiligen Task-Prozeduren schreiben, die die 
einzelnen Funktionen des Tasks aufrufen und am Ende eine Routine 
aufrufen, die den Task aus dem Scheduling nimmt (im Prinzip den Task als 
inaktiv markieren und den nächsten Task aktivieren; dazu kann der 
Task-Switcher-Teil des Schedulers per Exception aufgerufen werden).

Dann setzt Du einen Timer-Interrupt für Deinen Scheduler auf.

Dieser Scheduler macht nichts anderes, als den Stackpointer des 
aktuellen Tasks zu speichern und ihn durch den Stackpointer des ersten 
aktiven Tasks zu ersetzen. Zusätzlich püft er noch, ob ein Task wieder 
aktiviert werden muss (dazu wird einfach der Stack wieder initialisiert 
und der Task als aktiv markiert), und ob die Task-Laufzeit die zulässige 
Grenze überschritten hat.

In Deinem Beispiel könnte der Scheduler alle 250 µs laufen und stets den 
250µs-Task, jedes 4. Mal den 1ms und jedes 200. mal den 50ms-Task 
aktivieren. Die Tasks würden preemptiv von den jeweils kürzeren 
unterbrochen, ohne dass der Scheduler oder die Tasks re-entrant sein 
müssen.

Allerdings verstehe ich noch nicht, wie Du ohne zusätzlichen Watchdog 
einen DC von >99% einhalten willst (speziell mit einem M0, der kein 
ECC-Schutz des Speichers hat). Dieser muss ja seit Mai 2013 auch bei der 
13849 nachgewiesen werden. Es reicht ja, dass der Oszillator einen 
Schaden hat, und schon wird der Scheduler nicht/zu selten aufgerufen und 
hat ohne externe Zeitbasis auch keine Möglichkeit dies zu erkennen (die 
Timer werden ja auch vom Oszillator gespeist).

Nur mal so zum Vergleich: Im Automotive-Bereich wird ASIL D 
(vergleichbar SIL 3 und PLe) inzwischen mit Dual-Core 
Lockstep-Prozessoren, deren kompletter Speicher ECC abgesichert ist, und 
die über einen externen Watchdog (der auch Betriebsspannungen etc. 
überprüft) überwacht werden, dargestellt. Da mit den Bordmitteln eines 
M0 hinzukommen stelle ich mir recht schwierig vor...

Schöne Grüße,
Martin

von Tom W. (nericoh)


Lesenswert?

Martin L. schrieb:
> der Scheduler selber muss nicht re-entrant sein, und Du benötigst auch
> nur einen Timer-Interrupt. Wenn Du dann noch zusätzlich (bei SIL 3
> ziemlich sicher) keine dynamische Task-Verwaltung hast, sondern nur eine
> fixe Anzahl von Tasks hast, und du genug Speicher für die Stacks der
> Tasks hast, dann kann der Scheduler auch sehr schlank ausgelegt sein (da
> er z.B. nicht gleich-priore Tasks verwalten muss):

-> eine dynamische Tastverwaltung habe ich genau wie du sagst in keinem 
Fall; zur Compilezeit ist die Anzahl der "Tasks" bekannt.

Martin L. schrieb:
> In Deinem Beispiel könnte der Scheduler alle 250 µs laufen und stets den
> 250µs-Task, jedes 4. Mal den 1ms und jedes 200. mal den 50ms-Task
> aktivieren. Die Tasks würden preemptiv von den jeweils kürzeren
> unterbrochen, ohne dass der Scheduler oder die Tasks re-entrant sein
> müssen.

Das ist exakt was ich anstrebe. Du glaubst garnet, wie froh ich bin, 
dass zumindest einer begriffen hat, was hier passieren soll :)

Martin L. schrieb:
> Allerdings verstehe ich noch nicht, wie Du ohne zusätzlichen Watchdog
> einen DC von >99% einhalten willst (speziell mit einem M0, der kein
> ECC-Schutz des Speichers hat). Dieser muss ja seit Mai 2013 auch bei der
> 13849 nachgewiesen werden. Es reicht ja, dass der Oszillator einen
> Schaden hat, und schon wird der Scheduler nicht/zu selten aufgerufen und
> hat ohne externe Zeitbasis auch keine Möglichkeit dies zu erkennen (die
> Timer werden ja auch vom Oszillator gespeist).

-> Da habe ich mich auch etwas mißverständlich ausgedrückt: ein 
SIL3-System würde ich natürlich nicht mit nur einem uC bauen. Allerdings 
versuche ich mir ein möglichst generisches "Framework" zu schaffen, das 
u.U. auch einkanalig für non-safety-Anwendungen verwendet werden kann. 
Und da ist es einfach praktisch, wenn ich auch mit nur einem uC und im 
zweifel sogar ohne WD merke, wenn ich Laufzeitverletzungen habe.

Martin L. schrieb:
> Nur mal so zum Vergleich: Im Automotive-Bereich wird ASIL D
> (vergleichbar SIL 3 und PLe) inzwischen mit Dual-Core
> Lockstep-Prozessoren, deren kompletter Speicher ECC abgesichert ist, und
> die über einen externen Watchdog (der auch Betriebsspannungen etc.
> überprüft) überwacht werden, dargestellt. Da mit den Bordmitteln eines
> M0 hinzukommen stelle ich mir recht schwierig vor...

-> Die Lockstep-Dinger (TMS570 etc.) kenne ich. Im Industrie-Bereich 
setzen wir aber zur Zeit noch lieber auf zwei dedizierte Controller. Wie 
gesagt, ich versuche nicht, SIL3/PLe/... mit nur einem uC zu machen.

Martin L. schrieb:
> Du legst entsprechend der Anzahl der Tasks Stacks im Speicher an, in die
> Du jeweils als Initialisierung einen Exception-Stackframe schreibst
> (siehe Referenzmanual zur Exception-Init). In diesem steht als PC die
> Adresse deiner Task-Prozedur. Dabei sollte die Reihenfolge der Stacks so
> angeordnet sein, dass die niedrigprioren nicht die hochprioren
> überschreiben können, wenn sie überlaufen.
>
> Dann kanst Du Deine jeweiligen Task-Prozeduren schreiben, die die
> einzelnen Funktionen des Tasks aufrufen und am Ende eine Routine
> aufrufen, die den Task aus dem Scheduling nimmt (im Prinzip den Task als
> inaktiv markieren und den nächsten Task aktivieren; dazu kann der
> Task-Switcher-Teil des Schedulers per Exception aufgerufen werden).
>
> Dann setzt Du einen Timer-Interrupt für Deinen Scheduler auf.
>
> Dieser Scheduler macht nichts anderes, als den Stackpointer des
> aktuellen Tasks zu speichern und ihn durch den Stackpointer des ersten
> aktiven Tasks zu ersetzen. Zusätzlich püft er noch, ob ein Task wieder
> aktiviert werden muss (dazu wird einfach der Stack wieder initialisiert
> und der Task als aktiv markiert), und ob die Task-Laufzeit die zulässige
> Grenze überschritten hat.

-> Das muss ich mir jetzt erstmal in Ruhe zu Gemüte führen und 
nachvollziehen. Grundsätzlich klingt das sehr plausibel für mich - ob 
ich es mit meinen bescheidenen Kenntnissen so umsetzen kann, würde ich 
erstmal bezweifeln. Der von mir angedachte Weg schien mir da deutlich 
simpler - wobei ich ja auch den bisher noch nicht umgesetzt bekommen 
habe.

Siehst du denn bei dem von mir angedachten Ansatz einen expliziten 
Nachteil gegenüber deinem Vorschlag?

: Bearbeitet durch User
von Martin L. (maveric00)


Lesenswert?

Hallo,

naja, bei jeder Re-Entrant-Funktion kann es prinzipiell zu Problemen mit 
Stack-Überlauf kommen; wenn Dein Scheduler aber nach ein paar Mal 
abbricht, dann wohl nicht.

Das Link-Register enthält wie geschrieben nicht die Rücksprungadresse, 
sondern nur Informationen, welcher Stack verwendet werden soll und 
welcher Run-Mode (Privilegiert oder Normal); die Rücksprungadresse ist 
auf dem Stack an der Stelle, wo der PC abgelegt wurde. Um 
Stack-Manipulationen kommst Du also auch nicht herum.

Und das größte Problem liegt darin, zu bestimmen wohin die Routine, die 
Du nach Deinem Scheduler aufrufst, springen soll wenn sie fertig ist. 
Dazu müsstest Du im Prinzip den Stack noch weiter manipulieren und die 
Rücksprungadresse (die im eigentlichen Exception-Framle liegt) vor den 
Exception-Frame legen.

Alles im allem würde ich sagen, dass das Endergebnis nicht wesentlich 
einfacher zu implementieren sein wird als der Mini-Scheduler oben (die 
Arbeit steckt in beiden Fällen im Detail). Und dann würde ich den 
Scheduler bevorzugen, da die Struktur klarer und leichter zu warten und 
zu skalieren ist (was ist, wenn Du zwei höherpriore Tasks hast, oder 
mehr als 3 Tasks insgesamt?). Aber eventuell bin ich auch parteiisch ;-)

Schöne Grüße,
Martin

von Tom W. (nericoh)


Lesenswert?

Hallo,

nochmals vielen Dank dafür, dass du dich (im Gegensatz zu den meisten 
hier) ernsthaft mit meinem Anliegen auseinandergesetzt hast!

Martin L. schrieb:
> Alles im allem würde ich sagen, dass das Endergebnis nicht wesentlich
> einfacher zu implementieren sein wird als der Mini-Scheduler oben (die
> Arbeit steckt in beiden Fällen im Detail). Und dann würde ich den
> Scheduler bevorzugen, da die Struktur klarer und leichter zu warten und
> zu skalieren ist (was ist, wenn Du zwei höherpriore Tasks hast, oder
> mehr als 3 Tasks insgesamt?). Aber eventuell bin ich auch parteiisch ;-)

-> Was den Aufwand angeht, hast du vermutlich recht: unterm Strich nimmt 
es sich nix. Daraus, dass du "parteiisch" bist, schließe ich, dass 
du/ihr das genau so wie von dir beschrieben macht? Interessant - dann 
sind wir da doch garnicht soooo exotisch unterwegs :)

Ich konnte mein Anliegen jetzt doch sehr viel einfach lösen: ein Kollege 
hat mich darauf aufmerksam gemacht, dass ich bei den Cortexen 
prinzipiell alle Interrupts einfach per SW auf "pending" setzen kann. 
Dadurch kann ich das einfach wie in meinem Thread zu dem M3 beschrieben 
machen (nutze einfach einige Peripherie-IRs, die nicht anderweitig 
genutzt werden) und komme um die häßlichen Dinge (mehrere Stacks anlegen 
und umschalten etc) herum: der Compiler macht einfach alle nötige Arbeit 
für mich.

: Bearbeitet durch User
von busman (Gast)


Lesenswert?

Es macht meiner Meinung nach wirklich ab und zu Sinn einen Scheduler zu 
haben bei welchem alle Tasks auf dem Selben Stack laufen.

Falls jemand noch dasselbe Problem hat:

https://sites.google.com/site/sippeyfunlabs/embedded-system/how-to-run-re-entrant-task-scheduler-on-arm-cortex-m4

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.