Forum: Mikrocontroller und Digitale Elektronik STM32 EXTI-Entprellung mit HAL


von Arthur K. (krteque)


Lesenswert?

Hallo zusammen,

um eine codelastige Taster-Entprellung zu umgehen, möchte ich einen 
External Interrupt bei steigender Flanke triggern, welcher sich 
innerhalb der ISR selbst deaktiviert um sich beim Prellen nicht noch 
einmal aufzurufen. Im Betrieb soll der Interrupt dann wieder von einer 
Timer-Routine aktiviert werden, testweise habe ich diese Funktion jedoch 
von einer anderen Tastereingabe (PD8) abhängig gemacht.

PD9 -> Triggert EXTI9_5 -> Toggelt PE8 (LED) & Deaktiviert EXTI9_5

PD8 -> Löscht Interrupt Queue EXTI9_5 und aktiviert EXTI9_5

Leider wird nach dem drücken von PD8 die ISR regelmäßig noch einmal 
ausgeführt (und die LED ein weiteres mal getoggelt), was ich nur auf 
eine nicht gelöschte Queue (generiert durch vorheriges Prellen) 
zurückführen kann. Habe ich da etwas falsch verstanden? Vielleicht sieht 
jemand den Fehler den ich mache, oder kann mir erklären was hier vor 
sich geht.

Danke im Voraus!

Hier der Code:
1
void EXTI9_5_IRQHandler(void)
2
{
3
4
  /* USER CODE BEGIN EXTI9_5_IRQn 0 */
5
6
  HAL_NVIC_DisableIRQ(EXTI9_5_IRQn); // Disable Interrupt request
7
  HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_8);
8
9
  /* USER CODE END EXTI9_5_IRQn 0 */
10
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
11
  /* USER CODE BEGIN EXTI9_5_IRQn 1 */
12
13
14
  /* USER CODE END EXTI9_5_IRQn 1 */
15
}
16
17
void TIM7_IRQHandler(void)
18
{
19
  /* USER CODE BEGIN TIM7_IRQn 0 */
20
21
  if(DebouncedDelayToggle(GPIOD, GPIO_PIN_8, &prevPushButtonStatus[0], 
22
  &released[0], 20, SETTIME, &memSaveTimeDebounce[0])) // debounce 20ms
23
  {
24
    HAL_NVIC_ClearPendingIRQ(EXTI9_5_IRQn);  // Clear Interrupt queue
25
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);    // Enable EXTI9_5 Interrupt
26
  }
27
}

von Carl D. (jcw2)


Lesenswert?

Ich würde einfach mal PeDa's Code Größe mit dem der gerufenen 
HAL-Funktionen vergleichen. Gerade ARM kann PeDa's Code praktisch 1:1 in 
Befehle umsetzen.
Dann ist das Thema "code-lastig" schnell erledigt. Mit dem Vorteil, daß 
es funktioniert!

von Arthur K. (krteque)


Lesenswert?

Das stimmt, allerdings wäre es mir schon auch wichtig, das nach 
Möglichkeit in einem Interrupt zu realisieren. Mit den AVRs hat die 
deaktivierung soweit ich mich erinnere, relativ Reibungslos funktioniert 
und ich frage mich, was nun das Problem beim STM32 ist. An sich ist ja 
die Interrupt-Entprellung schon wie ich finde die eleganteste Lösung.

von Alfred K. (Gast)


Lesenswert?

Arthur K. schrieb:
> HAL_NVIC_ClearPendingIRQ(EXTI9_5_IRQn);  // Clear Interrupt queue
> HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);    // Enable EXTI9_5 Interrupt

Hier wird vermutlich der Interrupt freigegeben, bevor das Flag 
gelöscht ist.
Füge zwischen die Aufrufe ein __ISB(); ein, damit die 1. Funktion 
beendet ist bevor die nächste startet.

Arthur K. schrieb:
> An sich ist ja
> die Interrupt-Entprellung schon wie ich finde die eleganteste Lösung.

Eine Entprellung findet ja garnicht statt. Jeder Störimpuls würde auch 
als Tastendruck gewertet werden.

von Carl D. (jcw2)


Lesenswert?

Arthur K. schrieb:
> Das stimmt, allerdings wäre es mir schon auch wichtig, das nach
> Möglichkeit in einem Interrupt zu realisieren. Mit den AVRs hat die
> deaktivierung soweit ich mich erinnere, relativ Reibungslos funktioniert
> und ich frage mich, was nun das Problem beim STM32 ist. An sich ist ja
> die Interrupt-Entprellung schon wie ich finde die eleganteste Lösung.

Elegant, aber nicht funktionierend!

von Peter D. (peda)


Lesenswert?

Arthur K. schrieb:
> An sich ist ja
> die Interrupt-Entprellung schon wie ich finde die eleganteste Lösung.

Ich sehe darin nichts elegantes, noch zusätzlich einen externen 
Interrupt zu nehmen, wenn der Timerinterrupt schon völlig ausreichend 
ist. Und bei mehreren Tasten steigt der Aufwand noch weiter an.

Nur bei Batteriebetrieb nehme ich noch den Pin-Change-Interrupt zum 
Aufwachen. Praktischer Weise kann der auch gleich einen ganzen Port 
überwachen. Es bleibt also bei 2 Interrupts für 8 Tasten (AVR) bzw. 32 
Tasten (ARM).

von Nop (Gast)


Lesenswert?

Arthur K. schrieb:

> um eine codelastige Taster-Entprellung zu umgehen, möchte ich einen
> External Interrupt bei steigender Flanke triggern, welcher sich
> innerhalb der ISR selbst deaktiviert um sich beim Prellen nicht noch
> einmal aufzurufen.

Das scheitert doch schon ganz grundsätzlich, weil das Prellen der Taste 
viel länger anhält als die Verarbeitung des Interrupts.

 Im Betrieb soll der Interrupt dann wieder von einer
> Timer-Routine aktiviert werden

Das scheitert daran, daß der Tastendruck als externes Ereignis asynchron 
zum Timer läuft und das Ereignis immer kurz vor dem Timer-Interrupt 
passieren kann, so daß die Taste dann immer noch prellt.

von Thorsten S. (thosch)


Lesenswert?

Als einzig sinvollen Grund, einen Taster einen Interrupt auslösen zu 
lassen, betrachte ich das Aufwecken des μC aus einem Sleep-Mode heraus.
Nach dem Aufwachen erfolgt die weitere Abfrage und Entprellung dann wie 
üblich im Ticker-Interrupt.

Falls das Prellen schon beim Wakeup stört (was üblicherweise nicht der 
Fall sein sollte), entprellt man per Hardware (RC-Glied und Schmitt 
Trigger, oder spezielles Entprell-IC)

von Arthur K. (krteque)


Angehängte Dateien:

Lesenswert?

Erst einmal Vorweg:
Ich verstehe, dass im Allgemeinen hier davon abgeraten wird externe 
Interrupts für das Entprellen zu verwenden, jedoch wurde uns diese 
Strategie mit externen Interrupts im Studium als besonders effizient 
beschrieben (was mich nun wegen der Antworten hier etwas wundert). Die 
Applikation ist für Batteriebetrieb ausgelegt.

Es geht mir jedoch auch unabhängig davon um das Verständnis des Vorgangs 
im uC.

Alfred K. schrieb:
> Arthur K. schrieb:
>> HAL_NVIC_ClearPendingIRQ(EXTI9_5_IRQn);  // Clear Interrupt queue
>> HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);    // Enable EXTI9_5 Interrupt
>
> Hier wird vermutlich der Interrupt freigegeben, bevor das Flag
> gelöscht ist.
> Füge zwischen die Aufrufe ein __ISB(); ein, damit die 1. Funktion
> beendet ist bevor die nächste startet.

Danke, ich habe dies versucht, leider ohne Auswirkung.
Ich habe mir einmal die Control Register genauer angeschaut. Sobald der 
Interrupt wieder Enabled (Anhang: "Interrupt Mask Register") wird, wird 
das EXTI.PR (Pending Register) für EXTI9 auf 1 gesetzt und lässt sich 
danach NICHT durch HAL_NVIC_ClearPendingIRQ(EXTI9_5_IRQn) zurücksetzen. 
Obwohl das Register als RW gekennzeichnet ist, lässt sich auch nicht 
direkt drauf schreiben (wie bei anderen Registern möglich).

Ich frage mich, ob dies mit dem "Edge detect circuit" in dem angehängten 
Block-Diagramm zu tun hat, da das "SW Interrupt Event Register" auf 0x00 
ist. Warum der "Edge Detect Circuit" allerdings über längere Zeit (ich 
habe 10 Minuten verstreichen lassen) TRUE senden sollte bleibt mir 
schleierhaft.

Wenn ein EXTI immer mehrmals hintereinander ausgeführt würde, wäre das 
ziemlich unpraktisch.


Alfred K. schrieb:
> Eine Entprellung findet ja garnicht statt. Jeder Störimpuls würde auch
> als Tastendruck gewertet werden.

Daran habe ich noch nicht gedacht, danke! Unabhängig davon versuche ich 
wie gesagt nur zu verstehen, was in dem Mikrocontroller genau vor sich 
geht.


Nop schrieb:
> Das scheitert doch schon ganz grundsätzlich, weil das Prellen der Taste
> viel länger anhält als die Verarbeitung des Interrupts.

Deshalb soll der Interrupt bei dem ersten Aufruf deaktiviert werden, 
damit er nicht erneut aufgerufen wird. Der Timer soll erst im EXTI 
gestartet werden und dient nur dazu den EXTI nach einer gewissen Zeit 
wieder zu aktivieren, wobei der Timer wieder deaktiviert wird. (Soweit 
die Idee)

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Arthur K. schrieb:

> jedoch wurde uns diese
> Strategie mit externen Interrupts im Studium als besonders effizient
> beschrieben

Wie Thorsten schon schrieb, das ist sinnvoll zum Aufwecken, wenn die 
Anwendung ansonsten im Tiefschlaf sein soll.

> Obwohl das Register als RW gekennzeichnet ist, lässt sich auch nicht
> direkt drauf schreiben (wie bei anderen Registern möglich).

Mein Refman für STM32F405 sagt dazu:

"This bit is set when the selected edge event arrives on the external 
interrupt line.
This bit is cleared by programming it to ‘1’."

Eine 1 schreiben zum Setzen auf 0 ist etwas ungewöhnlich. Versuch's mal 
ohne HAL mit direktem Registerzugriff - vielleicht hat die HAL ja nen 
Bug und versucht, das Bit durch Schreiben mit 0 zu löschen.

> Der Timer soll erst im EXTI
> gestartet werden und dient nur dazu den EXTI nach einer gewissen Zeit
> wieder zu aktivieren, wobei der Timer wieder deaktiviert wird. (Soweit
> die Idee)

Dann mußt Du aber mit einem Oszi nachmessen, daß die Zeit wirklich 
ausreichend ist. Entprellung hast Du dann aber immer noch nicht.

Insbesondere, wenn die Taste wieder losgelassen wird, dann wirst Du auch 
Flanken in beide Richtungen beobachten, das ist ja Prellen. 
Möglicherweise ist das auch der Grund, wieso Du zwei Tastendrücke 
siehst.

von Stefan F. (Gast)


Lesenswert?

Arthur K. schrieb:
> jedoch wurde uns diese Strategie mit externen Interrupts im Studium
> als besonders effizient beschrieben

Da wurdest du falsch beraten. Das Aufrufen der ISR und der Rücksprung 
ins Hauptprogramm kostet schon signifikant CPU Zeit. Da der Taster 
beliebig oft prellen kann, ist nicht kalkulierbar, wie CPU Zeit das sein 
wird.

Im Extremfall wäre denkbar, dass ein defekter Kontakt ununterbrochen 
hunderte male pro Sekunde prellt - und damit womöglich dein ganzes 
System blockiert.

Wenn du den Taster in regelmäßigen Timer-Intervallen abfragst (z.B. alle 
10ms) ist die CPU Last hingegen vorhersehbar und konstant.

> Deshalb soll der Interrupt bei dem ersten Aufruf deaktiviert werden,
> damit er nicht erneut aufgerufen wird.

Damit löst du das Problem.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Arthur K. schrieb:
> jedoch wurde uns diese Strategie mit externen Interrupts im Studium als
> besonders effizient beschrieben (was mich nun wegen der Antworten hier
> etwas wundert).
Du glaubst nicht, was einem von Leuten alles erzählt wird. Auch von 
denen, die man im Studium trifft...

> jedoch wurde uns diese Strategie mit externen Interrupts im Studium als
> besonders effizient beschrieben
Eine etwaige "Effizienz dieser Strategie" kann man sich nur dann aus den 
Fingern saugen, wenn man diese Aufgabe völlig isoliert betrachtet und 
nur diesen einen ungestörten Taster einzulesen hat.
Im echten Leben sind da aber 15 Eingangssignale von einigen Sensoren 
einzulesen. Diese Signale bringen auch beliebige Störungen und dubioses 
Prellen oder Schaltverhalten mit sich.
Und dann gehen dir 1. die Interrupts ratzfatz aus, und 2. hast du da 
ganz schnell eine üble Suppe an Interrupts, die z.B. auch andere 
Interrupts unterbrechen können oder unterbrechen sollten oder niemals 
unterbrechen dürften.

Wer das einmal mitgemacht hat, der erledigt so viel als irgend möglich 
in der Hauptschleife und bestenfalls noch im 1ms-Timerinterrupt.

Arthur K. schrieb:
> Die Applikation ist für Batteriebetrieb ausgelegt.
Auch dann würde ich einfach mal probieren, ob du da so einen zyklischen 
Ablauf hineinbekommst und z.B. deinen µC 4x pro Sekunde aufwecken und 
die Taste abfragen kannst. Ich habe das schon ein paar Mal mit einem AVR 
und dem Watchdog-Reset gemacht, die älteste 1216er Knopfzelle hält nun 
da drin schon über 7 Jahre...

: Bearbeitet durch Moderator
von Peter D. (peda)


Lesenswert?

Arthur K. schrieb:
> Deshalb soll der Interrupt bei dem ersten Aufruf deaktiviert werden,
> damit er nicht erneut aufgerufen wird.

Das hat aber keinen Einfluß auf das Setzen des Pending-Flags.
Z.B. kann eine Flanke geschehen und nach einem Jahr gibst Du den 
Interrupt frei. Dann reagiert der Interrupt auf die Flanke von vor einem 
Jahr.
Daher muß vor der erneuten Freigabe des Interrupts erst das Pending-Flag 
gelöscht werden.

von Johnny B. (johnnyb)


Lesenswert?

Nop schrieb:
> Arthur K. schrieb:
>
>> jedoch wurde uns diese
>> Strategie mit externen Interrupts im Studium als besonders effizient
>> beschrieben
>
> Wie Thorsten schon schrieb, das ist sinnvoll zum Aufwecken, wenn die
> Anwendung ansonsten im Tiefschlaf sein soll.

So ist es; ein Interrupt ist hier nur sinnvoll zum Aufwecken aus einem 
Sleep-Mode.
Früher (so vor 20 Jahren) als die uC noch sehr langsam waren, hat es 
vielleicht noch Sinn gemacht, die Tasten generell über Interrupts 
auszuwerten, aber diese Zeiten sind schon länger vorbei.

von W.S. (Gast)


Lesenswert?

Arthur K. schrieb:
> um eine codelastige Taster-Entprellung zu umgehen, möchte ich einen
> External Interrupt bei steigender Flanke triggern, welcher sich
> innerhalb der ISR selbst deaktiviert um sich beim Prellen nicht noch
> einmal aufzurufen.

Herrje, warum nur versuchst du bloß, die allereinfachsten Dinge auf die 
allerkomplizieteste Weise machen zu wollen?

Also:
1. Maßnahme: wenn du deinem Taster einfach einen 22nF Kondensator gegen 
Masse spendierst, dann bist du 99% aller eventuellen Prellereien bereits 
los - und es kostet dich nur ein paar Cent. Voraussetzung: es ist ein 
einzelner Taster und nicht eine Tastatur-Matrix.

2. Maßnahme: eigentlich sollte jeder µC über sowas wie eine Systemuhr in 
Software verfügen. Die braucht nicht wirklich schnell im 1 ms Takt zu 
arbeiten, sondern für die meisten Fälle reicht ein Takt von 10 ms aus.

Und in den zugehörigen Handler baust du ganz einfach deine Tastenabfrage 
ein. Wie man eine Taste sauber abfragt, das solltest du aus eigener 
Kraft eigentlich wissen und können. Es ist wirklich nur ein ganz 
klitzekleines Stückchen logisches Denken dazu erforderlich.

3. Nur für den Fall, daß dein µC aus dem Tiefschlaf gerissen werden 
soll, oder daß es sich nicht um eine Taste, sondern um einen Drehgeber 
handelt (wo man zeitnah ein zweites Signal abfragen muß), solltest du an 
einen separaten Interrupt denken.

Ich beginne so langsam eine Allergie zu kriegen gegen Leute, die sich 
gewaltig großartige Projekte vornehmen, aber selbst eine poplige 
Tastenabfrage nicht selber auf die Reihe kriegen, geschweige denn soviel 
Verständnis für Elektronik aufbringen, um selbst die billigsten 
Hardwaredinge in den Griff zu kriegen.

W.S.

von Peter D. (peda)


Lesenswert?

Johnny B. schrieb:
> Früher (so vor 20 Jahren) als die uC noch sehr langsam waren, hat es
> vielleicht noch Sinn gemacht, die Tasten generell über Interrupts
> auszuwerten, aber diese Zeiten sind schon länger vorbei.

Auch der alte 8051 von 1980 gähnt dabei vor Langeweile. Man hat es 
früher einfach nur nicht besser gewußt und die Compiler waren auch nicht 
so hoch optimiert bzw. nicht vorhanden.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Man hat es früher einfach nur nicht besser gewußt
+1
+1
+1
....

Und leider stehen diese alte Denkweisen und Designstrategien noch immer 
in den Büchern und sind noch immer in den Köpfen.

Peter D. schrieb:
> Auch der alte 8051 von 1980 gähnt dabei vor Langeweile.
Sogar einer dessen Vorgänger, der 8042, konnte als Tataturcontroller 
ohne jeden Interrupt an die hundert Tasten auswerten...   ;-)

: Bearbeitet durch Moderator
von Arthur K. (krteque)


Lesenswert?

Erst einmal vielen Dank für die Antworten, für jemanden der noch nicht 
so lange bei der Embedded-Programmierung dabei ist, war das sehr 
aufschlußreich.
Ich werde jetzt schauen wie ich die Routine von PeDa für den STM32 
adaptieren kann und den EXTI nurnoch zum aufwecken benutzen.

Um nochmal auf die Frage zum merkwürdigen Verhalten des Pending 
Registers einzugehen:
Das Pending Register lässt sich weder mit 0 noch mit 1 beschrieben 
(weder durch direktes Schreiben, noch durch die 
HAL_NVIC_ClearPendingIRQ() Funktion), so wie es im Manual zur Löschung 
des Bits beschrieben wurde. Ich werde das noch ein bisschen untersuchen 
und dann ggf. mal in den ST-Foren fragen ob es eine Erklärung für das 
Verhalten gibt.

Wie gesagt wird das Pending Bit erst gesetzt, wenn der Interrupt erneut 
aktiviert wird.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Wrum löscht du die Pendings nicht auch in der EXTI ISR, sondern nur beim 
Start?
Wenn ich das so mache:
1
// check the solenoid line from the Fostex controller to switch to Play
2
void EXTI9_5_IRQHandler(void)
3
{
4
5
  if(EXTI_GetITStatus(PLAY_EXTI_LINE) != RESET)
6
  {
7
    /* Clear the EXTI line pending bit */
8
    EXTI_ClearITPendingBit(PLAY_EXTI_LINE);
9
    if (!fastFlags.PlayUpdate) {
10
        fastFlags.PlayUpdate = TRUE;
11
    }
12
  }
13
}
triggert die Routine genau einmal. Ich halte von HAL nicht soviel, 
deswegen hier mal in SPL.

: Bearbeitet durch User
von Arthur K. (krteque)


Lesenswert?

Matthias S. schrieb:
> Wrum löscht du die Pendings nicht auch in der EXTI ISR, sondern nur beim
> Start?

Ich habe das auch versucht, nur nicht alle Varianten hier gepostet. 
Werde das morgen mal Versuchen zu reproduzieren, was du gepostet hast.

von Peter D. (peda)


Lesenswert?

Arthur K. schrieb:
> Wie gesagt wird das Pending Bit erst gesetzt, wenn der Interrupt erneut
> aktiviert wird.

Das glaub ich mal nicht.
Ein Pending-Bit hat genau den Grund, daß keine Ereignisse verloren 
gehen, wenn ein Interrupt mal temporär gesperrt werden muß, bzw. es 
erlaubt es auch, auf ein Interruptereignis zu pollen.
Ich kenne auch keine Architektur, wo Pending-Bits nicht in SW löschbar 
sind.

von Arthur K. (krteque)


Lesenswert?

Peter D. schrieb:
> Arthur K. schrieb:
>> Wie gesagt wird das Pending Bit erst gesetzt, wenn der Interrupt erneut
>> aktiviert wird.
>
> Das glaub ich mal nicht.
> Ein Pending-Bit hat genau den Grund, daß keine Ereignisse verloren
> gehen, wenn ein Interrupt mal temporär gesperrt werden muß, bzw. es
> erlaubt es auch, auf ein Interruptereignis zu pollen.
> Ich kenne auch keine Architektur, wo Pending-Bits nicht in SW löschbar
> sind.

Der Effekt trat definitiv auf. Ich vermute, dass hängt irgendwie mit dem 
ST-Link Debugger zusammen. Der Wert wechselt manchmal zwischen 0 und 1 
obwohl der Interrupt deaktiviert ist.

Ich konnte nun auch erfolgreich das EXTI Pending Bit löschen, indem ich 
eine 1 rein geschrieben habe, wobei ich dies IIRC vorher schon einmal 
ohne Erfolg versucht hatte.

Was ich dabei irgendwie übersehen habe ist, dass es 2 (!) Pending 
Register gibt, nämlich ein EXTI Pending und ein NVIC Pending (für 
letztes gibt es die HAL_NVIC_ClearPendingIRQ() funktion). Beide müssen 
auf Null gesetzt werden und das geht AFAIK nur wenn das EXTI Pending Bit 
zuerst auf Null gesetzt wird.

Folgender Code funktioniert nun ausnahmslos:
1
    EXTI->PR |= (1<<9);       // Clear EXTI9_5 in EXTI Pending Register by writing 1 to IT line 9
2
    HAL_NVIC_ClearPendingIRQ(EXTI9_5_IRQn); // Clear NVIC Pending Register
3
    HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);    // Enable EXTI9_5 Interrupt

: Bearbeitet durch User
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.