1
0
mirror of https://github.com/arduino-libraries/ArduinoLowPower.git synced 2025-07-29 21:21:10 +03:00

Add support for ADC wakeup interrupt on SAMD21

This can be used to configure the ADC window interrupt on the SAMD21. It
uses OSCULP32K via GCLK6 to clock the ADC while in sleep mode (the same
as used for the EIC).

Note that attachAdcInterrupt()/detachAdcInterrupt() should be called
immediately before/after LowPower.sleep() otherwise analogRead() will
not work as expected.

There is also an example (AdcWakeup.ino) which is much like the
ExternalWakeup example but uses the ADC interrupt instead.
This commit is contained in:
Simon Knopp
2020-02-18 14:59:44 +13:00
parent c1b24fb456
commit fa71703f58
3 changed files with 205 additions and 15 deletions

View File

@ -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);

View File

@ -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);
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);
configGCLK6();
// Enable wakeup capability on pin in case being used during sleep
EIC->WAKEUP.reg |= (1 << in);
}
/* Errata: Make sure that the Flash does not power all the way down
* when in sleep mode. */
void ArduinoLowPowerClass::attachAdcInterrupt(uint32_t pin, voidFuncPtr callback, adc_interrupt mode, uint16_t lo, uint16_t hi)
{
uint8_t winmode = 0;
NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;
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;
}
adc_cb = callback;
configGCLK6();
// 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;