Czy funkcja może być wywoływana automatycznie, gdy zmienia się wejście?

21

Obecnie mój szkic sprawdza pin wejściowy za każdym razem wokół głównej pętli. Jeśli wykryje zmianę, wywołuje niestandardową funkcję, aby na nią zareagować. Oto kod (skrócony do najważniejszych):

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

Niestety, nie zawsze działa to poprawnie w przypadku bardzo krótkich zmian na wejściu (np. Krótkich impulsów), szczególnie jeśli loop()działa nieco wolniej.

Czy istnieje sposób, aby Arduino wykrył zmianę wejścia i automatycznie wywołał moją funkcję?

Peter Bloomfield
źródło
1
To, czego szukasz, to Zewnętrzne przerwanie
Butzke,

Odpowiedzi:

26

Możesz to zrobić za pomocą zewnętrznych przerwań. Większość Arduinos obsługuje to jednak tylko na ograniczonej liczbie pinów. Aby uzyskać szczegółowe informacje, zobacz dokumentację na attachInterrupt().

Zakładając, że używasz Uno, możesz to zrobić w następujący sposób:

void pinChanged()
{
    //...
}

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

void loop()
{
}

Wywoła się pinChanged()za każdym razem, gdy zostanie wykryta zmiana w zewnętrznym przerwaniu 0. Na Uno, które odpowiada pinowi GPIO 2. Numeracja zewnętrznych przerwań jest inna na innych płytach, dlatego ważne jest sprawdzenie odpowiedniej dokumentacji.

Istnieją jednak ograniczenia tego podejścia. Funkcja niestandardowa pinChanged()jest używana jako procedura obsługi przerwań (ISR). Oznacza to, że reszta kodu (wszystko w środku loop()) jest tymczasowo zatrzymywana podczas wykonywania połączenia. Aby zapobiec zakłóceniu ważnych terminów, powinieneś dążyć do jak najszybszego uzyskania ISR.

Ważne jest również, aby pamiętać, że żadne inne zakłócenia nie będą działać podczas twojego ISR. Oznacza to, że wszystko zależne od przerwań (takie jak rdzeń delay()i millis()funkcje) może nie działać poprawnie w nim.

Na koniec, jeśli twój ISR musi zmienić dowolne zmienne globalne w szkicu, zwykle powinny być zadeklarowane jako volatile, np .:

volatile int someNumber;

Jest to ważne, ponieważ informuje kompilator, że wartość może się nieoczekiwanie zmienić, dlatego należy uważać, aby nie używać żadnych nieaktualnych kopii / pamięci podręcznych.

Peter Bloomfield
źródło
w odniesieniu do „krótkich impulsów” wymienionych w pytaniu, czy jest minimalny czas, w którym pin musi być w stanie, aby mógł wywołać przerwanie? (oczywiście będzie to znacznie mniej niż ankietowanie, które zależy od tego, co jeszcze dzieje się w pętli)
sachleen
1
@sachleen Będzie to działało, dopóki tak się nie stanie podczas wykonywania funkcji ISR ​​(jak wyjaśniono w odpowiedzi); dlatego pinChanged()powinien być jak najkrótszy. Stąd zwykle minimalny czas powinien być czasem wykonania pinChanged()samej funkcji.
jfpoilpret
2
+1 za tę bardzo szczegółową odpowiedź, która obejmuje wszystkie nieistotne rzeczy, o które trzeba dbać podczas korzystania z przerwań!
jfpoilpret
3
Oprócz deklarowania globalnych współużytkowanych volatile, jeśli zmienna globalna jest szersza niż 1 bajt, tak jak niektóreNumer, musisz zabezpieczyć się przed przerwaniem zmiany pinów występującym między programami przez dostęp do bajtów. Instrukcja typu someNumber +=5;obejmuje dodawanie niskich bajtów i dodawanie wysokich bajtów z dołączonym przeniesieniem. Te dwa (więcej, w przypadku szerszych zmiennych) nie mogą być dzielone przez przerwanie. Wystarczy wyłączyć przerwania i przywrócić je odpowiednio przed i po operacji.
JRobert
@sachleen - odnośnie minimalnego rozmiaru impulsu. Trudno jest znaleźć jednoznaczną odpowiedź w arkuszu danych, ale sądząc po czasie dla przerwań zmiany pinów, są one blokowane w ciągu pół cyklu zegarowego. Gdy przerwanie zostanie „zapamiętane”, pozostanie zapamiętane, dopóki ISR ​​nie rozpocznie i nie zajmie się tym.
Nick Gammon
5

Każdy stan zmiany dowolnego pinu skonfigurowanego jako wejście cyfrowe może spowodować przerwanie. W przeciwieństwie do unikalnych wektorów dla przerwań spowodowanych przez INT1 lub INT2, funkcja PinChangeInt wykorzystuje wspólny wektor, a następnie Procedura Przerwania Usługi (inaczej ISR) dla tego wektora musi następnie ustalić, który pin został zmieniony.

Na szczęście biblioteka PinChangeInt ułatwia to.

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
źródło
0

W przypadku, gdy chcesz wykryć napięcie przekraczające próg , zamiast być WYSOKIM lub NISKIM, możesz użyć komparatora analogowego. Przykładowy szkic:

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

Może to być przydatne w przypadku detektorów światła, w których może być konieczne wykrycie zmiany z (powiedzmy) 1V na 2V na wejściu.

Przykładowy obwód:

wprowadź opis zdjęcia tutaj

Możesz także użyć Input Capture Unit na procesorze, który zapamięta dokładny czas niektórych wejść, zapisując bieżącą liczbę Timer / Counter 1. Pozwala to zachować dokładny (dobrze, prawie dokładny) moment, w którym zdarzenie pojawiło się zainteresowanie, zamiast wprowadzać opóźnienie (prawdopodobnie kilka mikrosekund), zanim ISR będzie można wykorzystać do uchwycenia bieżącego czasu.

W przypadku aplikacji o krytycznym czasie może to dać nieco większą dokładność.

Przykład zastosowania: Zamień Arduino w tester kondensatorów

Nick Gammon
źródło