diff --git a/examples/AdcWakeup/AdcWakeup.ino b/examples/AdcWakeup/AdcWakeup.ino new file mode 100644 index 0000000..24cafe2 --- /dev/null +++ b/examples/AdcWakeup/AdcWakeup.ino @@ -0,0 +1,61 @@ +/* + AdcWakeup + + This sketch demonstrates the usage of the ADC to wakeup a chip in sleep mode. + Sleep modes allow a significant drop in the power usage of a board while it does nothing waiting for an event to happen. Battery powered application can take advantage of these modes to enhance battery life significantly. + + In this sketch, changing the voltage on pin A0 will wake up the board. You can test this by connecting a potentiometer between VCC, A0, and GND. + Please note that, if the processor is sleeping, a new sketch can't be uploaded. To overcome this, manually reset the board (usually with a single or double tap to the RESET button) + + This example code is in the public domain. +*/ + +#include "ArduinoLowPower.h" + +// Blink sequence number +// Declare it volatile since it's incremented inside an interrupt +volatile int repetitions = 1; + +// Pin used to trigger a wakeup +const int pin = A0; +// How sensitive to be to changes in voltage +const int margin = 10; + +void setup() { + pinMode(LED_BUILTIN, OUTPUT); + pinMode(pin, INPUT); +} + +void loop() { + for (int i = 0; i < repetitions; i++) { + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + } + + // Read the voltage at the ADC pin + int value = analogRead(pin); + + // Define a window around that value + uint16_t lo = max(value - margin, 0); + uint16_t hi = min(value + margin, UINT16_MAX); + + // Attach an ADC interrupt on pin A0, calling repetitionsIncrease when the voltage is outside the given range. + // This should be called immediately before LowPower.sleep() because it reconfigures the ADC internally. + LowPower.attachAdcInterrupt(pin, repetitionsIncrease, ADC_INT_OUTSIDE, lo, hi); + + // Triggers an infinite sleep (the device will be woken up only by the registered wakeup sources) + // The power consumption of the chip will drop consistently + LowPower.sleep(); + + // Detach the ADC interrupt. This should be called immediately after LowPower.sleep() because it restores the ADC configuration after waking up. + LowPower.detachAdcInterrupt(); +} + +void repetitionsIncrease() { + // This function will be called once on device wakeup + // You can do some little operations here (like changing variables which will be used in the loop) + // Remember to avoid calling delay() and long running functions since this functions executes in interrupt context + repetitions ++; +} \ No newline at end of file diff --git a/src/ArduinoLowPower.h b/src/ArduinoLowPower.h index 474c879..6863349 100644 --- a/src/ArduinoLowPower.h +++ b/src/ArduinoLowPower.h @@ -28,6 +28,16 @@ typedef enum{ ANALOG_COMPARATOR_WAKEUP = 3 } wakeup_reason; +#ifdef ARDUINO_ARCH_SAMD +enum adc_interrupt +{ + ADC_INT_BETWEEN, + ADC_INT_OUTSIDE, + ADC_INT_ABOVE_MIN, + ADC_INT_BELOW_MAX, +}; +#endif + class ArduinoLowPowerClass { public: @@ -68,10 +78,17 @@ class ArduinoLowPowerClass { wakeup_reason wakeupReason(); #endif + #ifdef ARDUINO_ARCH_SAMD + void attachAdcInterrupt(uint32_t pin, voidFuncPtr callback, adc_interrupt mode, uint16_t lo, uint16_t hi); + void detachAdcInterrupt(); + #endif + private: void setAlarmIn(uint32_t millis); #ifdef ARDUINO_ARCH_SAMD RTCZero rtc; + voidFuncPtr adc_cb; + friend void ADC_Handler(); #endif #ifdef BOARD_HAS_COMPANION_CHIP void (*companionSleepCB)(bool); diff --git a/src/samd/ArduinoLowPower.cpp b/src/samd/ArduinoLowPower.cpp index a775137..fb8646f 100644 --- a/src/samd/ArduinoLowPower.cpp +++ b/src/samd/ArduinoLowPower.cpp @@ -3,6 +3,27 @@ #include "ArduinoLowPower.h" #include "WInterrupts.h" +static void configGCLK6() +{ + // enable EIC clock + GCLK->CLKCTRL.bit.CLKEN = 0; //disable GCLK module + while (GCLK->STATUS.bit.SYNCBUSY); + + GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK6 | GCLK_CLKCTRL_ID( GCM_EIC )) ; //EIC clock switched on GCLK6 + while (GCLK->STATUS.bit.SYNCBUSY); + + GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(6)); //source for GCLK6 is OSCULP32K + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); + + GCLK->GENCTRL.bit.RUNSTDBY = 1; //GCLK6 run standby + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); + + /* Errata: Make sure that the Flash does not power all the way down + * when in sleep mode. */ + + NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val; +} + void ArduinoLowPowerClass::idle() { SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; PM->SLEEP.reg = 2; @@ -80,26 +101,117 @@ void ArduinoLowPowerClass::attachInterruptWakeup(uint32_t pin, voidFuncPtr callb //pinMode(pin, INPUT_PULLUP); attachInterrupt(pin, callback, mode); - // enable EIC clock - GCLK->CLKCTRL.bit.CLKEN = 0; //disable GCLK module - while (GCLK->STATUS.bit.SYNCBUSY); + configGCLK6(); - GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK6 | GCLK_CLKCTRL_ID( GCM_EIC )) ; //EIC clock switched on GCLK6 - while (GCLK->STATUS.bit.SYNCBUSY); + // Enable wakeup capability on pin in case being used during sleep + EIC->WAKEUP.reg |= (1 << in); +} - GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(6)); //source for GCLK6 is OSCULP32K - while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); +void ArduinoLowPowerClass::attachAdcInterrupt(uint32_t pin, voidFuncPtr callback, adc_interrupt mode, uint16_t lo, uint16_t hi) +{ + uint8_t winmode = 0; - GCLK->GENCTRL.bit.RUNSTDBY = 1; //GCLK6 run standby - while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); + switch (mode) { + case ADC_INT_BETWEEN: winmode = ADC_WINCTRL_WINMODE_MODE3; break; + case ADC_INT_OUTSIDE: winmode = ADC_WINCTRL_WINMODE_MODE4; break; + case ADC_INT_ABOVE_MIN: winmode = ADC_WINCTRL_WINMODE_MODE1; break; + case ADC_INT_BELOW_MAX: winmode = ADC_WINCTRL_WINMODE_MODE2; break; + default: return; + } - // Enable wakeup capability on pin in case being used during sleep - EIC->WAKEUP.reg |= (1 << in); + adc_cb = callback; - /* Errata: Make sure that the Flash does not power all the way down - * when in sleep mode. */ + configGCLK6(); - NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val; + // Configure ADC to use GCLK6 (OSCULP32K) + while (GCLK->STATUS.bit.SYNCBUSY) {} + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_ADC + | GCLK_CLKCTRL_GEN_GCLK6 + | GCLK_CLKCTRL_CLKEN; + while (GCLK->STATUS.bit.SYNCBUSY) {} + + // Set ADC prescaler as low as possible + ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV4; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Configure window mode + ADC->WINLT.reg = lo; + ADC->WINUT.reg = hi; + ADC->WINCTRL.reg = winmode; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable window interrupt + ADC->INTENSET.bit.WINMON = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable ADC in standby mode + ADC->CTRLA.bit.RUNSTDBY = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable continuous conversions + ADC->CTRLB.bit.FREERUN = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Configure input mux + ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable the ADC + ADC->CTRLA.bit.ENABLE = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Start continuous conversions + ADC->SWTRIG.bit.START = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable the ADC interrupt + NVIC_EnableIRQ(ADC_IRQn); +} + +void ArduinoLowPowerClass::detachAdcInterrupt() +{ + // Disable the ADC interrupt + NVIC_DisableIRQ(ADC_IRQn); + + // Disable the ADC + ADC->CTRLA.bit.ENABLE = 0; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable continuous conversions + ADC->CTRLB.bit.FREERUN = 0; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable ADC in standby mode + ADC->CTRLA.bit.RUNSTDBY = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable window interrupt + ADC->INTENCLR.bit.WINMON = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable window mode + ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Restore ADC prescaler + ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV512_Val; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Restore ADC clock + while (GCLK->STATUS.bit.SYNCBUSY) {} + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_ADC + | GCLK_CLKCTRL_GEN_GCLK0 + | GCLK_CLKCTRL_CLKEN; + while (GCLK->STATUS.bit.SYNCBUSY) {} + + adc_cb = nullptr; +} + +void ADC_Handler() +{ + // Clear the interrupt flag + ADC->INTFLAG.bit.WINMON = 1; + LowPower.adc_cb(); } ArduinoLowPowerClass LowPower;