Skip to content

Commit 6fd308a

Browse files
authored
Merge pull request #25 from Lincoln-Agritech/master
Add support for ADC wakeup interrupt on SAMD21
2 parents c1b24fb + fa71703 commit 6fd308a

File tree

3 files changed

+204
-14
lines changed

3 files changed

+204
-14
lines changed

Diff for: examples/AdcWakeup/AdcWakeup.ino

+61
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+
}

Diff for: src/ArduinoLowPower.h

+17
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);

Diff for: src/samd/ArduinoLowPower.cpp

+126-14
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)