initial commit
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
// 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 "WatchdogSAMD.h"
|
||||
#include <sam.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;
|
||||
uint8_t bits;
|
||||
|
||||
if (!_initialized)
|
||||
_initialize_wdt();
|
||||
|
||||
#if defined(__SAMD51__)
|
||||
WDT->CTRLA.reg = 0; // Disable watchdog for config
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
;
|
||||
#else
|
||||
WDT->CTRL.reg = 0; // Disable watchdog for config
|
||||
while (WDT->STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
#endif
|
||||
|
||||
// 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 defined(__SAMD51__)
|
||||
if (isForSleep) {
|
||||
WDT->INTFLAG.bit.EW = 1; // Clear interrupt flag
|
||||
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->EWCTRL.bit.EWOFFSET = 0x0; // Early warning offset
|
||||
WDT->CTRLA.bit.WEN = 1; // Enable window mode
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
; // Sync CTRL write
|
||||
} else {
|
||||
WDT->INTENCLR.bit.EW = 1; // Disable early warning interrupt
|
||||
WDT->CONFIG.bit.PER = bits; // Set period for chip reset
|
||||
WDT->CTRLA.bit.WEN = 0; // Disable window mode
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
; // Sync CTRL write
|
||||
}
|
||||
|
||||
reset(); // Clear watchdog interval
|
||||
WDT->CTRLA.bit.ENABLE = 1; // Start watchdog now!
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
;
|
||||
#else
|
||||
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
|
||||
}
|
||||
|
||||
reset(); // Clear watchdog interval
|
||||
WDT->CTRL.bit.ENABLE = 1; // Start watchdog now!
|
||||
while (WDT->STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
#endif
|
||||
|
||||
return (cycles * 1000L + 512) / 1024; // WDT cycles -> ms
|
||||
}
|
||||
|
||||
void WatchdogSAMD::reset() {
|
||||
// Write the watchdog clear key value (0xA5) to the watchdog
|
||||
// clear register to clear the watchdog timer and reset it.
|
||||
#if defined(__SAMD51__)
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
;
|
||||
#else
|
||||
while (WDT->STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
#endif
|
||||
WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
|
||||
}
|
||||
|
||||
uint8_t WatchdogSAMD::resetCause() {
|
||||
#if defined(__SAMD51__)
|
||||
return RSTC->RCAUSE.reg;
|
||||
#else
|
||||
return PM->RCAUSE.reg;
|
||||
#endif
|
||||
}
|
||||
|
||||
void WatchdogSAMD::disable() {
|
||||
#if defined(__SAMD51__)
|
||||
WDT->CTRLA.bit.ENABLE = 0;
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
;
|
||||
#else
|
||||
WDT->CTRL.bit.ENABLE = 0;
|
||||
while (WDT->STATUS.bit.SYNCBUSY)
|
||||
;
|
||||
#endif
|
||||
}
|
||||
|
||||
void WDT_Handler(void) {
|
||||
// ISR for watchdog early warning, DO NOT RENAME!
|
||||
#if defined(__SAMD51__)
|
||||
WDT->CTRLA.bit.ENABLE = 0; // Disable watchdog
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
;
|
||||
#else
|
||||
WDT->CTRL.bit.ENABLE = 0; // Disable watchdog
|
||||
while (WDT->STATUS.bit.SYNCBUSY)
|
||||
; // Sync CTRL write
|
||||
#endif
|
||||
WDT->INTFLAG.bit.EW = 1; // Clear interrupt flag
|
||||
}
|
||||
|
||||
int WatchdogSAMD::sleep(int maxPeriodMS) {
|
||||
|
||||
int actualPeriodMS = enable(maxPeriodMS, true); // true = for sleep
|
||||
|
||||
// Enable standby sleep mode (deepest sleep) and activate.
|
||||
// Insights from Atmel ASF library.
|
||||
#if (SAMD20_SERIES || SAMD21_SERIES)
|
||||
// Don't fully power down flash when in sleep
|
||||
NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val;
|
||||
#endif
|
||||
#if defined(__SAMD51__)
|
||||
PM->SLEEPCFG.bit.SLEEPMODE = 0x4; // Standby sleep mode
|
||||
while (PM->SLEEPCFG.bit.SLEEPMODE != 0x4)
|
||||
; // Wait for it to take
|
||||
#else
|
||||
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
|
||||
// Due to a hardware bug on the SAMD21, the SysTick interrupts become
|
||||
// active before the flash has powered up from sleep, causing a hard fault.
|
||||
// To prevent this the SysTick interrupts are disabled before entering sleep
|
||||
// mode.
|
||||
SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; // Disable SysTick interrupts
|
||||
#endif
|
||||
|
||||
__DSB(); // Data sync to ensure outgoing memory accesses complete
|
||||
__WFI(); // Wait for interrupt (places device in sleep mode)
|
||||
|
||||
#if (SAMD20_SERIES || SAMD21_SERIES)
|
||||
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; // Enable SysTick interrupts
|
||||
#endif
|
||||
|
||||
// Code resumes here on wake (WDT early warning interrupt).
|
||||
// Bug: the return value assumes the WDT has run its course;
|
||||
// incorrect if the device woke due to an external interrupt.
|
||||
// Without an external RTC there's no way to provide a correct
|
||||
// sleep period in the latter case...but at the very least,
|
||||
// might indicate said condition occurred by returning 0 instead
|
||||
// (assuming we can pin down which interrupt caused the wake).
|
||||
|
||||
return actualPeriodMS;
|
||||
}
|
||||
|
||||
void WatchdogSAMD::_initialize_wdt() {
|
||||
// One-time initialization of watchdog timer.
|
||||
// Insights from rickrlh and rbrucemtl in Arduino forum!
|
||||
|
||||
#if defined(__SAMD51__)
|
||||
// SAMD51 WDT uses OSCULP32k as input clock now
|
||||
// section: 20.5.3
|
||||
OSC32KCTRL->OSCULP32K.bit.EN1K = 1; // Enable out 1K (for WDT)
|
||||
OSC32KCTRL->OSCULP32K.bit.EN32K = 0; // Disable out 32K
|
||||
|
||||
// Enable WDT early-warning interrupt
|
||||
NVIC_DisableIRQ(WDT_IRQn);
|
||||
NVIC_ClearPendingIRQ(WDT_IRQn);
|
||||
NVIC_SetPriority(WDT_IRQn, 0); // Top priority
|
||||
NVIC_EnableIRQ(WDT_IRQn);
|
||||
|
||||
while (WDT->SYNCBUSY.reg)
|
||||
;
|
||||
|
||||
USB->DEVICE.CTRLA.bit.ENABLE = 0; // Disable the USB peripheral
|
||||
while (USB->DEVICE.SYNCBUSY.bit.ENABLE)
|
||||
; // Wait for synchronization
|
||||
USB->DEVICE.CTRLA.bit.RUNSTDBY = 0; // Deactivate run on standby
|
||||
USB->DEVICE.CTRLA.bit.ENABLE = 1; // Enable the USB peripheral
|
||||
while (USB->DEVICE.SYNCBUSY.bit.ENABLE)
|
||||
; // Wait for synchronization
|
||||
#else
|
||||
// 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);
|
||||
#endif
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
#endif // defined(ARDUINO_ARCH_SAMD)
|
||||
Reference in New Issue
Block a user