initial commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
// 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_AVR) || defined(__AVR__)
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/power.h>
|
||||
#include <avr/sleep.h>
|
||||
#include <avr/wdt.h>
|
||||
|
||||
#include "WatchdogAVR.h"
|
||||
|
||||
// Define watchdog timer interrupt.
|
||||
ISR(WDT_vect) {
|
||||
// Nothing needs to be done, however interrupt handler must be defined to
|
||||
// prevent a reset.
|
||||
}
|
||||
|
||||
int WatchdogAVR::enable(int maxPeriodMS) {
|
||||
// Pick the closest appropriate watchdog timer value.
|
||||
int actualMS;
|
||||
_setPeriod(maxPeriodMS, _wdto, actualMS);
|
||||
// Enable the watchdog and return the actual countdown value.
|
||||
wdt_enable(_wdto);
|
||||
return actualMS;
|
||||
}
|
||||
|
||||
void WatchdogAVR::reset() {
|
||||
// Reset the watchdog.
|
||||
wdt_reset();
|
||||
}
|
||||
|
||||
void WatchdogAVR::disable() {
|
||||
// Disable the watchdog and clear any saved watchdog timer value.
|
||||
wdt_disable();
|
||||
_wdto = -1;
|
||||
}
|
||||
|
||||
int WatchdogAVR::sleep(int maxPeriodMS) {
|
||||
// Pick the closest appropriate watchdog timer value.
|
||||
int sleepWDTO, actualMS;
|
||||
_setPeriod(maxPeriodMS, sleepWDTO, actualMS);
|
||||
|
||||
// Build watchdog prescaler register value before timing critical code.
|
||||
uint8_t wdps = ((sleepWDTO & 0x08 ? 1 : 0) << WDP3) |
|
||||
((sleepWDTO & 0x04 ? 1 : 0) << WDP2) |
|
||||
((sleepWDTO & 0x02 ? 1 : 0) << WDP1) |
|
||||
((sleepWDTO & 0x01 ? 1 : 0) << WDP0);
|
||||
|
||||
// The next section is timing critical so interrupts are disabled.
|
||||
cli();
|
||||
// First clear any previous watchdog reset.
|
||||
MCUSR &= ~(1 << WDRF);
|
||||
// Now change the watchdog prescaler and interrupt enable bit so the
|
||||
// watchdog reset only triggers the interrupt (and wakes from deep sleep)
|
||||
// and not a full device reset. This is a timing critical section of
|
||||
// code that must happen in 4 cycles.
|
||||
_WD_CONTROL_REG |=
|
||||
(1 << WDCE) | (1 << WDE); // Set WDCE and WDE to enable changes.
|
||||
_WD_CONTROL_REG = wdps; // Set the prescaler bit values.
|
||||
_WD_CONTROL_REG |= (1 << WDIE); // Enable only watchdog interrupts.
|
||||
// Critical section finished, re-enable interrupts.
|
||||
sei();
|
||||
|
||||
// Disable USB if it exists
|
||||
#ifdef USBCON
|
||||
USBCON |= _BV(FRZCLK); // freeze USB clock
|
||||
PLLCSR &= ~_BV(PLLE); // turn off USB PLL
|
||||
USBCON &= ~_BV(USBE); // disable USB
|
||||
#endif
|
||||
|
||||
// Set full power-down sleep mode and go to sleep.
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
sleep_mode();
|
||||
|
||||
// Chip is now asleep!
|
||||
|
||||
// Once awakened by the watchdog execution resumes here.
|
||||
// Start by disabling sleep.
|
||||
sleep_disable();
|
||||
|
||||
// Check if user had the watchdog enabled before sleep and re-enable it.
|
||||
if (_wdto != -1)
|
||||
wdt_enable(_wdto);
|
||||
|
||||
// Return how many actual milliseconds were spent sleeping.
|
||||
return actualMS;
|
||||
}
|
||||
|
||||
void WatchdogAVR::_setPeriod(int maxMS, int &wdto, int &actualMS) {
|
||||
// Note the order of these if statements from highest to lowest is
|
||||
// important so that control flow cascades down to the right value based
|
||||
// on its position in the range of discrete timeouts.
|
||||
if ((maxMS >= 8000) || (maxMS == 0)) {
|
||||
wdto = WDTO_8S;
|
||||
actualMS = 8000;
|
||||
} else if (maxMS >= 4000) {
|
||||
wdto = WDTO_4S;
|
||||
actualMS = 4000;
|
||||
} else if (maxMS >= 2000) {
|
||||
wdto = WDTO_2S;
|
||||
actualMS = 2000;
|
||||
} else if (maxMS >= 1000) {
|
||||
wdto = WDTO_1S;
|
||||
actualMS = 1000;
|
||||
} else if (maxMS >= 500) {
|
||||
wdto = WDTO_500MS;
|
||||
actualMS = 500;
|
||||
} else if (maxMS >= 250) {
|
||||
wdto = WDTO_250MS;
|
||||
actualMS = 250;
|
||||
} else if (maxMS >= 120) {
|
||||
wdto = WDTO_120MS;
|
||||
actualMS = 120;
|
||||
} else if (maxMS >= 60) {
|
||||
wdto = WDTO_60MS;
|
||||
actualMS = 60;
|
||||
} else if (maxMS >= 30) {
|
||||
wdto = WDTO_30MS;
|
||||
actualMS = 30;
|
||||
} else {
|
||||
wdto = WDTO_15MS;
|
||||
actualMS = 15;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef WATCHDOGAVR_H
|
||||
#define WATCHDOGAVR_H
|
||||
|
||||
class WatchdogAVR {
|
||||
public:
|
||||
WatchdogAVR() : _wdto(-1) {}
|
||||
|
||||
// Enable the watchdog timer to reset the machine after a period of time
|
||||
// without any calls to reset(). The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value.
|
||||
//
|
||||
// The actual period (in milliseconds) before a watchdog timer reset is
|
||||
// returned.
|
||||
int enable(int maxPeriodMS = 0);
|
||||
|
||||
// Reset or 'kick' the watchdog timer to prevent a reset of the device.
|
||||
void reset();
|
||||
|
||||
// Completely disable the watchdog timer.
|
||||
void disable();
|
||||
|
||||
// Enter the lowest power sleep mode (using the watchdog timer) for the
|
||||
// desired period of time. The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value
|
||||
//
|
||||
// The actual period (in milliseconds) that the hardware was asleep will be
|
||||
// returned.
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
|
||||
private:
|
||||
// Pick the closest (but not higher) watchdog timer value from the provided
|
||||
// maximum period. Sets wdto to the chosen period value suitable for
|
||||
// passing to wdt_enable(), and actualMS to the chosen period value in
|
||||
// milliseconds. A max value of 0 will pick the longest value possible.
|
||||
void _setPeriod(int maxMS, int &wdto, int &actualMS);
|
||||
|
||||
// Keep the last selected watchdog timer period so that the watchdog can be
|
||||
// re-enabled at that rate after sleep. A value of -1 means no watchdog
|
||||
// timer was enabled.
|
||||
int _wdto;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "WatchdogESP32.h"
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Initializes the ESP32's Task Watchdog Timer (TWDT) and
|
||||
subscribes to the current running task.
|
||||
@param maxPeriodMS
|
||||
Timeout period of TWDT in seconds
|
||||
@return The actual period (in milliseconds) before a watchdog timer
|
||||
reset is returned. 0 otherwise.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int WatchdogESP32::enable(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 1)
|
||||
// Initialize the wdt configuration for ESP-IDF v5.x and above
|
||||
#include "soc/soc_caps.h"
|
||||
esp_task_wdt_config_t wdt_config = {
|
||||
.timeout_ms = (uint32_t)maxPeriodMS,
|
||||
.idle_core_mask = 0, // Subscribe to the idle task on the APP CPU
|
||||
.trigger_panic = true,
|
||||
};
|
||||
esp_err_t err = esp_task_wdt_init(&wdt_config);
|
||||
// Reconfigure in case TWDT was already initialized
|
||||
if (err == ESP_ERR_INVALID_STATE)
|
||||
err = esp_task_wdt_reconfigure(&wdt_config);
|
||||
#else
|
||||
// IDF V4.x and below expect TWDT in seconds
|
||||
uint32_t maxPeriod = maxPeriodMS / 1000;
|
||||
// Enable the TWDT and execute the esp32 panic handler when TWDT times out
|
||||
esp_err_t err = esp_task_wdt_init(maxPeriod, true);
|
||||
#endif
|
||||
|
||||
if (err != ESP_OK)
|
||||
return 0; // Failed to initialize TWDT
|
||||
|
||||
// NULL to subscribe the current running task to the TWDT
|
||||
err = esp_task_wdt_add(NULL);
|
||||
if (err != ESP_OK)
|
||||
return 0; // Failed to subscribe to TWDT, may be already subscribed
|
||||
|
||||
_wdto = maxPeriodMS;
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Resets the Task Watchdog Timer (TWDT) on behalf
|
||||
of the currently running task.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogESP32::reset() {
|
||||
// NOTE: This blindly resets the TWDT and does not return the esp_err.
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Unsubscribes the currently running task from the the TWDT,
|
||||
unsubscribes idle tasks from the TWDT, and deinitializes
|
||||
the TWDT.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogESP32::disable() {
|
||||
esp_task_wdt_delete(NULL); // Unsubscribe from the task we created
|
||||
esp_task_wdt_deinit(); // Deinitialize the TWDT and unsubscribe from any idle
|
||||
// tasks
|
||||
_wdto = 0; // Reset the timeout value
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Configures the ESP32 to enter a low-power sleep mode for a
|
||||
desired amount of time.
|
||||
@param maxPeriodMS
|
||||
Time to sleep the ESP32, in millis.
|
||||
@return The actual period (in milliseconds) that the hardware was
|
||||
asleep will be returned. Otherwise, 0 will be returned if the
|
||||
hardware could not enter the low-power mode.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int WatchdogESP32::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
// Convert from MS to microseconds
|
||||
uint64_t sleepTime = maxPeriodMS * 1000;
|
||||
// Enable wakeup by timer
|
||||
esp_err_t err = esp_sleep_enable_timer_wakeup(sleepTime);
|
||||
if (err != ESP_OK) {
|
||||
return 0; // sleepTime is out of range
|
||||
}
|
||||
// Enter light sleep with the timer wakeup option configured
|
||||
err = esp_light_sleep_start();
|
||||
if (err != ESP_OK) {
|
||||
return 0; // ESP_ERR_INVALID_STATE if WiFi or BT is not stopped
|
||||
}
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
@@ -0,0 +1,38 @@
|
||||
/*!
|
||||
* @file WatchdogESP32.h
|
||||
*
|
||||
* Support for ESP32 TWDT and low-power sleep modes.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* Written by Brent Rubell for Adafruit Industries.
|
||||
*
|
||||
* MIT License, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
#ifndef WATCHDOGESP32_H_
|
||||
#define WATCHDOGESP32_H_
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_task_wdt.h"
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Class that contains functions for interacting with the ESP32's
|
||||
WDT and low-power sleep functions.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class WatchdogESP32 {
|
||||
public:
|
||||
WatchdogESP32() : _wdto(-1){};
|
||||
int enable(int maxPeriodMS = 0);
|
||||
void reset();
|
||||
void disable();
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
|
||||
private:
|
||||
int _wdto;
|
||||
};
|
||||
|
||||
#endif // WATCHDOGESP32_H
|
||||
@@ -0,0 +1,76 @@
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
|
||||
#include "WatchdogESP8266.h"
|
||||
|
||||
/**********************************************************************************************/
|
||||
/*!
|
||||
@brief Initializes the ESP8266's software WDT
|
||||
@param maxPeriodMS
|
||||
Timeout period of WDT in milliseconds
|
||||
@return The actual period (in milliseconds) before a watchdog timer
|
||||
reset is returned, 0 otherwise.
|
||||
NOTE: Configuring the software WDT timeout maxPeriodMS value is NOT
|
||||
IMPLEMENTED in the ESP8266 BSP [1]. Further, an investigation into the
|
||||
default software WDT time yielded a fixed timeout period of 3.2 seconds [2].
|
||||
[1] https://github.com/esp8266/Arduino/blob/master/cores/esp8266/Esp.h#L91
|
||||
[2]https://sigmdel.ca/michel/program/esp8266/arduino/watchdogs_en.html#ESP8266_WDT_TIMEOUT
|
||||
*/
|
||||
/**********************************************************************************************/
|
||||
int WatchdogESP8266::enable(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// Enable the WDT
|
||||
ESP.wdtEnable(0);
|
||||
|
||||
_wdto = maxPeriodMS;
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Feeds the Watchdog timer.
|
||||
NOTE: Calling yield() or delay() also feeds the hardware and software
|
||||
watchdog timers.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogESP8266::reset() { ESP.wdtFeed(); }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Disables the Watchdog Timer.
|
||||
NOTE: Please don't stop the software WDT too long
|
||||
(less than 6 seconds), otherwise it will trigger a hardware
|
||||
watchdog reset!
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogESP8266::disable() { ESP.wdtDisable(); }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Configures the ESP8266 to enter a low-power sleep mode for a
|
||||
desired amount of time.
|
||||
@param maxPeriodMS
|
||||
Time to sleep the ESP8266, in millis.
|
||||
@return The actual period (in milliseconds) that the hardware was
|
||||
asleep will be returned. Otherwise, 0 will be returned if the
|
||||
hardware could not enter the low-power mode.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int WatchdogESP8266::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
// Convert from MS to microseconds
|
||||
uint64_t sleepTime = maxPeriodMS * 1000;
|
||||
|
||||
// Assert that we can not sleep longer than the max. time calculated by ESP
|
||||
if (sleepTime > ESP.deepSleepMax())
|
||||
return 0;
|
||||
|
||||
// Enters deep sleep with mode WAKE_RF_DEFAULT
|
||||
ESP.deepSleep(sleepTime);
|
||||
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_ESP8266
|
||||
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* @file WatchdogESP8266.h
|
||||
*
|
||||
* Support for ESP8266 WDT and low-power sleep modes.
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* Written by Brent Rubell for Adafruit Industries.
|
||||
*
|
||||
* MIT License, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
#ifndef WATCHDOGESP8266_H_
|
||||
#define WATCHDOGESP8266_H_
|
||||
|
||||
// #include "esp_sleep.h"
|
||||
// #include "esp_task_wdt.h"
|
||||
#include "Esp.h"
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Class that contains functions for interacting with the
|
||||
ESP8266's WDT and low-power sleep functions.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class WatchdogESP8266 {
|
||||
public:
|
||||
WatchdogESP8266() : _wdto(-1){};
|
||||
int enable(int maxPeriodMS = 0);
|
||||
void reset();
|
||||
void disable();
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
|
||||
private:
|
||||
int _wdto;
|
||||
};
|
||||
|
||||
#endif // WATCHDOGESP8266_H
|
||||
@@ -0,0 +1,100 @@
|
||||
// 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(__MK20DX128__) || defined(__MK20DX256__) || \
|
||||
defined(__MK64FX512__) || defined(__MK66FX1M0__)
|
||||
|
||||
#include "WatchdogKinetisK.h"
|
||||
#include <kinetis.h>
|
||||
|
||||
static void one_bus_cycle(void) __attribute__((always_inline));
|
||||
static void watchdog_config(int cfg, int val);
|
||||
|
||||
// Enable the watchdog timer to reset the machine after a period of time
|
||||
// without any calls to reset(). The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value.
|
||||
//
|
||||
// The actual period (in milliseconds) before a watchdog timer reset is
|
||||
// returned.
|
||||
int WatchdogKinetisKseries::enable(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 4) {
|
||||
maxPeriodMS = 8000; // default is 8 seconds
|
||||
}
|
||||
if (setting != maxPeriodMS) {
|
||||
watchdog_config(WDOG_STCTRLH_WDOGEN, maxPeriodMS);
|
||||
setting = maxPeriodMS;
|
||||
}
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
// Reset or 'kick' the watchdog timer to prevent a reset of the device.
|
||||
void WatchdogKinetisKseries::reset() {
|
||||
__disable_irq();
|
||||
WDOG_REFRESH = 0xA602;
|
||||
WDOG_REFRESH = 0xB480;
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
// Completely disable the watchdog timer.
|
||||
void WatchdogKinetisKseries::disable() {
|
||||
if (setting > 0) {
|
||||
watchdog_config(0, 4);
|
||||
setting = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Enter the lowest power sleep mode for the desired period of time. The
|
||||
// passed in period (in milliseconds) is just a suggestion and a lower value
|
||||
// might be picked if the hardware does not support the exact desired value
|
||||
//
|
||||
// The actual period (in milliseconds) that the hardware was asleep will be
|
||||
// returned.
|
||||
int WatchdogKinetisKseries::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS <= 0)
|
||||
return 0;
|
||||
// TODO....
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void watchdog_config(int cfg, int val) {
|
||||
__disable_irq();
|
||||
WDOG_UNLOCK = WDOG_UNLOCK_SEQ1;
|
||||
WDOG_UNLOCK = WDOG_UNLOCK_SEQ2;
|
||||
one_bus_cycle();
|
||||
WDOG_STCTRLH = cfg | WDOG_STCTRLH_ALLOWUPDATE;
|
||||
WDOG_TOVALH = val >> 16;
|
||||
WDOG_TOVALL = val;
|
||||
WDOG_PRESC = 0;
|
||||
__enable_irq();
|
||||
for (int i = 0; i < 256; i++) {
|
||||
one_bus_cycle();
|
||||
}
|
||||
}
|
||||
|
||||
static void one_bus_cycle(void) {
|
||||
__asm__ volatile("nop");
|
||||
#if (F_CPU / F_BUS) > 1
|
||||
__asm__ volatile("nop");
|
||||
#endif
|
||||
#if (F_CPU / F_BUS) > 2
|
||||
__asm__ volatile("nop");
|
||||
#endif
|
||||
#if (F_CPU / F_BUS) > 3
|
||||
__asm__ volatile("nop");
|
||||
#endif
|
||||
#if (F_CPU / F_BUS) > 4
|
||||
__asm__ volatile("nop");
|
||||
#endif
|
||||
#if (F_CPU / F_BUS) > 5
|
||||
__asm__ volatile("nop");
|
||||
#endif
|
||||
#if (F_CPU / F_BUS) > 6
|
||||
__asm__ volatile("nop");
|
||||
#endif
|
||||
#if (F_CPU / F_BUS) > 7
|
||||
__asm__ volatile("nop");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef WATCHDOGKINETISK_H
|
||||
#define WATCHDOGKINETISK_H
|
||||
|
||||
class WatchdogKinetisKseries {
|
||||
public:
|
||||
WatchdogKinetisKseries() : setting(0) {}
|
||||
|
||||
// Enable the watchdog timer to reset the machine after a period of time
|
||||
// without any calls to reset(). The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value.
|
||||
//
|
||||
// The actual period (in milliseconds) before a watchdog timer reset is
|
||||
// returned.
|
||||
int enable(int maxPeriodMS = 0);
|
||||
|
||||
// Reset or 'kick' the watchdog timer to prevent a reset of the device.
|
||||
void reset();
|
||||
|
||||
// Completely disable the watchdog timer.
|
||||
void disable();
|
||||
|
||||
// Enter the lowest power sleep mode (using the watchdog timer) for the
|
||||
// desired period of time. The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value
|
||||
//
|
||||
// The actual period (in milliseconds) that the hardware was asleep will be
|
||||
// returned.
|
||||
//
|
||||
// NOTE: This is currently not implemented on the SAMD21!
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
|
||||
private:
|
||||
int setting;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,72 @@
|
||||
// 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(__MKL26Z64__)
|
||||
|
||||
#include "WatchdogKinetisL.h"
|
||||
#include <kinetis.h>
|
||||
|
||||
// Normally the watchdog is disabled at startup. This removes the startup
|
||||
// code. The watchdog will be active with 1024 ms timeout. Hopefully the
|
||||
// user will configure the watchdog and begin resetting it before it causes
|
||||
// a reboot. There is no way to start up without the watchdog and then
|
||||
// enable it later...
|
||||
extern "C" void startup_early_hook(void) {}
|
||||
|
||||
// Enable the watchdog timer to reset the machine after a period of time
|
||||
// without any calls to reset(). The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value.
|
||||
//
|
||||
// The actual period (in milliseconds) before a watchdog timer reset is
|
||||
// returned.
|
||||
int WatchdogKinetisLseries::enable(int maxPeriodMS) {
|
||||
// The watchdog can only be programmed once. Then it's forever
|
||||
// locked to this setting (until the chip reboots).
|
||||
if (maxPeriodMS <= 0 || maxPeriodMS > 256) {
|
||||
SIM_COPC = 12;
|
||||
} else if (maxPeriodMS > 32) {
|
||||
SIM_COPC = 8;
|
||||
} else {
|
||||
SIM_COPC = 4;
|
||||
}
|
||||
// Read the actual setting.
|
||||
int val = SIM_COPC & 12;
|
||||
if (val == 12)
|
||||
return 1024;
|
||||
if (val == 8)
|
||||
return 256;
|
||||
return 32;
|
||||
}
|
||||
|
||||
// Reset or 'kick' the watchdog timer to prevent a reset of the device.
|
||||
void WatchdogKinetisLseries::reset() {
|
||||
__disable_irq();
|
||||
SIM_SRVCOP = 0x55;
|
||||
SIM_SRVCOP = 0xAA;
|
||||
__enable_irq();
|
||||
}
|
||||
|
||||
// Completely disable the watchdog timer.
|
||||
void WatchdogKinetisLseries::disable() {
|
||||
// no can do....
|
||||
// The watchdog timer in this chip is write-once.
|
||||
// The chip boots up with the watchdog at 1024 ms.
|
||||
// You only get to configure it once. Then it
|
||||
// remains locked to that setting, until a reboot.
|
||||
}
|
||||
|
||||
// Enter the lowest power sleep mode for the desired period of time. The
|
||||
// passed in period (in milliseconds) is just a suggestion and a lower value
|
||||
// might be picked if the hardware does not support the exact desired value
|
||||
//
|
||||
// The actual period (in milliseconds) that the hardware was asleep will be
|
||||
// returned.
|
||||
int WatchdogKinetisLseries::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS <= 0)
|
||||
return 0;
|
||||
// TODO....
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef WATCHDOGKINETISL_H
|
||||
#define WATCHDOGKINETISL_H
|
||||
|
||||
class WatchdogKinetisLseries {
|
||||
public:
|
||||
WatchdogKinetisLseries() {}
|
||||
|
||||
// Enable the watchdog timer to reset the machine after a period of time
|
||||
// without any calls to reset(). The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value.
|
||||
//
|
||||
// The actual period (in milliseconds) before a watchdog timer reset is
|
||||
// returned.
|
||||
int enable(int maxPeriodMS = 0);
|
||||
|
||||
// Reset or 'kick' the watchdog timer to prevent a reset of the device.
|
||||
void reset();
|
||||
|
||||
// Completely disable the watchdog timer.
|
||||
void disable();
|
||||
|
||||
// Enter the lowest power sleep mode (using the watchdog timer) for the
|
||||
// desired period of time. The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value
|
||||
//
|
||||
// The actual period (in milliseconds) that the hardware was asleep will be
|
||||
// returned.
|
||||
//
|
||||
// NOTE: This is currently not implemented on the SAMD21!
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifdef NRF52_SERIES
|
||||
|
||||
#include "WatchdogNRF.h"
|
||||
#include "Arduino.h"
|
||||
#include "nrf_wdt.h"
|
||||
|
||||
WatchdogNRF::WatchdogNRF() { _wdto = -1; }
|
||||
|
||||
int WatchdogNRF::enable(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// cannot change wdt config register once it is started
|
||||
// return previous configured timeout
|
||||
if (nrf_wdt_started(NRF_WDT))
|
||||
return _wdto;
|
||||
|
||||
// WDT run when CPU is sleep
|
||||
nrf_wdt_behaviour_set(NRF_WDT, NRF_WDT_BEHAVIOUR_RUN_SLEEP);
|
||||
uint32_t wdt_val = ((uint64_t)maxPeriodMS * 32768) / 1000;
|
||||
nrf_wdt_reload_value_set(NRF_WDT, wdt_val);
|
||||
|
||||
// use channel 0
|
||||
nrf_wdt_reload_request_enable(NRF_WDT, NRF_WDT_RR0);
|
||||
|
||||
// Start WDT
|
||||
// After started CRV, RREN and CONFIG is blocked
|
||||
// There is no way to stop/disable watchdog using source code
|
||||
// It can only be reset by WDT timeout, Pin reset, Power reset
|
||||
nrf_wdt_task_trigger(NRF_WDT, NRF_WDT_TASK_START);
|
||||
|
||||
_wdto = maxPeriodMS;
|
||||
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
void WatchdogNRF::reset() { nrf_wdt_reload_request_set(NRF_WDT, NRF_WDT_RR0); }
|
||||
|
||||
// There is no way to stop/disable watchdog using source code
|
||||
void WatchdogNRF::disable() {}
|
||||
|
||||
int WatchdogNRF::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
#ifdef ARDUINO_NRF52_ADAFRUIT
|
||||
// Mimic AVR to use 8 seconds.
|
||||
if (maxPeriodMS == 0)
|
||||
maxPeriodMS = 8000;
|
||||
|
||||
// Bluefruit freeRTOS tickless implementation will
|
||||
// automatically put CPU into low power mode with delay()
|
||||
delay(maxPeriodMS);
|
||||
|
||||
return maxPeriodMS;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef WATCHDOGNRF_H_
|
||||
#define WATCHDOGNRF_H_
|
||||
|
||||
class WatchdogNRF {
|
||||
public:
|
||||
WatchdogNRF();
|
||||
|
||||
// Enable the watchdog timer to reset the machine after a period of time
|
||||
// without any calls to reset(). The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value.
|
||||
//
|
||||
// The actual period (in milliseconds) before a watchdog timer reset is
|
||||
// returned.
|
||||
int enable(int maxPeriodMS = 0);
|
||||
|
||||
// Reset or 'kick' the watchdog timer to prevent a reset of the device.
|
||||
void reset();
|
||||
|
||||
// Completely disable the watchdog timer.
|
||||
void disable()
|
||||
__attribute__((error("nRF's WDT cannot be disabled once enabled")));
|
||||
|
||||
// Enter the lowest power sleep mode (using the watchdog timer) for the
|
||||
// desired period of time. The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware does
|
||||
// not support the exact desired value
|
||||
//
|
||||
// The actual period (in milliseconds) that the hardware was asleep will be
|
||||
// returned.
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
|
||||
private:
|
||||
int _wdto;
|
||||
};
|
||||
|
||||
#endif /* WATCHDOGNRF_H_ */
|
||||
@@ -0,0 +1,63 @@
|
||||
#if defined(ARDUINO_ARCH_RP2040)
|
||||
|
||||
#include "WatchdogRP2040.h"
|
||||
|
||||
/**********************************************************************************************/
|
||||
/*!
|
||||
@brief Initializes the RP2040's hardware watchdog timer.
|
||||
@param maxPeriodMS
|
||||
Timeout period of WDT in milliseconds
|
||||
@return The actual period (in milliseconds) before a watchdog timer
|
||||
reset is returned, 0 otherwise.
|
||||
*/
|
||||
/**********************************************************************************************/
|
||||
int WatchdogRP2040::enable(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// Enables the RP2040's hardware WDT with maxPeriodMS delay
|
||||
// (wdt should be updated every maxPeriodMS ms) and
|
||||
// enables pausing the WDT on debugging when stepping thru
|
||||
watchdog_enable(maxPeriodMS, 1);
|
||||
|
||||
_wdto = maxPeriodMS;
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Reload the watchdog counter with the amount of time set in
|
||||
enable().
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2040::reset() { watchdog_update(); }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Once enabled, the RP2040's Watchdog Timer can NOT be disabled.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2040::disable() {}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Configures the RP2040 to enter a lower power (WFE) sleep
|
||||
for a period of time.
|
||||
@param maxPeriodMS
|
||||
Time to sleep the RP2040, in millis.
|
||||
@return The actual period (in milliseconds) that the hardware was
|
||||
asleep will be returned. Otherwise, 0 will be returned if the
|
||||
hardware could not enter the low-power mode.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int WatchdogRP2040::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// perform a lower power (WFE) sleep (pico-core calls sleep_ms(sleepTime))
|
||||
sleep_ms(maxPeriodMS);
|
||||
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_RP2040
|
||||
@@ -0,0 +1,40 @@
|
||||
/*!
|
||||
* @file WatchdogRP2040.h
|
||||
*
|
||||
* Support for RP2040 Hardware Watchdog Timer API
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* Written by Brent Rubell for Adafruit Industries.
|
||||
*
|
||||
* MIT License, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
#ifndef WATCHDOGRP2040_H_
|
||||
#define WATCHDOGRP2040_H_
|
||||
|
||||
#include <hardware/watchdog.h>
|
||||
#include <pico/time.h>
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Class that contains functions for interacting with the
|
||||
RP2040's hardware watchdog timer
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class WatchdogRP2040 {
|
||||
public:
|
||||
WatchdogRP2040() : _wdto(-1){};
|
||||
int enable(int maxPeriodMS = 0);
|
||||
void disable()
|
||||
__attribute__((error("RP2040 WDT cannot be disabled once enabled")));
|
||||
void reset();
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
|
||||
private:
|
||||
int _wdto;
|
||||
};
|
||||
|
||||
#endif // WatchdogRP2040_H
|
||||
@@ -0,0 +1,188 @@
|
||||
#if defined(ARDUINO_ARCH_RP2350) || defined(PICO_RP2350) || \
|
||||
defined(TARGET_RP2350)
|
||||
|
||||
#include "WatchdogRP2350/WatchdogRP2350.h"
|
||||
|
||||
/**********************************************************************************************/
|
||||
/*!
|
||||
@brief Initializes the RP2350's hardware watchdog timer.
|
||||
@param maxPeriodMS
|
||||
Timeout period of WDT in milliseconds
|
||||
@return The actual period (in milliseconds) before a watchdog timer
|
||||
reset is returned, 0 otherwise.
|
||||
*/
|
||||
/**********************************************************************************************/
|
||||
int WatchdogRP2350::enable(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// Enables the RP2350's hardware WDT with maxPeriodMS delay
|
||||
// (wdt should be updated every maxPeriodMS ms) and
|
||||
// enables pausing the WDT on debugging when stepping thru
|
||||
watchdog_enable(maxPeriodMS, 1);
|
||||
|
||||
_wdto = maxPeriodMS;
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Reload the watchdog counter with the amount of time set in
|
||||
enable().
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::reset() { watchdog_update(); }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Once enabled, the RP2350's Watchdog Timer can NOT be disabled.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::disable() {}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Configures the RP2350 to enter a lower power (WFE) sleep
|
||||
for a period of time.
|
||||
@param maxPeriodMS
|
||||
Time to sleep the RP2350, in millis.
|
||||
@return The actual period (in milliseconds) that the hardware was
|
||||
asleep will be returned. Otherwise, 0 will be returned if the
|
||||
hardware could not enter the low-power mode.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int WatchdogRP2350::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// perform a lower power (WFE) sleep (pico-core calls sleep_ms(sleepTime))
|
||||
sleep_ms(maxPeriodMS);
|
||||
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Re-enables clock sources and generators after waking from sleep,
|
||||
must be called by user code.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::resumeFromSleep() {
|
||||
// Re-enable clock sources and generators
|
||||
sleep_power_up();
|
||||
|
||||
// Try to reattach USB connection on "native USB" boards (connection is
|
||||
// lost on sleep). Host will also need to reattach to the Serial monitor.
|
||||
// Seems not entirely reliable, hence the LED indicator fallback.
|
||||
#if defined(USBCON) && !defined(USE_TINYUSB)
|
||||
USBDevice.attach();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Sets a callback function to be called upon wake from sleep. Only one
|
||||
callback can be set at a time.
|
||||
@param cb
|
||||
Pointer to the user-defined callback function.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::setWakeCb(WakeCb cb) { _cb_wake = cb; }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Gets the duration of the previous sleep cycle, in milliseconds.
|
||||
@return The sleep duration in milliseconds.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
long WatchdogRP2350::getSleepDuration() {
|
||||
// Obtain the current time from the AON timer
|
||||
struct timespec ts_sleep_end;
|
||||
aon_timer_get_time(&ts_sleep_end);
|
||||
// Compare timespecs to get sleep duration
|
||||
long sleep_duration_ms =
|
||||
(ts_sleep_end.tv_sec - _ts_sleep_start.tv_sec) * 1000 +
|
||||
(ts_sleep_end.tv_nsec - _ts_sleep_start.tv_nsec) / 1000000;
|
||||
return sleep_duration_ms;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Puts the RP2350 into Sleep State (6.5.2, 6.5.3 in RP2350 Datasheet)
|
||||
for a specified period of time, uses the AON timer to wake the device.
|
||||
@param max_period_ms
|
||||
Desired sleep period, in milliseconds.
|
||||
@param is_dormant
|
||||
If true, enters Dormant State (6.5.3) instead of Sleep State.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::goToSleepUntil(int max_period_ms, bool is_dormant) {
|
||||
if (max_period_ms < 0)
|
||||
return;
|
||||
|
||||
// Configure the AON timer
|
||||
startAonTimer();
|
||||
|
||||
// Get and store the sleep start time
|
||||
aon_timer_get_time(&_ts_sleep_start);
|
||||
|
||||
// Configure the sleep clock source
|
||||
if (!is_dormant) {
|
||||
sleep_run_from_xosc();
|
||||
} else {
|
||||
sleep_run_from_lposc();
|
||||
}
|
||||
|
||||
// Configure aon_timer alarm
|
||||
struct timespec ts;
|
||||
aon_timer_get_time(&ts);
|
||||
ts.tv_sec += max_period_ms / 1000;
|
||||
|
||||
// Enter sleep/dormant mode until the AON timer alarm fires
|
||||
if (!is_dormant) {
|
||||
sleep_goto_sleep_until(&ts, _cb_wake);
|
||||
} else {
|
||||
sleep_goto_dormant_until(&ts, _cb_wake);
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Puts the RP2350 into Dormant State (6.5.3 in RP2350 Datasheet) until
|
||||
the specified GPIO pin changes state.
|
||||
@param gpio_pin
|
||||
GPIO pin to monitor for wakeup.
|
||||
@param edge
|
||||
True for rising edge, False for falling edge
|
||||
@param high
|
||||
True for active high, False for active low.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::goToSleepUntilPin(uint gpio_pin, bool edge, bool high) {
|
||||
// Set the crystal oscillator as the dormant clock source, UART will be
|
||||
// reconfigured from here This is necessary before sending the pico into
|
||||
// dormancy
|
||||
// NOTE: Because we are using the crystal oscillator as the clock source, the
|
||||
// AON timer will not run, so we can not use the AON timer to measure sleep
|
||||
// duration like we do in goToSleepUntil().
|
||||
sleep_run_from_xosc();
|
||||
|
||||
// Enter dormant state until the specified GPIO pin changes state
|
||||
sleep_goto_dormant_until_pin(gpio_pin, edge, high);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Helper function to initialize and check AON timer.
|
||||
@return True if the AON timer has been started, False otherwise.
|
||||
*/
|
||||
bool WatchdogRP2350::startAonTimer() {
|
||||
if (!_aon_timer_started) {
|
||||
struct timespec ts_init = {.tv_sec = 1723124088, .tv_nsec = 0};
|
||||
aon_timer_start(&ts_init);
|
||||
_aon_timer_started = true;
|
||||
}
|
||||
return _aon_timer_started;
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_RP2350 || PICO_RP2350 || TARGET_RP2350
|
||||
@@ -0,0 +1,188 @@
|
||||
#if defined(ARDUINO_ARCH_RP2350) || defined(PICO_RP2350) || \
|
||||
defined(TARGET_RP2350)
|
||||
|
||||
#include "WatchdogRP2350.h"
|
||||
|
||||
/**********************************************************************************************/
|
||||
/*!
|
||||
@brief Initializes the RP2350's hardware watchdog timer.
|
||||
@param maxPeriodMS
|
||||
Timeout period of WDT in milliseconds
|
||||
@return The actual period (in milliseconds) before a watchdog timer
|
||||
reset is returned, 0 otherwise.
|
||||
*/
|
||||
/**********************************************************************************************/
|
||||
int WatchdogRP2350::enable(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// Enables the RP2350's hardware WDT with maxPeriodMS delay
|
||||
// (wdt should be updated every maxPeriodMS ms) and
|
||||
// enables pausing the WDT on debugging when stepping thru
|
||||
watchdog_enable(maxPeriodMS, 1);
|
||||
|
||||
_wdto = maxPeriodMS;
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Reload the watchdog counter with the amount of time set in
|
||||
enable().
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::reset() { watchdog_update(); }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Once enabled, the RP2350's Watchdog Timer can NOT be disabled.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::disable() {}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Configures the RP2350 to enter a lower power (WFE) sleep
|
||||
for a period of time.
|
||||
@param maxPeriodMS
|
||||
Time to sleep the RP2350, in millis.
|
||||
@return The actual period (in milliseconds) that the hardware was
|
||||
asleep will be returned. Otherwise, 0 will be returned if the
|
||||
hardware could not enter the low-power mode.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
int WatchdogRP2350::sleep(int maxPeriodMS) {
|
||||
if (maxPeriodMS < 0)
|
||||
return 0;
|
||||
|
||||
// perform a lower power (WFE) sleep (pico-core calls sleep_ms(sleepTime))
|
||||
sleep_ms(maxPeriodMS);
|
||||
|
||||
return maxPeriodMS;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Re-enables clock sources and generators after waking from sleep,
|
||||
must be called by user code.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::resumeFromSleep() {
|
||||
// Re-enable clock sources and generators
|
||||
sleep_power_up();
|
||||
|
||||
// Try to reattach USB connection on "native USB" boards (connection is
|
||||
// lost on sleep). Host will also need to reattach to the Serial monitor.
|
||||
// Seems not entirely reliable, hence the LED indicator fallback.
|
||||
#if defined(USBCON) && !defined(USE_TINYUSB)
|
||||
USBDevice.attach();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Sets a callback function to be called upon wake from sleep. Only one
|
||||
callback can be set at a time.
|
||||
@param cb
|
||||
Pointer to the user-defined callback function.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::setWakeCb(WakeCb cb) { _cb_wake = cb; }
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Gets the duration of the previous sleep cycle, in milliseconds.
|
||||
@return The sleep duration in milliseconds.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
long WatchdogRP2350::getSleepDuration() {
|
||||
// Obtain the current time from the AON timer
|
||||
struct timespec ts_sleep_end;
|
||||
aon_timer_get_time(&ts_sleep_end);
|
||||
// Compare timespecs to get sleep duration
|
||||
long sleep_duration_ms =
|
||||
(ts_sleep_end.tv_sec - _ts_sleep_start.tv_sec) * 1000 +
|
||||
(ts_sleep_end.tv_nsec - _ts_sleep_start.tv_nsec) / 1000000;
|
||||
return sleep_duration_ms;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Puts the RP2350 into Sleep State (6.5.2, 6.5.3 in RP2350 Datasheet)
|
||||
for a specified period of time, uses the AON timer to wake the device.
|
||||
@param max_period_ms
|
||||
Desired sleep period, in milliseconds.
|
||||
@param is_dormant
|
||||
If true, enters Dormant State (6.5.3) instead of Sleep State.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::goToSleepUntil(int max_period_ms, bool is_dormant) {
|
||||
if (max_period_ms < 0)
|
||||
return;
|
||||
|
||||
// Configure the AON timer
|
||||
startAonTimer();
|
||||
|
||||
// Get and store the sleep start time
|
||||
aon_timer_get_time(&_ts_sleep_start);
|
||||
|
||||
// Configure the sleep clock source
|
||||
if (!is_dormant) {
|
||||
sleep_run_from_xosc();
|
||||
} else {
|
||||
sleep_run_from_lposc();
|
||||
}
|
||||
|
||||
// Configure aon_timer alarm
|
||||
struct timespec ts;
|
||||
aon_timer_get_time(&ts);
|
||||
ts.tv_sec += max_period_ms / 1000;
|
||||
|
||||
// Enter sleep/dormant mode until the AON timer alarm fires
|
||||
if (!is_dormant) {
|
||||
sleep_goto_sleep_until(&ts, _cb_wake);
|
||||
} else {
|
||||
sleep_goto_dormant_until(&ts, _cb_wake);
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Puts the RP2350 into Dormant State (6.5.3 in RP2350 Datasheet) until
|
||||
the specified GPIO pin changes state.
|
||||
@param gpio_pin
|
||||
GPIO pin to monitor for wakeup.
|
||||
@param edge
|
||||
True for rising edge, False for falling edge
|
||||
@param high
|
||||
True for active high, False for active low.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
void WatchdogRP2350::goToSleepUntilPin(uint gpio_pin, bool edge, bool high) {
|
||||
// Set the crystal oscillator as the dormant clock source, UART will be
|
||||
// reconfigured from here This is necessary before sending the pico into
|
||||
// dormancy
|
||||
// NOTE: Because we are using the crystal oscillator as the clock source, the
|
||||
// AON timer will not run, so we can not use the AON timer to measure sleep
|
||||
// duration like we do in goToSleepUntil().
|
||||
sleep_run_from_xosc();
|
||||
|
||||
// Enter dormant state until the specified GPIO pin changes state
|
||||
sleep_goto_dormant_until_pin(gpio_pin, edge, high);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Helper function to initialize and check AON timer.
|
||||
@return True if the AON timer has been started, False otherwise.
|
||||
*/
|
||||
bool WatchdogRP2350::startAonTimer() {
|
||||
if (!_aon_timer_started) {
|
||||
struct timespec ts_init = {.tv_sec = 1723124088, .tv_nsec = 0};
|
||||
aon_timer_start(&ts_init);
|
||||
_aon_timer_started = true;
|
||||
}
|
||||
return _aon_timer_started;
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_RP2350 || PICO_RP2350 || TARGET_RP2350
|
||||
@@ -0,0 +1,60 @@
|
||||
/*!
|
||||
* @file WatchdogRP2350.h
|
||||
*
|
||||
* Support for RP2350 Hardware Watchdog Timer API
|
||||
*
|
||||
* Adafruit invests time and resources providing this open source code,
|
||||
* please support Adafruit and open-source hardware by purchasing
|
||||
* products from Adafruit!
|
||||
*
|
||||
* Written by Brent Rubell for Adafruit Industries.
|
||||
*
|
||||
* MIT License, all text here must be included in any redistribution.
|
||||
*
|
||||
*/
|
||||
#ifndef WATCHDOGRP2350_H_
|
||||
#define WATCHDOGRP2350_H_
|
||||
|
||||
#include "pico/aon_timer.h"
|
||||
#include "pico_sleep/include/pico/sleep.h"
|
||||
#include <hardware/watchdog.h>
|
||||
#include <pico/time.h>
|
||||
|
||||
// Callback type for wake-from-sleep functions
|
||||
typedef void (*WakeCb)(void);
|
||||
|
||||
/**************************************************************************/
|
||||
/*!
|
||||
@brief Class that contains functions for interacting with the
|
||||
RP2350's hardware watchdog timer and low-power sleep functionality.
|
||||
*/
|
||||
/**************************************************************************/
|
||||
class WatchdogRP2350 {
|
||||
public:
|
||||
WatchdogRP2350() : _wdto(-1), _aon_timer_started(false){};
|
||||
// Watchdog API
|
||||
int enable(int maxPeriodMS = 0);
|
||||
void disable()
|
||||
__attribute__((error("RP2350 WDT cannot be disabled once enabled")));
|
||||
void reset();
|
||||
// Low-power ("sleep") mode API
|
||||
int sleep(
|
||||
int maxPeriodMS = 0); // Basic sleep function, handled by software timer
|
||||
// Advanced "Sleep" State API
|
||||
// Sleep State (6.5.2, 6.5.3 in RP2350 Datasheet)
|
||||
void goToSleepUntil(int max_period_ms = 0, bool is_dormant = false);
|
||||
void goToSleepUntilPin(uint gpio_pin, bool edge = false, bool high = false);
|
||||
// Advanced sleep state helper functions
|
||||
void setWakeCb(WakeCb cb);
|
||||
void resumeFromSleep();
|
||||
long getSleepDuration();
|
||||
|
||||
private:
|
||||
int _wdto;
|
||||
WakeCb _cb_wake = nullptr;
|
||||
struct timespec _ts_sleep_start;
|
||||
bool _aon_timer_started;
|
||||
bool startAonTimer();
|
||||
};
|
||||
|
||||
#endif // WATCHDOGRP2350_H_
|
||||
+1
@@ -0,0 +1 @@
|
||||
pico_simple_hardware_target(rosc)
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2350) || defined(PICO_RP2350) || \
|
||||
defined(TARGET_RP2350)
|
||||
|
||||
#ifndef _HARDWARE_ROSC_H_
|
||||
#define _HARDWARE_ROSC_H_
|
||||
|
||||
#include "hardware/structs/rosc.h"
|
||||
#include "pico.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file rosc.h
|
||||
* \defgroup hardware_rosc hardware_rosc
|
||||
*
|
||||
* Ring Oscillator (ROSC) API
|
||||
*
|
||||
* A Ring Oscillator is an on-chip oscillator that requires no external crystal.
|
||||
* Instead, the output is generated from a series of inverters that are chained
|
||||
* together to create a feedback loop. RP2040 boots from the ring oscillator
|
||||
* initially, meaning the first stages of the bootrom, including booting from
|
||||
* SPI flash, will be clocked by the ring oscillator. If your design has a
|
||||
* crystal oscillator, you'll likely want to switch to this as your reference
|
||||
* clock as soon as possible, because the frequency is more accurate than the
|
||||
* ring oscillator.
|
||||
*/
|
||||
|
||||
/*! \brief Set frequency of the Ring Oscillator
|
||||
* \ingroup hardware_rosc
|
||||
*
|
||||
* \param code The drive strengths. See the RP2040 datasheet for information on
|
||||
* this value.
|
||||
*/
|
||||
void rosc_set_freq(uint32_t code);
|
||||
|
||||
/*! \brief Set range of the Ring Oscillator
|
||||
* \ingroup hardware_rosc
|
||||
*
|
||||
* Frequency range. Frequencies will vary with Process, Voltage & Temperature
|
||||
* (PVT). Clock output will not glitch when changing the range up one step at a
|
||||
* time.
|
||||
*
|
||||
* \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High.
|
||||
*/
|
||||
void rosc_set_range(uint range);
|
||||
|
||||
/*! \brief Disable the Ring Oscillator
|
||||
* \ingroup hardware_rosc
|
||||
*
|
||||
*/
|
||||
void rosc_disable(void);
|
||||
|
||||
/*! \brief Put Ring Oscillator in to dormant mode.
|
||||
* \ingroup hardware_rosc
|
||||
*
|
||||
* The ROSC supports a dormant mode,which stops oscillation until woken up up by
|
||||
* an asynchronous interrupt. This can either come from the RTC, being clocked
|
||||
* by an external clock, or a GPIO pin going high or low. If no IRQ is
|
||||
* configured before going into dormant mode the ROSC will never restart.
|
||||
*
|
||||
* PLLs should be stopped before selecting dormant mode.
|
||||
*/
|
||||
void rosc_set_dormant(void);
|
||||
|
||||
// FIXME: Add doxygen
|
||||
|
||||
uint32_t next_rosc_code(uint32_t code);
|
||||
|
||||
uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz);
|
||||
|
||||
void rosc_set_div(uint32_t div);
|
||||
|
||||
inline static void rosc_clear_bad_write(void) {
|
||||
hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS);
|
||||
}
|
||||
|
||||
inline static bool rosc_write_okay(void) {
|
||||
return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS);
|
||||
}
|
||||
|
||||
inline static void rosc_write(io_rw_32 *addr, uint32_t value) {
|
||||
rosc_clear_bad_write();
|
||||
assert(rosc_write_okay());
|
||||
*addr = value;
|
||||
assert(rosc_write_okay());
|
||||
};
|
||||
|
||||
void rosc_enable(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _HARDWARE_ROSC_H_
|
||||
#endif // ARDUINO_ARCH_RP2350
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2350)
|
||||
#include "pico.h"
|
||||
|
||||
// For MHZ definitions etc
|
||||
#include "hardware/clocks.h"
|
||||
#include "include/hardware/rosc.h"
|
||||
|
||||
// Given a ROSC delay stage code, return the next-numerically-higher code.
|
||||
// Top result bit is set when called on maximum ROSC code.
|
||||
uint32_t next_rosc_code(uint32_t code) {
|
||||
return ((code | 0x08888888u) + 1u) & 0xf7777777u;
|
||||
}
|
||||
|
||||
uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) {
|
||||
// TODO: This could be a lot better
|
||||
rosc_set_div(1);
|
||||
for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) {
|
||||
rosc_set_freq(code);
|
||||
uint rosc_mhz =
|
||||
frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000;
|
||||
if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) {
|
||||
return rosc_mhz;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rosc_set_div(uint32_t div) {
|
||||
assert(div <= 31 && div >= 1);
|
||||
rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div);
|
||||
}
|
||||
|
||||
void rosc_set_freq(uint32_t code) {
|
||||
rosc_write(&rosc_hw->freqa,
|
||||
(ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) |
|
||||
(code & 0xffffu));
|
||||
rosc_write(&rosc_hw->freqb,
|
||||
(ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) |
|
||||
(code >> 16u));
|
||||
}
|
||||
|
||||
void rosc_set_range(uint range) {
|
||||
// Range should use enumvals from the headers and thus have the password
|
||||
// correct
|
||||
rosc_write(&rosc_hw->ctrl,
|
||||
(ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range);
|
||||
}
|
||||
|
||||
void rosc_disable(void) {
|
||||
uint32_t tmp = rosc_hw->ctrl;
|
||||
tmp &= (~ROSC_CTRL_ENABLE_BITS);
|
||||
tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB);
|
||||
rosc_write(&rosc_hw->ctrl, tmp);
|
||||
// Wait for stable to go away
|
||||
while (rosc_hw->status & ROSC_STATUS_STABLE_BITS)
|
||||
;
|
||||
}
|
||||
|
||||
void rosc_set_dormant(void) {
|
||||
// WARNING: This stops the rosc until woken up by an irq
|
||||
rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT);
|
||||
// Wait for it to become stable once woken up
|
||||
while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS))
|
||||
;
|
||||
}
|
||||
|
||||
void rosc_enable(void) {
|
||||
// Re-enable the rosc
|
||||
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);
|
||||
|
||||
// Wait for it to become stable once restarted
|
||||
while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS))
|
||||
;
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_RP2350
|
||||
@@ -0,0 +1,5 @@
|
||||
pico_simple_hardware_target(sleep) pico_mirrored_target_link_libraries(
|
||||
hardware_sleep INTERFACE hardware_clocks hardware_rosc hardware_irq
|
||||
pico_aon_timer) target_include_directories(hardware_sleep INTERFACE ${
|
||||
CMAKE_CURRENT_LIST_DIR} /
|
||||
include)
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2350) || defined(PICO_RP2350) || \
|
||||
defined(TARGET_RP2350)
|
||||
|
||||
#ifndef _PICO_SLEEP_H_
|
||||
#define _PICO_SLEEP_H_
|
||||
|
||||
#include "../../../hardware_rosc/include/hardware/rosc.h"
|
||||
#include "hardware/timer.h"
|
||||
#include "pico.h"
|
||||
#include "pico/aon_timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file sleep.h
|
||||
* \defgroup hardware_sleep hardware_sleep
|
||||
*
|
||||
* Lower Power Sleep API
|
||||
*
|
||||
* The difference between sleep and dormant is that ALL clocks are stopped in
|
||||
dormant mode,
|
||||
* until the source (either xosc or rosc) is started again by an external event.
|
||||
* In sleep mode some clocks can be left running controlled by the SLEEP_EN
|
||||
registers in the clocks
|
||||
* block. For example you could keep clk_rtc running. Some destinations (proc0
|
||||
and proc1 wakeup logic)
|
||||
* can't be stopped in sleep mode otherwise there wouldn't be enough logic to
|
||||
wake up again.
|
||||
*
|
||||
* \subsection sleep_example Example
|
||||
* \addtogroup hardware_sleep
|
||||
* \include hello_sleep.c
|
||||
|
||||
*/
|
||||
typedef enum {
|
||||
DORMANT_SOURCE_NONE,
|
||||
DORMANT_SOURCE_XOSC,
|
||||
DORMANT_SOURCE_ROSC,
|
||||
DORMANT_SOURCE_LPOSC, // rp2350 only
|
||||
} dormant_source_t;
|
||||
|
||||
/*! \brief Set all clock sources to the the dormant clock source to prepare for
|
||||
* sleep. \ingroup hardware_sleep
|
||||
*
|
||||
* \param dormant_source The dormant clock source to use
|
||||
*/
|
||||
void sleep_run_from_dormant_source(dormant_source_t dormant_source);
|
||||
|
||||
/*! \brief Set the dormant clock source to be the crystal oscillator
|
||||
* \ingroup hardware_sleep
|
||||
*/
|
||||
static inline void sleep_run_from_xosc(void) {
|
||||
sleep_run_from_dormant_source(DORMANT_SOURCE_XOSC);
|
||||
}
|
||||
|
||||
#if !PICO_RP2040
|
||||
static inline void sleep_run_from_lposc(void) {
|
||||
sleep_run_from_dormant_source(DORMANT_SOURCE_LPOSC);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*! \brief Set the dormant clock source to be the ring oscillator
|
||||
* \ingroup hardware_sleep
|
||||
*/
|
||||
static inline void sleep_run_from_rosc(void) {
|
||||
sleep_run_from_dormant_source(DORMANT_SOURCE_ROSC);
|
||||
}
|
||||
|
||||
/*! \brief Send system to sleep until the specified time
|
||||
* \ingroup hardware_sleep
|
||||
*
|
||||
* One of the sleep_run_* functions must be called prior to this call
|
||||
*
|
||||
* \param ts The time to wake up
|
||||
* \param callback Function to call on wakeup.
|
||||
*/
|
||||
void sleep_goto_sleep_until(struct timespec *ts,
|
||||
aon_timer_alarm_handler_t callback);
|
||||
|
||||
/*! \brief Send system to sleep for a specified duration in milliseconds. This
|
||||
provides an alternative to sleep_goto_sleep_until to allow for shorter duration
|
||||
sleeps.
|
||||
* \ingroup hardware_sleep
|
||||
*
|
||||
* One of the sleep_run_* functions must be called prior to this call
|
||||
*
|
||||
* \param delay_ms The duration to sleep for in milliseconds.
|
||||
* \param callback Function to call on wakeup.
|
||||
* \return Returns true if the device went to sleep
|
||||
*/
|
||||
bool sleep_goto_sleep_for(uint32_t delay_ms,
|
||||
hardware_alarm_callback_t callback);
|
||||
|
||||
/*! \brief Send system to dormant until the specified time, note for RP2040 the
|
||||
* RTC must be driven by an external clock \ingroup hardware_sleep
|
||||
*
|
||||
* One of the sleep_run_* functions must be called prior to this call
|
||||
*
|
||||
* \param ts The time to wake up
|
||||
* \param callback Function to call on wakeup.
|
||||
*/
|
||||
void sleep_goto_dormant_until(struct timespec *ts,
|
||||
aon_timer_alarm_handler_t callback);
|
||||
|
||||
/*! \brief Send system to sleep until the specified GPIO changes
|
||||
* \ingroup hardware_sleep
|
||||
*
|
||||
* One of the sleep_run_* functions must be called prior to this call
|
||||
*
|
||||
* \param gpio_pin The pin to provide the wake up
|
||||
* \param edge true for leading edge, false for trailing edge
|
||||
* \param high true for active high, false for active low
|
||||
*/
|
||||
|
||||
void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high);
|
||||
|
||||
/*! \brief Send system to sleep until a leading high edge is detected on GPIO
|
||||
* \ingroup hardware_sleep
|
||||
*
|
||||
* One of the sleep_run_* functions must be called prior to this call
|
||||
*
|
||||
* \param gpio_pin The pin to provide the wake up
|
||||
*/
|
||||
static inline void sleep_goto_dormant_until_edge_high(uint gpio_pin) {
|
||||
sleep_goto_dormant_until_pin(gpio_pin, true, true);
|
||||
}
|
||||
|
||||
/*! \brief Send system to sleep until a high level is detected on GPIO
|
||||
* \ingroup hardware_sleep
|
||||
*
|
||||
* One of the sleep_run_* functions must be called prior to this call
|
||||
*
|
||||
* \param gpio_pin The pin to provide the wake up
|
||||
*/
|
||||
static inline void sleep_goto_dormant_until_level_high(uint gpio_pin) {
|
||||
sleep_goto_dormant_until_pin(gpio_pin, false, true);
|
||||
}
|
||||
|
||||
/*! \brief Reconfigure clocks to wake up properly from sleep/dormant mode
|
||||
* \ingroup hardware_sleep
|
||||
*
|
||||
* This must be called immediately after continuing execution when waking up
|
||||
* from sleep/dormant mode
|
||||
*
|
||||
*/
|
||||
void sleep_power_up(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _PICO_SLEEP_H_
|
||||
#endif // ARDUINO_ARCH_RP2350
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2350)
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
#include "include/pico/sleep.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/pll.h"
|
||||
#include "hardware/regs/clocks.h"
|
||||
#include "hardware/regs/io_bank0.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "hardware/xosc.h"
|
||||
// For __wfi
|
||||
#include "hardware/sync.h"
|
||||
#include "pico/runtime_init.h"
|
||||
|
||||
#ifdef __riscv
|
||||
#include "hardware/riscv.h"
|
||||
#else
|
||||
// For scb_hw so we can enable deep sleep
|
||||
#include "hardware/structs/scb.h"
|
||||
#endif
|
||||
|
||||
#if !PICO_RP2040
|
||||
#include "hardware/powman.h"
|
||||
#endif
|
||||
|
||||
// The difference between sleep and dormant is that ALL clocks are stopped in
|
||||
// dormant mode, until the source (either xosc or rosc) is started again by an
|
||||
// external event. In sleep mode some clocks can be left running controlled by
|
||||
// the SLEEP_EN registers in the clocks block. For example you could keep
|
||||
// clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) can't be
|
||||
// stopped in sleep mode otherwise there wouldn't be enough logic to wake up
|
||||
// again.
|
||||
|
||||
static dormant_source_t _dormant_source;
|
||||
|
||||
bool dormant_source_valid(dormant_source_t dormant_source) {
|
||||
switch (dormant_source) {
|
||||
case DORMANT_SOURCE_XOSC:
|
||||
return true;
|
||||
case DORMANT_SOURCE_ROSC:
|
||||
return true;
|
||||
#if !PICO_RP2040
|
||||
case DORMANT_SOURCE_LPOSC:
|
||||
return true;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In order to go into dormant mode we need to be running from a stoppable clock
|
||||
// source: either the xosc or rosc with no PLLs running. This means we disable
|
||||
// the USB and ADC clocks and all PLLs
|
||||
void sleep_run_from_dormant_source(dormant_source_t dormant_source) {
|
||||
assert(dormant_source_valid(dormant_source));
|
||||
_dormant_source = dormant_source;
|
||||
|
||||
uint src_hz;
|
||||
uint clk_ref_src;
|
||||
switch (dormant_source) {
|
||||
case DORMANT_SOURCE_XOSC:
|
||||
src_hz = XOSC_HZ;
|
||||
clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC;
|
||||
break;
|
||||
case DORMANT_SOURCE_ROSC:
|
||||
src_hz = 6500 * KHZ; // todo
|
||||
clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH;
|
||||
break;
|
||||
#if !PICO_RP2040
|
||||
case DORMANT_SOURCE_LPOSC:
|
||||
src_hz = 32 * KHZ;
|
||||
clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_LPOSC_CLKSRC;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
hard_assert(false);
|
||||
}
|
||||
|
||||
// CLK_REF = XOSC or ROSC
|
||||
clock_configure(clk_ref, clk_ref_src,
|
||||
0, // No aux mux
|
||||
src_hz, src_hz);
|
||||
|
||||
// CLK SYS = CLK_REF
|
||||
clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF,
|
||||
0, // Using glitchless mux
|
||||
src_hz, src_hz);
|
||||
|
||||
// CLK ADC = 0MHz
|
||||
clock_stop(clk_adc);
|
||||
clock_stop(clk_usb);
|
||||
#if PICO_RP2350
|
||||
clock_stop(clk_hstx);
|
||||
#endif
|
||||
|
||||
#if PICO_RP2040
|
||||
// CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc
|
||||
uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC)
|
||||
? CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC
|
||||
: CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH;
|
||||
|
||||
clock_configure(clk_rtc,
|
||||
0, // No GLMUX
|
||||
clk_rtc_src, src_hz, 46875);
|
||||
#endif
|
||||
|
||||
// CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so
|
||||
// just select and enable
|
||||
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
|
||||
src_hz, src_hz);
|
||||
|
||||
pll_deinit(pll_sys);
|
||||
pll_deinit(pll_usb);
|
||||
|
||||
// Assuming both xosc and rosc are running at the moment
|
||||
if (dormant_source == DORMANT_SOURCE_XOSC) {
|
||||
// Can disable rosc
|
||||
rosc_disable();
|
||||
} else {
|
||||
// Can disable xosc
|
||||
xosc_disable();
|
||||
}
|
||||
|
||||
// Reconfigure uart with new clocks
|
||||
// setup_default_uart();
|
||||
}
|
||||
|
||||
static void processor_deep_sleep(void) {
|
||||
// Enable deep sleep at the proc
|
||||
#ifdef __riscv
|
||||
uint32_t bits = RVCSR_MSLEEP_POWERDOWN_BITS;
|
||||
if (!get_core_num()) {
|
||||
bits |= RVCSR_MSLEEP_DEEPSLEEP_BITS;
|
||||
}
|
||||
riscv_set_csr(RVCSR_MSLEEP_OFFSET, bits);
|
||||
#else
|
||||
scb_hw->scr |= ARM_CPU_PREFIXED(SCR_SLEEPDEEP_BITS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sleep_goto_sleep_until(struct timespec *ts,
|
||||
aon_timer_alarm_handler_t callback) {
|
||||
|
||||
// We should have already called the sleep_run_from_dormant_source function
|
||||
// This is only needed for dormancy although it saves power running from xosc
|
||||
// while sleeping
|
||||
// assert(dormant_source_valid(_dormant_source));
|
||||
|
||||
#if PICO_RP2040
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#else
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_REF_POWMAN_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#endif
|
||||
|
||||
aon_timer_enable_alarm(ts, callback, false);
|
||||
|
||||
// stdio_flush();
|
||||
|
||||
// Enable deep sleep at the proc
|
||||
processor_deep_sleep();
|
||||
|
||||
// Go to sleep
|
||||
__wfi();
|
||||
}
|
||||
|
||||
bool sleep_goto_sleep_for(uint32_t delay_ms,
|
||||
hardware_alarm_callback_t callback) {
|
||||
// We should have already called the sleep_run_from_dormant_source function
|
||||
// This is only needed for dormancy although it saves power running from xosc
|
||||
// while sleeping
|
||||
// assert(dormant_source_valid(_dormant_source));
|
||||
|
||||
// Turn off all clocks except for the timer
|
||||
clocks_hw->sleep_en0 = 0x0;
|
||||
#if PICO_RP2040
|
||||
clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS;
|
||||
#elif PICO_RP2350
|
||||
clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_REF_TICKS_BITS |
|
||||
CLOCKS_SLEEP_EN1_CLK_SYS_TIMER0_BITS;
|
||||
#else
|
||||
#error Unknown processor
|
||||
#endif
|
||||
|
||||
int alarm_num = hardware_alarm_claim_unused(true);
|
||||
hardware_alarm_set_callback(alarm_num, callback);
|
||||
absolute_time_t t = make_timeout_time_ms(delay_ms);
|
||||
if (hardware_alarm_set_target(alarm_num, t)) {
|
||||
hardware_alarm_set_callback(alarm_num, NULL);
|
||||
hardware_alarm_unclaim(alarm_num);
|
||||
return false;
|
||||
}
|
||||
|
||||
// stdio_flush();
|
||||
|
||||
// Enable deep sleep at the proc
|
||||
processor_deep_sleep();
|
||||
|
||||
// Go to sleep
|
||||
__wfi();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _go_dormant(void) {
|
||||
assert(dormant_source_valid(_dormant_source));
|
||||
|
||||
if (_dormant_source == DORMANT_SOURCE_XOSC) {
|
||||
xosc_dormant();
|
||||
} else {
|
||||
rosc_set_dormant();
|
||||
}
|
||||
}
|
||||
|
||||
void sleep_goto_dormant_until(struct timespec *ts,
|
||||
aon_timer_alarm_handler_t callback) {
|
||||
// We should have already called the sleep_run_from_dormant_source function
|
||||
|
||||
#if PICO_RP2040
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#else
|
||||
assert(_dormant_source == DORMANT_SOURCE_LPOSC);
|
||||
uint64_t restore_ms = powman_timer_get_ms();
|
||||
powman_timer_set_1khz_tick_source_lposc();
|
||||
powman_timer_set_ms(restore_ms);
|
||||
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_REF_POWMAN_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#endif
|
||||
|
||||
// Set the AON timer to wake up the proc from dormant mode
|
||||
aon_timer_enable_alarm(ts, callback, true);
|
||||
|
||||
// stdio_flush();
|
||||
|
||||
// Enable deep sleep at the proc
|
||||
processor_deep_sleep();
|
||||
|
||||
// Go dormant
|
||||
_go_dormant();
|
||||
}
|
||||
|
||||
void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) {
|
||||
bool low = !high;
|
||||
bool level = !edge;
|
||||
|
||||
// Configure the appropriate IRQ at IO bank 0
|
||||
assert(gpio_pin < NUM_BANK0_GPIOS);
|
||||
|
||||
uint32_t event = 0;
|
||||
|
||||
if (level && low)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS;
|
||||
if (level && high)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS;
|
||||
if (edge && high)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS;
|
||||
if (edge && low)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS;
|
||||
|
||||
gpio_init(gpio_pin);
|
||||
gpio_set_input_enabled(gpio_pin, true);
|
||||
gpio_set_dormant_irq_enabled(gpio_pin, event, true);
|
||||
|
||||
_go_dormant();
|
||||
// Execution stops here until woken up
|
||||
|
||||
// Clear the irq so we can go back to dormant mode again if we want
|
||||
gpio_acknowledge_irq(gpio_pin, event);
|
||||
gpio_set_input_enabled(gpio_pin, false);
|
||||
}
|
||||
|
||||
// To be called after waking up from sleep/dormant mode to restore system clocks
|
||||
// properly
|
||||
void sleep_power_up(void) {
|
||||
// Re-enable the ring oscillator, which will essentially kickstart the proc
|
||||
rosc_enable();
|
||||
|
||||
// Reset the sleep enable register so peripherals and other hardware can be
|
||||
// used
|
||||
clocks_hw->sleep_en0 |= ~(0u);
|
||||
clocks_hw->sleep_en1 |= ~(0u);
|
||||
|
||||
// Restore all clocks
|
||||
clocks_init();
|
||||
|
||||
#if PICO_RP2350
|
||||
// make powerman use xosc again
|
||||
uint64_t restore_ms = powman_timer_get_ms();
|
||||
powman_timer_set_1khz_tick_source_xosc();
|
||||
powman_timer_set_ms(restore_ms);
|
||||
#endif
|
||||
|
||||
// UART needs to be reinitialised with the new clock frequencies for stable
|
||||
// output setup_default_uart();
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_RP2350
|
||||
@@ -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)
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef WATCHDOGSAMD_H
|
||||
#define WATCHDOGSAMD_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class WatchdogSAMD {
|
||||
public:
|
||||
WatchdogSAMD() : _initialized(false) {}
|
||||
|
||||
// Enable the watchdog timer to reset the machine after a period of time
|
||||
// without any calls to reset(). The passed in period (in milliseconds)
|
||||
// is just a suggestion and a lower value might be picked if the hardware
|
||||
// does not support the exact desired value.
|
||||
// User code should NOT set the 'isForSleep' argument either way --
|
||||
// it's used internally by the library, but your sketch should leave this
|
||||
// out when calling enable(), just let the default have its way.
|
||||
//
|
||||
// The actual period (in milliseconds) before a watchdog timer reset is
|
||||
// returned.
|
||||
int enable(int maxPeriodMS = 0, bool isForSleep = false);
|
||||
|
||||
// Reset or 'kick' the watchdog timer to prevent a reset of the device.
|
||||
void reset();
|
||||
|
||||
// Find out the cause of the last reset - see datasheet for bitmask
|
||||
uint8_t resetCause();
|
||||
|
||||
// Completely disable the watchdog timer.
|
||||
void disable();
|
||||
|
||||
// Enter the lowest power sleep mode (using the watchdog timer) for the
|
||||
// desired period of time. The passed in period (in milliseconds) is
|
||||
// just a suggestion and a lower value might be picked if the hardware
|
||||
// does not support the exact desired value
|
||||
//
|
||||
// The actual period (in milliseconds) that the hardware was asleep will be
|
||||
// returned.
|
||||
int sleep(int maxPeriodMS = 0);
|
||||
|
||||
private:
|
||||
void _initialize_wdt();
|
||||
|
||||
bool _initialized;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef _HARDWARE_POWMAN_H
|
||||
#define _HARDWARE_POWMAN_H
|
||||
|
||||
#include "hardware/structs/powman.h"
|
||||
#include "pico.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** \file hardware/powman.h
|
||||
* \defgroup hardware_powman hardware_powman
|
||||
*
|
||||
* \brief Power Management API
|
||||
*
|
||||
*/
|
||||
|
||||
// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_HARDWARE_POWMAN, Enable/disable
|
||||
// hardware_powman assertions, type=bool, default=0, group=hardware_powman
|
||||
#ifndef PARAM_ASSERTIONS_ENABLED_HARDWARE_POWMAN
|
||||
#define PARAM_ASSERTIONS_ENABLED_HARDWARE_POWMAN 0
|
||||
#endif
|
||||
|
||||
/*! \brief Use the ~32KHz low power oscillator as the powman timer source
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
void powman_timer_set_1khz_tick_source_lposc(void);
|
||||
|
||||
/*! \brief Use the low power oscillator (specifying frequency) as the powman
|
||||
* timer source
|
||||
* \ingroup hardware_powman
|
||||
* \param lposc_freq_hz specify an exact lposc freq to trim it
|
||||
*/
|
||||
void powman_timer_set_1khz_tick_source_lposc_with_hz(uint32_t lposc_freq_hz);
|
||||
|
||||
/*! \brief Use the crystal oscillator as the powman timer source
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
void powman_timer_set_1khz_tick_source_xosc(void);
|
||||
|
||||
/*! \brief Use the crystal oscillator as the powman timer source
|
||||
* \ingroup hardware_powman
|
||||
* \param xosc_freq_hz specify a crystal frequency
|
||||
*/
|
||||
void powman_timer_set_1khz_tick_source_xosc_with_hz(uint32_t xosc_freq_hz);
|
||||
|
||||
/*! \brief Use a 1KHz external tick as the powman timer source
|
||||
* \ingroup hardware_powman
|
||||
* \param gpio the gpio to use. must be 12, 14, 20, 22
|
||||
*/
|
||||
void powman_timer_set_1khz_tick_source_gpio(uint32_t gpio);
|
||||
|
||||
/*! \brief Use a 1Hz external signal as the powman timer source for seconds only
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* Use a 1hz sync signal, such as from a gps for the seconds component of the
|
||||
* timer. The milliseconds will still come from another configured source such
|
||||
* as xosc or lposc
|
||||
*
|
||||
* \param gpio the gpio to use. must be 12, 14, 20, 22
|
||||
*/
|
||||
void powman_timer_enable_gpio_1hz_sync(uint32_t gpio);
|
||||
|
||||
/*! \brief Stop using 1Hz external signal as the powman timer source for seconds
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
void powman_timer_disable_gpio_1hz_sync(void);
|
||||
|
||||
/*! \brief Returns current time in ms
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
uint64_t powman_timer_get_ms(void);
|
||||
|
||||
/*! \brief Set current time in ms
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* \param time_ms Current time in ms
|
||||
*/
|
||||
void powman_timer_set_ms(uint64_t time_ms);
|
||||
|
||||
/*! \brief Set an alarm at an absolute time in ms
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* Note, the timer is stopped and then restarted as part of this function. This
|
||||
* only controls the alarm if you want to use the alarm to wake up powman then
|
||||
* you should use \ref powman_enable_alarm_wakeup_at_ms
|
||||
*
|
||||
* \param alarm_time_ms time at which the alarm will fire
|
||||
*/
|
||||
void powman_timer_enable_alarm_at_ms(uint64_t alarm_time_ms);
|
||||
|
||||
/*! \brief Disable the alarm
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* Once an alarm has fired it must be disabled to stop firing as the alarm
|
||||
* comparison is alarm = alarm_time >= current_time
|
||||
*/
|
||||
void powman_timer_disable_alarm(void);
|
||||
|
||||
/*! \brief hw_set_bits helper function
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* \param reg register to set
|
||||
* \param bits bits of register to set
|
||||
* Powman needs a password for writes, to prevent accidentally writing to it.
|
||||
* This function implements hw_set_bits with an appropriate password.
|
||||
*/
|
||||
static inline void powman_set_bits(volatile uint32_t *reg, uint32_t bits) {
|
||||
invalid_params_if(HARDWARE_POWMAN, bits >> 16);
|
||||
hw_set_bits(reg, POWMAN_PASSWORD_BITS | bits);
|
||||
}
|
||||
|
||||
/*! \brief hw_clear_bits helper function
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* Powman needs a password for writes, to prevent accidentally writing to it.
|
||||
* This function implements hw_clear_bits with an appropriate password.
|
||||
*
|
||||
* \param reg register to clear
|
||||
* \param bits bits of register to clear
|
||||
*/
|
||||
static inline void powman_clear_bits(volatile uint32_t *reg, uint32_t bits) {
|
||||
invalid_params_if(HARDWARE_POWMAN, bits >> 16);
|
||||
hw_clear_bits(reg, POWMAN_PASSWORD_BITS | bits);
|
||||
}
|
||||
|
||||
/*! \brief Determine if the powman timer is running
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
static inline bool powman_timer_is_running(void) {
|
||||
return powman_hw->timer & POWMAN_TIMER_RUN_BITS;
|
||||
}
|
||||
|
||||
/*! \brief Stop the powman timer
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
static inline void powman_timer_stop(void) {
|
||||
powman_clear_bits(&powman_hw->timer, POWMAN_TIMER_RUN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Start the powman timer
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
static inline void powman_timer_start(void) {
|
||||
powman_set_bits(&powman_hw->timer, POWMAN_TIMER_RUN_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Clears the powman alarm
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* Note, the alarm must be disabled (see \ref powman_timer_disable_alarm) before
|
||||
* clearing the alarm, as the alarm fires if the time is greater than equal to
|
||||
* the target, so once the time has passed the alarm will always fire while
|
||||
* enabled.
|
||||
*/
|
||||
static inline void powman_clear_alarm(void) {
|
||||
powman_clear_bits(&powman_hw->timer, POWMAN_TIMER_ALARM_BITS);
|
||||
}
|
||||
|
||||
/*! \brief Power domains of powman
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
enum powman_power_domains {
|
||||
POWMAN_POWER_DOMAIN_SRAM_BANK1 =
|
||||
0, ///< bank1 includes the top 256K of sram plus sram 8 and 9 (scratch x
|
||||
///< and scratch y)
|
||||
POWMAN_POWER_DOMAIN_SRAM_BANK0 = 1, ///< bank0 is bottom 256K of sSRAM
|
||||
POWMAN_POWER_DOMAIN_XIP_CACHE = 2, ///< XIP cache is 2x8K instances
|
||||
POWMAN_POWER_DOMAIN_SWITCHED_CORE =
|
||||
3, ///< Switched core logic (processors, busfabric, peris etc)
|
||||
POWMAN_POWER_DOMAIN_COUNT = 4,
|
||||
};
|
||||
|
||||
typedef uint32_t powman_power_state;
|
||||
|
||||
/*! \brief Get the current power state
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
powman_power_state powman_get_power_state(void);
|
||||
|
||||
/*! \brief Set the power state
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* Check the desired state is valid. Powman will go to the state if it is valid
|
||||
* and there are no pending power up requests.
|
||||
*
|
||||
* Note that if you are turning off the switched core then this function will
|
||||
* never return as the processor will have been turned off at the end.
|
||||
*
|
||||
* \param state the power state to go to
|
||||
* \returns PICO_OK if the state is valid. Misc PICO_ERRORs are returned if not
|
||||
*/
|
||||
int powman_set_power_state(powman_power_state state);
|
||||
|
||||
#define POWMAN_POWER_STATE_NONE 0
|
||||
|
||||
/*! \brief Helper function modify a powman_power_state to turn a domain on
|
||||
* \ingroup hardware_powman
|
||||
* \param orig original state
|
||||
* \param domain domain to turn on
|
||||
*/
|
||||
static inline powman_power_state
|
||||
powman_power_state_with_domain_on(powman_power_state orig,
|
||||
enum powman_power_domains domain) {
|
||||
invalid_params_if(HARDWARE_POWMAN, domain >= POWMAN_POWER_DOMAIN_COUNT);
|
||||
return orig | (1u << domain);
|
||||
}
|
||||
|
||||
/*! \brief Helper function modify a powman_power_state to turn a domain off
|
||||
* \ingroup hardware_powman
|
||||
* \param orig original state
|
||||
* \param domain domain to turn off
|
||||
*/
|
||||
static inline powman_power_state
|
||||
powman_power_state_with_domain_off(powman_power_state orig,
|
||||
enum powman_power_domains domain) {
|
||||
invalid_params_if(HARDWARE_POWMAN, domain >= POWMAN_POWER_DOMAIN_COUNT);
|
||||
return orig &= ~(1u << domain);
|
||||
}
|
||||
|
||||
/*! \brief Helper function to check if a domain is on in a given
|
||||
* powman_power_state
|
||||
* \ingroup hardware_powman
|
||||
* \param state powman_power_state
|
||||
* \param domain domain to check is on
|
||||
*/
|
||||
static inline bool
|
||||
powman_power_state_is_domain_on(powman_power_state state,
|
||||
enum powman_power_domains domain) {
|
||||
invalid_params_if(HARDWARE_POWMAN, domain >= POWMAN_POWER_DOMAIN_COUNT);
|
||||
return state & (1u << domain);
|
||||
}
|
||||
|
||||
/*! \brief Wake up from an alarm at a given time
|
||||
* \ingroup hardware_powman
|
||||
* \param alarm_time_ms time to wake up in ms
|
||||
*/
|
||||
void powman_enable_alarm_wakeup_at_ms(uint64_t alarm_time_ms);
|
||||
|
||||
/*! \brief Wake up from a gpio
|
||||
* \ingroup hardware_powman
|
||||
* \param gpio_wakeup_num hardware wakeup instance to use (0-3)
|
||||
* \param gpio gpio to wake up from (0-47)
|
||||
* \param edge true for edge sensitive, false for level sensitive
|
||||
* \param high true for active high, false active low
|
||||
*/
|
||||
void powman_enable_gpio_wakeup(uint gpio_wakeup_num, uint32_t gpio, bool edge,
|
||||
bool high);
|
||||
|
||||
/*! \brief Disable waking up from alarm
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
void powman_disable_alarm_wakeup(void);
|
||||
|
||||
/*! \brief Disable wake up from a gpio
|
||||
* \ingroup hardware_powman
|
||||
* \param gpio_wakeup_num hardware wakeup instance to use (0-3)
|
||||
*/
|
||||
void powman_disable_gpio_wakeup(uint gpio_wakeup_num);
|
||||
|
||||
/*! \brief Disable all wakeup sources
|
||||
* \ingroup hardware_powman
|
||||
*/
|
||||
void powman_disable_all_wakeups(void);
|
||||
|
||||
/*! \brief Configure sleep state and wakeup state
|
||||
* \ingroup hardware_powman
|
||||
* \param sleep_state power state powman will go to when sleeping, used to
|
||||
* validate the wakeup state
|
||||
* \param wakeup_state power state powman will go to when waking up. Note
|
||||
* switched core and xip always power up. SRAM bank0 and bank1 can be left
|
||||
* powered off
|
||||
* \returns true if the state is valid, false if not
|
||||
*/
|
||||
bool powman_configure_wakeup_state(powman_power_state sleep_state,
|
||||
powman_power_state wakeup_state);
|
||||
|
||||
/*! \brief Ignore wake up when the debugger is attached
|
||||
* \ingroup hardware_powman
|
||||
*
|
||||
* Typically, when a debugger is attached it will assert the pwrupreq signal.
|
||||
* OpenOCD does not clear this signal, even when you quit. This means once you
|
||||
* have attached a debugger powman will never go to sleep. This function lets
|
||||
* you ignore the debugger pwrupreq which means you can go to sleep with a
|
||||
* debugger attached. The debugger will error out if you go to turn off the
|
||||
* switch core with it attached, as the processors have been powered off.
|
||||
*
|
||||
* \param ignored should the debugger power up request be ignored
|
||||
*/
|
||||
static inline void powman_set_debug_power_request_ignored(bool ignored) {
|
||||
if (ignored)
|
||||
powman_set_bits(&powman_hw->dbg_pwrcfg, 1);
|
||||
else
|
||||
powman_clear_bits(&powman_hw->dbg_pwrcfg, 0);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2350) || defined(PICO_RP2350) || \
|
||||
defined(TARGET_RP2350)
|
||||
#include "pico.h"
|
||||
|
||||
// For MHZ definitions etc
|
||||
#include "WatchdogRP2350/hardware_rosc/include/hardware/rosc.h"
|
||||
#include "hardware/clocks.h"
|
||||
|
||||
// Given a ROSC delay stage code, return the next-numerically-higher code.
|
||||
// Top result bit is set when called on maximum ROSC code.
|
||||
uint32_t next_rosc_code(uint32_t code) {
|
||||
return ((code | 0x08888888u) + 1u) & 0xf7777777u;
|
||||
}
|
||||
|
||||
uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) {
|
||||
// TODO: This could be a lot better
|
||||
rosc_set_div(1);
|
||||
for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) {
|
||||
rosc_set_freq(code);
|
||||
uint rosc_mhz =
|
||||
frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000;
|
||||
if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) {
|
||||
return rosc_mhz;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rosc_set_div(uint32_t div) {
|
||||
assert(div <= 31 && div >= 1);
|
||||
rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div);
|
||||
}
|
||||
|
||||
void rosc_set_freq(uint32_t code) {
|
||||
rosc_write(&rosc_hw->freqa,
|
||||
(ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) |
|
||||
(code & 0xffffu));
|
||||
rosc_write(&rosc_hw->freqb,
|
||||
(ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) |
|
||||
(code >> 16u));
|
||||
}
|
||||
|
||||
void rosc_set_range(uint range) {
|
||||
// Range should use enumvals from the headers and thus have the password
|
||||
// correct
|
||||
rosc_write(&rosc_hw->ctrl,
|
||||
(ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range);
|
||||
}
|
||||
|
||||
void rosc_disable(void) {
|
||||
uint32_t tmp = rosc_hw->ctrl;
|
||||
tmp &= (~ROSC_CTRL_ENABLE_BITS);
|
||||
tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB);
|
||||
rosc_write(&rosc_hw->ctrl, tmp);
|
||||
// Wait for stable to go away
|
||||
while (rosc_hw->status & ROSC_STATUS_STABLE_BITS)
|
||||
;
|
||||
}
|
||||
|
||||
void rosc_set_dormant(void) {
|
||||
// WARNING: This stops the rosc until woken up by an irq
|
||||
rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT);
|
||||
// Wait for it to become stable once woken up
|
||||
while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS))
|
||||
;
|
||||
}
|
||||
|
||||
void rosc_enable(void) {
|
||||
// Re-enable the rosc
|
||||
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);
|
||||
|
||||
// Wait for it to become stable once restarted
|
||||
while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS))
|
||||
;
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_RP2350 || PICO_RP2350 || TARGET_RP2350
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#if defined(ARDUINO_ARCH_RP2350) || defined(PICO_RP2350) || \
|
||||
defined(TARGET_RP2350)
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "pico.h"
|
||||
|
||||
#include "WatchdogRP2350/pico_sleep/include/pico/sleep.h"
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "hardware/clocks.h"
|
||||
#include "hardware/pll.h"
|
||||
#include "hardware/regs/clocks.h"
|
||||
#include "hardware/regs/io_bank0.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "hardware/xosc.h"
|
||||
// For __wfi
|
||||
#include "hardware/sync.h"
|
||||
#include "pico/runtime_init.h"
|
||||
|
||||
#ifdef __riscv
|
||||
#include "hardware/riscv.h"
|
||||
#else
|
||||
// For scb_hw so we can enable deep sleep
|
||||
#include "hardware/structs/scb.h"
|
||||
#endif
|
||||
|
||||
#if !PICO_RP2040
|
||||
#include "hardware/powman.h"
|
||||
#endif
|
||||
|
||||
// The difference between sleep and dormant is that ALL clocks are stopped in
|
||||
// dormant mode, until the source (either xosc or rosc) is started again by an
|
||||
// external event. In sleep mode some clocks can be left running controlled by
|
||||
// the SLEEP_EN registers in the clocks block. For example you could keep
|
||||
// clk_rtc running. Some destinations (proc0 and proc1 wakeup logic) can't be
|
||||
// stopped in sleep mode otherwise there wouldn't be enough logic to wake up
|
||||
// again.
|
||||
|
||||
static dormant_source_t _dormant_source;
|
||||
|
||||
bool dormant_source_valid(dormant_source_t dormant_source) {
|
||||
switch (dormant_source) {
|
||||
case DORMANT_SOURCE_XOSC:
|
||||
return true;
|
||||
case DORMANT_SOURCE_ROSC:
|
||||
return true;
|
||||
#if !PICO_RP2040
|
||||
case DORMANT_SOURCE_LPOSC:
|
||||
return true;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// In order to go into dormant mode we need to be running from a stoppable clock
|
||||
// source: either the xosc or rosc with no PLLs running. This means we disable
|
||||
// the USB and ADC clocks and all PLLs
|
||||
void sleep_run_from_dormant_source(dormant_source_t dormant_source) {
|
||||
assert(dormant_source_valid(dormant_source));
|
||||
_dormant_source = dormant_source;
|
||||
|
||||
uint src_hz;
|
||||
uint clk_ref_src;
|
||||
switch (dormant_source) {
|
||||
case DORMANT_SOURCE_XOSC:
|
||||
src_hz = XOSC_HZ;
|
||||
clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC;
|
||||
break;
|
||||
case DORMANT_SOURCE_ROSC:
|
||||
src_hz = 6500 * KHZ; // todo
|
||||
clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_ROSC_CLKSRC_PH;
|
||||
break;
|
||||
#if !PICO_RP2040
|
||||
case DORMANT_SOURCE_LPOSC:
|
||||
src_hz = 32 * KHZ;
|
||||
clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_LPOSC_CLKSRC;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
hard_assert(false);
|
||||
}
|
||||
|
||||
// CLK_REF = XOSC or ROSC
|
||||
clock_configure(clk_ref, clk_ref_src,
|
||||
0, // No aux mux
|
||||
src_hz, src_hz);
|
||||
|
||||
// CLK SYS = CLK_REF
|
||||
clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF,
|
||||
0, // Using glitchless mux
|
||||
src_hz, src_hz);
|
||||
|
||||
// CLK ADC = 0MHz
|
||||
clock_stop(clk_adc);
|
||||
clock_stop(clk_usb);
|
||||
#if PICO_RP2350
|
||||
clock_stop(clk_hstx);
|
||||
#endif
|
||||
|
||||
#if PICO_RP2040
|
||||
// CLK RTC = ideally XOSC (12MHz) / 256 = 46875Hz but could be rosc
|
||||
uint clk_rtc_src = (dormant_source == DORMANT_SOURCE_XOSC)
|
||||
? CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC
|
||||
: CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_ROSC_CLKSRC_PH;
|
||||
|
||||
clock_configure(clk_rtc,
|
||||
0, // No GLMUX
|
||||
clk_rtc_src, src_hz, 46875);
|
||||
#endif
|
||||
|
||||
// CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so
|
||||
// just select and enable
|
||||
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS,
|
||||
src_hz, src_hz);
|
||||
|
||||
pll_deinit(pll_sys);
|
||||
pll_deinit(pll_usb);
|
||||
|
||||
// Assuming both xosc and rosc are running at the moment
|
||||
if (dormant_source == DORMANT_SOURCE_XOSC) {
|
||||
// Can disable rosc
|
||||
rosc_disable();
|
||||
} else {
|
||||
// Can disable xosc
|
||||
xosc_disable();
|
||||
}
|
||||
|
||||
// Reconfigure uart with new clocks
|
||||
// setup_default_uart();
|
||||
}
|
||||
|
||||
static void processor_deep_sleep(void) {
|
||||
// Enable deep sleep at the proc
|
||||
#ifdef __riscv
|
||||
uint32_t bits = RVCSR_MSLEEP_POWERDOWN_BITS;
|
||||
if (!get_core_num()) {
|
||||
bits |= RVCSR_MSLEEP_DEEPSLEEP_BITS;
|
||||
}
|
||||
riscv_set_csr(RVCSR_MSLEEP_OFFSET, bits);
|
||||
#else
|
||||
scb_hw->scr |= ARM_CPU_PREFIXED(SCR_SLEEPDEEP_BITS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void sleep_goto_sleep_until(struct timespec *ts,
|
||||
aon_timer_alarm_handler_t callback) {
|
||||
|
||||
// We should have already called the sleep_run_from_dormant_source function
|
||||
// This is only needed for dormancy although it saves power running from xosc
|
||||
// while sleeping
|
||||
// assert(dormant_source_valid(_dormant_source));
|
||||
|
||||
#if PICO_RP2040
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#else
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_REF_POWMAN_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#endif
|
||||
|
||||
aon_timer_enable_alarm(ts, callback, false);
|
||||
|
||||
// stdio_flush();
|
||||
|
||||
// Enable deep sleep at the proc
|
||||
processor_deep_sleep();
|
||||
|
||||
// Go to sleep
|
||||
__wfi();
|
||||
}
|
||||
|
||||
bool sleep_goto_sleep_for(uint32_t delay_ms,
|
||||
hardware_alarm_callback_t callback) {
|
||||
// We should have already called the sleep_run_from_dormant_source function
|
||||
// This is only needed for dormancy although it saves power running from xosc
|
||||
// while sleeping
|
||||
// assert(dormant_source_valid(_dormant_source));
|
||||
|
||||
// Turn off all clocks except for the timer
|
||||
clocks_hw->sleep_en0 = 0x0;
|
||||
#if PICO_RP2040
|
||||
clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS;
|
||||
#elif PICO_RP2350
|
||||
clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_REF_TICKS_BITS |
|
||||
CLOCKS_SLEEP_EN1_CLK_SYS_TIMER0_BITS;
|
||||
#else
|
||||
#error Unknown processor
|
||||
#endif
|
||||
|
||||
int alarm_num = hardware_alarm_claim_unused(true);
|
||||
hardware_alarm_set_callback(alarm_num, callback);
|
||||
absolute_time_t t = make_timeout_time_ms(delay_ms);
|
||||
if (hardware_alarm_set_target(alarm_num, t)) {
|
||||
hardware_alarm_set_callback(alarm_num, NULL);
|
||||
hardware_alarm_unclaim(alarm_num);
|
||||
return false;
|
||||
}
|
||||
|
||||
// stdio_flush();
|
||||
|
||||
// Enable deep sleep at the proc
|
||||
processor_deep_sleep();
|
||||
|
||||
// Go to sleep
|
||||
__wfi();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _go_dormant(void) {
|
||||
assert(dormant_source_valid(_dormant_source));
|
||||
|
||||
if (_dormant_source == DORMANT_SOURCE_XOSC) {
|
||||
xosc_dormant();
|
||||
} else {
|
||||
rosc_set_dormant();
|
||||
}
|
||||
}
|
||||
|
||||
void sleep_goto_dormant_until(struct timespec *ts,
|
||||
aon_timer_alarm_handler_t callback) {
|
||||
// We should have already called the sleep_run_from_dormant_source function
|
||||
|
||||
#if PICO_RP2040
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#else
|
||||
assert(_dormant_source == DORMANT_SOURCE_LPOSC);
|
||||
uint64_t restore_ms = powman_timer_get_ms();
|
||||
powman_timer_set_1khz_tick_source_lposc();
|
||||
powman_timer_set_ms(restore_ms);
|
||||
|
||||
clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_REF_POWMAN_BITS;
|
||||
clocks_hw->sleep_en1 = 0x0;
|
||||
#endif
|
||||
|
||||
// Set the AON timer to wake up the proc from dormant mode
|
||||
aon_timer_enable_alarm(ts, callback, true);
|
||||
|
||||
// stdio_flush();
|
||||
|
||||
// Enable deep sleep at the proc
|
||||
processor_deep_sleep();
|
||||
|
||||
// Go dormant
|
||||
_go_dormant();
|
||||
}
|
||||
|
||||
void sleep_goto_dormant_until_pin(uint gpio_pin, bool edge, bool high) {
|
||||
bool low = !high;
|
||||
bool level = !edge;
|
||||
|
||||
// Configure the appropriate IRQ at IO bank 0
|
||||
assert(gpio_pin < NUM_BANK0_GPIOS);
|
||||
|
||||
uint32_t event = 0;
|
||||
|
||||
if (level && low)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS;
|
||||
if (level && high)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS;
|
||||
if (edge && high)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS;
|
||||
if (edge && low)
|
||||
event = IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS;
|
||||
|
||||
gpio_init(gpio_pin);
|
||||
gpio_set_input_enabled(gpio_pin, true);
|
||||
gpio_set_dormant_irq_enabled(gpio_pin, event, true);
|
||||
|
||||
_go_dormant();
|
||||
// Execution stops here until woken up
|
||||
|
||||
// Clear the irq so we can go back to dormant mode again if we want
|
||||
gpio_acknowledge_irq(gpio_pin, event);
|
||||
gpio_set_input_enabled(gpio_pin, false);
|
||||
}
|
||||
|
||||
// To be called after waking up from sleep/dormant mode to restore system clocks
|
||||
// properly
|
||||
void sleep_power_up(void) {
|
||||
// Re-enable the ring oscillator, which will essentially kickstart the proc
|
||||
rosc_enable();
|
||||
|
||||
// Reset the sleep enable register so peripherals and other hardware can be
|
||||
// used
|
||||
clocks_hw->sleep_en0 |= ~(0u);
|
||||
clocks_hw->sleep_en1 |= ~(0u);
|
||||
|
||||
// Restore all clocks
|
||||
clocks_init();
|
||||
|
||||
#if PICO_RP2350
|
||||
// make powerman use xosc again
|
||||
uint64_t restore_ms = powman_timer_get_ms();
|
||||
powman_timer_set_1khz_tick_source_xosc();
|
||||
powman_timer_set_ms(restore_ms);
|
||||
#endif
|
||||
|
||||
// UART needs to be reinitialised with the new clock frequencies for stable
|
||||
// output setup_default_uart();
|
||||
}
|
||||
|
||||
#endif // ARDUINO_ARCH_RP2350 || PICO_RP2350 || TARGET_RP2350
|
||||
Reference in New Issue
Block a user