Sensor PIR, resistencia LDR, LEDS SMD3538 e interrupciones hardware para luz guía nocturna de bajo consumo

Buenas, la idea de este proyecto es aprovechar los leds "no inteligentes" SMD3538 cuyo precio por lote de 30 leds ronda en oferta un precio inferior a un euro ,para iluminar aquellas zonas lúgubres de nuestra casa, donde siempre es necesario encender la luz de un mayor consum. En combinación con el sensor PIR y la resistencia LDR, de los que ya hablamos en otras entradas, en nuestro controlador (elegimos un arduino nano, pero podríamos intentarlo con el ATTINY) nos permite llevar a cabo dicha funcionalidad

Escenarios

Un posible escenario sería el siguiente: 

  • La resistencia LDR lee los valores de luz, cuando baja de un determinado umbral le pasa el control al sensor PIR
  • El sensor PIR se activa si el valor del LDR baja de un umbral, si detecta movimiento enciende los leds, en caso contrario no hace nada, y sigue esperando a estar activo si detecta movimiento

El consumo de un Arduino nano es de 15 mA por defecto (en este primer escenario con el controlador encendido todo el día), si añadimos además la posibilidad de canalizar la detección del PIR a través de una interrupción asociada al pin digital D2, de tal manera que mientras no detecta movimiento gracias a la librería LowPower.h  se encuentre apagado, y el controlador solo se encendería cuando detecta movimiento. El consumo estando apagado siempre que no detecte movimiento es de solo aproximadamente unos 4 - 5 mA, un tercio del consumo estando encendido. Este escenario resulta más ventajoso y atractivo para ahorrar energía y por tanto limitar el consumo, es más podemos hacer funcionar nuestro proyecto con un batería de litio recargable si fuera necesario y usar este proyecto en zonas aisladas sin conexión eléctrica.

En este segundo escenario, que es el que vamos a implementar, la secuencia de acciones sería:

  • El sensor PIR se asocia a la interrupción de nuestro arduino Nano, en el pin D2 (veremos cómo en el siguiente apartado)
  • Si el sensor PIR detecta movimiento enciende nuestro arduino nano, si no detecta movimiento apaga arduino nano, con el consiguiente ahorro de consumo.
  • Si el PIR además de detectar movimiento, nuestra resistencia LDR lee un valor de luminosidad inferior al umbral , enciende los leds

Las interrupciones de hardware en Arduino y el sensor PIR

El flujo de control de un sketch es secuencia e iterativo (consultar, comprobar la condición, ejecutar). Esto puede ser útil para muchos de los proyectos pero resulta ineficiente y a veces inservible si queremos ahorrar energía.
 
Las interrupciones son  un mecanismo que permiten ejecutar una función cuando se produce un determinado evento permitiendo abstraerse del flujo de control de instrucciones habitual. Esta función asociada se denomina ISR (Interruption Service Rutine).
 
Cuando ocurre el evento gracias la asociación de la interrupción con la función  ISR el procesador se aleja del flujo normal de instrucciones secuenciales que vendrían a continuación y sin embargo ejecuta la función ISR asociada. Al finalizar la función ISR asociada, el procesador recupera el flujo principal, siempre en el mismo punto donde había sido interrumpido debido al evento que lanzó la función ISR. La función ejecuta un pequeño segmento de código que no debe durar mucho tiempo como la asignación de un valor a una variable global, etc
 
Las interrupciones de hardware responden a eventos ocurridos en ciertos pines físicos. Cuando se gestiona interrupciones en Arduino, hay tres cosas importantes que definen este tipo de interrupción:
  1. El pin utilizado para la interrupción
  2. Las funciones utilizadas cuando se produce la interrupción
  3. El modo que se aplica para la interrupción

El método que nos permite asociar el pin de interrupción a la rutina de interrupción con un modo de interrupción es la siguiente, donde se observan los tres parámetros indicados anteriormente, el pin, la función ISR (interrupt_routine), y el modo de interrupción o interrupt_mode que describimos a continuación:

attachInterrupt(digitalPinToInterrupt(pir_pin), interrupt_routine, interrupt_mode);

En este tipo de interrupciones Arduino es capaz de detectar los siguientes eventos o modos en los que se aplica la interrupción.

  • RISING, cuando el pin cambia de LOW a HIGH.
  • FALLING, cuando el pin cambia de HIGH a LOW.
  • CHANGING, ocurre cuando el pin cambia de estado LOW a HIGH or HIGH a LOW (rising + falling).
  • LOW, se ejecuta continuamente mientras el pin está en estado LOW.
En Arduino y Nano se dispone de dos interrupciones, 0 y 1, asociados a los pines digitales 2 y 3, para más información ver el siguiente enlace
 
El método digitalPinToInterrupt "traduce" el número de la interrupción (0,1) en base al pin utilizado (D2 o D3), independientemente del tipo de controlador que utilicemos, por lo que hace más portable el sketch. 
 
Aclarar que para poder modificar una variable externa a la función ISR dentro de la misma debemos utilizar la palabra "volatile" que  indica al compilador que la variable siempre tiene que ser consultada antes de ser usada. por tanto solo debemos marcar como volatile las variables que realmente lo requieran, es decir, las que se usan tanto en el bucle principal (fuera de la rutina ISR) como dentro de la ISR.
 
Cuando el sensor de infrarrojos pasivo detecta algo manda una señal HIGH al pin de salida donde hayamos conectado el pin a ARDUINO, como la capacidad de manejo de las interrupciones se encuentran en los pines D2  o D3, será en alguno de estos pines.

Si elegimos el D2, gracias al método
 
attachInterrupt(digitalPinToInterrupt(2), interrupt_routine, RISING);
 
cuando se produzca el interrupt_mode RISING en el pin 2 se lanzará la interrupt_routine 
Por qué elegimos RISING: el sensor PIR registra el valor LOW cuando no se detecta movimiento por  lo que nos interesa es el cambio de valor de LOW a HIGH, es decir RISING
 

Diagrama del circuito

El diagrama del circuito sería el siguiente 
 
Arduino nano gestionando una tira de leds smd3528 con sensor PIR y LDR

Los puntos fuertes del diagrama son
  • El pin de datos del PIR va al pin digital D2 de Arduino nano, para poder "cazar" la interrupción una vez se asocie en el código, en cada loop.
  • El pin VCC de LDR va a una resistencia de 10K (divisor de tensión para poder leer el valor) y posteriormente al A0 para poder medir el umbral de luminosidad.
  • Por último la tiras de LED smd 3528 van al pin D5 con una resistencia de 470k para proteger la intensidad del pin arduino para manejar la tira de led. Recordar que debido a las limitaciones de la placa, no podemos conectar más de 6 leds a un pin de Arduino, si superamos esta cantidad de leds habría que utilizar otra fuente de alimentación y conectar la masa GND de ambas fuentes de alimentación (Arduino y fuente externa leds). Si queremos un consumo menor (conexión directa a Arduino) y con un buen rango de luminosidad, para tal caso con 3 leds tendríamos suficiente.

Código

#include <LowPower.h>

#define led_pin 5
#define PIR 7 //no se usa se pincha al D2 donde se asocia la interrupción
#define delayPIR 120000
#define UMBRAL 1
#define LDRPin  A0   //Pin del LDR

const byte interrupt_pin = 2;
volatile byte state = LOW;
byte V;
void setup() {
  Serial.begin(9600);
  pinMode(led_pin,OUTPUT);
}

void loop() {
    attachInterrupt(digitalPinToInterrupt(interrupt_pin),interrupt_routine,RISING);
  LowPower.powerDown(SLEEP_FOREVER,ADC_OFF,BOD_OFF);   detachInterrupt(digitalPinToInterrupt(interrupt_pin)); 

  if (state==HIGH){

     V = analogRead(LDRPin);
     if(V <= UMBRAL){
        digitalWrite(led_pin,HIGH);
        delay(delayPIR);
        //delay(5000);
     }
 
  }
  if (state==HIGH){
    state = LOW;
    digitalWrite(led_pin,LOW);
  }
}

void interrupt_routine(){
  state = HIGH;
}
 
La interrupción de ser asociada en cada bucle con:
  attachInterrupt(digitalPinToInterrupt(interrupt_pin),interrupt_routine,RISING);
 si se produce el evento que dispara la interrupción saltaríamos al bloque de código de la función interrupt_routine que cambia el valor de la variable global a HIGH, en tal caso continuaríamos el bloque del primer if
 
Se utiliza este método de la librería para dormir /apagar arduino hasta que "salte la interrupción", es decir el sensor PIR detecte algún movimiento
  LowPower.powerDown(SLEEP_FOREVER,ADC_OFF,BOD_OFF); 
con los parámetros ADC_OFF y BOD_OFF indicamos que mantenga apagados los convertidores analógico a digital, y el circuito BOD (O Brown Out Detection). El BOD es un circuito para detector niveles peligrosamente bajos de tensión.
 
Desasociamos la interrupción mientras está dormido con
  detachInterrupt(digitalPinToInterrupt(interrupt_pin));
Esto se hace para prevenir llamadas sucesivas al rutina ISR antes de que la rutina ISR previa haya finalizado, debemos apagar por tanto las interrupciones tan pronto como se reciben. 
 
Esto es todo, espero que hay sido de tu interés, gracias por tu tiempo y atención
 

Referencias

  • https://makersportal.com/blog/2019/5/27/arduino-interrupts-with-pir-motion-detector
  • https://github.com/rocketscream/Low-Power/blob/master/Examples/powerDownWakeExternalInterrupt/powerDownWakeExternalInterrupt.ino
  • https://www.prometec.net/el-modo-sleep-en-arduino/