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
intmain()
2
{
3
while(FOREVER)
4
{
5
/* tue irgendwas */
6
}
7
}
8
9
voidSysTick_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
voidmyFunction(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
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...
Ö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.
>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.
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
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
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ß,
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 ;)
>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.
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"
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 :)
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.
Ü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.
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?
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.
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.
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.
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.
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:
voidRuntimeControl()// 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
}elseif(!Execute_MEDIUM){
40
Execute_MEDIUM=TRUE;
41
}elseif(!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
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.
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.
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
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.
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.
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
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?
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
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.