Kann eine Funktion automatisch aufgerufen werden, wenn sich ein Eingang ändert?

21

Derzeit prüft meine Skizze jedes Mal einen Eingabestift in der Hauptschleife. Wenn eine Änderung festgestellt wird, wird eine benutzerdefinierte Funktion aufgerufen, um darauf zu reagieren. Hier ist der Code (auf das Wesentliche reduziert):

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

Leider funktioniert dies bei sehr kurzen Änderungen am Eingang (z. B. kurzen Impulsen) nicht immer einwandfrei, insbesondere wenn dieser loop()etwas langsam läuft.

Gibt es eine Möglichkeit, den Arduino die Eingabeänderung erkennen zu lassen und meine Funktion automatisch aufzurufen?

Peter Bloomfield
quelle
1
Was Sie suchen, ist ein externer Interrupt
Butzke

Antworten:

26

Sie können dazu externe Interrupts verwenden. Die meisten Arduinos unterstützen dies jedoch nur mit einer begrenzten Anzahl von Pins. Ausführliche Informationen finden Sie in der Dokumentation zu attachInterrupt().

Angenommen, Sie verwenden ein Uno, könnten Sie dies folgendermaßen tun:

void pinChanged()
{
    //...
}

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

void loop()
{
}

Dies wird pinChanged()immer dann aufgerufen, wenn eine Änderung am externen Interrupt 0 festgestellt wird. Auf der Uno entspricht dies dem GPIO-Pin 2. Die Nummerierung der externen Interrupts unterscheidet sich auf anderen Karten, daher ist es wichtig, die entsprechende Dokumentation zu überprüfen.

Diesem Ansatz sind jedoch Grenzen gesetzt. Die benutzerdefinierte pinChanged()Funktion wird als Interrupt Service Routine (ISR) verwendet. Das bedeutet, dass der Rest des Codes (alles in loop()) vorübergehend angehalten wird, während der Aufruf ausgeführt wird. Um zu verhindern, dass wichtige Timings gestört werden, sollten Sie versuchen, ISRs so schnell wie möglich zu machen.

Es ist auch wichtig zu beachten, dass während Ihres ISR keine anderen Interrupts ausgeführt werden. Das bedeutet, dass alles, was auf Interrupts angewiesen ist (wie der Kern delay()und die millis()Funktionen), möglicherweise nicht richtig funktioniert.

Wenn Ihr ISR globale Variablen in der Skizze ändern muss, sollten diese normalerweise wie folgt deklariert volatilewerden:

volatile int someNumber;

Dies ist wichtig, da der Compiler darauf hingewiesen wird, dass sich der Wert unerwartet ändern kann. Daher sollte darauf geachtet werden, keine veralteten Kopien / Caches zu verwenden.

Peter Bloomfield
quelle
Gibt es in Bezug auf die in der Frage erwähnten "kurzen Impulse" eine Mindestzeit, die der Pin in einem Zustand sein muss, damit er den Interrupt auslöst? (Offensichtlich wird es viel weniger als Umfragen sein, was davon abhängt, was sonst noch in der Schleife passiert)
Sachleen
1
@sachleen Das funktioniert, solange es nicht während der Ausführung einer ISR-Funktion auftritt (wie in der Antwort erläutert). deshalb pinChanged()sollte es so kurz wie möglich sein. Daher sollte die minimale Zeit normalerweise die Zeit sein, um die pinChanged()Funktion selbst auszuführen .
jfpoilpret
2
+1 für diese sehr detaillierte Antwort, die alle wichtigen Dinge enthält, die man bei der Verwendung von Interrupts beachten muss!
Jfpoilpret
3
volatileWenn die globale Variable, wie someNumber, breiter als 1 Byte ist, müssen Sie nicht nur gemeinsam genutzte Globale deklarieren , sondern auch vor dem Pin-Wechsel-Interrupt schützen, der zwischen den Byte-Zugriffen des Programms auftritt. Bei einer Anweisung wie dieser werden someNumber +=5;die niedrigen Bytes und die hohen Bytes mit eingeschlossenem Übertrag hinzugefügt. Diese beiden (für breitere Variablen) dürfen nicht durch einen Interrupt geteilt werden. Das Ausschalten und Wiederherstellen der Interrupts vor bzw. nach der Operation ist ausreichend.
JRobert
@sachleen - in Bezug auf die minimale Pulsgröße. Es ist schwer, eine eindeutige Antwort im Datenblatt zu finden, aber gemessen am Timing für Pinwechsel-Interrupts werden sie innerhalb eines halben Taktzyklus zwischengespeichert. Sobald der Interrupt "gespeichert" ist, bleibt er gespeichert, bis der ISR einschaltet und sich damit befasst.
Nick Gammon
5

Jeder Änderungszustand an einem als Digitaleingang konfigurierten Pin kann zu einem Interrupt führen. Im Gegensatz zu den eindeutigen Vektoren für die Interrupts, die von INT1 oder INT2 verursacht werden, verwendet die Funktion PinChangeInt einen gemeinsamen Vektor und die Interrupt Service Routine (auch bekannt als ISR) für diesen Vektor muss dann bestimmen, welcher Pin geändert wurde.

Glücklicherweise macht PinChangeInt Library dies einfach.

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
quelle
0

Für den Fall, dass Sie eine Spannung erkennen möchten, die einen Schwellenwert überschreitet , anstatt nur HOCH oder NIEDRIG zu sein, können Sie den analogen Komparator verwenden. Beispielskizze:

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

Dies kann nützlich sein für Dinge wie Lichtdetektoren, bei denen Sie möglicherweise eine Änderung von (sagen wir) 1 V auf 2 V an einem Eingang feststellen müssen.

Beispielschaltung:

Bildbeschreibung hier eingeben

Sie können auch die Input Capture Unit auf dem Prozessor verwenden, die die genaue Zeit bestimmter Eingänge speichert, indem Sie die aktuelle Zählung von Timer / Counter 1 speichern. Auf diese Weise können Sie den genauen (nahezu genauen) Zeitpunkt des Ereignisses speichern Anstatt die Verzögerung (von wahrscheinlich einigen Mikrosekunden) einzuführen, bevor ein ISR zum Erfassen der aktuellen Zeit verwendet werden kann, trat Interesse auf.

Für zeitkritische Anwendungen kann dies zu einer etwas höheren Genauigkeit führen.

Beispielanwendung: Verwandeln Sie Ihren Arduino in einen Kondensatortester

Nick Gammon
quelle