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
intmain()
2
{
3
SYSTEM_INIT();
4
while(1){}
5
return0;
6
}
7
8
voidSysTick_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
voidMyCrazyFunction(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"
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.
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.
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...
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.
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.
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.
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.
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.
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.
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.
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.
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
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?
>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;)
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.
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?
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.
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
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 ?
>> 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.
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).
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.
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?