Skip to content

Commit fa71703

Browse files
committed
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.
1 parent c1b24fb commit fa71703

File tree

3 files changed

+204
-14
lines changed

3 files changed

+204
-14
lines changed

examples/AdcWakeup/AdcWakeup.ino

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
AdcWakeup
3+
4+
This sketch demonstrates the usage of the ADC to wakeup a chip in sleep mode.
5+
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.
6+
7+
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.
8+
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)
9+
10+
This example code is in the public domain.
11+
*/
12+
13+
#include "ArduinoLowPower.h"
14+
15+
// Blink sequence number
16+
// Declare it volatile since it's incremented inside an interrupt
17+
volatile int repetitions = 1;
18+
19+
// Pin used to trigger a wakeup
20+
const int pin = A0;
21+
// How sensitive to be to changes in voltage
22+
const int margin = 10;
23+
24+
void setup() {
25+
pinMode(LED_BUILTIN, OUTPUT);
26+
pinMode(pin, INPUT);
27+
}
28+
29+
void loop() {
30+
for (int i = 0; i < repetitions; i++) {
31+
digitalWrite(LED_BUILTIN, HIGH);
32+
delay(500);
33+
digitalWrite(LED_BUILTIN, LOW);
34+
delay(500);
35+
}
36+
37+
// Read the voltage at the ADC pin
38+
int value = analogRead(pin);
39+
40+
// Define a window around that value
41+
uint16_t lo = max(value - margin, 0);
42+
uint16_t hi = min(value + margin, UINT16_MAX);
43+
44+
// Attach an ADC interrupt on pin A0, calling repetitionsIncrease when the voltage is outside the given range.
45+
// This should be called immediately before LowPower.sleep() because it reconfigures the ADC internally.
46+
LowPower.attachAdcInterrupt(pin, repetitionsIncrease, ADC_INT_OUTSIDE, lo, hi);
47+
48+
// Triggers an infinite sleep (the device will be woken up only by the registered wakeup sources)
49+
// The power consumption of the chip will drop consistently
50+
LowPower.sleep();
51+
52+
// Detach the ADC interrupt. This should be called immediately after LowPower.sleep() because it restores the ADC configuration after waking up.
53+
LowPower.detachAdcInterrupt();
54+
}
55+
56+
void repetitionsIncrease() {
57+
// This function will be called once on device wakeup
58+
// You can do some little operations here (like changing variables which will be used in the loop)
59+
// Remember to avoid calling delay() and long running functions since this functions executes in interrupt context
60+
repetitions ++;
61+
}

src/ArduinoLowPower.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ typedef enum{
2828
ANALOG_COMPARATOR_WAKEUP = 3
2929
} wakeup_reason;
3030

31+
#ifdef ARDUINO_ARCH_SAMD
32+
enum adc_interrupt
33+
{
34+
ADC_INT_BETWEEN,
35+
ADC_INT_OUTSIDE,
36+
ADC_INT_ABOVE_MIN,
37+
ADC_INT_BELOW_MAX,
38+
};
39+
#endif
40+
3141

3242
class ArduinoLowPowerClass {
3343
public:
@@ -68,10 +78,17 @@ class ArduinoLowPowerClass {
6878
wakeup_reason wakeupReason();
6979
#endif
7080

81+
#ifdef ARDUINO_ARCH_SAMD
82+
void attachAdcInterrupt(uint32_t pin, voidFuncPtr callback, adc_interrupt mode, uint16_t lo, uint16_t hi);
83+
void detachAdcInterrupt();
84+
#endif
85+
7186
private:
7287
void setAlarmIn(uint32_t millis);
7388
#ifdef ARDUINO_ARCH_SAMD
7489
RTCZero rtc;
90+
voidFuncPtr adc_cb;
91+
friend void ADC_Handler();
7592
#endif
7693
#ifdef BOARD_HAS_COMPANION_CHIP
7794
void (*companionSleepCB)(bool);

src/samd/ArduinoLowPower.cpp

Lines changed: 126 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,27 @@
33
#include "ArduinoLowPower.h"
44
#include "WInterrupts.h"
55

6+
static void configGCLK6()
7+
{
8+
// enable EIC clock
9+
GCLK->CLKCTRL.bit.CLKEN = 0; //disable GCLK module
10+
while (GCLK->STATUS.bit.SYNCBUSY);
11+
12+
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK6 | GCLK_CLKCTRL_ID( GCM_EIC )) ; //EIC clock switched on GCLK6
13+
while (GCLK->STATUS.bit.SYNCBUSY);
14+
15+
GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(6)); //source for GCLK6 is OSCULP32K
16+
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
17+
18+
GCLK->GENCTRL.bit.RUNSTDBY = 1; //GCLK6 run standby
19+
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
20+
21+
/* Errata: Make sure that the Flash does not power all the way down
22+
* when in sleep mode. */
23+
24+
NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;
25+
}
26+
627
void ArduinoLowPowerClass::idle() {
728
SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
829
PM->SLEEP.reg = 2;
@@ -80,26 +101,117 @@ void ArduinoLowPowerClass::attachInterruptWakeup(uint32_t pin, voidFuncPtr callb
80101
//pinMode(pin, INPUT_PULLUP);
81102
attachInterrupt(pin, callback, mode);
82103

83-
// enable EIC clock
84-
GCLK->CLKCTRL.bit.CLKEN = 0; //disable GCLK module
85-
while (GCLK->STATUS.bit.SYNCBUSY);
104+
configGCLK6();
86105

87-
GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK6 | GCLK_CLKCTRL_ID( GCM_EIC )) ; //EIC clock switched on GCLK6
88-
while (GCLK->STATUS.bit.SYNCBUSY);
106+
// Enable wakeup capability on pin in case being used during sleep
107+
EIC->WAKEUP.reg |= (1 << in);
108+
}
89109

90-
GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(6)); //source for GCLK6 is OSCULP32K
91-
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
110+
void ArduinoLowPowerClass::attachAdcInterrupt(uint32_t pin, voidFuncPtr callback, adc_interrupt mode, uint16_t lo, uint16_t hi)
111+
{
112+
uint8_t winmode = 0;
92113

93-
GCLK->GENCTRL.bit.RUNSTDBY = 1; //GCLK6 run standby
94-
while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY);
114+
switch (mode) {
115+
case ADC_INT_BETWEEN: winmode = ADC_WINCTRL_WINMODE_MODE3; break;
116+
case ADC_INT_OUTSIDE: winmode = ADC_WINCTRL_WINMODE_MODE4; break;
117+
case ADC_INT_ABOVE_MIN: winmode = ADC_WINCTRL_WINMODE_MODE1; break;
118+
case ADC_INT_BELOW_MAX: winmode = ADC_WINCTRL_WINMODE_MODE2; break;
119+
default: return;
120+
}
95121

96-
// Enable wakeup capability on pin in case being used during sleep
97-
EIC->WAKEUP.reg |= (1 << in);
122+
adc_cb = callback;
98123

99-
/* Errata: Make sure that the Flash does not power all the way down
100-
* when in sleep mode. */
124+
configGCLK6();
101125

102-
NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;
126+
// Configure ADC to use GCLK6 (OSCULP32K)
127+
while (GCLK->STATUS.bit.SYNCBUSY) {}
128+
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_ADC
129+
| GCLK_CLKCTRL_GEN_GCLK6
130+
| GCLK_CLKCTRL_CLKEN;
131+
while (GCLK->STATUS.bit.SYNCBUSY) {}
132+
133+
// Set ADC prescaler as low as possible
134+
ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV4;
135+
while (ADC->STATUS.bit.SYNCBUSY) {}
136+
137+
// Configure window mode
138+
ADC->WINLT.reg = lo;
139+
ADC->WINUT.reg = hi;
140+
ADC->WINCTRL.reg = winmode;
141+
while (ADC->STATUS.bit.SYNCBUSY) {}
142+
143+
// Enable window interrupt
144+
ADC->INTENSET.bit.WINMON = 1;
145+
while (ADC->STATUS.bit.SYNCBUSY) {}
146+
147+
// Enable ADC in standby mode
148+
ADC->CTRLA.bit.RUNSTDBY = 1;
149+
while (ADC->STATUS.bit.SYNCBUSY) {}
150+
151+
// Enable continuous conversions
152+
ADC->CTRLB.bit.FREERUN = 1;
153+
while (ADC->STATUS.bit.SYNCBUSY) {}
154+
155+
// Configure input mux
156+
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber;
157+
while (ADC->STATUS.bit.SYNCBUSY) {}
158+
159+
// Enable the ADC
160+
ADC->CTRLA.bit.ENABLE = 1;
161+
while (ADC->STATUS.bit.SYNCBUSY) {}
162+
163+
// Start continuous conversions
164+
ADC->SWTRIG.bit.START = 1;
165+
while (ADC->STATUS.bit.SYNCBUSY) {}
166+
167+
// Enable the ADC interrupt
168+
NVIC_EnableIRQ(ADC_IRQn);
169+
}
170+
171+
void ArduinoLowPowerClass::detachAdcInterrupt()
172+
{
173+
// Disable the ADC interrupt
174+
NVIC_DisableIRQ(ADC_IRQn);
175+
176+
// Disable the ADC
177+
ADC->CTRLA.bit.ENABLE = 0;
178+
while (ADC->STATUS.bit.SYNCBUSY) {}
179+
180+
// Disable continuous conversions
181+
ADC->CTRLB.bit.FREERUN = 0;
182+
while (ADC->STATUS.bit.SYNCBUSY) {}
183+
184+
// Disable ADC in standby mode
185+
ADC->CTRLA.bit.RUNSTDBY = 1;
186+
while (ADC->STATUS.bit.SYNCBUSY) {}
187+
188+
// Disable window interrupt
189+
ADC->INTENCLR.bit.WINMON = 1;
190+
while (ADC->STATUS.bit.SYNCBUSY) {}
191+
192+
// Disable window mode
193+
ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE;
194+
while (ADC->STATUS.bit.SYNCBUSY) {}
195+
196+
// Restore ADC prescaler
197+
ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV512_Val;
198+
while (ADC->STATUS.bit.SYNCBUSY) {}
199+
200+
// Restore ADC clock
201+
while (GCLK->STATUS.bit.SYNCBUSY) {}
202+
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_ADC
203+
| GCLK_CLKCTRL_GEN_GCLK0
204+
| GCLK_CLKCTRL_CLKEN;
205+
while (GCLK->STATUS.bit.SYNCBUSY) {}
206+
207+
adc_cb = nullptr;
208+
}
209+
210+
void ADC_Handler()
211+
{
212+
// Clear the interrupt flag
213+
ADC->INTFLAG.bit.WINMON = 1;
214+
LowPower.adc_cb();
103215
}
104216

105217
ArduinoLowPowerClass LowPower;

0 commit comments

Comments
 (0)