Mehrere Tasten von einem Interrupt abfragen

Ich versuche, 3 verschiedene Tasten von einem Watchdog-Timeout-Interrupt auf einem ATtiny13 abzufragen.

Mein Code funktioniert perfekt für einzelne Schaltflächen, aber ich kann anscheinend nicht alle 3 in einer Schleife abfragen. Alle Schaltflächen sind mit einer beliebigen Nummer zur Identifizierung in 0x01, 0x02 und 0x04 verknüpft.

Dieser Code fragt beispielsweise die Schaltfläche 0x02 ab und funktioniert einwandfrei:

ISR (WDT_vect){
if (debounce(0x02)==1){
    PORTB ^= _BV(PB1);//flip led 1
}}

Wenn ich jedoch versuche, alle 3 in einer Schleife abzufragen, scheint keine Erkennung zu erfolgen. In diesem Beispiel schalte ich einfach dieselbe LED für alle 3 Tasten um:

ISR (WDT_vect){     
    for (int d=0x01;d<0x04;d<<1){
    if (debounce(d)==1){
        PORTB ^= _BV(PB1);//flip led 1
    }}}

Gestapelte if-else funktionieren auch nicht:

ISR (WDT_vect){

if (debounce(0x02)==1){
    PORTB ^= _BV(PB1);//flip led 1
} else  if (debounce(0x04)==1){
    PORTB ^= _BV(PB3);//flip led 2
}
}

Der Rest des relevanten Codes zur Verdeutlichung gekürzt:

/***
 * curbtn: one of 0x01,0x02,0x04, matches mute, vol+,vol-
 * returns 1 if the button is considered pressed
 */
uint8_t debounce(uint8_t  curbtn){
static uint8_t button_history = 0;
uint8_t pressed = 0;
button_history = button_history << 1;
button_history |= read_btn(curbtn);
if ((button_history & 0b11000111) == 0b00000111) {
    pressed = 1;
    button_history = 0b11111111;
}
return pressed;
}
/**
 * sets up ports to read a specific button
 * returns 1 if the selected button is pressed
 */
uint8_t read_btn(uint8_t  curbtn){
uint8_t ret=0x00;
if (curbtn==0x01){
    DDRB &=~_BV(PB2);//PB2 en entree
    PORTB |=_BV(PB2);//pull-up actif
    ret= ( (PINB & _BV(PB2)) == 0 );
} else if (curbtn==0x02){
    DDRB |=_BV(PB2);//PB2 en sortie
    PORTB &=~_BV(PB2);//PB2 a 0
    DDRB &=~_BV(PB0);//PB0 en entree
    PORTB |=_BV(PB0);//pull up sur PB0
    ret= ( (PINB & _BV(PB0)) == 0 );
} else if (curbtn==0x04){
    DDRB |=_BV(PB0);//PB0 en sortie
    PORTB &=~_BV(PB0);//PB0 a 0
    DDRB &=~_BV(PB4);//PB4 en entree
    PORTB |=_BV(PB4);//pull up sur PB4
    ret= ((PINB & (1<<PB4)) == 0);//lecture de PB0
}
return ret;
}

Das ist der Schaltungsaufbau:Geben Sie hier die Bildbeschreibung ein

Ich würde gerne wissen, ob ich in die richtige Richtung gehe und wie mein Abfragecode korrigiert werden sollte.

Schema, wie die Tasten angebracht sind?
Warum ziehen Sie es vor, abzufragen, anstatt einen Pin-Änderungs-Interrupt zu verwenden?
@bigjosh Beitrag bearbeitet, um den Schaltplan aufzunehmen. Meine Schaltung muss den Zustand der Pins um einen Schalter herum aktiv ändern. Um beispielsweise nach dem Stummschalten ein Lesen auf vol+ zu ermöglichen, müsste PB2 zu einem niedrigen Ausgang werden. So wie ich es verstehe, bin ich mir nicht sicher, ob ein Pin-Wechsel-Interrupt angemessen wäre, da ich bereits auf die Pin-Zustände reagiere. Der Switch-Teil ist ein geschlossenes Layout, das ich wiederverwende, daher das exotische Layout.
Verfolgt debounce()der Schaltflächenstatus von einem Aufruf zum nächsten? Wenn ja, kann es mehrere verschiedene Schaltflächen verfolgen oder nur eine einzelne Schaltfläche?
@kkrambo Ich habe die komplette Funktion eingefügt, Sie haben wahrscheinlich das Problem gefunden. Ja, es behält den Wert von einem Aufruf zum anderen bei, ist aber nicht für mehr als eine Schaltfläche geeignet. Ich nehme an, der nächste Schritt wäre, a button_history_xxfür jede Schaltfläche richtig zu implementieren?
Sie haben eine einzelne statische Variable (button_history) in der Debounce-Funktion, die Sie versuchen, für alle Schaltflächen "zusammen" zu verwenden, nicht wahr? Wenn Sie also über Schaltflächen schleifen, erhalten Sie dort verschachtelte Bits von allen drei BTNs, und es entprellt nie, denke ich.
Dies ist ein ernsthaft obskurer Code für etwas so Einfaches. Ihre Funktion "read_btn" scheint sich aus unbekannten Gründen hauptsächlich darauf zu konzentrieren, den Inhalt aller E / A-Register zu zerstören. Der Code macht einfach keinen Sinn. Außerdem sind Atmel-MCUs ziemlich beschissen darin, Dinge direkt von E / A-Pins zu treiben ... sind Sie sicher, dass Sie etwa 10-20 mA aus diesen Pins herausholen können?
@Lundin Unsinn. Mit 60-80mA Sourcing-Fähigkeit (AMR) pro Pin sollten Sie sich wirklich nicht über die Ausgangstreiber beschweren. Das Treiben von 20 mA durch eine LED war mit AVRs nie ein Problem.
@ Martin Ich denke, das war das Problem. Ich habe separate Verlaufsvariablen für jede Schaltfläche hinzugefügt, aber ich kann die einzelnen Abfragetests für jede Schaltfläche in der ISR immer noch nicht korrekt stapeln. Diesmal scheint nur der letzte ausgewertet zu werden (statt gar keiner)
@JimmyB Dies ist meine Erfahrung mit Atmel - obwohl ich AVR nicht verwendet habe, sondern nur ihre ARM-Teile. Was die Besonderheiten dieses speziellen Teils ATtiny13 betrifft, so ist das Handbuch schlecht geschrieben. Sie finden anscheinend den Nennstrom zwischen den Zeilen, in denen die Ausgangs-Nieder- / Hochspannung aufgeführt ist. Angeblich kann dieser Pin -20mA bis +20mA verarbeiten, also sollte es in Ordnung sein.
@Lundin Das dachte ich mir. Aber die AVRs hatten schon immer vergleichsweise starke Ausgangstreiber. Siehe zB das Datenblatt des Tiny13, Seite 135, Abbildung 19-25.

Antworten (2)

Große Fortschritte gemacht, es gab 2 Probleme.

Wie von Martin und kkraambo vorgeschlagen, gab es das Problem, den Status aller 3 Schaltflächen korrekt zu verfolgen. Der von mir gepostete Code behielt eine statische Verlaufsvariable. Jetzt enthält der Hauptcode 3 verschiedene Verlaufsvariablen.

uint8_t mute_history=0;
uint8_t volp_history=0;
uint8_t volm_history=0;

uint8_t read_btn(uint8_t  curbtn){
uint8_t ret=0x00;
if (curbtn==0x01){
    DDRB &=~_BV(PB2);//PB2 en entree
    PORTB |=_BV(PB2);//pull-up actif
    nop();nop();nop();nop();
    ret= ( (PINB & _BV(PB2)) == 0 );
} else if (curbtn==0x02){
    DDRB |=_BV(PB2);//PB2 en sortie
    PORTB &=~_BV(PB2);//PB2 a 0
    DDRB &=~_BV(PB0);//PB0 en entree
    PORTB |=_BV(PB0);//pull up sur PB0
    nop();nop();nop();nop();
    ret= ( (PINB & _BV(PB0)) == 0 );
} else if (curbtn==0x04){
    DDRB |=_BV(PB0);//PB0 en sortie
    PORTB &=~_BV(PB0);//PB0 a 0
    DDRB &=~_BV(PB4);//PB4 en entree
    PORTB |=_BV(PB4);//pull up sur PB4
    nop();nop();nop();nop();
    ret= ((PINB & (1<<PB4)) == 0);//lecture de PB0
}
return ret;
}

uint8_t debounce(uint8_t  *button_history,uint8_t  curbtn){
uint8_t pressed = 0;
*button_history = *button_history << 1;
*button_history |= read_btn(curbtn);
if ((*button_history & 0b11000111) == 0b00000111) {
    pressed=1;
    *button_history = 0b11111111;
}
return pressed;
}


ISR (WDT_vect){

if (debounce(&volp_history,0x02)==1){
    PORTB ^= _BV(PB3);//flip led 2
}

if (debounce(&mute_history,0x01)==1){
    PORTB &= ~_BV(PB1);//turn off
    PORTB &= ~_BV(PB3);//turn off
}

if (debounce(&volm_history,0x04)==1){
    PORTB ^= _BV(PB1);//flip led 2
}

}

Das zweite Problem war ein Timing-Problem, das mir von Benutzer Tom Carpenter in diesem Thread vorgeschlagen wurde . Seine Kommentare waren:

@Polyphil versuchen Sie, einige Nop-Anweisungen hinzuzufügen. Sie können Folgendes verwenden: #define nop() __asm__ __volatile__ ("nop \n\t"), und dann in Ihrem Code nop();nop();nop(); kurz bevor Sie die return-Anweisung machen.

@Polyphil Die Eingänge am ATTiny haben aufgrund einer Synchronisiererkette eine Latenzzeit von zwei Taktzyklen, sodass es nach dem Ändern des Pullup-Werts mindestens 2-3 Taktzyklen dauert, bis er sich im PIN-Register widerspiegelt. Das Hinzufügen eines nop veranlasst den Prozessor, einen Taktzyklus zu warten.

daher die nops().

Die Schaltung verhält sich im Moment wie erwartet.

Wie oft rufen Sie beim WDT an? Warum wählen Sie nicht einen WDT-Prescaller, damit Sie überhaupt nicht entprellen müssen? Wenn Sie die Schaltflächen nur alle (sagen wir) 50-100 ms überprüfen, sollten sie sich selbst entprellen, und Ihr Code kann so einfach sein, dass Sie einfach jede der Schaltflächen der Reihe nach nacheinander überprüfen und auf den Zustand reagieren.

Ich habe die Prescaler-Einstellungen nicht geändert. Unter der Annahme, dass standardmäßig keine Vorskalierung eingestellt ist, sollte dies laut Datenblatt alle 16 ms erfolgen. Mir gefällt, wie das System mit dieser speziellen Entprellfunktion reagiert, aber ich sehe nicht, wie Ihre Idee nicht funktionieren würde. Der Unterschied wäre, dass das Gedrückthalten der Taste jedes Mal, wenn der WDT abläuft, als erneutes Drücken angezeigt wird.
@bigjosh Sie müssen mindestens zweimal abtasten, um tatsächlich zu entprellen, oder Ihr Ergebnis kann inkonsistent sein, wenn Sie während des Abprallens abtasten.
@jimmyb Wenn Sie langsamer als die maximale Absprungzeit und schneller als die kürzeste Auf- / Abwärtszeit abtasten, sehen Sie immer jede Zustandsänderung und kein Abprallen. Wenn Sie in der Praxis alle 50 ms einmal auf eine Schaltfläche klicken, werden die Bounces .ISS.
@polyphil Versuchen Sie, den Prescaler um ein oder zwei Schritte zu erhöhen, und ich wette, Sie werden keine Bounces mehr sehen. In der Praxis halte ich es für unwahrscheinlich, dass Sie eine Änderung der Reaktionsfähigkeit spüren werden.