Utilisation correcte d'une interruption de changement de broche

10

J'essaie d'utiliser des interruptions de changement de broche pour détecter les boutons enfoncés. Jusqu'à présent, je n'ai jamais travaillé avec ce type d'interruptions et il y a quelques problèmes, donc je veux m'assurer que c'est la bonne utilisation.

Si j'ai bien compris la fiche technique, les choses suivantes doivent être faites pour utiliser une interruption de changement de broche:

  1. Définissez les codes PIN que vous souhaitez contrôler dans le registre PCMSK
  2. Activer le registre PIN pour le contrôle d'interruption de changement de broche (PCICR)
  3. Activer les interruptions
  4. Utilisez le vecteur d'interruption correspondant

Projet: Simple Moodlamp, couleurs contrôlées via 4 boutons.

Installer:

  • Atmega168A-PU
  • 4 mini interrupteurs à bouton-poussoir
  • MOSFETS pour contrôler ma LED RGB 3 Watt

Voici le code que j'utilise qui ne fonctionne pas comme prévu:

#include <avr/io.h>
#include <stdint.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define BUTTON1 (1<<PC5) 
#define BUTTON2 (1<<PC4) 
#define BUTTON3 (1<<PC3) 
#define BUTTON4 (1<<PC2) 

#define GREEN   (1<<PB1) 
#define BLUE    (1<<PB2) 
#define RED     (1<<PB3) 

void init() {

        // enable LED
        DDRB |= GREEN;
        DDRB |= BLUE;
        DDRB |= RED;

        // button pullups
        PORTC |= BUTTON1;
        PORTC |= BUTTON2;
        PORTC |= BUTTON3;
        PORTC |= BUTTON4;

        // pin change interrupts for buttons
        PCMSK1 |= PCINT13;
        PCMSK1 |= PCINT12;
        PCMSK1 |= PCINT11;
        PCMSK1 |= PCINT10;

        // enable pin change for buttons
        PCICR |= PCIE2;

        sei();

}

ISR(PCINT2_vect) {

                PORTB = BLUE;
}


void ledTest() {

                PORTB ^= RED;
                _delay_ms(250);
                PORTB ^= RED;
                _delay_ms(250);
                PORTB ^= RED;
                _delay_ms(250);
                PORTB ^= RED;


                PORTB ^= BLUE;
                _delay_ms(250);
                PORTB ^= BLUE;
                _delay_ms(250);
                PORTB ^= BLUE;
                _delay_ms(250);
                PORTB ^= BLUE;

                PORTB ^= GREEN;
                _delay_ms(250);
                PORTB ^= GREEN;
                _delay_ms(250);
                PORTB ^= GREEN;
                _delay_ms(250);
                PORTB ^= GREEN;
}

int main() {

        init();
        ledTest();

        _delay_ms(500);
        PORTB |= GREEN;

        while(1) {
                _delay_ms(100);
        }
}

Remarque: les boutons doivent être anti-rebond. Étant donné que j'essaie de procéder pas à pas et que cela ne devrait pas être important pour allumer la LED, je l'ai ignoré ici.

Question: La manière dont j'essaie d'utiliser les interruptions est-elle correcte?

Problèmes avec ma configuration:

  • Les boutons 1-3 sont totalement ignorés.
  • Button4 déclenche une réinitialisation de l'atmega

Choses que j'ai vérifiées:

  • Les boutons ne sont en aucun cas connectés au PIN de réinitialisation
  • Les boutons sont correctement connectés à GND s'ils sont enfoncés
  • Les boutons ne sont pas connectés à GND s'ils ne sont pas enfoncés
  • Les boutons fonctionnent bien si je les utilise sans interruption, par exemple:

    si (! (PINC & BUTTON4)) {PORTB ^ = BLUE; }

  • Cristal externe 16MHZ / cristal interne
  • Toute erreur dans le routage
  • J'utilise un condensateur 100nF entre PWR et GND sur l'atmega
  • VCC (7), GND (8), GND (22), AVCC (20) sont connectés (car je n'ai pas besoin d'AREF, ce n'est pas connecté)
écho
la source
Vous avez besoin du drapeau PCIE1 (pas PCIE2) et PCINT1_vect (pas PCINT2)
microtherion
Pourquoi PCIE1? J'utilise le registre C, donc si je compte, ce serait A (PCIE0), B (PCIE1), C (PCIE2)? Quoi qu'il en soit, je l'ai essayé avec PCIE1 nad PCINT1_vect et il n'y a aucune réaction si j'appuie sur les boutons.
echox
1
Il peut être un peu risqué de supposer l'ortogonalité dans de telles missions. Dans ce cas particulier, vous auriez presque raison, sauf que l'ATmega168 n'a pas de port A. En tout cas, je suis passé par la fiche technique et le brochage. Un autre conseil était que vous utilisiez PCIE2, mais que vous définissiez des bits dans PCMSK1; cela ne peut pas être vrai (malheureusement, je ne sais pas pourquoi votre croquis révisé ne fonctionne toujours pas).
microtherion
Merci, je comprends aussi que la combinaison de logiciels de débogage qui dépend du matériel de construction automatique n'est pas si simple ;-)
echox

Réponses:

14

Les interruptions de changement de broche ne sont généralement pas un bon moyen de détecter les actions des boutons. En effet, les boutons mécaniques rebondissent et vous obtiendrez de nombreuses interruptions dénuées de sens, et vous devrez quand même faire un anti-rebond de toute façon.

Une meilleure façon est d'avoir une interruption périodique, comme toutes les 1 ms (fréquence de 1 kHz). Cela prend beaucoup de temps sur la plupart des processeurs, donc la fraction de temps passée dans l'interruption sera petite. Échantillonnez simplement l'état du bouton à chaque interruption. Déclarez un nouvel état de bouton si vous avez vu le nouvel état 50 ms d'affilée. 50 ms est plus long que la plupart des boutons rebondissent, mais il est encore assez court pour que les humains ne remarquent pas le décalage ou ne s'en soucient pas.

Notez que de cette façon, vous pouvez également gérer plusieurs boutons dans la même interruption périodique de 1 ms. Tout ce dont vous avez besoin est d'un compteur pour chaque bouton.

Plus d'informations sur le temps de rebond:

Parfois, comme dans ce cas, quelqu'un dit que 50 ms est un temps de rebond trop long. Ce n'est pas vrai pour les boutons ordinaires appuyés par les humains. Cela pourrait être un problème dans des applications très critiques comme le chronomètre, mais jusqu'à présent, je n'en ai pas rencontré un. J'ai fait des tests là-dessus au début des années 80, et beaucoup d'autres personnes l'ont fait aussi.

Il est vrai que le temps de rebond typique des boutons-poussoirs est d'environ 10 ms, avec à peu près tous une stabilisation de 25 ms. Le facteur limitant du temps de rebond est la perception humaine. 50 ms est un peu plus court que lorsque les gens commencent à remarquer un retard lorsqu'ils ne le recherchent pas. Même alors, cela prend beaucoup plus de temps pour être ennuyeux. Il peut être possible dans certains cas pour un humain de détecter une différence entre 50 ms et 0 ms de retard s'il le recherche spécifiquement , mais c'est très différent de pousser un bouton et de voir quelque chose se produire et de ne pas penser au retard.

50 ms est donc un bon temps de rebond car le retard est inférieur à la limite de perception dans les applications ordinaires, bien inférieur à la limite de gêne, et bien supérieur au temps de rebond de la plupart des commutateurs. J'ai trouvé des commutateurs qui ont rebondi pendant presque aussi longtemps, alors vous pourriez aussi bien pousser à la limite de perception car il n'y a rien à perdre.

J'ai fait de nombreux produits avec des boutons anti-firmware avec un temps de anti-rebond de 50 ms. Pas une seule fois un client n'a mentionné avoir remarqué un retard. Ils ont tous accepté les boutons comme fonctionnant bien sans problème.

Olin Lathrop
la source
1
50 ms peuvent être trop longs pour certains cas (généralement, 10-20 ms est la limite de la perception humaine, et cela devrait être suffisant pour rebondir), mais la méthode décrite ici est la voie à suivre.
Laszlo Valko
1
@Laszlo: Non, 50 ms n'est pas trop long pour le cas ordinaire. Voir l'addition à ma réponse.
Olin Lathrop
J'ai essayé 50 ms, ce qui fonctionne bien pour moi :-) Je suis toujours curieux de savoir pourquoi l'interruption de changement de broche ne fonctionne pas (à côté des trucs rebondissants), mais cela fonctionne :-) Merci.
echox
1

Les interruptions de changement de broche sont un meilleur moyen de rebondir que d'interroger. L'interruption passe généralement par une logique telle qu'un D-Flip Flop ou D-Latch. Bien que cela soit vrai, il est plus difficile d'implémenter cette routine anti-rebond avec des compilateurs de niveau supérieur. Une fois que l'interruption se produit, le drapeau d'interruption n'est pas effacé et la validation d'interruption est effacée jusqu'à ce qu'un retard se soit produit. Une fois que le retard s'est produit, l'état de la broche est vérifié et s'il est toujours dans l'état donné qui a déclenché l'interruption, l'état du bouton est modifié et le drapeau d'interruption est effacé et la validation d'interruption est définie. S'il n'est pas dans l'état qui a provoqué l'initiation, la validation d'interruption est définie et l'état reste le même. Cela libère le processeur pour d'autres tâches. Des interruptions périodiques perdent du temps dans le programme.

Jim Vernay
la source
-1

"Les interruptions de changement de broche ne sont généralement pas un bon moyen de détecter les actions des boutons."

Faux. PC INT est la meilleure option. Si vous utilisez l'interrogation pour vérifier l'état d'un bouton, rien ne sera effectué la plupart du temps. Vous perdez beaucoup de temps CPU précieux. PC INT permet d'exécuter des actions uniquement sur demande.

"C'est parce que les boutons mécaniques rebondissent, et vous obtiendrez de nombreuses interruptions insignifiantes, et vous devrez quand même faire un anti-rebond de toute façon."

Corriger le rebond. Pourtant, vous ne devez JAMAIS faire rebondir un bouton / commutateur à l'intérieur d'une routine d'interruption (même raison: perte de temps CPU). Les ISR sont censés être vraiment courts et efficaces, au niveau du code. Utilisez simplement le rebouncing matériel. Gardez votre logiciel propre!

Le rebouncing matériel est plus pratique, voir ici / Debouncing RC + déclencheur Schmitt pour référence. Je l'ai utilisé d'innombrables fois avec PC INT, il n'a jamais échoué.

Alors oui, vous pouvez (et devriez) utiliser PC INT pour obtenir un état de bouton. Mais vous devez également utiliser un anti-rebond matériel approprié.

Nelson
la source
2
Le anti-rebond logiciel est une approche valable, et la plupart du temps, le peu de surcharge supplémentaire du processeur n'est pas pertinent. Dire que vous devriez généralement rebondir sur le matériel est au mieux discutable. Dire que vous devez utiliser le anti-rebond matériel dans tous les cas est tout simplement faux.
Olin Lathrop
Dans la plupart des applications, le contrôleur fonctionne de façon inactive la plupart du temps, exécutant la boucle principale. De plus, le temps CPU requis pour effectuer une vérification d'état d'E / S et potentiellement incrémenter une variable est minime. La mise en œuvre d'un simple anti-rebond dans le logiciel est presque gratuite, le matériel coûte de l'argent. Et ne riez pas de quelques centimes, l'assemblage coûte aussi de l'argent et si vous utilisez des volumes moyens à élevés d'un produit, ce n'est pas négligeable. Il est vrai que le temps ISR doit être court, mais ce n'est guère un argument dans ce cas. Son probablement plus critique si le PC INT ISR tire 50 fois de suite en raison de rebondir.
Rev1.0
@Nelson, la «perte de temps CPU» est importante dans certaines applications et pas dans beaucoup d'autres. Vous devez qualifier votre réponse pour une situation où le temps processeur est critique.
user1139880