initial commit

This commit is contained in:
2026-09-25 10:48:51 +10:00
commit b90beea8ae
1020 changed files with 131665 additions and 0 deletions
@@ -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)