Il codice seguente ottiene ciò che stai chiedendo:
#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
Ho usato un'interruzione di cambio pin sul pin Rx per notare quando arrivano i dati seriali. In questo test la scheda entra in modalità di sospensione se non viene rilevata alcuna attività dopo 5 secondi (il LED "sveglia" si spegne). I dati seriali in entrata causano l'interruzione del cambio pin per riattivare la scheda. Cerca un numero e lampeggia il LED "verde" quel numero di volte.
Corrente misurata
Funzionando a 5 V, ho misurato circa 120 nA di corrente durante il sonno (0,120 µA).
Messaggio di risveglio
Un problema tuttavia è che il primo byte in arrivo viene perso a causa del fatto che l'hardware seriale si aspetta un livello in calo su Rx (il bit di avvio) che è già arrivato quando è completamente attivo.
Suggerisco (come nella risposta di geometrikal) di inviare prima un messaggio "sveglio", quindi di metterlo in pausa per un breve periodo. La pausa è assicurarsi che l'hardware non interpreti il byte successivo come parte del messaggio di attivazione. Dopodiché dovrebbe funzionare bene.
Poiché utilizza un interrupt di cambio pin, non è richiesto altro hardware.
Versione modificata utilizzando SoftwareSerial
La versione seguente elabora correttamente il primo byte ricevuto in seriale. Lo fa da:
Utilizzo di SoftwareSerial che utilizza interrupt di cambio pin. L'interruzione causata dal bit di avvio del primo byte seriale attiva anche il processore.
Impostare i fusibili in modo che utilizziamo:
- Oscillatore RC interno
- BOD disabilitato
- I fusibili erano: Basso: 0xD2, Alto: 0xDF, Esteso: 0xFF
Ispirato da FarO in un commento, questo consente al processore di svegliarsi in 6 cicli di clock (750 ns). A 9600 baud il tempo di ogni bit è 1/9600 (104,2 µs), quindi il ritardo extra è 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
Il consumo di energia durante il sonno è stato misurato a 260 nA (0,260 µA), quindi è un consumo molto basso quando non necessario.
Si noti che con i fusibili impostati in questo modo, il processore funziona a 8 MHz. Pertanto è necessario comunicarlo all'IDE (ad es. Selezionare "Lilypad" come tipo di scheda). In questo modo i ritardi e SoftwareSerial funzioneranno alla velocità corretta.