sleep-until-switch

nkrkv/avr-sleep/sleep-until-switch

Puts the mictrocontroller to deep sleep until the signal changes on a port either from high to low or from low to high.
sleep-until-switch
@/sleep-until-switch
Puts the mictrocontroller to deep sleep until the signal changes on a port either from high to low or from low to high.
WAKEport
The board port to use for sleep exit. The port should support hardware interrupts. For example, Arduino Uno and Nano only support interrupts on D2 and D3.
SLPpulse
Put the board to the deep sleep.
sleep-until-switch
WAKE
SLP
DONE
DONEpulse
Triggers when the board exits the sleep mode.
To use the node in your project you should have the nkrkv/avr-sleep library installed. Use the “File → Add Library” menu item in XOD IDE if you don’t have it yet. See Using libraries for more info.

C++ implementation

// Based on https://github.com/RalphBacon/Arduino-Deep-Sleep/blob/master/Sleep_ATMEGA328P.ino

{{#global}}
#include <avr/sleep.h>
{{/global}}

struct State { };

{{ GENERATED_CODE }}

// To transfer the context to the ISR function, we have to keep the
// interrupt number global. Will set it in `evaluate`
volatile uint8_t g_interruptNum = NOT_AN_INTERRUPT;

// Also store the ADC state to bring it back
volatile uint8_t g_prevADCSRA;

void wakeISR() {
    // Prevent sleep mode, so we don't enter it again,
    // except deliberately, by code
    sleep_disable();

    // Detach the interrupt that brought us out of sleep
    detachInterrupt(g_interruptNum);
    g_interruptNum = NOT_AN_INTERRUPT;

    // Restore ADC state
    ADCSRA = g_prevADCSRA;
}

void evaluate(Context ctx) {
    if (!isInputDirty<input_SLP>(ctx))
        return;

    auto wakePort = getValue<input_WAKE>(ctx);
    g_interruptNum = digitalPinToInterrupt(wakePort); // save globally
    if (g_interruptNum == NOT_AN_INTERRUPT) {
        // The pin does not support interrupts
        raiseError(ctx);
        return;
    }

    // Disable the ADC (Analog to digital converter, pins A0 [14] to A5 [19])
    g_prevADCSRA = ADCSRA;
    ADCSRA = 0;

    // Ensure we can wake up again by first disabling interupts (temporarily) so
    // the wakeISR does not run before we are asleep and then prevent interrupts,
    // and then defining the ISR (Interrupt Service Routine) to run when poked awake
    noInterrupts();
    attachInterrupt(g_interruptNum, wakeISR, CHANGE);

    // Clear pending external interrupts on the pin if any
    EIFR |= (1 << g_interruptNum);

    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    sleep_bod_disable();
    interrupts();
    sleep_cpu();

    emitValue<output_DONE>(ctx, 1);
}