arduino/libraries/Adafruit_SleepyDog_Library/utility/WatchdogSAMD.cpp
2018-11-03 20:21:33 +00:00

176 lines
6.4 KiB
C++

// Requires Adafruit_ASFcore library!
// Be careful to use a platform-specific conditional include to only make the
// code visible for the appropriate platform. Arduino will try to compile and
// link all .cpp files regardless of platform.
#if defined(ARDUINO_ARCH_SAMD)
#include <sam.h>
#include <Adafruit_ASFcore.h>
#include <power.h>
#include "WatchdogSAMD.h"
int WatchdogSAMD::enable(int maxPeriodMS, bool isForSleep) {
// Enable the watchdog with a period up to the specified max period in
// milliseconds.
// Review the watchdog section from the SAMD21 datasheet section 17:
// http://www.atmel.com/images/atmel-42181-sam-d21_datasheet.pdf
int cycles, actualMS;
uint8_t bits;
if(!_initialized) _initialize_wdt();
WDT->CTRL.reg = 0; // Disable watchdog for config
while(WDT->STATUS.bit.SYNCBUSY);
// You'll see some occasional conversion here compensating between
// milliseconds (1000 Hz) and WDT clock cycles (~1024 Hz). The low-
// power oscillator used by the WDT ostensibly runs at 32,768 Hz with
// a 1:32 prescale, thus 1024 Hz, though probably not super precise.
if((maxPeriodMS >= 16000) || !maxPeriodMS) {
cycles = 16384;
bits = 0xB;
} else {
cycles = (maxPeriodMS * 1024L + 500) / 1000; // ms -> WDT cycles
if(cycles >= 8192) {
cycles = 8192;
bits = 0xA;
} else if(cycles >= 4096) {
cycles = 4096;
bits = 0x9;
} else if(cycles >= 2048) {
cycles = 2048;
bits = 0x8;
} else if(cycles >= 1024) {
cycles = 1024;
bits = 0x7;
} else if(cycles >= 512) {
cycles = 512;
bits = 0x6;
} else if(cycles >= 256) {
cycles = 256;
bits = 0x5;
} else if(cycles >= 128) {
cycles = 128;
bits = 0x4;
} else if(cycles >= 64) {
cycles = 64;
bits = 0x3;
} else if(cycles >= 32) {
cycles = 32;
bits = 0x2;
} else if(cycles >= 16) {
cycles = 16;
bits = 0x1;
} else {
cycles = 8;
bits = 0x0;
}
}
// Watchdog timer on SAMD is a slightly different animal than on AVR.
// On AVR, the WTD timeout is configured in one register and then an
// interrupt can optionally be enabled to handle the timeout in code
// (as in waking from sleep) vs resetting the chip. Easy.
// On SAMD, when the WDT fires, that's it, the chip's getting reset.
// Instead, it has an "early warning interrupt" with a different set
// interval prior to the reset. For equivalent behavior to the AVR
// library, this requires a slightly different configuration depending
// whether we're coming from the sleep() function (which needs the
// interrupt), or just enable() (no interrupt, we want the chip reset
// unless the WDT is cleared first). In the sleep case, 'windowed'
// mode is used in order to allow access to the longest available
// sleep interval (about 16 sec); the WDT 'period' (when a reset
// occurs) follows this and is always just set to the max, since the
// interrupt will trigger first. In the enable case, windowed mode
// is not used, the WDT period is set and that's that.
// The 'isForSleep' argument determines which behavior is used;
// this isn't present in the AVR code, just here. It defaults to
// 'false' so existing Arduino code works as normal, while the sleep()
// function (later in this file) explicitly passes 'true' to get the
// alternate behavior.
if(isForSleep) {
WDT->INTENSET.bit.EW = 1; // Enable early warning interrupt
WDT->CONFIG.bit.PER = 0xB; // Period = max
WDT->CONFIG.bit.WINDOW = bits; // Set time of interrupt
WDT->CTRL.bit.WEN = 1; // Enable window mode
while(WDT->STATUS.bit.SYNCBUSY); // Sync CTRL write
} else {
WDT->INTENCLR.bit.EW = 1; // Disable early warning interrupt
WDT->CONFIG.bit.PER = bits; // Set period for chip reset
WDT->CTRL.bit.WEN = 0; // Disable window mode
while(WDT->STATUS.bit.SYNCBUSY); // Sync CTRL write
}
actualMS = (cycles * 1000L + 512) / 1024; // WDT cycles -> ms
reset(); // Clear watchdog interval
WDT->CTRL.bit.ENABLE = 1; // Start watchdog now!
while(WDT->STATUS.bit.SYNCBUSY);
return actualMS;
}
void WatchdogSAMD::reset() {
// Write the watchdog clear key value (0xA5) to the watchdog
// clear register to clear the watchdog timer and reset it.
WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
while(WDT->STATUS.bit.SYNCBUSY);
}
void WatchdogSAMD::disable() {
WDT->CTRL.bit.ENABLE = 0;
while(WDT->STATUS.bit.SYNCBUSY);
}
void WDT_Handler(void) {
// ISR for watchdog early warning, DO NOT RENAME!
WDT->CTRL.bit.ENABLE = 0; // Disable watchdog
while(WDT->STATUS.bit.SYNCBUSY); // Sync CTRL write
WDT->INTFLAG.bit.EW = 1; // Clear interrupt flag
}
int WatchdogSAMD::sleep(int maxPeriodMS) {
int actualPeriodMS = enable(maxPeriodMS, true); // true = for sleep
system_set_sleepmode(SYSTEM_SLEEPMODE_STANDBY); // Deepest sleep
system_sleep();
// Code resumes here on wake (WDT early warning interrupt)
return actualPeriodMS;
}
void WatchdogSAMD::_initialize_wdt() {
// One-time initialization of watchdog timer.
// Insights from rickrlh and rbrucemtl in Arduino forum!
// Generic clock generator 2, divisor = 32 (2^(DIV+1))
GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4);
// Enable clock generator 2 using low-power 32KHz oscillator.
// With /32 divisor above, this yields 1024Hz(ish) clock.
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) |
GCLK_GENCTRL_GENEN |
GCLK_GENCTRL_SRC_OSCULP32K |
GCLK_GENCTRL_DIVSEL;
while(GCLK->STATUS.bit.SYNCBUSY);
// WDT clock = clock gen 2
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT |
GCLK_CLKCTRL_CLKEN |
GCLK_CLKCTRL_GEN_GCLK2;
// Enable WDT early-warning interrupt
NVIC_DisableIRQ(WDT_IRQn);
NVIC_ClearPendingIRQ(WDT_IRQn);
NVIC_SetPriority(WDT_IRQn, 0); // Top priority
NVIC_EnableIRQ(WDT_IRQn);
_initialized = true;
}
#endif