sleep-until-switch

wayland/avr-sleep/sleep-until-switch

Puts the microcontroller into 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 microcontroller into 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.
Sleeppulse
Put the board into a deep sleep.
sleep-until-switch
Wake
Sleep
Done
Donepulse
Pulse emitted when the board exits the sleep mode.
To use the node in your project you should have the wayland/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

#include <avr/sleep.h>

// 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;

node {
    static_assert(
        digitalPinToInterrupt(constant_input_Wake) != NOT_AN_INTERRUPT,
        "Port should support interrupts"
    );

    static 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_Sleep>(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();
        
        g_interruptNum = digitalPinToInterrupt(constant_input_Wake); // save globally
        
        ::pinMode(constant_input_Wake, INPUT);
        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);
    }
}