Une fonction peut-elle être appelée automatiquement lorsqu'une entrée change?

21

Actuellement, mon croquis vérifie une broche d'entrée à chaque fois dans la boucle principale. S'il détecte un changement, il appelle une fonction personnalisée pour y répondre. Voici le code (réduit à l'essentiel):

int pinValue = LOW;

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
}

void loop()
{
    // Read current input
    int newValue = digitalRead(2);

    // Has the input changed?
    if (newValue != pinValue) {
        pinValue = newValue;
        pinChanged();
    }
}

Malheureusement, cela ne fonctionne pas toujours correctement pour des changements très courts sur l'entrée (par exemple de brèves impulsions), surtout si elle loop()fonctionne un peu lentement.

Existe-t-il un moyen de faire en sorte que l'Arduino détecte le changement d'entrée et appelle automatiquement ma fonction?

Peter Bloomfield
la source
1
Ce que vous cherchez, c'est une interruption externe
Butzke

Réponses:

26

Vous pouvez le faire en utilisant des interruptions externes. La plupart des Arduinos ne prennent en charge cela que sur un nombre limité de broches. Pour plus de détails, consultez la documentation sur attachInterrupt().

En supposant que vous utilisez un Uno, vous pouvez le faire comme ceci:

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
    attachInterrupt(0, pinChanged, CHANGE);
}

void loop()
{
}

Cela appellera pinChanged()chaque fois qu'un changement est détecté sur l'interruption externe 0. Sur l'Uno, cela correspond à la broche GPIO 2. La numérotation des interruptions externes est différente sur les autres cartes, il est donc important de vérifier la documentation pertinente.

Il y a cependant des limites à cette approche. La pinChanged()fonction personnalisée est utilisée comme routine de service d'interruption (ISR). Cela signifie que le reste du code (tout ce qu'il contient loop()) est temporairement arrêté pendant l'exécution de l'appel. Afin d'éviter de perturber un timing important, vous devriez viser à rendre les ISR aussi rapides que possible.

Il est également important de noter qu'aucune autre interruption ne s'exécutera pendant votre ISR. Cela signifie que tout ce qui s'appuie sur des interruptions (comme le noyau delay()et les millis()fonctions) peut ne pas fonctionner correctement à l'intérieur.

Enfin, si votre ISR doit modifier des variables globales dans l'esquisse, elles doivent généralement être déclarées comme volatile, par exemple:

volatile int someNumber;

C'est important car il indique au compilateur que la valeur peut changer de manière inattendue, il convient donc de ne pas en utiliser de copie / cache obsolète.

Peter Bloomfield
la source
en ce qui concerne les "brèves impulsions" mentionnées dans la question, y a-t-il un temps minimum pendant lequel la broche doit être dans un état pour déclencher l'interruption? (évidemment, ce sera beaucoup moins que le sondage, qui dépend de ce qui se passe dans la boucle)
sachleen
1
@sachleen Cela fonctionnera tant que cela ne se produit pas pendant l'exécution d'une fonction ISR (comme expliqué dans la réponse); c'est pourquoi pinChanged()devrait être aussi court que possible. Par conséquent, le temps minimum devrait généralement être le temps pour exécuter la pinChanged()fonction elle-même.
jfpoilpret
2
+1 pour cette réponse très détaillée qui comprend toutes les choses importantes dont il faut se soucier lors de l'utilisation des interruptions!
jfpoilpret
3
En plus de déclarer des globales partagées volatile, si la variable globale est plus large que 1 octet, comme l'est certains numéros, vous devez vous protéger contre l'interruption de changement de broche qui se produit entre les accès en octets par le programme. Une déclaration comme someNumber +=5;implique l'ajout des octets bas et l'ajout des octets hauts avec report inclus. Ces deux (plus, pour les variables plus larges) ne doivent pas être divisés par une interruption. La désactivation des interruptions et leur restauration (respectivement) avant et après l'opération est suffisante.
JRobert
@sachleen - concernant la taille d'impulsion minimale. Il est difficile de trouver une réponse définitive dans la fiche technique, mais à en juger par le moment des interruptions de changement de broche, elles sont verrouillées dans un demi-cycle d'horloge. Une fois que l'interruption est "mémorisée", elle reste mémorisée jusqu'à ce que l'ISR intervienne et la gère.
Nick Gammon
5

Tout état de changement sur n'importe quelle broche configurée comme entrée numérique peut créer une interruption. Contrairement aux vecteurs uniques pour les causes d'interruptions par INT1 ou INT2, la fonction PinChangeInt utilise un vecteur commun, puis la routine de service d'interruption (aka ISR) pour ce vecteur doit ensuite déterminer quelle broche a changé.

Heureusement, la bibliothèque PinChangeInt facilite cela.

PCintPort::attachInterrupt(PIN, burpcount,RISING); // attach a PinChange Interrupt to our pin on the rising edge
// (RISING, FALLING and CHANGE all work with this library)
// and execute the function burpcount when that pin changes
mpflaga
la source
0

Dans le cas où vous souhaitez détecter une tension dépassant un seuil , plutôt que d'être simplement HAUT ou BAS, vous pouvez utiliser le comparateur analogique. Exemple d'esquisse:

volatile boolean triggered;

ISR (ANALOG_COMP_vect)
  {
  triggered = true;
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ("Started.");
  ADCSRB = 0;           // (Disable) ACME: Analog Comparator Multiplexer Enable
  ACSR =  bit (ACI)     // (Clear) Analog Comparator Interrupt Flag
        | bit (ACIE)    // Analog Comparator Interrupt Enable
        | bit (ACIS1);  // ACIS1, ACIS0: Analog Comparator Interrupt Mode Select (trigger on falling edge)
   }  // end of setup

void loop ()
  {
  if (triggered)
    {
    Serial.println ("Triggered!"); 
    triggered = false;
    }

  }  // end of loop

Cela peut être utile pour des choses comme les détecteurs de lumière, où vous devrez peut-être détecter un changement (disons) de 1 V à 2 V sur une entrée.

Exemple de circuit:

entrez la description de l'image ici

Vous pouvez également utiliser l'unité de capture d'entrée sur le processeur, qui se souviendra de l'heure exacte de certaines entrées, en enregistrant le nombre actuel de Timer / Counter 1. Cela vous permet de stocker le moment exact (enfin, presque exact) que l'événement de l'intérêt s'est produit, plutôt que d'introduire le délai (probablement de quelques microsecondes) avant qu'un ISR puisse être utilisé pour capturer l'heure actuelle.

Pour les applications à synchronisation critique, cela peut donner une précision quelque peu accrue.

Exemple d'application: Transformez votre Arduino en testeur de condensateur

Nick Gammon
la source