Una funzione può essere chiamata automaticamente quando un input cambia?


21

Attualmente, il mio schizzo sta controllando un pin di input ogni volta attorno al loop principale. Se rileva una modifica, chiama una funzione personalizzata per rispondere ad essa. Ecco il codice (ridotto all'essenziale):

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();
    }
}

Sfortunatamente, questo non funziona sempre correttamente per modifiche molto brevi sull'ingresso (ad es. Brevi impulsi), specialmente se loop()funziona un po 'lentamente.

C'è un modo per fare in modo che Arduino rilevi la modifica dell'ingresso e chiami automaticamente la mia funzione?


1
Quello che stai cercando è un interrupt esterno
Butzke,

Risposte:


26

Puoi farlo usando interrupt esterni. La maggior parte degli Arduinos lo supporta solo su un numero limitato di pin. Per i dettagli completi, consultare la documentazione su attachInterrupt().

Supponendo che tu stia utilizzando uno Uno, potresti farlo in questo modo:

void pinChanged()
{
    //...
}

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

void loop()
{
}

Questo chiamerà pinChanged()ogni volta che viene rilevata una modifica sull'interrupt esterno 0. Su Uno, che corrisponde al pin GPIO 2. La numerazione degli interrupt esterni è diversa su altre schede, quindi è importante controllare la relativa documentazione.

Vi sono tuttavia limiti a questo approccio. La pinChanged()funzione personalizzata viene utilizzata come routine di servizio di interruzione (ISR). Ciò significa che il resto del codice (tutto in loop()) viene temporaneamente arrestato durante l'esecuzione della chiamata. Al fine di prevenire l'interruzione di qualsiasi tempismo importante, si dovrebbe mirare a rendere gli ISR ​​il più rapidamente possibile.

È anche importante notare che durante il tuo ISR non verranno eseguiti altri interrupt. Ciò significa che tutto ciò che si basa sugli interrupt (come il core delay()e le millis()funzioni) potrebbe non funzionare correttamente al suo interno.

Infine, se il tuo ISR deve modificare eventuali variabili globali nello schizzo, di solito dovrebbero essere dichiarate come volatile, ad esempio:

volatile int someNumber;

Questo è importante perché dice al compilatore che il valore potrebbe cambiare inaspettatamente, quindi si dovrebbe fare attenzione a non utilizzare copie / cache non aggiornate.


per quanto riguarda i "brevi impulsi" menzionati nella domanda, esiste un tempo minimo in cui il pin deve essere in uno stato per attivare l'interrupt? (ovviamente sarà molto meno del polling, che dipende da cos'altro sta accadendo nel loop)
sachleen,

1
@sachleen Funzionerà finché non si verifica durante l'esecuzione di una funzione ISR (come spiegato nella risposta); ecco perché pinChanged()dovrebbe essere il più corto possibile. Quindi, in genere, il tempo minimo dovrebbe essere il tempo per eseguire la pinChanged()funzione stessa.
jfpoilpret,

2
+1 per questa risposta molto dettagliata che include tutte le cose importanti di cui ci si deve preoccupare quando si usano gli interrupt!
jfpoilpret,

3
Oltre a dichiarare i globali condivisi volatile, se la variabile globale è più larga di 1 byte, come lo è someNumber, è necessario proteggersi dall'interruzione di cambio pin che si verifica tra gli accessi ai byte da parte del programma. Un'istruzione del tipo someNumber +=5;comporta l'aggiunta di byte bassi e l'aggiunta di byte alti con carry incluso. Questi due (più, per variabili più ampie) non devono essere divisi da un interrupt. Disattivare gli interrupt e ripristinarli prima e dopo l'operazione (rispettivamente) è sufficiente.
JRobert,

@sachleen - per quanto riguarda la dimensione minima dell'impulso. È difficile trovare una risposta definita nel foglio dati, ma a giudicare dalla tempistica degli interrupt di cambio pin, vengono bloccati in mezzo ciclo di clock. Una volta "ricordato" l'interrupt rimane memorizzato fino a quando l'ISR non interviene e si occupa di esso.
Nick Gammon

5

Qualsiasi stato di cambiamento su qualsiasi pin configurato come input digitale può creare un interrupt. A differenza dei vettori univoci per le cause di interruzione di INT1 o INT2, la funzione PinChangeInt utilizza un vettore comune e quindi la routine di servizio di interruzione (aka ISR) per questo vettore deve determinare quale pin è stato modificato.

Fortunatamente PinChangeInt Library rende tutto più semplice.

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

0

Nel caso in cui si desideri rilevare una tensione che passa una soglia , anziché essere semplicemente ALTA o BASSA, è possibile utilizzare il comparatore analogico. Esempio di schizzo:

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

Questo può essere utile per cose come i rivelatori di luce, in cui potrebbe essere necessario rilevare un cambiamento da (diciamo) 1V a 2V su un ingresso.

Esempio di circuito:

inserisci qui la descrizione dell'immagine

È inoltre possibile utilizzare l'Input Capture Unit sul processore, che ricorderà l'ora esatta di determinati input, salvando il conteggio corrente di Timer / Contatore 1. Ciò consente di memorizzare il momento esatto (o quasi, esatto) in cui l'evento di si è verificato un interesse, anziché introdurre il ritardo (probabilmente di alcuni microsecondi) prima che un ISR possa essere utilizzato per acquisire l'ora corrente.

Per applicazioni critiche in termini di tempi, ciò può fornire una maggiore precisione.

Esempio di applicazione: trasforma il tuo Arduino in un tester per condensatori

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.