Solamente sale de modo sleep una vez, a veces

Vamos a ver si consigo escribir todo esto según las normas del foro ya que es mi primera pregunta con código, diagrama y demás en el foro.

Primero os cuento lo que quiero hacer y luego os pongo el diagrama y el código.

Esto es parte de un proyecto más grande, que estoy rehaciendo bien poco a poco. Por eso en el diagrama veréis algún elemento más que se inicializa en el código, pero no se usa de momento. He preferido dejarlo por si estuviera afectando a lo que no termina de funcionar...

Bien, esto es va a ser una especie de cerradura con clave. La parte que estoy desarrollando ahora es en la que el sistema esta dormido, hasta que se acerca alguien. En ese momento el sensor PIR lo detecta y enciende, para que se pueda operar.

Es un arduino MEGA 2560, pero no el original, una compatible.

El problema es que actualmente el funcionamiento es así:

  1. Arranca
  2. Cuando no detecta a nadie se apaga
  3. Si no ha pasado mucho tiempo y detecta algo el sensor PIR despierta (si ha pasado mucho tiempo ya no despierta)
  4. Cuando vuelve a no detectar nada se vuelve a dormir
  5. Ya no despierta nunca más

Aquí tenéis mi código:

// Para el manejo de la pantalla
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
//Para el manerjo del teclado
#include <Keypad.h>
//Para dormir
#include <avr/sleep.h>

//CONFIGURACION TECLADO
//determinamos numero de filas y columnas del teclado matricial, es este caso 4x3
const byte filas = 4;                     //numero de filas
const byte columnas = 3;                  //numero de columnas
//dos vectores donde colocaremos los pines de arduino asociados al teclado matricial:
byte pinsFilas[filas] = {24, 34, 32, 28};     //pines donde van conectadas las filas
byte pinsColumnas[columnas] = {26, 22, 30};   //pines donde van conectadas las columnas
//establecemos la matriz que nos dara cada uno de los valores del teclado matricial
char teclas[filas][columnas] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
};
//FIN _CONFIGURACION TECLADO

//OBJETOS
//Creamos el objeto teclado
Keypad teclado = Keypad(makeKeymap(teclas), pinsFilas, pinsColumnas, filas, columnas);
//Crear el objeto lcd  dirección  0x27 y 20 columnas x 4 filas
LiquidCrystal_I2C lcd(0x27, 20, 4);
//FIN OBJETOS

//SENSORES
//Sensor de Ultrasonidos
const int EchoPin = 40;
const int TriggerPin = 38;
const int hueco = 4;       
//Sensor PIR
const int pinPIR = 19;       //INT.2 en Pin 19 para Arduino MEGA
//FIN SENSORES


///////////////////////////////////////////////////////////////////////////
// FUNCION: setup                                                        //
// Inicialización de la placa                                            //
///////////////////////////////////////////////////////////////////////////
void setup() {
  
  Serial.begin(9600); 
  Serial.print("\r\n");
  Serial.println ("setup"); 
  
  //Inicializa sensor ultra sonidos
  pinMode(TriggerPin, OUTPUT);
  pinMode(EchoPin, INPUT);

  //Inicializa sensor PIR
  pinMode(pinPIR, INPUT_PULLUP);

  // Inicializa la pantalla
  lcd.backlight();   // Encender la luz de fondo.
  lcd.init();        // Inicializar el LCD
  lcd.clear();       // Limpiar la pantalla
  //FIN Inicializa la pantalla

} // FIN FUNCION setup()


///////////////////////////////////////////////////////////////////////////
// FUNCION: loop                                                         //
// Bucle principal                                            //
///////////////////////////////////////////////////////////////////////////
void loop() {
  Serial.println ("loop");  //Quitar
       
  lcd.clear();
       
  Serial.println ("leemos PIR");
  int presencia = digitalRead(pinPIR); //leemos sensor PIR

  if (presencia == HIGH){   //Si hay alguien delante
    Serial.println ("loop-Hay alguien");  //Quitar  

    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("HOLA MUNDO!");
    delay(1000);
  
  }
  else{                     //Si NO hay alguien delante
    Serial.println ("loop-NO hay alguien");  //Quitar    
    
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("ADIOS");
    delay (1000);

    aDormir(); 

  }
} // FIN FUNCION loop()


///////////////////////////////////////////////////////////////////////////
// FUNCION: aDormir                                                      //
// Se encarga de preparar y poner al sistema en modo ahorro energia.     //
// Tambien del despertar                                                 //
///////////////////////////////////////////////////////////////////////////
void aDormir(){
  Serial.println ("aDormir");
 
  sleep_enable();                                                     // activamos el modo dormir
  attachInterrupt(digitalPinToInterrupt(pinPIR), aDespertar, HIGH);    // Añadimos una interrupción para despertar del modo Sleep. En este caso asociada al sensor de movimiento PIR y cuando este de señal de presencia (HIGH)
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);                               // Elegimos el modo de "dormir" en este caso PWR_DOWN
  delay (1000); 
  lcd.noBacklight();                                                  // Apagamos la luz de la pantalla
  lcd.off();                                                         // Apagamos la pantalla
  sleep_cpu();                                                        // "Dormimos" la placa
  //aqui continua  cuando vuelve
  sleep_disable();                                                    // Desactivamos el modo dormir
  lcd.on();                                                           // Encendemos la pantalla
  lcd.backlight();                                                    // Encendemos la luz de la pantalla  
  }


///////////////////////////////////////////////////////////////////////////
// FUNCION: aDespertar                                                    //
// Es llamada con una interrupción para despertar a la placa             //
//                                                                       //
///////////////////////////////////////////////////////////////////////////
void aDespertar(){
  Serial.println ("aDespertar");  
  detachInterrupt(digitalPinToInterrupt(pinPIR));                     // Quitamos la interrupción
  }

He hecho mediciones en el sensor PIR, para ver si el problema es que se apagaba todo o que el sensor no estaba bien, pero devuelve sus 3 y pico voltios cuando detecta movimiento y 0.19 cuando no, devolviendo estos valores incluso cuando ya no despierta.

He leido y releido documentación y posts, y llegado hasta es código, pero no encuentro donde puedo estar fallando. Seguramente se me haya pasado alguna cosa por alto, pero no termino de dar con que es. Y es una pena, porque creo que un esqueleto así es una buena base para muchos proyectos...

Acepto humildemente cualquier comentario, referencia a articulos y lo que sea. Mil gracias por adelantado.

Primero que nada, felicitaciones por hacer las cosas como se debe.

Ahora el primer problema:
No puedes efectuar operaciones con el puerto serie dentro de una rutina de interrupción.

Prueba estos cambios

void aDormir(){
  Serial.println ("aDormir");
  delay (1000);   // quieres un delay? Hazlo aqui
  lcd.noBacklight();                                                  // Apagamos la luz de la pantalla
  lcd.off();                                                         // Apagamos la pantalla 
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);                               // Elegimos el modo de "dormir" en este caso PWR_DOWN  
  noInterrupts();  //deshabilitamos momentaneamente las interrupciones 
  attachInterrupt(digitalPinToInterrupt(pinPIR), aDespertar, RISING);    // Añadimos una interrupción para despertar del modo Sleep. En este caso asociada al sensor de movimiento PIR y al flanco ascendente cuando este de señal de presencia (HIGH)
  sleep_enable();                                                     // activamos el modo dormir
  interrupts();  // rehabilitamos las interrupciones
  sleep_cpu();                                                        // "Dormimos" la placa
  //aqui continua  cuando vuelve
                                                      
  Serial.println ("Despierto");
  lcd.on();                                                           // Encendemos la pantalla
  lcd.backlight()
}


void aDespertar(){
    
  sleep_disable();    // Desactivamos el modo dormir
  detachInterrupt(digitalPinToInterrupt(pinPIR));                     // Quitamos la interrupción
}

Saludos

Agrego: Te sugiero una excelente guía sobre interrupciones
https://www.gammon.com.au/interrupts

Muchas gracias @MaximoEsfuerzo por la rápida y acertada respuesta :slightly_smiling_face:.

Probé los cambios que indicaste y funciona perfecto, mil gracias. Estaba desesperado ya....

Una ultima cuestión que me asalta. En la guía que dejaste a mano y en otros sitios he visto que ponen la rutina de interrupción por delante de la función setup. Pero no he encontrado (o se me ha pasado por alto) nada que lo justifique. En este caso, que sigue estando por debajo del loop funciona perfectamente. ¿Hay que colocar las rutinas de interrupción por encima del loop? ¿es por norma o por facilitar la lectura del código?

Son cosas diferentes.
Una cuestión es que agregues una interrupción externa para seguir un evento. Una diferente es tu caso donde quieres que el Arduino se duerma, despierte luego de X tiempo y se vuelva a dormir.
En el primer caso con indicarlo 1 vez alcanza.
En tu caso debes desactivar la interrupción, algo que haces con deatach hacer lo que debes y luego volver a pedirle que se duerma conla interrupción nuevamente activada y por eso lo haces en el loop.
setup es una rutina que solo ocurre una vez.
loop es una rutina que se repite por siempre. Si desactivas interrupciones y luego quieres que vuelva a reaccionar, no tienes mas remedio que hacerlo ahi.

Vale, creo que ya me he enterado.

Cuando es una rutina de interrupción para seguir un evento recurrente en el tiempo, hay que poner esas rutina antes de setup. En este caso, como solamente va a ser para despertar cuando la duermo se puede poner en otra zona. ¿No?

No necesariamente.
En realidad, en lenguaje C no puedes invocar una función (o rutina) que no haya sido aún declarada.

En el C de la IDE de arduino da lo mismo, por eso puedes declarar una función debajo de setup() y loop().
No me parece buena idea pero ya está.

El problema surge cuando por x razón quieres/debes usar otra IDE (como VSCode + PlatformIO) que respetan el estándar y empiezan a saltar errores por todos lados.

En lo personal, por costumbre, siempre primero declaro todo. Entonces, lo último en mis códigos son setup() y loop()

Saludos

Gracias por la aclaración.

Me parece una practica bastante saludable hacerlo como dices. Así que, aprovechando que estoy reescribiendo todo este proyecto voy a poner en practica lo de declarar todo antes de usarlo. Así voy cogiendo buenas costumbres :slight_smile:

En C hay una manera de poner el cuerpo de una función "debajo" de donde se invoca y es colocando antes el encabezado de la función, pongo un ejemplo para que sea más claro

int mi_funcion(int un_parametro);  // solo declara la función

void setup() {
// lo que corresponda
}

void loop() {
// lo que sea
  int x = mi_funcion(1234);
// lo que sigue
}

int mi_funcion (int un_parametro) {  // ahora si, el codigo completo de la función
  return un_parametro * 2; // tanto para poner algo
}

Saludos

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.