Low power con control remoto

Hola,

Muchas gracias. Lo he probado, pero funciona más o menos como antes. Es decir, parece que si coincide el momento de pulsar el botón en el control remoto con la frecuencia de lectura del sensor ir sí que para ese bucle y hace otra cosa, pero hay que pulsar varias veces hasta que funciona, no es una interrupción como tal.

Solo por entender, ¿por qué no funciona una interrupción dentro del condicional if (bucleOn) de forma similar a la del if (pwrOff) ?

Porque activamos la interrupción cuando vamos a mandarlo a dormir para poder luego despertarlo y en bucleOn ya está bien despierto.

Después de varios días probando con el código, al final he llegado a esta solución para poder utilizar dos interrupciones:

#include <IRremote.h>
#include "LowPower.h"

#define Boton_Off 0xFD02EF00
#define Boton_On  0xFC03EF00
#define Boton_1   0xF807EF00

const byte SENSOR = 2;
const byte PinINT = 3;

unsigned long time;

bool pwrOff = false;
bool bucleOn = false;

int ledW = 9;
int i = 0;

void IR_ISR() {  // INT0
  detachInterrupt(digitalPinToInterrupt(SENSOR));
}

void setup() {
  Serial.begin(115200);
  pinMode(SENSOR, INPUT);
  IrReceiver.begin(SENSOR, ENABLE_LED_FEEDBACK);

  pinMode(3,INPUT);
  digitalWrite(PinINT, HIGH);
  
  attachInterrupt(digitalPinToInterrupt(PinINT), led, LOW);
}

void loop() {

  if (IrReceiver.decode()) {
    Serial.println(IrReceiver.decodedIRData.decodedRawData, HEX);
    digitalWrite(PinINT, LOW);
  }

  if (pwrOff) {
    delay(500);  // para evitar detección de nuevos pulsos del C/R
    noInterrupts();
    EIFR = bit(INTF0);  // limpia interrupciones que pudiesen quedar pendientes
    attachInterrupt(digitalPinToInterrupt(SENSOR), IR_ISR, LOW);
    interrupts();
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);  // *** A dormir! ***

    pwrOff = false;
    
  }

  if (bucleOn) {
    if (i<=255){
      analogWrite(ledW, i);
      if (millis() - time > 200UL){
        i++;
        time = millis();
        }
    } else {
      bucleOn = false;
    }
    
  }

  IrReceiver.resume();
}

void led() {
  digitalWrite(PinINT, HIGH);
  switch (IrReceiver.decodedIRData.decodedRawData) {
      case Boton_On:
        analogWrite(ledW, 0);
        bucleOn = false;
        break;
      case Boton_1:
        i = 0;
        time = millis();
        if (i<=255){
          analogWrite(ledW, i);
          if (millis() - time > 200UL){
            i++;
            time = millis();
        }}
        break;
      case Boton_Off:
        analogWrite(ledW, 255);
        bucleOn = false;
        pwrOff = true;
        break;
    }
}

De esta forma, la interrupción en el pin 3 sirve para salir del bucle y la del pin 2 para despertar el Arduino Nano.

Sin embargo, una vez pongo a dormir la placa pulsando "off" en el mando a distancia, al cabo de un rato el arduino sale de ese modo solo, como si recibiera señal del sensor IR (un VS1838B). Esto pasa sin un patrón repetitivo, es decir, a veces ha sido después de unos segundos y otras veces tras unos minutos. Pero en ningún momento se ha pulsado ningún botón del control remoto. Por si sirve de algo la información, estoy alimentando todo desde un portapilas para 4xAAA (6V).

¿Alguien sabe por qué puede estar pasando?

Gracias,
Javi

Te olvidaste un pequeño detalle...
El micro se "despierta" con una interrupción hardware, cualquiera de las dos.

La interrupción 0 (pin 2) provoca que el micro se "despierte", la rutina de servicio de interrupción IR_ISR() solo desengancha (detach) la ISR de la interrupción.

La interrupción 1 (pin 3) también despierta al micro, la rutina de servicio led(), hmmm... ¿Qué intentas hacer?

Incluí la interrupción 1 porque no era posible utilizar la misma interrupción para todo (salir del bucle y despertar al arduino), por lo que se ha comentado en respuestas anteriores.

La idea de la rutina led() era interpretar el código recibido y encender/apagar led según sea ese código. Puede hacerse en el loop, pero entonces no podía salir de ese bucle activado por el Boton_1.

Puede que no sea correcto (y me gustaría saber cuál es la mejor forma de hacerlo, para eso abrí esta consulta), pero ¿eso explica que el Nano salga del modo LowPower solo?

El fallo que me refiero es que tras pulsar "Off", entra en el modo LowPower pero después de un tiempo arbitrario se despierta, sin haber pulsado de nuevo ningún botón y por tanto, sin haber activado ninguna de las interrupciones.

void led() {
  digitalWrite(PinINT, HIGH);
  switch (IrReceiver.decodedIRData.decodedRawData) {  // este dato
// no va a estar disponible hasta que se hayan leido todos los pulsos,
// nunca se van a cumplir las condiciones del switch
// salvo luego de una nueva pulsación
      case Boton_On:
        analogWrite(ledW, 0);
        bucleOn = false;
        break;
      case Boton_1:
        i = 0;
        time = millis(); // el valor de millis() no se actualiza en la ISR
        if (i<=255){  // i = 0 entonces se cumple siempre
          analogWrite(ledW, i);
          if (millis() - time > 200UL){ // millis() - time = 0, entonces nunca se cumple
            i++;
            time = millis();
        }}
        break;
      case Boton_Off:
        analogWrite(ledW, 255);
        bucleOn = false;
        pwrOff = true;
        break;
    }
}

Lectura recomendadísima
https://gammon.com.au/interrupts

Gracias MaximoEsfuerzo.

Leeré el link y revisaré todo el código con más calma.

Para empezar, he hecho una pequeña prueba con el código que tú me propusiste en el post #9:

#include <IRremote.h>
#include "LowPower.h"

#define Boton_Off 0xE31CFF00  // cambia los códigos por los de tu control
#define Boton_0   0xE619FF00
#define Boton_1   0xBA45FF00
#define Boton_2   0xB946FF00

const byte SENSOR = 2;

unsigned long oldTime;  // para el parpadeo de muestra

bool pwrOff = false;

IRrecv irrecv(SENSOR);
decode_results codigo;

void IR_ISR() {  // INT0
  detachInterrupt(digitalPinToInterrupt(SENSOR));
}

void setup() {
  Serial.begin(115200);
  pinMode(SENSOR, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  irrecv.enableIRIn();

  oldTime = millis();  // para el parpadeo de muestra
}

void loop() {
  if (millis() - oldTime >= 250UL) {  // parpadeo del led para ver cuando "duerme"
    oldTime += 250UL;
    digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) == HIGH ? LOW : HIGH);
  }

  if (irrecv.decode()) {
    Serial.println(irrecv.decodedIRData.decodedRawData, HEX);
    switch (irrecv.decodedIRData.decodedRawData) {
      case Boton_0:
      case Boton_1:
      case Boton_2:
        break;
      case Boton_Off:
        digitalWrite(LED_BUILTIN, LOW);
        pwrOff = true;
        break;
    }
  }

  if (pwrOff) {
    delay(500);  // para evitar detección de nuevos pulsos del C/R
    noInterrupts();
    EIFR = bit(INTF0);  // limpia interrupciones que pudiesen quedar pendientes
    attachInterrupt(digitalPinToInterrupt(SENSOR), IR_ISR, LOW);
    interrupts();
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);  // *** A dormir! ***

    pwrOff = false;
  }
  irrecv.resume();
}

Conectado al pc, todo funciona perfectamente. Sin embargo, cuando paso a alimentar el circuito con el portapilas, es cuando sucede el último error que reporté: el nano está en modo Lowpower, y tras unos segundos (arbitrarios, no siempre igual) y sin que se pulse ningún botón en el mando a distancia, despierta y el led vuelve a parpadear.

¿Puede tener algo que ver que la alimentación sea desde el portapilas para que dispare la interrupción?

He probado con dos juegos de 4 pilas nuevas, en ambas el tester marca 5.8 V a la salida del portapilas.

Lo mínimo para alimentar por Vin son 6.5V, seguramente ese es el problema.

El tema del LED:
Cuando pulsas ON que encienda gradual, cuando pulsas OFF que apague gradual.
Si está, por ej., apagándose y pulsas ON, aunque no se haya apagado totalmente, inmediatamente vuelva a encender (gradual, obvio).
¿Correcto?

Agrego:

Fijate si es algo así lo que querés (es mejorable pero para ser domingo...)

#include <IRremote.h>
#include <LowPower.h>

#define Boton_Off 0xE31CFF00  // cambia los códigos por los de tu control
#define Boton_0   0xE619FF00
#define Boton_1   0xBA45FF00
#define Boton_2   0xB946FF00

const byte SENSOR = 2;
const byte LED = 9;


int brilloLed = 0;
const unsigned long FADEDELAY = 10;  // tiempo entre saltos de brillo del led
unsigned long newTime, oldTime;

bool pwrOff = false; 
bool pwrState = pwrOff;

IRrecv irrecv(SENSOR);
decode_results codigo;

void IR_ISR() {  // INT0
  detachInterrupt(digitalPinToInterrupt(SENSOR));
}

void manejar_led() {
  newTime = millis();
  if (newTime - oldTime >= FADEDELAY ) {  // delay entre saltos de brillo
    oldTime = newTime;
    if (!pwrOff && brilloLed < 255) {  // encendiendo led
      brilloLed ++;
      analogWrite(LED, brilloLed );
    }
    else if (pwrOff && brilloLed > 0) {  // apagando led
      brilloLed --;
      analogWrite(LED, brilloLed );
    }
  }
}

void setup() {
  pinMode(SENSOR, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, pwrOff == false);
  irrecv.enableIRIn();

  oldTime = millis();
}

void loop() {

  if (irrecv.decode()) {
    switch (irrecv.decodedIRData.decodedRawData) {
      case Boton_0:  // botones habilitados para powerUp
      case Boton_1:
      case Boton_2:
        pwrOff = false;
        pwrState = false;
        break;
      case Boton_Off:  // boton para powerDown
        pwrOff = true;
        pwrState = true;
        break;
      default:  // cualquier otro botón mantiene el ultimo estado, powerDown o powerUp
        pwrOff = pwrState;
    }
    irrecv.resume();
  }

  manejar_led();

  if (pwrOff && brilloLed == 0 ) {  // espera a que el LED se apague antes de dormirse 
    delay(200);  // para evitar detección de nuevos pulsos del C/R
    digitalWrite(LED_BUILTIN, LOW);
    noInterrupts();
    EIFR = bit(INTF0);  // limpia interrupciones que pudiesen quedar pendientes
    attachInterrupt(digitalPinToInterrupt(SENSOR), IR_ISR, FALLING);
    interrupts();
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);  // *** A dormir! ***

// *** despierta ***
    digitalWrite(LED_BUILTIN, HIGH);
    pwrOff = false;
  }
//  irrecv.resume();
}

Simulación

Hola MaximoEsfuerzo,

Disculpa que haya pasado tanto tiempo sin responder. He estado fuera y no he podido probar lo que habías escrito hasta ahora.

Muchísimas gracias por el código y el simulador. He adaptado el programa con los códigos de mi control remoto y lo he probado. Básicamente es ese el funcionamiento que buscaba.

Sin embargo, cuesta mucho que el IR reconozca los códigos del control. Es decir, debo pulsar varias veces al OFF para que por fin el led se apague y se ponga a dormir. Si muestro en el serial monitor los códigos que recibe con Serial.println(irrecv.decodedIRData.decodedRawData, HEX); justo después de if (irrecv.decode()) {, antes del switch, la mayoría de las veces escribe un "0" en lugar del código correcto (por ejemplo "FD02EF00" para el botón OFF).

El receptor IR que estoy utilizando es un VS1838b. ¿Alguna idea o forma de mejorar esto?

Muchas gracias de nuevo por vuestra ayuda

Prueba cambiando de lugar la sentencia

  irrecv.resume();

porque creo que está mal ubicada.

Guiándome por los ejemplos, debería ir luego de switch() (dentro del if() ) y al escribir el código no me di cuenta que me quedó antes del final de loop().

Sinceramente no se si eso cambia algo (en la simulación da lo mismo) pero con probar...

Nota: Ya corregí el código de #29.

Pues sí, cambiando la sentencia al final del if () funciona perfectamente. Muchísimas gracias por vuestra ayuda.