El siguiente código logra lo que está pidiendo:
#include <avr/sleep.h>
#include <avr/power.h>
const byte AWAKE_LED = 8;
const byte GREEN_LED = 9;
const unsigned long WAIT_TIME = 5000;
ISR (PCINT2_vect)
{
// handle pin change interrupt for D0 to D7 here
} // end of PCINT2_vect
void setup()
{
pinMode (GREEN_LED, OUTPUT);
pinMode (AWAKE_LED, OUTPUT);
digitalWrite (AWAKE_LED, HIGH);
Serial.begin (9600);
} // end of setup
unsigned long lastSleep;
void loop()
{
if (millis () - lastSleep >= WAIT_TIME)
{
lastSleep = millis ();
noInterrupts ();
byte old_ADCSRA = ADCSRA;
// disable ADC
ADCSRA = 0;
// pin change interrupt (example for D0)
PCMSK2 |= bit (PCINT16); // want pin 0
PCIFR |= bit (PCIF2); // clear any outstanding interrupts
PCICR |= bit (PCIE2); // enable pin change interrupts for D0 to D7
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();
UCSR0B &= ~bit (RXEN0); // disable receiver
UCSR0B &= ~bit (TXEN0); // disable transmitter
sleep_enable();
digitalWrite (AWAKE_LED, LOW);
interrupts ();
sleep_cpu ();
digitalWrite (AWAKE_LED, HIGH);
sleep_disable();
power_all_enable();
ADCSRA = old_ADCSRA;
PCICR &= ~bit (PCIE2); // disable pin change interrupts for D0 to D7
UCSR0B |= bit (RXEN0); // enable receiver
UCSR0B |= bit (TXEN0); // enable transmitter
} // end of time to sleep
if (Serial.available () > 0)
{
byte flashes = Serial.read () - '0';
if (flashes > 0 && flashes < 10)
{
// flash LED x times
for (byte i = 0; i < flashes; i++)
{
digitalWrite (GREEN_LED, HIGH);
delay (200);
digitalWrite (GREEN_LED, LOW);
delay (200);
}
}
} // end of if
} // end of loop
Usé una interrupción de cambio de pin en el pin Rx para notar cuándo llegan los datos en serie. En esta prueba, la placa se duerme si no hay actividad después de 5 segundos (el LED "despierto" se apaga). Los datos seriales entrantes provocan que la interrupción de cambio de clavija active la placa. Busca un número y parpadea el LED "verde" esa cantidad de veces.
Corriente medida
Corriendo a 5 V, medí aproximadamente 120 nA de corriente cuando estaba dormido (0.120 µA).
Mensaje de despertar
Sin embargo, un problema es que el primer byte de llegada se pierde debido al hecho de que el hardware en serie espera un nivel descendente en Rx (el bit de inicio) que ya ha llegado cuando está completamente despierto.
Sugiero (como en la respuesta de geometrikal) que primero envíe un mensaje "despierto", y luego haga una pausa por un corto tiempo. La pausa es asegurarse de que el hardware no interprete el siguiente byte como parte del mensaje despierto. Después de eso debería funcionar bien.
Dado que esto utiliza una interrupción de cambio de pin, no se requiere ningún otro hardware.
Versión modificada usando SoftwareSerial
La siguiente versión procesa con éxito el primer byte recibido en serie. Hace esto por:
Usando SoftwareSerial que usa interrupciones de cambio de pin. La interrupción causada por el bit de inicio del primer byte en serie también activa el procesador.
Configurando los fusibles para que usemos:
- Oscilador interno RC
- DBO deshabilitado
- Los fusibles fueron: Bajo: 0xD2, Alto: 0xDF, Extendido: 0xFF
Inspirado por FarO en un comentario, esto permite que el procesador se active en 6 ciclos de reloj (750 ns). A 9600 baudios, cada tiempo de bit es 1/9600 (104.2 µs), por lo que el retraso adicional es insignificante.
#include <avr/sleep.h>
#include <avr/power.h>
#include <SoftwareSerial.h>
const byte AWAKE_LED = 8;
const byte GREEN_LED = 9;
const unsigned long WAIT_TIME = 5000;
const byte RX_PIN = 4;
const byte TX_PIN = 5;
SoftwareSerial mySerial(RX_PIN, TX_PIN); // RX, TX
void setup()
{
pinMode (GREEN_LED, OUTPUT);
pinMode (AWAKE_LED, OUTPUT);
digitalWrite (AWAKE_LED, HIGH);
mySerial.begin(9600);
} // end of setup
unsigned long lastSleep;
void loop()
{
if (millis () - lastSleep >= WAIT_TIME)
{
lastSleep = millis ();
noInterrupts ();
byte old_ADCSRA = ADCSRA;
// disable ADC
ADCSRA = 0;
set_sleep_mode (SLEEP_MODE_PWR_DOWN);
power_adc_disable();
power_spi_disable();
power_timer0_disable();
power_timer1_disable();
power_timer2_disable();
power_twi_disable();
sleep_enable();
digitalWrite (AWAKE_LED, LOW);
interrupts ();
sleep_cpu ();
digitalWrite (AWAKE_LED, HIGH);
sleep_disable();
power_all_enable();
ADCSRA = old_ADCSRA;
} // end of time to sleep
if (mySerial.available () > 0)
{
byte flashes = mySerial.read () - '0';
if (flashes > 0 && flashes < 10)
{
// flash LED x times
for (byte i = 0; i < flashes; i++)
{
digitalWrite (GREEN_LED, HIGH);
delay (200);
digitalWrite (GREEN_LED, LOW);
delay (200);
}
}
} // end of if
} // end of loop
El consumo de energía cuando estaba dormido se midió como 260 nA (0.260 µA), por lo que es un consumo muy bajo cuando no se necesita.
Tenga en cuenta que con los fusibles configurados así, el procesador funciona a 8 MHz. Por lo tanto, debe informarle al IDE sobre eso (por ejemplo, seleccione "Lilypad" como tipo de placa). De esa forma, los retrasos y SoftwareSerial funcionarán a la velocidad correcta.