Le code ci-dessous réalise ce que vous demandez:
#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
J'ai utilisé une interruption de changement de broche sur la broche Rx pour remarquer quand les données série arrivent. Dans ce test, la carte se met en veille s'il n'y a pas d'activité au bout de 5 secondes (la LED "éveillé" s'éteint). Les données série entrantes provoquent l'interruption du changement de broche pour réveiller la carte. Il recherche un nombre et clignote la LED "verte" ce nombre de fois.
Courant mesuré
Fonctionnant à 5 V, j'ai mesuré environ 120 nA de courant pendant le sommeil (0,120 µA).
Message d'éveil
Un problème est cependant que le premier octet arrivant est perdu en raison du fait que le matériel série s'attend à une baisse du niveau sur Rx (le bit de démarrage) qui est déjà arrivé au moment où il est complètement réveillé.
Je suggère (comme dans la réponse de geometrikal) que vous envoyiez d'abord un message "éveillé", puis que vous vous arrêtiez un court instant. La pause consiste à s'assurer que le matériel n'interprète pas l'octet suivant comme faisant partie du message d'éveil. Après cela, cela devrait bien fonctionner.
Comme cela utilise une interruption par changement de broches, aucun autre matériel n'est requis.
Version modifiée utilisant SoftwareSerial
La version ci-dessous traite avec succès le premier octet reçu en série. Il le fait en:
Utilisation de SoftwareSerial qui utilise des interruptions de changement de broche. L'interruption provoquée par le bit de démarrage du premier octet série réveille également le processeur.
Réglage des fusibles pour utiliser:
- Oscillateur RC interne
- DBO désactivé
- Les fusibles étaient: Low: 0xD2, High: 0xDF, Extended: 0xFF
Inspiré par FarO dans un commentaire, cela permet au processeur de se réveiller en 6 cycles d'horloge (750 ns). À 9600 bauds, chaque temps de bit est de 1/9600 (104,2 µs), donc le retard supplémentaire est insignifiant.
#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
La consommation d'énergie pendant le sommeil a été mesurée à 260 nA (0,260 µA), ce qui représente une très faible consommation lorsqu'elle n'est pas nécessaire.
Notez qu'avec les fusibles réglés comme ça, le processeur fonctionne à 8 MHz. Vous devez donc en informer l'IDE (par exemple, sélectionnez "Lilypad" comme type de carte). De cette façon, les retards et SoftwareSerial fonctionneront à la bonne vitesse.