Verstehen von Interrupts und Entprellen von Softwaretasten

Ich bin ziemlich neu in der AVR-Programmierung (avr-gcc).

Um auf Tastendruck zu reagieren, verwende ich einen PCINT ISR mit aktiviertem internem Pullup-Widerstand wie folgt:

ISR(PCINT0_vect) {
    if (bit_is_clear(PINB, PB0)) {
        _delay_ms(40);
        if (bit_is_clear(PINB, PB0)) {
            // do something
        }
    }
}

Es funktioniert gut, aber ich denke, es ist nicht besonders schlau, 40 Millisekunden in einem ISR zu verbringen, nur um eine Taste zu entprellen.

Ich habe Ganssles Entprellung von Kontakten Teil 2 gelesen , der für meinen derzeitigen Erfahrungsstand etwas zu fortgeschritten ist, und es gibt eine Aussage, bei der ich mir nicht sicher bin:

Der nicht entprellte Schalter muss mit einem programmierten I/O-Pin verbunden sein, niemals mit einem Interrupt.

Ist mein Ansatz, den Button mit einem PCINT zu verbinden, also schon falsch? Ich bin noch nicht um Timer herumgekommen, aber sollte ich lieber eine Art zeitgesteuerten Interrupt verwenden, um den Schaltflächenstatus alle 1 ms oder so auszuwerten, anstatt dass die Schaltfläche direkt einen Interrupt auslöst?

Ich kann damit leben, dass meine Entprellung im Moment nicht sehr schlau ist, aber zumindest möchte ich die Grundlagen der Reaktion auf einen Knopfdruck richtig machen.

Antworten (4)

Ich schlage vor, Sie gewöhnen sich an, solche Dinge mit einem Timer-Interrupt zu tun und den Switch abzufragen, anstatt zu versuchen, Interrupts zu verwenden. 1kHz ist schnell genug.

Wie andere gesagt haben, erzeugt der Schalter zu unvorhersehbaren Zeiten mehrere Flanken, die Probleme oder Interaktionen mit anderen Routinen verursachen können.

Sie können nicht blockierende Routinen schreiben, die den Schalter entprellen.

Die Grundregel lautet, dass Sie nicht mehr Zeit in einem ISR verbringen, als Sie müssen. Lesen Sie den Zustand des Schalters aus, wenn er sich geändert hat, schieben Sie eine Zahl in eine statische Variable für die Anzahl der Millisekunden, bevor Sie eine Zustandsänderung akzeptieren, und Verringern Sie die Zahl jedes Mal, wenn Sie den Interrupt bedienen und der Status sich seit dem letzten Lesen nicht geändert hat, sich aber vom letzten akzeptierten Status unterscheidet. Wenn Sie eine stabile Zustandsänderung erhalten, schieben Sie sie in eine Warteschlange, damit die Hintergrundroutine damit umgehen kann (die Warteschlange könnte in einigen Fällen eine Tiefe von 1 haben). Aktualisieren Sie den letzten akzeptierten Status und fertig.

Ich werde mich für einen Timer-Interrupt entscheiden, dies scheint der übliche Weg zu sein, und versuchen, etwas zu implementieren, das Sie vorschlagen.

Torsten, ja, eine Verzögerung von 40 ms in die ISR einzuführen, ist eine falsche Idee. Normalerweise werden Schaltflächen in der main()-Schleife abgefragt.

Interrupts sind gut, wenn Sie ein Ereignis mit sehr geringer Latenz erkennen müssen oder wenn ein Ereignis sehr kurz ist. Tastendrücke treten in der Größenordnung von Hunderten von Millisekunden auf. Ein Bediener hält die Taste für 50 ms bis 100 ms gedrückt. Wenn Ihr Gerät dann innerhalb von 10 ms oder innerhalb von 70 ms reagiert, wird der Bediener den Unterschied nicht spüren. Das ist ziemlich langsam, also muss es nicht über einen Interrupt erkannt werden.

An den Interrupt könnte ein Taster angeschlossen werden, wenn man den Mikrocontroller über einen Interrupt aus dem Ruhezustand wecken möchte. Nach dem Aufwachen würden Sie die Schaltfläche abfragen.

Wenn ich in der Hauptschleife abfrage, muss ich sicher sein, dass die dort ausgeführte Arbeit nicht länger als beispielsweise 10 ms dauert, richtig? Wenn ich mir also nicht sicher bin, würde ich einen Timer-Interrupt verwenden?

Das Problem mit Interrupts und Buttons besteht darin, dass der Button viele Kanten bietet und daher ein Tastendruck dutzende Male einen Interrupt auslösen kann. Dies ist normalerweise nicht das, was Sie wollen. Einige MCUs mögen auch keine sehr kurzen Impulse am Interrupt-Eingang, aber das ist bei AVRs nicht der Fall.

Wenn Sie in Ihrem Fall wirklich PCINT verwenden möchten, können Sie dies tun, indem Sie weitere Interrupts im Handler deaktivieren. Auf diese Weise wird nur der allererste Druck erkannt. Natürlich möchten Sie den PCINT-Interrupt irgendwann wieder aktivieren, damit Sie den nächsten Tastendruck erkennen können. Dies bedeutet normalerweise, dass Sie einen Timer benötigen, und wenn Sie einen Timer haben, können Sie ihn genauso gut freilaufend lassen und Schaltflächen im Handler abfragen - das ist einfacher.

Allerdings kann die 'Taste zum Unterbrechen des Pins' gelegentlich nützlich sein, wenn Sie trotzdem einen Timer starten wollten, zum Beispiel für einen kurzen Piepton bei einem Tastendruck. In diesem Fall sieht das Szenario so aus:

  • Benutzer drückt einen Knopf, Feuer unterbrechen
  • Der Button-Interrupt-Handler deaktiviert Button-Interrupts, schaltet den Piepser ein und startet einen Timer
  • Wenn der Timer abläuft, schaltet der Timer-Interrupt den Piepser aus und aktiviert die Tasten-Interrupts wieder
Der Tastenbeep ist ein schönes Beispiel. Wenn die Verwendung von PCINT kein guter Weg ist, dann möchte ich es nicht verwenden, das nächste Kapitel in meinem Buch handelt von Timern, also werde ich diesen Weg gehen.

Ich versuche, den Antworten hier zu folgen, und nachdem ich mich über Timer informiert habe, möchte ich meine eigene einfache Lösung in den Ring werfen.

Da die CPU des ATmega328P mit 1 MHz läuft, richte ich den 8-Bit-Timer 0 im Normalmodus mit einem Taktvorskalierer /64 ein, sodass er alle 16 ms überläuft:

TCCR0B |= (1 << CS00) | (1 << CS01);

und ich aktiviere den Überlauf-Interrupt von Timer 0:

TIMSK0 |= (1 << TOIE0);

In der entsprechenden ISR überprüfe ich den Schaltflächenstatus:

volatile bool buttonPressed = false;    

ISR(TIMER0_OVF_vect) {
    if (bit_is_clear(PINB, PB0) && ! buttonPressed) {
        buttonPressed = true;
        // do something
    } else if (bit_is_set(PINB, PB0)) {
        buttonPressed = false;
    }
}

Immer noch kein ausgeklügelter Entprellungsalgorithmus, aber jetzt wird keine Zeit in der ISR verschwendet, die Reaktionszeit ist gut und es gibt keine Probleme mit dem Prellen von Tasten, egal wie sehr ich es mit den verschiedenen Tasten, die ich hier habe, versuche.

AKTUALISIEREN:

Nach einigen wirklich ausgiebigen Tests scheint es mir, dass das Polling alle 31 ms perfekt entprellt, sogar besser als mit 16 ms - vielleicht, weil 16 ms ein bisschen nahe an der nominellen Prellzeit von 15 ms der von mir verwendeten Tasten liegen.

Um die 32 Hz zu erreichen, habe ich den Timer vom Überlaufmodus in den Clear-Timer im Vergleichsmodus mit einem Vorteiler / 256 geändert, der 3,9 kHz bei 1 MHz ergibt, und das entsprechende Ausgangsvergleichsregister auf 122 gesetzt.