Forum: Mikrocontroller und Digitale Elektronik Reentrant IR auf Cortex-M3


von Tom W. (nericoh)


Lesenswert?

Hallo zusammen !

Ich beschäftige mich zur Zeit mit dem Cortex-M3 und möchte in diesem 
Zusammenhang einen bestimmten Interrupt (z.B. SysTick) mehrfach 
ineinander verschachteln können ("reentrant ISR").

Auf den älteren ARM7TDMI war das mit etwas ASM-Getrickste möglich (siehe 
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka11008.html).

Leider konnte ich im Infocenter von ARM nichts Vergleichbares für den 
CM3  finden. Auch sonst fördert Google nur diverse Dokumente zu Tage, 
aus denen hervorgeht, dass der CM3 ohne weiteres keine reentrant IRs 
beherrscht (wie ja der ARM7TDMI auch schon nicht). Es werden einige 
Ansätze beschrieben, wie ähnlich dem oben genannten Link mit 
Kontextsicherung, Wechsel des Priveligierungsmodus etc. derartiges 
erreicht werden kann. Das geht aber alles nicht über die eher 
theoretische Erörterung hinaus. Daher wollte ich das Thema hier mal zur 
Diskussion stellen, da sich hier ja geballtes KnowHow sammelt und das 
Thema nach meinem Kenntnisstand hier noch nicht besprochen wurde.

Um also mal konkrete Fragenz zu formulieren:

- hat jemand hier Kenntnis eines Dokuments, das ähnlich wie das oben 
genannte diese Problematik für den CM3 behandelt?

- traut sich jemand zu, den im obigen Dokument beschrieben Mechanismus 
für dem CM3 umzusetzen?

Da ich eher im Bereich der Firmware-Architektur etc. heimisch bin, tue 
ich mich mit dieses wirklich hardwarenahen Sachen sehr schwer und hoffe 
auf eure immer wieder bewiesene Expertise :)

MfG,
Tom

von Schnurpsel (Gast)


Lesenswert?

Hat das für Dich eine praktische Relevanz oder machst Du Dir
nur gern einen Knoten in's Hirn?

Die Tatsache, dass man nicht viel Material findet, ist ein gutes Indiz 
dafür, dass man das vielleicht eher anders lösen sollte.

>Auch sonst fördert Google nur diverse Dokumente zu Tage,
>aus denen hervorgeht, dass der CM3 ohne weiteres keine reentrant IRs

Link?
Ich bezweifle, dass das stimmt.
CM3 macht im Interrupt hardwaregesteuert context save und restore.

von Schnurpsel (Gast)


Lesenswert?


von Peter D. (peda)


Lesenswert?

Beim 8051 konnte man den Prioritätslevel im Interrupt hochsetzen. Der 
Interrupt konnte sich dann selber unterbrechen. Bei 4 Interruptlevel 
also bis zu 3 mal.
Und da ja jeder Interrupt seine Register auf dem Stack sichert, war das 
natürlich auch reentrant.

Ob das einen praktischen Nutzen hat, steht freilich auf einem anderen 
Blatt.
In der Regel gibt es für entsprechende Aufgaben auch eine saubere 
Lösung.
Es war also mehr eine Spielerei und um die Interruptlogik zu verstehen.

von Dr. Sommer (Gast)


Lesenswert?

Ein Interrupt der schon "active" ist kann nicht "nocheinmal" 
gleichzeitig "active" werden. Der Status eines Interrupt wird über das 
"Interrupt Active Bit Register" (NVIC_IABR*) angezeigt, ist aber nicht 
modifizierbar... Du könntest höchstens über Stackmanipulationen 
(ReturnAddress) dem Core vortäuschen aus dem Interrupt herauszuspringen, 
ohne wirklich zur Originalstelle zu springen sondern im ISR-Code zu 
bleiben... Aber dann ist man nicht mehr im "Handler"-Mode, BASEPRI ist 
gesetzt, und der Rücksprung muss dann auch manuell erfolgen, aber ein 2. 
Interrupt des selben Typs könnte dann erfolgen.
Das ganze hört sich eher nach einer vermurksten Planung der 
Programmstruktur an, denn eine ISR sollte nicht so lange dauern, bis der 
nächste Interrupt des jeweiligen Typs auftritt. Längere Aufgaben macht 
man bekanntlich in der main().

von Kalle (Gast)


Lesenswert?

Tom W. schrieb:
> Ich beschäftige mich zur Zeit mit dem Cortex-M3 und möchte in diesem
> Zusammenhang einen bestimmten Interrupt (z.B. SysTick) mehrfach
> ineinander verschachteln können ("reentrant ISR").

Angenommen ich verstehe es richtig. Wenn deine ISR so lange braucht, 
dann liegt da dein Hauptproblem!

Angenommen du willst nested interrupts, dann funktioniert das natürlich 
auch auf dem CM3 und das auch viel einfacher!

> Auf den älteren ARM7TDMI war das mit etwas ASM-Getrickste möglich (siehe
> http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka11008.html).

Wohl eher Gefrickel ;-).

> Leider konnte ich im Infocenter von ARM nichts Vergleichbares für den
> CM3  finden. Auch sonst fördert Google nur diverse Dokumente zu Tage,
> aus denen hervorgeht, dass der CM3 ohne weiteres keine reentrant IRs
> beherrscht (wie ja der ARM7TDMI auch schon nicht). Es werden einige
> Ansätze beschrieben, wie ähnlich dem oben genannten Link mit
> Kontextsicherung, Wechsel des Priveligierungsmodus etc. derartiges
> erreicht werden kann. Das geht aber alles nicht über die eher

Literatur: The definitiv guide to the arm cortex-m3:
Chapter 2: Nested interrupt support


---

von Jim M. (turboj)


Lesenswert?

Tom W. schrieb:
> einen bestimmten Interrupt (z.B. SysTick) mehrfach
> ineinander verschachteln können ("reentrant ISR").

Das geht beim Cortex M3 wesentlich eleganter: Man setzt in einem 
(kurzen) hoch priorisierten Exception Handler einfach einen niedriger 
priorisierten via NVIC auf "pending" - der wird dann beim return 
automagisch via tail-chaining ausgeführt. Der Cortex M3 hat dafür sogar 
extra einen Vektor spendiert bekommen: PendSV. Man kann aber auch jeden 
anderen unbenutzen Exception Verktor benutzen.

Der höher priorisierte Vektor kann den niedrig priorisierten Handler 
unterbrechen.

ps. Zu bachten ist, dass beim Cortex M3 die niedirge Nummer höhere 
Priorität hat: Null (0) -default - ist die höchste Priorität (Faults 
haben negative Priorität), die niedrigste ist abhängig vom konkreten 
Chip: 7, 15, 31,...

von Tom W. (nericoh)


Lesenswert?

Danke erstmal für alle eure Antworten.
Ich möchte gerne einen Umstand klarstellen: es hat schon seine 
Richtigkeit, dass die ISR (potentiell) solange läuft, dass sie sich 
selber wieder unterbrechen würde. Mir ist bewusst, dass das etwas 
exotisch klingt, aber ich tue das sehr bewusst so, da sich so recht 
simpel ein einfacher Taskscheduler mit Priorisierung und impliziter 
Laufzeitüberwachung realisieren lässt.

Der Ansatz von turboj klingt sehr einleuchtend, ich hoffe ich komme noch 
im Laufe des Tages dazu, das auszuprobieren.

MfG,
Tom

von Walter (Gast)


Lesenswert?

Tom W. schrieb:
> exotisch klingt, aber ich tue das sehr bewusst so, da sich so recht
> simpel ein einfacher Taskscheduler mit Priorisierung und impliziter
> Laufzeitüberwachung realisieren lässt.

Kannst du das näher erläutern? Deine Tasks (mit eigenem Stack) laufen in 
der ISR (systick) und der core wird nicht benutzt?


---

von Tom W. (nericoh)


Lesenswert?

Ganz vereinfacht gesagt funktioniert das so:
Timer-IR ruft zyklisch (z.B. alle 250uS) den Scheduler auf. Bei jedem 
Aufruf zählt der Scheduler eine Variable hoch. Weiterhin überprüft er 
anhand des Werts der Variable, welche Task zu starten ist (z.B. muss bei 
Task_1ms Scheduler-Variable mod 4 gleich 0 sein). Die Task wird dann 
verriegelt (im Wesentlichen wird hier ein Flag gesetzt, das anzeigt, 
dass die Task läuft) und gestartet. Nach Abarbeitung wird die Task 
wieder entriegelt, vom Scheduler zurück in den IR-Handler gesprungen und 
dieser beendet.

Die 1mS-Task muss währenddessen von dem Timer-IR unterbrochen werden 
können. Da die 1mS-Task aber im Kontext der ISR läuft, beudetet das, 
dass der Timer-IR sich selbst unterbrechen können muss.

Die Laufzeitüberwachung steckt da implizit mit drin, da der Scheduler 
nach 4 weiteren Aufrufen erneut die 1ms-Task starten will, diese jedoch 
verriegelt vorfindet, was bedeutet, dass sie nicht innerhalb von 1ms 
abgearbeitet werden konnte. Das System würde dann in den Fehlerzustand 
gehen.

Da es eine genau definierte Anzahl an Tasks gibt (beispielsweise 4), ist 
genau vorhersagbar, wieviele Verschachtelungsebenen sich maximal ergeben 
können. Dies ist natürlich bei der Festlegung der Stackgröße zu 
berücksichtigen.

Weitere IRs gibt es in dem System nicht (bzw. nur solche, die direkt nen 
DMA triggern), weswegen das System so in seinem zeitlichen Verhalten 
angenehm vorhersagbar ist. Bisher habe ich nicht so recht einsehen 
können, was genau gegen ein solches Vorgehen sprechen soll, lasse mich 
da aber gerne eines Besseren belehren.

Tom

von Tom W. (nericoh)


Lesenswert?

Wenn ich turboj richtig verstehe, müsste ich für mein Vorhaben 
entweder mehrere solcher niederprioren IRs (alle mit niedriger Prio 
als Timer-IR, aber untereinander durchaus mit unterschiedlicher Prio) 
vorsehen oder aber ich definiere nur einen weiteren IR (beispielsweise 
wie vorgeschlagen den PendSV), habe dann aber das Problem lediglich von 
der Timer-ISR in die neue ISR verlagert.

Bisher habe ich bei dem CM3 noch keine Möglichkeit gefunden, mehrere 
Software-IRs mit unterschiedlicher Prio zu nutzen - da ich diesen Ansatz 
aber gerade erst aufgegriffen habe, muss ich hierzu wahrscheinlich das 
TRM des CM3 noch ein wenig weiter studieren. Hinweise sind natürlich 
trotzdem sehr willkommen ;)

Tom

von (prx) A. K. (prx)


Lesenswert?

Klingt nach einer umständlichen Art, ein einfaches Problem zu lösen. Der 
Timer-Interrupt gibt die Tasks per Flag frei, die Mainloop führt sie 
abhängig von den Flags aus.

von Tom W. (nericoh)


Lesenswert?

Für das reine Scheduling ist das so auch ausreichend, keine Frage. Was 
aber mache ich, wenn in einer der Tasks Unsinn gemacht wird und diese 
Task dadurch länger als ihre Zykluszeit läuft oder sogar in einer 
Endlosschleife hängen bleibt?

Mit meiner Methode stelle ich das fest und bin in dem Moment noch 
handlungsfähig, kann also mein System geordnet herunterfahren etc.

von (prx) A. K. (prx)


Lesenswert?

Tom W. schrieb:
> Für das reine Scheduling ist das so auch ausreichend, keine Frage. Was
> aber mache ich, wenn in einer der Tasks Unsinn gemacht wird und diese
> Task dadurch länger als ihre Zykluszeit läuft oder sogar in einer
> Endlosschleife hängen bleibt?

Ähnlich wie bei dir.

Prinzip, nach Bedarf zu variieren und zu optimieren: Nimm für die Flags 
Zähler. In der ISR werden Zähler die nicht 0 sind inkrementiert, 
Überlauf ist Timeout. Wenn eine Task frisch aktiviert werden soll, den 
entsprechenden Zähler auf 1 setzen. Eine Task löscht als letzte Aktion 
den Zähler.

Das kannst du sogar alternativ oder zusätzlich in Hardware giessen: Da 
die Mainloop spätestens nach soundsoviel Millisekunden wieder zum Zuge 
kommen müsste, wenn alles richtig läuft, passt genau an diese Stelle ein 
Watchdog-Clear prima hinein.

von Walter (Gast)


Lesenswert?

Tom W. schrieb:
> Für das reine Scheduling ist das so auch ausreichend, keine Frage. Was
> aber mache ich, wenn in einer der Tasks Unsinn gemacht wird und diese
> Task dadurch länger als ihre Zykluszeit läuft oder sogar in einer
> Endlosschleife hängen bleibt?

Ob dein Task im Main-Kontext oder Process-Kontext Unsinn macht ist 
eigentlich egal, im worst cast hängt das System und dann muss der 
Watchdog aus der Hütte kommen und für Ordnung sorgen ;-).

> Mit meiner Methode stelle ich das fest und bin in dem Moment noch
> handlungsfähig, kann also mein System geordnet herunterfahren etc.

Naja, ob das nun im Systick oder Watchdog passiert ist eigentlich egal.

Wie hier schon gesagt wurde, benutze ein paar Flags in Process-Kontext 
und steuer die Flags über deinen Systick. Dann hast du sogar getrennte 
Stacks für Process- und den Main-Kontext für lau.
Wenn eine Task fertig ist setze ein paar Flags und benutze den 
WFI-Befehl, der wartet bis zum nächsten Interrupt und dann fang wieder 
an mit dem nächsten Task.


---

von Dr. Sommer (Gast)


Lesenswert?

Warum so kompliziert? Der ARMv7 ist doch für "normales" Multitasking 
ausgelegt, und das ist gar nicht so schwer:
Man mache in der main() ein:
1
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
2
while(1) __WFI ();
Und stecke in die ISR vom PendSV-Interrupt einen Kontextwechsel in den 
1. Task, d.h. alle register PUSHen, stackpointer ändern, alle Register 
POPen. Dabei wird auf den Stack des alten Tasks die Rücksprungadresse 
des Interrupts gesichert, und nach Ende der ISR genau umgekehrt an die 
Rücksprungadresse des neuen Tasks gesprungen.
Das gleiche dann in den Timer-Interrupt den man alle 1ms ausführt, der 
dann immer in den nächsten Task switcht. Wenn ein Task mit seiner 
reguären Aufgabe fertig ist, kann er freiwillig per
1
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
 den Switch starten. Wenn das System bemerkt dass ein Task das gar nicht 
mehr tut und immer per Timer "hart" unterbrochen werden muss, kann es 
seinen Status resetten (Stack leeren, an den Anfang springen). Zu Beginn 
muss man noch die Stacks der Tasks so initialisieren, dass als 
"Rücksprungadresse" die zum Task gehörige Funktion drinsteht. Das ganze 
kann man dann noch priorisieren indem man in verschiedenen Interrupts zu 
verschiedenen Tasks wechselt und den Interrupts verschiedene Prioritäten 
gibt... Zurückfallen in untere Prioritäten dann wieder per PendSV. 
Kleines Beispiel für einen Kontextwechsel:
1
void __attribute__((naked,flatten)) PendSVHandler () {
2
  __disable_irq ();
3
  // Save registers
4
  asm volatile ("push {r4, r5, r6, r7, r8, r9, r10, r11, lr}" ::: "memory");
5
  // stack of current Task
6
  void* currentTaskStack = ...
7
  // Store Stack
8
  asm volatile ("str sp, [%0]" :: "r" currentTaskStack : "memory");
9
  void* newStack = ....// Get new tasks stack
10
  asm volatile ("ldr sp, [%0]" :: "r" newStack : "sp");
11
  // Restore registers
12
  asm volatile ("pop {r4, r5, r6, r7, r8, r9, r10, r11, lr}" ::: "memory");
13
  __enable_irq ();
14
  asm volatile ("bx lr");
15
}

von Tom W. (nericoh)


Lesenswert?

Ich habe jetzt folgenden Ansatz mal etwas weiter verfolgt:

- Timer-IR hat höchste PRIO und und triggert jeweils Software-IRs, aus 
denen heraus dann der Scheduler aufgerufen wird

folgendermaßen sieht das bisher für dem CM3 aus:

//initialisierung des IR-Moduls
void IRHandler_onInit(void)
{
  //set prio for TIM6 to 10 (0 is highest)
  NVIC->IP[TIM6_IRQn] = 10;

  //enable TIM6 ir (Nr. 54 is the 22nd bit in the 2nd ISER register)
  NVIC->ISER[1] |= NVIC_ISER_SETENA_22;

  //set prios for SWIs
  NVIC->IP[EXTI0_IRQn] = 20;
  NVIC->IP[EXTI1_IRQn] = 21;
  NVIC->IP[EXTI2_IRQn] = 22;
  NVIC->IP[EXTI3_IRQn] = 23;
  NVIC->IP[EXTI4_IRQn] = 24;

  //enable exti0-4 irs
  NVIC->ISER[0] |= ( NVIC_ISER_SETENA_10 | NVIC_ISER_SETENA_9 |
                      NVIC_ISER_SETENA_8 | NVIC_ISER_SETENA_7 |
                      NVIC_ISER_SETENA_6);

  EXTI->IMR |= (EXTI_IMR_MR4 | EXTI_IMR_MR3 | EXTI_IMR_MR2 |
                EXTI_IMR_MR1 | EXTI_IMR_MR0);
}


void TIM6_IRQHandler()
{
  //clear pending ir
  TIM6->SR &= ~TIM_SR_UIF;

  //hier prio erhoehen
  IRPrio++;

  switch(IRPrio)
  {
    case 1:
      //IRPrio++;
      //trigger exti4
      EXTI->SWIER |= EXTI_SWIER_SWIER4;
    break;

    case 2:
      //IRPrio++;
      //trigger exti3
      EXTI->SWIER |= EXTI_SWIER_SWIER3;
    break;

    case 3:
      //IRPrio++;
      //trigger exti2
      EXTI->SWIER |= EXTI_SWIER_SWIER2;
    break;

    case 4:
      //IRPrio++;
      //trigger exti1
      EXTI->SWIER |= EXTI_SWIER_SWIER1;
    break;

    case 5:
      //IRPrio++;
      //trigger exti0
      EXTI->SWIER |= EXTI_SWIER_SWIER0;
    break;

    default:

    break;
  }


}

void EXTI0_IRQHandler()
{
  EXTI->PR |= EXTI_PR_PR0;   //clear pending ir
  //Timer_onInterrupt();     //Timer triggern
  ISR_cbScheduler();
  IRPrio--;
}


void EXTI1_IRQHandler()
{
  //LED_STAT4_iON;
  EXTI->PR |= EXTI_PR_PR1; //clear pending ir
  //Timer_onInterrupt();    //Timer triggern
  ISR_cbScheduler();
  IRPrio--;
}

void EXTI2_IRQHandler()
{
  //LED_STAT4_iON;
  EXTI->PR |= EXTI_PR_PR2; //clear pending ir
  ISR_cbScheduler();
  IRPrio--;
}

void EXTI3_IRQHandler()
{
  LED_STAT3_iON;
  EXTI->PR |= EXTI_PR_PR3; //clear pending ir
  ISR_cbScheduler();
  IRPrio--;
  LED_STAT3_iOFF;
}

void EXTI4_IRQHandler()
{
  LED_STAT4_iON;
  EXTI->PR |= EXTI_PR_PR4; //clear pending ir
  ISR_cbScheduler();
  IRPrio--;
  LED_STAT4_iOFF;
}

Nach meinem Verständnis würde so der Scheduler zunächst immer über den 
EXTI4-Händler angesprungen. Erst in dem Fall, dass eine der vom 
Scheduler ausgeführten Tasks länger läuft als die Timer-Zykluszeit (in 
meinem Fall 250 uS) und somit der Scheduler erneut aufgerufen werden 
müsste (reentrant), würde die Timer-ISR den EXTI3 triggern. Dieser würde 
dann den erneuten Aufruf des Schedulers vornehmen. Soweit die Theorie.

Um meine Annahme zu Überprüfen, habe ich mal ganz stumpf jeweils eine 
LED am Anfang der EXTI-ISRs gesetzt und am Ende zurückgesetzt. Weiterhin 
habe ich eine Task implementiert, die ~500ms läuft und dadurch mehrfach 
vom Timer-IR unterbrochen werden müsste. In der Folge müssten 
zwischenzeitlich sowohl LED_STAT4 als auch LED_STAT3 an sein. 
Tatsächlich aber ist immer nur genau eine von beiden an. Sieht hier 
jemand meinen Denk- oder Implementierungsfehler?

Tom

von Tom W. (nericoh)


Lesenswert?

Die Frage, warum die EXTIs sich so nicht selbst unterbrechen konnte ich 
klären: in meinem Fall habe ich einen STM32 eingesetzt. Bei der von mir 
verwendeten Varianten hat der VIC nur 4 Prio-Bits, weswegen die von mir 
vergebenen Prios so nicht funktionierten.

Habe das gefixed, jetzt läuft alles und ich habe wieder wie vorher 
meinen schönen schlanken Scheduler mit impliziter Laufzeitüberwachung 
und Priorisierung.

Ich danke allen für ihre Beiträge !

von Martin (Gast)


Lesenswert?

> Bei der von mir verwendeten Varianten hat der VIC nur 4 Prio-Bits,
> weswegen die von mir vergebenen Prios so nicht funktionierten.
Und zwar das hier:

>  //set prios for SWIs
>  NVIC->IP[EXTI0_IRQn] = 20;
>  NVIC->IP[EXTI1_IRQn] = 21;
>  NVIC->IP[EXTI2_IRQn] = 22;
>  NVIC->IP[EXTI3_IRQn] = 23;
>  NVIC->IP[EXTI4_IRQn] = 24;

durch das ersetzt:
  NVIC_SetPriority(EXTI0_IRQn, 1); /* higher prio than EXTI1 */
  NVIC_SetPriority(EXTI1_IRQn, 2); /* lower prio than EXTI0 => cannot 
interrupt EXTI0 */
  [...]
usw.

Nur der Vollständigkeit halber, und weil ich auf das gleiche Problem 
gestoßen bin. :-)

@Nericoh: Ich frage mich tatsächlich, ob wir beiden die einzigen sind, 
die kein OS einsetzen, aber dennoch unterbrechbare Tasks haben wollen.
Ich habe deinen Parallelthread (Coretex M0) gelesen und mich und meine 
Situation ganz genau wieder gefunden. Danke für deine Ausführungen.

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.