Relé Arduino PIR con interruptor de anulación manual

Estoy configurando un relé activado por PIR (conectado a algunas luces) usando un Arduino Nano.

Tengo el código PIR funcionando (obtenido de un sitio de terceros), pero me gustaría tener la opción de anular la entrada PIR en función de una entrada de un interruptor de botón. Para agregar aún más complejidad, me gustaría que se encienda un LED para saber cuándo se anula manualmente el PIR (relé siempre encendido).

El botón es un simple pulsador.

Por defecto, quiero que la unidad esté en modo PIR. El relé se activa si se detecta movimiento y después de X segundos se apaga. Si presiono el botón una vez, se anula manualmente y el relé se enciende (constantemente) y el LED azul se activa para informarme que la unidad está encendida constantemente. Si se vuelve a pulsar el botón, la unidad vuelve al modo PIR.

Aquí está el código que tengo hasta ahora, pero el LED y el relé parecen estar constantemente activos. El PIR sigue leyendo (según el monitor serie).

/*
 * PIR sensor tester
 */ 


int PIR_override = 3;         // the number of the switch pin
int overridestatus = 12;       // the number of the led pin

int relaypin = 13;                // choose the pin for the LED
int inputPin = 2;               // choose the input pin (for PIR sensor)
int pirState = LOW;             // we start, assuming no motion detected
int val = 0;                    // variable for reading the pin status
int DELVAR = 3000;             // Delay in milliseconds
int override_state = LOW;      // the current state of the output pin

// set states for PIR override switch
int state = LOW;      // the current state of the output pin
int reading;           // the current reading from the input pin
int previous = HIGH;    // the previous reading from the input pin

///////////////////////////////////////////////////////
void setup() {
  pinMode(relaypin, OUTPUT);      // declare LED as output
  pinMode(inputPin, INPUT);     // declare sensor as input

  Serial.begin(9600);
}

void loop()
{
    digitalRead(PIR_override);  // read input value of PIR override switch
    if (val == LOW)
    {            // check if the input is LOW
        val = digitalRead(inputPin);  // read input value
        if (val == HIGH)
        {            // check if the input is HIGH
            digitalWrite(relaypin, HIGH);  // turn LED ON
            if (pirState == LOW)
            {
                // we have just turned on
                Serial.println("Motion detected!");
                // We only want to print on the output change, not state
                pirState = HIGH;
                delay(DELVAR);    // maximum delay is 32776 millisecons,
                //  delay(DELVAR);    // so add multiple delays together to get
                // delay(DELVAR);    // so add multiple delays together to get
            }
        }
        else
        {
            digitalWrite(relaypin, LOW); // turn LED OFF
            if (pirState == HIGH)
            {
                // we have just turned of
                Serial.println("Motion ended!");
                // We only want to print on the output change, not state
                pirState = LOW;
            }
            else
            {
                digitalWrite(overridestatus, HIGH);  // turn override status LED ON
                digitalWrite(relaypin, HIGH);  // turn relay ON
            }
        }
    }
}

Aquí está el código que tengo hasta ahora, pero el LED y el relé parecen estar constantemente activos. El PIR sigue leyendo (según el monitor serie).

Claramente, hay algo mal con mi bucle if-else, pero no estoy completamente seguro de dónde debo comenzar a buscar.

También incluyo una imagen básica de la placa de prueba del circuito (¡es mi primer Fritz, así que sea amable!) Además, ignore el pin en la placa de relés. Estoy usando un SRD-05VDC-SL-C pero está en una placa ligeramente diferente con solo 3 pines (IN, VCC, GND).

ingrese la descripción de la imagen aquí

Respuestas (2)

Creo que su principal problema es que no está asignando una variable o usando una ifdeclaración con su digitalRead(PIR_override);, pero incluso con eso puede perderse el botón presionado en el bucle. Si es un interruptor momentáneo, sube/baja solo durante el tiempo que se presiona, por lo que el procesador tendría que ver esto en el momento adecuado en el bucle while. Puede mantener presionado el botón durante aproximadamente un segundo o puede conectar el botón al pin 2 o 3 y usar una interrupción, que parece que su botón está conectado al pin 3 de acuerdo con su código. Creo que esta es una mejor solución. En el mundo incrustado, las interrupciones son tus amigas.

Entonces, según la documentación ( https://www.arduino.cc/en/Reference/AttachInterrupt ), podría hacer algo como esto:

const byte PIR_override = 3;        // the number of the switch pin
volatile boolean override = false;          // Use this to know if your currently  in override mode or not when looking at the button press

En el interior Setup()agregar:

pinMode(PIR_override, INPUT);
attachInterrupt(digitalPinToInterrupt(PIR_override), motionOverride, LOW); // the sense may need to be changed - from your image it looks like the button goes low when pressed

Así que ahora motionOverridese llamará cuando se presione el botón. Simplemente ponga lo que quiere que suceda dentro de esta función. Algo como esto:

void motionOverride()
{
    if(override) // overriding motion and the button has been pressed - got to PIR mode
    {
        digitalWrite(overridestatus, LOW);  // turn override status LED OFF
        digitalWrite(relaypin, LOW);  // turn relay OFF - you may not want to do this... depends on what you want
        override = false;               // set the override flag
    }
    else // not overriding motion and the button has been pressed - go to override mode
    {
        digitalWrite(overridestatus, HIGH);  // turn override status LED ON
        digitalWrite(relaypin, HIGH);  // turn relay ON
        override = true;                // set the override flag
    }

   // I guess Arduino takes care of clearing the interrupt for you
}

Luego, dentro de su principal, loop()solo mire la bandera de anulación (Nota: no verifiqué ninguna de sus lógicas de detección de movimiento PIR, y con suerte no arruiné sus corchetes allí):

void loop()
{
     if(!override)
     {
         val = digitalRead(inputPin);  // read PIR input value
         if (val == HIGH)  // check if the input is HIGH
         {           
              digitalWrite(relaypin, HIGH);  // turn LED ON
              if (pirState == LOW) 
              {
                   // we have just turned on
                   Serial.println("Motion detected!");
                   // We only want to print on the output change, not state
                   pirState = HIGH;
                   delay(DELVAR);    // maximum delay is 32776 millisecons,
                   //  delay(DELVAR);    // so add multiple delays together to get
                   // delay(DELVAR);    // so add multiple delays together to get
             }
        } 
        else
        {
            digitalWrite(relaypin, LOW); // turn LED OFF
            if (pirState == HIGH)
            {
                 // we have just turned of
                 Serial.println("Motion ended!");
                 // We only want to print on the output change, not state
                 pirState = LOW;
            } 
        }
     }
}

Pruébalo si quieres. Si hay un error, intentaré solucionarlo.

Gracias Digital Ninja. El código que ha sugerido parece "casi" hacer lo que busco. Sin embargo, el estado del interruptor parece ser un poco escamoso. He leído un poco sobre esto y parece que podría necesitar usar algo de "antirrebote". ¿Dónde sugeriría que coloque el código de rebote, en la función misma o en el ciclo principal?
Probablemente definitivamente antirrebote. Sería mejor una solución de hardware, para eso necesitaría cambiar el estado predeterminado del interruptor para que normalmente se baje y luego, cuando se presione, suba. Si hiciera eso, todo lo que necesitaría es un pequeño capacitor en la entrada al procesador y a tierra. Para el software, por lo que estás haciendo, simplemente pondría un pequeño retraso al final de la ISR. Esto evitará que la interrupción se active nuevamente inmediatamente después de la primera vez.
Dependiendo de cómo Arduino borre la interrupción, es posible que un retraso no funcione porque si ingresa al ISR y borra la bandera después del retraso, puede volver a ingresar al ISR en la interrupción pendiente. Además, debe usar delayMicroseconds()porque no usa interrupciones en sí mismo. Si no puede hacer la solución de hardware y la demora al final de la ISR no funciona, creo que tengo una solución que funcionará. Déjame saber como va.
Probablemente sea una pregunta estúpida, pero cuando dice agregar un condensador en el procesador de entrada, ¿se refiere a conectarlo al pin del interruptor (PIR_override = 3)?
Sí, coloque el condensador entre la salida del interruptor y la entrada del procesador. Pero recuerde que debe cambiar el estado predeterminado del interruptor para que sea bajo (tirado a tierra a través de su resistencia) y cuando se presiona sube (parece 5 V CC en su imagen). Cuando sube, el capacitor se carga y filtra el rebote. Además, si hace esto, recuerde cambiar el sentido de interrupción para que sea ALTO en lugar de BAJO en su código.
No estoy teniendo mucha suerte con el método del condensador. Cambié la resistencia para que esté tirando a tierra. Tan pronto como agrego un capacitor, se comporta aún más erráticamente. He probado varias clasificaciones de condensadores de 20uF a 220uf. Creo que me gustaría arreglar esto en el software, ya que está casi todo conectado a un cuadro de proyecto en este momento y no quiero desconectarlo más. Todavía no he intentado agregar un retraso al ISR: ¿el ISR es la función motionOverride o la sección "attachInterrupt" en setup ()?
En realidad, acabo de ver un artículo realmente útil en hackaday [enlace] hackaday.com/2015/12/09/… que me da un diagrama para un antirrebote junto con la explicación de qué resistencia pull-up en combinación con qué clasificación de capacitor usar (mucho más bajo de lo que había probado). Creo que lo entendí mal, ya que no estaba usando otro resistor complementario para subir/bajar, solo un solo resistor de 1k tirando hacia arriba. Coquetearé un poco más cuando llegue a casa.
Sí, tal vez no lo expliqué muy bien, pero si busca en Google "circuito de rebote de botón", obtendrá muchos ejemplos, y se puede hacer solo con una resistencia y un condensador (valor pequeño como alrededor de 0.1uF). El ISR es la motionOverridefunción. No estoy del todo seguro de cómo se comportará en la plataforma Arduino, por eso sugerí la solución de hardware.

He sugerido una edición de su código; las sangrías no estaban presentes, por lo que las agregué para que las declaraciones if-else anidadas fueran más claras.

Me gustaría abordar algunos problemas que tengo; Cambie las definiciones de sus pines:

int PIR_override = 3;         // the number of the switch pin
int overridestatus = 12;       // the number of the led pin
int relaypin = 13;                // choose the pin for the LED
int inputPin = 2;               // choose the input pin (for PIR sensor)

a algo como:

#define PIR_override 3         // the number of the switch pin
#define overridestatus 12       // the number of the led pin

#define relaypin 13                // choose the pin for the LED
#define inputPin 2               // choose the input pin (for PIR sensor)

Con su uso actual de variables enteras, es posible que su valor cambie, lo que ciertamente no desea hacer. El uso de números enteros para las definiciones de pines también consume espacio en su pila. Puede que esto nunca sea un problema real, pero es algo a considerar.

Se puede encontrar información más detallada sobre las directivas de preprocesador en otros lugares.

Cuando lees tu pin PIR_override:

digitalRead(PIR_override);  // read input value of PIR override switch

El valor no se está utilizando. Debe asignar esto a una variable.

También veo algunos problemas con su lógica if-else. Tome este código por ejemplo:

if (val == LOW)
{            // check if the input is LOW
    val = digitalRead(inputPin);  // read input value

Inicialmente, val está configurado para ser bajo. Está bien. La declaración if será verdadera y se reasignará val. Pero, ¿qué pasa si val se reasigna a ALTO? Una vez que se alcanza el final del ciclo y se reinicia, no hay otra declaración para manejar val == ¡ALTO!

Me parece que lo que está tratando de hacer con su lógica if-else anidada está cerca de una máquina de estado. Si aún no lo ha investigado, sugeriría buscar 'máquinas de estado if-else' o 'máquinas de estado switch-case'.

De lo contrario, si desea continuar con el código que tiene ahora, consideraría escribir los valores de las variables iniciales y "recorrer" manualmente su código.