¿Se puede invocar una función automáticamente cuando cambia una entrada?

21

Actualmente, mi boceto está comprobando un pin de entrada cada vez que rodea el bucle principal. Si detecta un cambio, llama a una función personalizada para responder. Aquí está el código (recortado a lo esencial):

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

Desafortunadamente, esto no siempre funciona correctamente para cambios muy cortos en la entrada (por ejemplo, pulsos breves), especialmente si loop()se ejecuta un poco lento.

¿Hay alguna manera de hacer que el Arduino detecte el cambio de entrada y llame a mi función automáticamente?

Peter Bloomfield
fuente
1
Lo que está buscando es una interrupción externa
Butzke

Respuestas:

26

Puede hacer esto usando interrupciones externas. Sin embargo, la mayoría de los Arduinos solo admiten esto en un número limitado de pines. Para más detalles, consulte la documentación en attachInterrupt().

Suponiendo que esté usando un Uno, podría hacerlo así:

void pinChanged()
{
    //...
}

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

void loop()
{
}

Esto llamará pinChanged()cada vez que se detecte un cambio en la interrupción externa 0. En el Uno, que corresponde al pin 2 de GPIO. La numeración de la interrupción externa es diferente en otras tarjetas, por lo que es importante verificar la documentación relevante.

Sin embargo, hay limitaciones para este enfoque. La pinChanged()función personalizada se está utilizando como una Rutina de servicio de interrupción (ISR). Eso significa que el resto del código (todo en loop()) se detiene temporalmente mientras se ejecuta la llamada. Para evitar la interrupción de cualquier momento importante, debe intentar hacer que los ISR sean lo más rápidos posible.

También es importante tener en cuenta que no se ejecutarán otras interrupciones durante su ISR. Eso significa que cualquier cosa que dependa de interrupciones (como el núcleo delay()y las millis()funciones) puede no funcionar correctamente dentro de él.

Por último, si su ISR necesita cambiar alguna variable global en el boceto, generalmente debe declararse como volatile, por ejemplo:

volatile int someNumber;

Eso es importante porque le dice al compilador que el valor puede cambiar inesperadamente, por lo que debe tener cuidado de no usar copias / cachés desactualizadas.

Peter Bloomfield
fuente
con respecto a los "pulsos breves" mencionados en la pregunta, ¿hay un tiempo mínimo que el pin debe estar en un estado para que active la interrupción? (obviamente será mucho menos de sondeo, lo que depende de lo que esté sucediendo en el bucle)
sachleen
1
@sachleen Eso funcionará mientras no ocurra durante la ejecución de una función ISR (como se explica en la respuesta); por eso pinChanged()debe ser lo más corto posible. Por lo tanto, normalmente el tiempo mínimo debería ser el tiempo para ejecutar la pinChanged()función en sí.
jfpoilpret
2
¡+1 para esta respuesta muy detallada que incluye todas las cosas importantes que uno debe preocuparse al usar interrupciones!
jfpoilpret
3
Además de declarar globals compartidos volatile, si la variable global es más ancha que 1 byte, como lo es algúnNumber, debe protegerse contra la interrupción de cambio de pin que ocurre entre los accesos de byte por el programa. Una declaración como someNumber +=5;implica agregar los bytes bajos y agregar los bytes altos con carry incluido. Estos dos (más, para variables más amplias) no deben dividirse por una interrupción. Apagar las interrupciones y restaurarlas antes y después de la operación (respectivamente) es suficiente.
JRobert
@sachleen: con respecto al tamaño mínimo del pulso. Es difícil encontrar una respuesta definitiva en la hoja de datos, pero a juzgar por el momento de las interrupciones de cambio de clavija, se enclavan en medio ciclo de reloj. Una vez que la interrupción es "recordada", se mantiene recordada hasta que el ISR se activa y se ocupa de ella.
Nick Gammon
5

Cualquier estado de cambio en cualquier pin configurado como entrada digital puede crear una interrupción. A diferencia de los vectores únicos para las interrupciones causadas por INT1 o INT2, la función PinChangeInt usa un vector común y luego la Rutina de servicio de interrupción (también conocida como ISR) para este vector necesita determinar qué pin cambió.

Afortunadamente, la biblioteca PinChangeInt lo hace fácil.

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

En el caso de que desee detectar un voltaje que pase un umbral , en lugar de ser simplemente ALTO o BAJO, puede usar el comparador analógico. Bosquejo de ejemplo:

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

Esto puede ser útil para cosas como detectores de luz, donde es posible que necesite detectar un cambio de (digamos) 1V a 2V en una entrada.

Circuito de ejemplo:

ingrese la descripción de la imagen aquí

También puede usar la Unidad de captura de entrada en el procesador, que recordará el tiempo exacto de ciertas entradas, guardando el recuento actual de Temporizador / Contador 1. Esto le permite almacenar el momento exacto (bueno, casi exacto) que el evento de se produjo interés, en lugar de introducir el retraso (probablemente de unos pocos microsegundos) antes de que un ISR pueda usarse para capturar la hora actual.

Para aplicaciones críticas de tiempo, esto puede proporcionar una precisión algo mayor.

Aplicación de ejemplo: Convierta su Arduino en un probador de condensadores

Nick Gammon
fuente