Forum: Mikrocontroller und Digitale Elektronik [STM32] Inaktiver Interrupt feuert?


von lars (Gast)


Angehängte Dateien:

Lesenswert?

Ich habe die externen Interrupts EXTI1 und EXTI0 für zwei Signale M und 
S so definiert, dass beide IR auf jeder Flanke feuern. EXTI1 hat eine 
höhere Prio als EXTI0.

M ist so etwas wie der Master von S, indem der EXTI1-Handler zu 
bestimmten Zeitpunkten EXTI0 aktiviert oder deaktiviert. Der 
EXTI0-Handler hat keine Kontrollfunktion.
1
void EXTI1_IRQHandler(void)  // M, high prio
2
{
3
  __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_1);
4
5
  TRIG_M_ON;
6
  if (M) {
7
    NVIC_EnableIRQ(EXTI0_IRQn);
8
    do_work_3();
9
  } else {
10
    NVIC_DisableIRQ(EXTI0_IRQn);
11
  }
12
  TRIG_M_OFF;
13
}
14
15
void EXTI0_IRQHandler(void)  // S, low prio
16
{
17
  __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
18
19
  TRIG_S_ON;
20
  if (S)
21
    do_work_1();
22
  else
23
    do_work_2();
24
  TRIG_S_OFF;
25
}

Die Makros TRIG_* schalten zwei weitere Signale high or low, um die 
Ausführung der Handler zu verbildlichen (hS, hM).

Wie man nun im angehängten Signalverlauf sieht, wird an (1) EXTI0 vom 
EXTI1-Handler aktiviert. EXTI1 soll dann feuern, wenn Signal S wechselt, 
tatsächlich sieht man aber zwei Durchläufe (2) und (3) des 
EXTI0-Handlers. (2) befindet sich sogar noch vor dem Wechsel von S, als 
ob dieser Lauf an (4) getriggert und bis (2) pending gewesen wäre - aber 
er war ja deaktiviert. (5) deaktiviert EXTI0 wieder.

Woher stammt dieser zweite Aufruf (2) des EXTI0-Handlers? Ist mein 
Aufruf zum Deaktivieren des EXTI0-IRs falsch?

von lars (Gast)


Lesenswert?

Na super, ich habe in der Beschreibung die Punkte 4 und 5 verwechselt. 
m(

von Stefan F. (Gast)


Lesenswert?

Kontrolliere mal ob deine Initialisierung einen Schreibzugriff auf das 
EXTI->PR Register macht.

Ich hatte beim STM32F3 festgestellt, dass mal dieses Regsiter während 
der Initialisierung beschrieben muss, um den ersten falschen Interrupt 
zu verhindern.

von Harry L. (mysth)


Lesenswert?

EXTI ist ein Sammel-Interrupt, und das Erste, was man im IRQ-Handler tun 
sollte, ist festzustellen, woher der IRQ kommt, bzw. was der eigentliche 
Auslöser war.
Davon ist in deinem Code aber nichts zu sehen.

von lars (Gast)


Lesenswert?

Harry L. schrieb:
> EXTI ist ein Sammel-Interrupt, und das Erste, was man im IRQ-Handler tun
> sollte, ist festzustellen, woher der IRQ kommt, bzw. was der eigentliche
> Auslöser war.
> Davon ist in deinem Code aber nichts zu sehen.

Es gibt nur eine Quelle, also ist auch keine Prüfung nötig.

von lars (Gast)


Lesenswert?

Stefanus F. schrieb:
> Kontrolliere mal ob deine Initialisierung einen Schreibzugriff auf das
> EXTI->PR Register macht.

Also immer
1
  NVIC_EnableIRQ(EXTI0_IRQn);
2
  NVIC_ClearPendingIRQ(EXTI0_IRQn);

schreiben?

Ich war mir da auch nicht sicher, ob das Aktivieren des IR diesen schon 
feuert.

Ich habe es eingebaut, aber zum Überprüfen muss ich erst wieder die 
richtige Stelle im Signal-Mitschnitt finden ...

von Stefan F. (Gast)


Lesenswert?

Das EXTI-PR Register befindet sich außerhalb des NVIC, ich wäre das her 
überrascht, wenn die Funktion NVIC_ClearPendingIRQ() hier die richtige 
wäre.

Beim STM32F303 tut sie es jedenfalls nicht:
1
/**
2
  \brief   Clear Pending Interrupt
3
  \details Clears the pending bit of an external interrupt.
4
  \param [in]      IRQn  External interrupt number. Value cannot be negative.
5
 */
6
__STATIC_INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
7
{
8
  NVIC->ICPR[(((uint32_t)(int32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)(int32_t)IRQn) & 0x1FUL));
9
}

von lars (Gast)


Lesenswert?

Stefanus F. schrieb:
> Das EXTI-PR Register befindet sich außerhalb des NVIC, ich wäre das her
> überrascht, wenn die Funktion NVIC_ClearPendingIRQ() hier die richtige
> wäre.

Achso, ich hatte als Beschreibung "pending" gelesen und dachte an das 
Pending Bit.

Dann aber:
1
NVIC_EnableIRQ(EXTI0_IRQn);
2
EXTI->PR = 1;  // This bit is cleared by programming it to ‘1’.

Werde den Effekt überprüfen.

von Stefan F. (Gast)


Lesenswert?

lars schrieb:
> Dann aber:
> NVIC_EnableIRQ(EXTI0_IRQn);
> EXTI->PR = 1;  // This bit is cleared by programming it to ‘1’.

Anders herum!

von lars (Gast)


Lesenswert?

Stefanus F. schrieb:
> Anders herum!

Ach, jetzt verstehe ich die Logik dahinter! :-)

Leider feuert EXTI0 auch damit noch doppelt.

von Stefan F. (Gast)


Lesenswert?

lars schrieb:
> Leider feuert EXTI0 auch damit noch doppelt.

Jedesmal oder nur bei jeder steigenden Flanke oder nur bei jeder 
fallenden Flanke oder nur bei der ersten Flanke?

von lars (Gast)


Lesenswert?

Stefanus F. schrieb:
> lars schrieb:
>> Leider feuert EXTI0 auch damit noch doppelt.
>
> Jedesmal oder nur bei jeder steigenden Flanke oder nur bei jeder
> fallenden Flanke oder nur bei der ersten Flanke?

Jedesmal, aber wenn ich wüsste, bei welcher Flanke, wäre ich schon einen 
großen Schritt weiter.

Eigentlich hätte ich auf steigende Flanke getippt (siehe Capture), aber 
wenn ich EXTI0 auf nur fallende Flanke umstelle, tritt der Fehler 
trotzdem auf. (Wenn ich auf nur steigende Flanke umstelle, verschwindet 
der richtige Aufruf, und der falsche bleibt.)

Eigentlich war Deine Idee mit EXTI->PR nicht schlecht. Vielleicht sind 
diese fehlerhaften Handler einfach nur lange pending (also noch vor 5), 
weil der EXTI0-IR zu spät deaktiviert wird.

Ich muß mir das nochmal anschauen.

von mitlesa (Gast)


Lesenswert?

lars schrieb:
> Werde den Effekt überprüfen.

Anderer Gedanke:

Bist du sicher dass

do_work_1();
do_work_2();

oder auch

do_work_3();

nicht zuviel Zeit brauchen?

Wenn Extis "prellen" kommen die Interrupts vielleicht zu
schnell daher?

Solche "verdächtige" Funktionen/Funktionalitäten sollten
möglichst ausserhalb einer ISR ausgeführt werden.

von lars (Gast)


Lesenswert?

mitlesa schrieb:
> Bist du sicher dass
>
> do_work_1();
> do_work_2();
> do_work_3();
>
> nicht zuviel Zeit brauchen?

Tatsächlich habe ich sehr viel Zeit damit verbracht, damit genau das 
nicht passiert. Ich würde sagen, daran liegt es nicht, man würde es ja 
auch auf dem Capture sehen.

von S. R. (svenska)


Lesenswert?

Der Interrupt-Pfad besteht aus der Hardware, dem NVIC und dem Prozessor.

Ein Interrupt kann sowohl in der Hardware (also dem GPIO-Block), als 
auch im NVIC als "pending" markiert sein. Du musst also, bevor du den 
Interrupt aktivierst, erst in der Hardware und danach im NVIC die 
Pending-Bits löschen. Hältst du die Reihenfolge nicht ein, kommt der 
Interrupt trotzdem durch.

von Stefan F. (Gast)


Lesenswert?

S. R. schrieb:
> Der Interrupt-Pfad besteht aus der Hardware, dem NVIC und dem Prozessor.

Ich glaube, zwischen Hardware und dem NVIC fehlt in dieser Aufzählung 
noch der Extended/External Interrupt Controller (EXTI). Vielleicht 
meinst du das Ding mit "Hardware".

von S. R. (svenska)


Lesenswert?

Ich hab mich im Detail nur mit den SAM3X befasst, dort gilt:
PIO -> NVIC -> Cortex-M3

Also ja, ich meinte wahrscheinlich den.
Die STM32 hab ich 2015 zum letzten Mal angeschaut, aber nicht so tief.

von lars (Gast)


Lesenswert?

Stefanus F. schrieb:
> Ich glaube, zwischen Hardware und dem NVIC fehlt in dieser Aufzählung
> noch der Extended/External Interrupt Controller (EXTI).

OK, aber
1
EXTI->PR = 1;  // nicht |= und nicht 0
2
NVIC_ClearPendingIRQ(EXTI0_IRQn);
3
NVIC_EnableIRQ(EXTI0_IRQn);

wäre dann schon komplett? Frage nur, weil es erstmal noch nicht 
funktioniert hat.

von Stefan F. (Gast)


Lesenswert?

Hast du mal eine andere Signalquelle versucht, die absolut sicher nur 
genau so viele Flanken erzeugt, wie du haben willst?

von Jim M. (turboj)


Lesenswert?

lars schrieb:
> OK, aber
> EXTI->PR = 1;  // nicht |= und nicht 0
> NVIC_ClearPendingIRQ(EXTI0_IRQn);
> NVIC_EnableIRQ(EXTI0_IRQn);
>
> wäre dann schon komplett? Frage nur, weil es erstmal noch nicht
> funktioniert hat.

Bei obigem Code kann IMHO ClearPending ausgeführt werden BEVOR die 
Hardware das Pending Bit auch wirklich außen am NVIC gelöscht hat - das 
macht ClearPending unwirksam.

Abhilfe: Auf die Hardware warten - das geht am Einfachsten mit einem 
Lesezugriff:
1
EXTI->PR = 1;  // nicht |= und nicht 0
2
(void) EXTI->PR; // dummy Lesezugriff
3
NVIC_ClearPendingIRQ(EXTI0_IRQn);
4
NVIC_EnableIRQ(EXTI0_IRQn);

von Jim M. (turboj)


Lesenswert?

Ach ja: Aus einem ganz ähnlichen Grund kann man zweimal in einen IQR 
Handler laufen, wenn das Löschen des Flags in der Hardware unmittelbar 
vor dem Rücksprung erfolgt.

Auch dann ist der Interrupt nämlich noch Pending, und der µC macht ein 
Tail-Chaining in dieselbe Funktion.

von Stefan F. (Gast)


Lesenswert?

Jim,
auf den von Dir beschriebenen Effekt bin ich mal in Zusammenhang mit der 
RTC gestoßen. Die hat jedoch einen Pegel-Interrupt. Bist du sicher, dass 
das Problem auch bei Flanken-Interrupts auftreten kann?

von lars (Gast)


Lesenswert?

Jim M. schrieb:
> Bei obigem Code kann IMHO ClearPending ausgeführt werden BEVOR die
> Hardware das Pending Bit auch wirklich außen am NVIC gelöscht hat - das
> macht ClearPending unwirksam.
>
> Abhilfe: Auf die Hardware warten - das geht am Einfachsten mit einem
> Lesezugriff:

Alle Achtung, diese Änderung hat jetzt auch noch die allerletze falsche 
Handlerausführung beseitigt. Darauf wäre ich jetzt nicht gekommen.

Herzlichen Dank an alle, Fall gelöst!

von Stefan F. (Gast)


Lesenswert?

lars schrieb:
> hat jetzt auch noch die allerletze falsche
> Handlerausführung beseitigt

Wow! Das sollte man sich auf einen roten Zettel schreiben und an die 
Wand hängen.

von Jim M. (turboj)


Lesenswert?

Stefanus F. schrieb:
> Bist du sicher, dass
> das Problem auch bei Flanken-Interrupts auftreten kann?

Oftmals wird der Flanken Trigger im Peripherial ausgewertet und am NVIC 
intern liegt ein Level Signal an.

von RAc (Gast)


Lesenswert?

Jim M. schrieb:
> lars schrieb:
>> OK, aber
>> EXTI->PR = 1;  // nicht |= und nicht 0
>> NVIC_ClearPendingIRQ(EXTI0_IRQn);
>> NVIC_EnableIRQ(EXTI0_IRQn);
>>
>> wäre dann schon komplett? Frage nur, weil es erstmal noch nicht
>> funktioniert hat.
>
> Bei obigem Code kann IMHO ClearPending ausgeführt werden BEVOR die
> Hardware das Pending Bit auch wirklich außen am NVIC gelöscht hat - das
> macht ClearPending unwirksam.
>
> Abhilfe: Auf die Hardware warten - das geht am Einfachsten mit einem
> Lesezugriff:
> EXTI->PR = 1;  // nicht |= und nicht 0
> (void) EXTI->PR; // dummy Lesezugriff
> NVIC_ClearPendingIRQ(EXTI0_IRQn);
> NVIC_EnableIRQ(EXTI0_IRQn);

vermutlich könnte die "sauberere" Lösung eine Barriere sein:

__asm volatile( "isb" );

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHJGFEE.html

aber das grenzt jetzt eher an Haarspalterei. Kudos an Jim!

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.