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
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.
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.
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().
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 ---
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,...
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
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? ---
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
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
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.
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.
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.
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. ---
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 | }
|
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
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 !
> 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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.