ESP8266-12E Interrupciones y temporizadores

Las interrupciones nos permiten detectar cambios en el estado de GPIO sin la necesidad de verificar constantemente su valor actual. Con las interrupciones, cuando se detecta un cambio, se activa un evento (se llama a una función).

Como ejemplo, detectaremos movimiento usando un sensor de movimiento PIR: cuando se detecta movimiento, el ESP8266 inicia un temporizador y enciende un LED durante una cantidad predefinida de segundos. Cuando el temporizador termina la cuenta regresiva, el LED se apaga automáticamente.

Para crear una interrupción, hay que llamar a attachInterrupt() y pasarle como argumentos el pin de interrupción GPIO, la ISR (función a llamar) y el modo. La función ISR debe tener declarado el atributo ICACHE_RAM_ATTR. El modo puede ser CAMBIO, ASCENSO o DESCENSO.

attachInterrupt(digitalPinToInterrupt(GPIO), ISR, mode);

Las interrupciones ESP8266

Las interrupciones son útiles para hacer que las cosas sucedan automáticamente en los programas de microcontroladores y pueden ayudar a resolver problemas de temporización.

Con las interrupciones, no se necesita verificar constantemente el valor del pin actual. Cuando se detecta un cambio, se activa un evento: se llama a una función. Esta función se llama rutina de servicio de interrupción (ISR).

Cuando ocurre una interrupción, el procesador detiene la ejecución del programa principal para ejecutar una tarea y luego regresa al programa principal.

Esto es especialmente útil para activar una acción cuando se detecta movimiento o cuando se presiona un botón sin la necesidad de verificar constantemente su estado.

 

Función attachInterrupt()

Para configurar una interrupción en el IDE de Arduino, se utiliza la función attachInterrupt(), que acepta como argumentos: el pin de interrupción GPIO, el nombre de la función a ejecutar y el modo:

attachInterrupt(digitalPinToInterrupt(GPIO), ISR, mode);

El primer argumento es una interrupción GPIO. Se debe usar digitalPinToInterrupt(GPIO) para configurar el GPIO real como un pin de interrupción. Por ejemplo, si se desea utilizar GPIO 14 como interrupción, escribir en el setup():

digitalPinToInterrupt(14);

El ESP8266 admite interrupciones en cualquier GPIO, excepto GPIO16.

El segundo argumento de la función attachInterrupt() es el nombre de la función que se llamará cada vez que se active la interrupción: la rutina de servicio de interrupción (ISR).

La función ISR debe ser lo más simple posible, para que el procesador vuelva rápidamente a la ejecución del programa principal.

El mejor enfoque es señalar al código principal que la interrupción ha ocurrido mediante el uso de una variable global y dentro del bucle () verificar y borrar esa bandera y ejecutar el código.

Los ISR deben tener ICACHE_RAM_ATTR antes de la definición de la función para ejecutar el código de interrupción en la RAM.

Modos de interrupción

El tercer argumento es el modo y hay 3 modos diferentes:

  1. CHANGE: para activar la interrupción siempre que el pin cambie de valor, por ejemplo, de ALTO a BAJO o de BAJO a ALTO;
  2. FALLING: para cuando el pin pasa de HIGH a LOW;
  3. RISING: para disparar cuando el pin va de BAJO a ALTO.

Para nuestro ejemplo, usaremos el modo RISING, porque cuando el sensor de movimiento PIR detecta movimiento, el GPIO al que está conectado pasa de BAJO a ALTO.

Los temporizadores ESP8266 NodeMCU.

Queremos que el LED permanezca encendido durante un número predeterminado de segundos después de que se detecte movimiento. En lugar de usar una función de retraso dela() que bloquea su código y no le permite hacer nada más durante un número determinado de segundos, usaremos un temporizador.

delay() vs millis()

La función delay() acepta un solo número int como argumento. Este número representa el tiempo en milisegundos que el programa debe esperar hasta pasar a la siguiente línea de código.

delay (tiempo en milisegundos);

Cuando se llama a delay(1000), el programa se detiene en esa línea durante 1 segundo. delay() es una función de bloqueo. Las funciones de bloqueo evitan que un programa haga cualquier otra cosa hasta que se complete esa tarea en particular. Si necesita que se realicen varias tareas al mismo tiempo, no puede usar delay(). Para la mayoría de los proyectos, debe evitar el uso de retrasos y utilizar temporizadores en su lugar.

Usando una función llamada millis(), podemos devolver la cantidad de milisegundos que han pasado desde que se inició el programa por primera vez.

milis();

¿Por qué es útil esa función? Porque al usar algunas matemáticas, puede verificar fácilmente cuánto tiempo ha pasado sin bloquear su código.

Parpadeando un LED usando millis() (sin demora)

El siguiente fragmento de código muestra cómo se puede usar la función millis() para crear un proyecto de parpadeo. Enciende un LED durante 1000 milisegundos y luego lo apaga.

// constants won't change. Used here to set a pin number :
const int ledPin =  26;      // the number of the LED pin

// Variables will change :
int ledState = LOW;             // ledState used to set the LED

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;        // will store last time LED was updated

// constants won't change :
const long interval = 1000;           // interval at which to blink (milliseconds)

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
}

void loop() {
  // here is where you'd put code that needs to be running all the time.

  // check to see if it's time to blink the LED; that is, if the
  // difference between the current time and last time you blinked
  // the LED is bigger than the interval at which you want to
  // blink the LED.
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

Esquema

Montar el sensor de movimiento PIR y un LED en el ESP8266. Conectaremos el LED a GPIO 12 (D6) y el pin de datos del sensor de movimiento PIR a GPIO 14 (D5).

esp8266_interrupts_pir_sensor_bb

Importante: el sensor de movimiento PIR Mini AM312 utilizado en este proyecto funciona a 3,3 V. Sin embargo, si está utilizando otro sensor de movimiento PIR como el HC-SR501, funciona a 5V. Puede modificarlo para que funcione a 3,3 V o simplemente alimentarlo con el pin Vin.

Código:

Se puede cargar el código tal como está, o se puede modificar la cantidad de segundos que se enciende el LED después de detectar movimiento. Simplemente cambie la variable timeSeconds con la cantidad de segundos que desee.

#define timeSeconds 10

// Set GPIOs for LED and PIR Motion Sensor
const int led = 12;
const int motionSensor = 14;

// Timer: Auxiliary variables
unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;

// Checks if motion was detected, sets LED HIGH and starts a timer
ICACHE_RAM_ATTR void detectsMovement() {
  Serial.println("MOTION DETECTED!!!");
  digitalWrite(led, HIGH);
  startTimer = true;
  lastTrigger = millis();
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(115200);
  
  // PIR Motion Sensor mode INPUT_PULLUP
  pinMode(motionSensor, INPUT_PULLUP);
  // Set motionSensor pin as interrupt, assign interrupt function and set RISING mode
  attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);

  // Set LED to LOW
  pinMode(led, OUTPUT);
  digitalWrite(led, LOW);
}

void loop() {
  // Current time
  now = millis();
  // Turn off the LED after the number of seconds defined in the timeSeconds variable
  if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
    Serial.println("Motion stopped...");
    digitalWrite(led, LOW);
    startTimer = false;
  }
}