Poniższy kod spełnia to, o co prosisz:
#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
Użyłem przerwania zmiany pinu na pinie Rx, aby zauważyć, kiedy nadchodzą dane szeregowe. W tym teście płyta przechodzi w tryb uśpienia, jeśli po 5 sekundach nie będzie żadnej aktywności (dioda LED „czuwania” gaśnie). Przychodzące dane szeregowe powodują, że przerwanie zmiany pinów budzi płytkę. Wyszukuje liczbę i tyle razy miga „zielona” dioda LED.
Mierzony prąd
Pracując przy 5 V, mierzyłem około 120 nA prądu podczas snu (0,120 µA).
Przebudzenie wiadomości
Problemem jest jednak to, że pierwszy przychodzący bajt zostaje utracony z powodu faktu, że sprzęt szeregowy oczekuje spadku poziomu na Rx (bicie początkowym), który już przybył do czasu pełnego przebudzenia.
Sugeruję (jak w odpowiedzi geometrikal), aby najpierw wysłać wiadomość „przebudzoną”, a następnie wstrzymać na chwilę. Przerwa polega na upewnieniu się, że sprzęt nie interpretuje następnego bajtu jako części przebudzonego komunikatu. Potem powinno działać dobrze.
Ponieważ wykorzystuje to przerwanie zmiany pinów, nie jest wymagany żaden inny sprzęt.
Wersja poprawiona za pomocą SoftwareSerial
Poniższa wersja z powodzeniem przetwarza pierwszy bajt odebrany na serial. Robi to poprzez:
Korzystanie z SoftwareSerial, które korzysta z przerwań zmiany pinów. Przerwanie spowodowane bitem początkowym pierwszego bajtu szeregowego również budzi procesor.
Ustawienie bezpieczników w celu użycia:
- Wewnętrzny oscylator RC
- BZT wyłączone
- Bezpieczniki były: Niska: 0xD2, Wysoka: 0xDF, Rozszerzona: 0xFF
Zainspirowany przez FarO w komentarzu, procesor budzi się w 6 cyklach zegara (750 ns). Przy 9600 bodów każdy bit wynosi 1/9600 (104,2 µs), więc dodatkowe opóźnienie jest nieznaczne.
#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
Pobór mocy podczas snu mierzono jako 260 nA (0,260 µA), co oznacza bardzo niskie zużycie, gdy nie jest potrzebne.
Zauważ, że przy takim ustawieniu bezpieczników procesor pracuje z częstotliwością 8 MHz. Dlatego musisz powiedzieć IDE o tym (np. Wybierz „Lilypad” jako typ płytki). W ten sposób opóźnienia i SoftwareSerial będą działać z właściwą prędkością.