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:
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!
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.
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.
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!
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).
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.
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)
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)
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.
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.
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...
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.
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.
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.
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.
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... ;-)
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.
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.
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.
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