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


von Tom W. (nericoh)


Lesenswert?

Hallo zusammen!

Da mein Ursprungsthread sich mittlerweile deutlich von der Ausgangsfrage 
entfernt hat, möchte ich meine Frage etwas konkretisiert erneut zur 
Diskussion stellen:

Es gilt auf einem Cortex-M0 eine bestimmte Funktion in einem definierten 
zeitlichen Raster auszuführen - und das selbst dann, wenn sie aktuell 
noch läuft (reentrant). Ja, mir ist bewusst, dass das etwas verrückt 
klingt und sich 99% aller Probleme anders besser lösen lassen. In diesem 
einen speziellen Fall möchte ich aber wirklich genau das tun, was ich 
hier beschreibe:
1
int main()
2
{
3
  SYSTEM_INIT(); 
4
  while(1){}
5
  return 0;
6
}
7
8
void SysTick_Handler()
9
{  
10
  NVIC_ClearPendingIRQ(SysTick_IRQn);
11
  SysTick->LOAD = SysTick_LOAD; 
12
  
13
  /* Hier soll dafür gesorgt werden, dass  
14
     'MyCrazyFunction' _nach dem Verlassen der
15
     ISR_ angesprungen wird. Geht das mit dem LR? 
16
     Ggf. noch SP entsprechend modifizieren?*/
17
}
18
19
void MyCrazyFunction(void)
20
{
21
  /* Hier wird ein ganz verrückter
22
     Algorithmus abgearbeitet, der sinnvoll
23
     nur dann funktioniert, wenn diese Funktion
24
     in einem festen Zeitraster aufgerufen wird -
25
     und das unabhängig davon, ob sie bereits 
26
     läuft oder nicht (reentrant) */
27
}

Lösungs_ansätze_ sind wie in dem Kommentar angedeutet das LR. Ich gehe 
davon aus, dass noch etwas mehr Aufwand nötig ist. Ich bin dankbar für 
jeden Hinweis, der in Richtung Ziel deutet :)

Tom

P.S. für alle Interessierten hier der Ursprungsthread, in dem 
ausführlich diskutiert wird, ob das Angestrebte überhaupt sinnvoll sein 
könnte - entsprechende Anmerkungen bitte ich in dem Ursprungsthread zu 
machen. Wäre schön, wenn wir in diesem Thread die eigentliche 
Fragestellung diskutieren könnten (was vermutlich utopisch ist, aber 
träumen ist ja erlaubt ;))

Beitrag "Cortex-M(0) - reentrant Funktion aus SysTick-Handler anspringen"

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Nehme z.B. 10 Timer und lasse die mit jeweils 1/10 der Systick 
Geschwindigkeit laufen. Bei jedem Timer Interrupt rufst Du 
MyCrazyFunction() auf.
Wenn nun die Funktion mehr als das 10-Fache der Zeit des Systick's 
braucht wird halt ein Aufruf "vergessen".

Problem gelöst.

Gleichzeitig wird die Funktion dennoch nicht bearbeitet, sondern je nach 
Priorität der Interrupts wird die eine unterbrochen und macht an der 
neuen weiter. Aber das ist Dein Bier.

von Tom W. (nericoh)


Lesenswert?

Das würde das Problem in der Tat lösen - dummerweise habe ich keine 10 
Timer.

Zielführend sind alle Ansätze, die das mit einem Timer und einem IR 
umsetzen können. Der von mir angedachte scheint mir noch der 
"einfachste" und generischte zu sein - ich benötige einfach nur ein paar 
Hilfestellungen bzgl Assembler und Cortex-M0.

von c-hater (Gast)


Lesenswert?

Tom W. schrieb:

> Es gilt auf einem Cortex-M0 eine bestimmte Funktion in einem definierten
> zeitlichen Raster auszuführen - und das selbst dann, wenn sie aktuell
> noch läuft (reentrant). Ja, mir ist bewusst, dass das etwas verrückt
> klingt

Das klingt nicht nur verrückt, das IST völlig verrückt. Denn das 
impliziert die Möglichkeit des Stack-Overflow.

Das wurde im ursprünglichen Thread auch bereits mehrfach angemerkt.

Wenn du zu doof bist, das und die Konsequenzen daraus zu begreifen, dann 
liegt das nicht am Thread oder den Postern dieses Threads...

von Tom W. (nericoh)


Lesenswert?

c-hater schrieb:
> Das klingt nicht nur verrückt, das IST völlig verrückt. Denn das
> impliziert die Möglichkeit des Stack-Overflow.
>
> Das wurde im ursprünglichen Thread auch bereits mehrfach angemerkt.
>
> Wenn du zu doof bist, das und die Konsequenzen daraus zu begreifen, dann
> liegt das nicht am Thread oder den Postern dieses Threads...

seufz...

also nochmal: dass das auf einen Stack-Overflow hinausläuft, wenn man 
das unkontrolliert eine beliebige Anzahl von Malen macht, ist doch 
völlig klar. Das passiert hier aber nicht, da es eine genau definierte 
Anzahl von Malen passiert und der Stack entsprechend auslegegt ist.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Tom W. schrieb:
> Das würde das Problem in der Tat lösen - dummerweise habe ich keine 10
> Timer.

Es gibt STM32 µC mit 16 Timer, also nehme so einen.

Problem wieder gelöst.

von Tom W. (nericoh)


Lesenswert?

Markus Müller schrieb:
> Gleichzeitig wird die Funktion dennoch nicht bearbeitet, sondern je nach
> Priorität der Interrupts wird die eine unterbrochen und macht an der
> neuen weiter. Aber das ist Dein Bier.

Ja stimmt. Es kann sinnvoll nur dann funktionieren, wenn die Funktion 
garantiert von einem erneuten IR unterbrochen und erneut aufgerufen 
wird. Also entweder Reentrant IR mit jeweils höherer Prio oder eben die 
von mir anskizzierte Variante. Weitere Möglichkeiten sind mir bisher 
ncht eingefallen.

Und nochmal: wenn jemand eine andere Möglichkeit kennt, einen kleinen 
preemptiven Scheduler mit Taskprios und impliziter Laufzeitüberwachung 
zu realisieren, ohne für jeden Taskwechsel auch den ganzen 
Kontextwechsel realisieren zu müssen, bin ich jederzeit offen für 
entsprechende Anregungen. In jedem anderen Fall bitte ich erneut 
darum, entweder zur eigentliche Frage etwas beizutragen oder einfach zu 
schweigen.

von Tom W. (nericoh)


Lesenswert?

Markus Müller schrieb:
> Es gibt STM32 µC mit 16 Timer, also nehme so einen.
>
> Problem wieder gelöst.

Du hast selber schon gesagt, warum das doch nicht zielführend ist. 
Abgesehen davon versuche ich einen Ansatz zu finden, der nicht nur für 
bestimmte Derivate des Cortex-M0 funktioniert, sondern für alle. Und 
eben darum möchte ich mich auf den SysTick beschränken, weil der nach 
meinem Verständnis genau für RTOSe gedacht ist.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Ja, wer sucht, der findet. Musst nur lange genug suchen, du findest 
garantiert eine Lösung.
Viel Spaß wünsche ich Dir dabei.

Hr. Maier ist ein Säufer. Er kann in 10 Sekunden ein Glas Bier leer 
trinken und das ununterbrochen.
Nun Frau Birgit ist eine gute Kellnerin und kann jede 8 Sekunden ein 
Bier einschenken, die Aufgabe von Hr. Maier ist es alle Biere leer zu 
trinken, egal welche Reihenfolge.
Da er aber zu langsam ist, stapeln sich die Biergläser vor seiner Nase. 
Nun hat er schon 0xFFFF Gläser da stehen und Birgit stellt noch eines 
oben drauf. Dann fällt der ganze Gläserhaufen in sich zusammen und 
begräbt Hr. Maier unter sich und er macht keinen Mucks mehr.

von Tom W. (nericoh)


Lesenswert?

Schöne Analogie :)

Vllt hilft das Bild ja, um euch begreiflich zu machen, warum es nicht 
ganz so böööööse ist, was ich vorhabe:

zum Einen schenke ich nicht dauerhaft schneller nach, als getrunken wird 
- dass das auf Dauer nicht gutgehen kann, dürfte wohl jedem klar sein.

Zum anderen geht es darum, dass Herr Maier ja zwischendurch mal dadurch 
abgelenkt ist, dass er sich eine Zigarette ansteckt. In dem Fall wird 
vorübergehend schneller nachgeschenkt, als weggetrunken wird. Daher 
können sich zeitweise eine definierte Anzahl Gläser (beispielsweise 4) 
"stapeln". Natürlich muss dann auch irgendwann eine Phase folgen, in der 
langsamer nachgeschenkt als weggetrunken wird.

von Markus M. (Firma: EleLa - www.elela.de) (mmvisual)


Lesenswert?

Mache eine Zähler-Variable im Systick Handler, die Incrementiert 
einfach.
Im Main-Loop, wenn die Zahl größer gleich 1 ist rufst Du 
MyCrazyFunction() auf und decrementiert diese Variable.
Nur mit dem exakten zeitlichen Raster wird das nur hin hauen wenn im 
Main-Loop nicht viel drin ist.

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


Lesenswert?

Tom W. schrieb:
> Zielführend sind alle Ansätze, die das mit einem Timer und einem IR
> umsetzen können. Der von mir angedachte scheint mir noch der
> "einfachste" und generischte zu sein - ich benötige einfach nur ein paar

 Zum zweiten Mal:
 Das von dir angedachte läuft im Endeffekt auf dasselbe hinaus, was dir
 schon von A.K. und mir vorgeschlagen wurde. Nur willst du es unbedingt
 als bessere "Lösung" hinstellen. Rumspielen mit Stack war und ist keine
 gute Lösung.
 Eine ISR mit niedrigster Priorität ist aber eine.
 Und wenn du in der MyDumbFunction() beim Eintritt einen Zähler erhöhst,
 beim Austritt (regulär) wieder erniedrigst, kannst du sogar beim
 Achten Bier zuviel die Kellnerin zurückschicken oder erschiessen.

von Tom W. (nericoh)


Lesenswert?

Leider reicht die Forderung einer möglichst leeren Mainloop nicht aus, 
um das exakte zeitliche Raster zu gewährleisten.

Ich hab dann mittlerweile eingesehen, dass das Problem wohl zu speziell 
ist, um hier jemanden zu finden, der sowohl willens als auch in der Lage 
ist, da weiterzuhelfen. Aber den Versuch wars zumindest wert :)

Danke trotzdem an die wenigen, die versucht haben, konstruktiv etwas 
beizutragen.

Tom

von Tom W. (nericoh)


Lesenswert?

Marc Vesely schrieb:
> Zum zweiten Mal:
>  Das von dir angedachte läuft im Endeffekt auf dasselbe hinaus, was dir
>  schon von A.K. und mir vorgeschlagen wurde. Nur willst du es unbedingt
>  als bessere "Lösung" hinstellen. Rumspielen mit Stack war und ist keine
>  gute Lösung.
>  Eine ISR mit niedrigster Priorität ist aber eine.
>  Und wenn du in der MyDumbFunction() beim Eintritt einen Zähler erhöhst,
>  beim Austritt (regulär) wieder erniedrigst, kannst du sogar beim
>  Achten Bier zuviel die Kellnerin zurückschicken oder erschiessen.

Vielleicht bin ich auch einfach zu beschränkt, um dir folgen zu können:

Um das mit dem Zähler so sinnvoll zu machen, muss ich doch überhaupt 
erstmal dahin kommen, dass die Funktion erneut aufgerufen werden kann. 
auch wenn sie schon läuft? Genau daran scheiterts doch aber bisher... 
Oder verstehe ich dich einfach falsch?

von holger (Gast)


Lesenswert?

>Um das mit dem Zähler so sinnvoll zu machen, muss ich doch überhaupt
>erstmal dahin kommen, dass die Funktion erneut aufgerufen werden kann.
>auch wenn sie schon läuft? Genau daran scheiterts doch aber bisher...

Leg ihre Adresse auf den Return Stack der ISR.
Dann geht das schon;)

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


Lesenswert?

Tom W. schrieb:
> Um das mit dem Zähler so sinnvoll zu machen, muss ich doch überhaupt
> erstmal dahin kommen, dass die Funktion erneut aufgerufen werden kann.
> auch wenn sie schon läuft? Genau daran scheiterts doch aber bisher...
> Oder verstehe ich dich einfach falsch?

 Was du machen willst ist folgendes:
 Die Adresse von deiner Funktion über der ReturnAdresse von SysTick
 schieben, so dass SysTick nach Abarbeitung dorthin springt, oder ?


 Was dir vorgeschlagen wird ist folgendes:
 Eine Routine mit höherer Priorität kann von einer Routine mit
 niedrigerer Priorität nicht unterbrochen werden. Umgekehrt geht das
 beim ARM aber immer.
 Also:
 SysTick wird immer bis zum Ende abgearbeitet und wenn es von dir so
 gewollt ist, wird gleich danach deine Funktion (auch als Interrupt)
 abgearbeitet. Deine Funktion kann aber von SysTick jederzeit und
 wiederholt unterbrochen werden. Wenn diese Funktion wirklich reentrant
 sein soll, muss es aber sichergestellt werden, dass wenn diese beim
 z.B. Zweiten Aufruf unterbrochen wurde, beim Dritten Aufruf keinen
 Mist mit Daten oder Tasks, die beim Zweitem Aufruf nicht abgearbeitet
 wurden, anstellt.
 Deswegen der Zähler in deiner Funktion.

von Tom W. (nericoh)


Lesenswert?

holger schrieb:
> Leg ihre Adresse auf den Return Stack der ISR.
> Dann geht das schon;)
-> genau darauf will ich hinaus :) Aber an deinem ";)" wird ja schon 
deutlich, für wie blödsinnig du das zu halten scheinst ;p

Hier nochmal aus dem anderen Thread ein Versuch von mir, euch meine 
Problemstellung so zu verdeutlichen, dass erkennbar wird, warum ich 
überhaupt über soetwas nachdenke:

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.

Die 50ms-Aufgaben können selbstverständlich von den 1ms-Aufgaben und 
diese wiederum von den 250us-Aufgaben unterbrochen werden. Jedoch muss 
in Summe sichergestellt sein, dass auch die 50ms-Geschichte inkl. aller 
zwischenzeitlich aufgetretenen Unterbrechungen nach spätestens 50ms 
abgearbeitet ist.

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.

Gibt es noch einen dritten Ansatz, den ich einfach nicht sehe? Eure 
teils sehr skeptischen Antworten legen das irgendwie nahe - ohne das 
bisher irgendjemand einen solchen Ansatz skizziert hätte. Oder habe ich 
es einfach nur übersehen?

von Tom W. (nericoh)


Lesenswert?

Marc Vesely schrieb:
> Was du machen willst ist folgendes:
>  Die Adresse von deiner Funktion über der ReturnAdresse von SysTick
>  schieben, so dass SysTick nach Abarbeitung dorthin springt, oder ?
>
>  Was dir vorgeschlagen wird ist folgendes:
>  Eine Routine mit höherer Priorität kann von einer Routine mit
>  niedrigerer Priorität nicht unterbrochen werden. Umgekehrt geht das
>  beim ARM aber immer.
>  Also:
>  SysTick wird immer bis zum Ende abgearbeitet und wenn es von dir so
>  gewollt ist, wird gleich danach deine Funktion (auch als Interrupt)
>  abgearbeitet. Deine Funktion kann aber von SysTick jederzeit und
>  wiederholt unterbrochen werden. Wenn diese Funktion wirklich reentrant
>  sein soll, muss es aber sichergestellt werden, dass wenn diese beim
>  z.B. Zweiten Aufruf unterbrochen wurde, beim Dritten Aufruf keinen
>  Mist mit Daten oder Tasks, die beim Zweitem Aufruf nicht abgearbeitet
>  wurden, anstellt.
>  Deswegen der Zähler in deiner Funktion.

Genau darauf will ich hinaus!
Dass die Reentrant-Funktion auch bei wiederholter Unterbrechung und 
erneutem Ansprung keinen Mist macht, kann ich sicherstellen - der Teil 
des Problems ist bereits gelöst :) Nur eben der andere Teil noch nicht.

von Tom W. (nericoh)


Lesenswert?

Marc Vesely schrieb:
> SysTick wird immer bis zum Ende abgearbeitet und wenn es von dir so
>  gewollt ist, wird gleich danach deine Funktion (auch als Interrupt)
>  abgearbeitet. Deine Funktion kann aber von SysTick jederzeit und
>  wiederholt unterbrochen werden. Wenn diese Funktion wirklich reentrant
>  sein soll, muss es aber sichergestellt werden, dass wenn diese beim
>  z.B. Zweiten Aufruf unterbrochen wurde, beim Dritten Aufruf keinen
>  Mist mit Daten oder Tasks, die beim Zweitem Aufruf nicht abgearbeitet
>  wurden, anstellt.
>  Deswegen der Zähler in deiner Funktion.

Wenn ich genau dafür eine Lösung hätte, wäre ich unendlich glücklich :) 
Bisher habe ich es auf Cortex-M3 wie in folgendem Thread beschrieben 
gelöst:

Beitrag "Reentrant IR auf Cortex-M3"

Bei dem jetzt vorliegenden M0 habe ich aber nicht ausreichend Extis, um 
das tun zu können. Nur darum komme ich ja auf so verrückte Ideen ;p

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


Lesenswert?

Tom W. schrieb:
> Dass die Reentrant-Funktion auch bei wiederholter Unterbrechung und
> erneutem Ansprung keinen Mist macht, kann ich sicherstellen - der Teil
> des Problems ist bereits gelöst :) Nur eben der andere Teil noch nicht.
 Welcher Teil ?

Tom W. schrieb:
> Bei dem jetzt vorliegenden M0 habe ich aber nicht ausreichend Extis, um
> das tun zu können. Nur darum komme ich ja auf so verrückte Ideen ;p

 Was für Extis jetzt ?
 So wie ich dich verstanden habe, ist für deine Funktion praktisch nur
 der genaue Zeitraster wichtig ?

von holger (Gast)


Lesenswert?

>> Leg ihre Adresse auf den Return Stack der ISR.
>> Dann geht das schon;)
>-> genau darauf will ich hinaus :) Aber an deinem ";)" wird ja schon
>deutlich, für wie blödsinnig du das zu halten scheinst ;p

Naja, hat der einen Return Stack? Wenn man sich FreeRTOS anschaut
wird das über den PendSV und SVC Handler gemacht.
Ich weiss nicht genau wie es gemacht wird, aber es wird
wohl ein Interrupt mit niedrigerer Priorität initiiert.
Beim verlassen der Systick ISR wird dieser aufgerufen
und kann vom Systick auch wieder unterbrochen werden.

Dann stellt sich die Frage was passiert wenn noch ein
Interrupt der gleichen Priorität gestartet wird. Wird dann
erst der ältere ausgeführt oder springt das Programm in den Wald?

Eine sichere Option wäre wohl nur, diesen zweiten Start
gar nicht erst zuzulassen bis der alte Kram abgearbeitet ist.

von (prx) A. K. (prx)


Lesenswert?

Tom W. schrieb:
> Leider reicht die Forderung einer möglichst leeren Mainloop nicht aus,
> um das exakte zeitliche Raster zu gewährleisten.

Wie ist die Anforderung an das zeitliche Raster, in den 3 Klassen?

In einem rein über Timer und priorisierte Interrupts gesteuertes 
Verfahren liegt die maximale Reaktionszeit des 50ms Timers bei der 
Ausführungszeit des 1ms Handlers plus der des 250µs Handlers. Das 
schafft auch jede sonst einigermassen leere Mainloop, daher ist dein 
obiger Satz nicht recht verständlich.

Kann es sein, dass du eigentlich auf ein top/bottom half handler Prinzip 
raus willst? Also die 1m/50ms Handler in zwei Teile zu splittest. Einen 
kurzen Teil mit hoher Echtzeitanforderung, der ziemlich sofort loslegen 
muss und innerhalb des Systick Handlers ausgeführt werden kann (top 
half), und einen zweiten Teil, der etwas Zeit hat und mehr Zeit braucht 
(bottom half).

von aaaaaaaaaa (Gast)


Lesenswert?


von (prx) A. K. (prx)


Lesenswert?

holger schrieb:
> Naja, hat der einen Return Stack? Wenn man sich FreeRTOS anschaut
> wird das über den PendSV und SVC Handler gemacht.
> Ich weiss nicht genau wie es gemacht wird, aber es wird
> wohl ein Interrupt mit niedrigerer Priorität initiiert.
> Beim verlassen der Systick ISR wird dieser aufgerufen
> und kann vom Systick auch wieder unterbrochen werden.

So ist es. Der PendSV Handler läuft mit niedrigster Priorität, weshalb 
er erst zum Zuge kommt, wenn alle möglicherweise verschachtelten Handler 
durch sind. Wird irgendwo festgestellt, dass ein Taskswitch nötig ist, 
wird einfach das PendSV Bit gesetzt.

Für ein RTOS bietet das den Vorteil, dass man weder in den Int-Handlern 
den Nesting-Level mitzählen muss um den letzten Handler-Return 
festzustellen. Noch muss man den Stackframe der Interrupt-Handler so 
einrichten, dass ein Taskswitch möglich ist - das ist nur im PendSV 
Handler nötig. Beides verkürzt Aufwand und Reaktionszeit von Handlern 
erheblich.

von (prx) A. K. (prx)


Lesenswert?

Tom W. schrieb:
> Jedoch muss
> in Summe sichergestellt sein, dass auch die 50ms-Geschichte inkl. aller
> zwischenzeitlich aufgetretenen Unterbrechungen nach spätestens 50ms
> abgearbeitet ist.

Dieser Satz lässt sich auf zwei Arten interpretieren:

(1) Die 50ms Aktivität (plus 1ms/250µs Kram zwischendrin) dauert 
garantiert nie so lange, dass der nächste 50ms Trigger schon ansteht 
bevor der Handler durch ist.

(2) Doch, das kann passieren und es muss als Teil der Lösung 
sichergestellt werden, dass der erste 50m Handler abgebrochen werden 
kann, oder eine Queue ist akzeptabel.

Was stimmt?

von (prx) A. K. (prx)


Lesenswert?

aaaaaaaaaa schrieb:
> 
http://coactionos.com/embedded%20design%20tips/2013/10/09/Tips-Context-Switching-on-the-Cortex-M3/

Yep. Mit den Cortex M lässt sich mit geringem Aufwand ein minimaler 
RTOS-Kernel bauen, der gegenüber anderen Verfahren nur einen sehr 
geringen Overhead einbringt. Scheint mir hier durchaus einer Betrachtung 
würdig.

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.