initial commit

This commit is contained in:
2026-09-25 10:48:51 +10:00
commit b90beea8ae
1020 changed files with 131665 additions and 0 deletions
@@ -0,0 +1,34 @@
/*!
* @file Adafruit_SleepyDog.cpp
*
* @mainpage Adafruit SleepyDog Library
*
* @section intro_sec Introduction
*
* Arduino library to use the watchdog timer for system reset and low power
* sleep.
*
* Currently supports the following hardware:
*
* Arduino Uno or other ATmega328P-based boards.
* Arduino Mega or other ATmega2560- or 1280-based boards.
* Arduino Zero, Adafruit Feather M0 (ATSAMD21).
* Arduino Leonardo or other 32u4-based boards (e.g. Adafruit Feather) WITH
* CAVEAT: USB Serial connection is clobbered on sleep; if sketch does not
* require Serial comms, this is not a concern. The example sketches all print
* to Serial and appear frozen, but the logic does otherwise continue to run.
* You can restore the USB serial connection after waking up using
* `USBDevice.attach();` and then reconnect to USB serial from the host machine.
* Partial support for Teensy 3.X and LC (watchdog, no sleep).
* ESP32/ESP32-S2
* ESP8266
*
* Adafruit Trinket and other boards using ATtiny MCUs are NOT supported.
*/
#include "Adafruit_SleepyDog.h"
/*!
* @brief Global instance of the main class for sketches to use.
*/
WatchdogType Watchdog;
@@ -0,0 +1,54 @@
/*!
* @file Adafruit_SleepyDog.h
*/
#ifndef ADAFRUIT_SLEEPYDOG_H
#define ADAFRUIT_SLEEPYDOG_H
// Platform-specific code goes below. Each #ifdef should check for the presence
// of their platform and pull in the appropriate watchdog implementation type,
// then typedef it to WatchdogType so the .cpp file can create a global
// instance.
#if defined(ARDUINO_ARCH_AVR) || defined(__AVR__)
#include "utility/WatchdogAVR.h"
typedef WatchdogAVR WatchdogType;
#elif defined(ARDUINO_ARCH_SAMD)
// Arduino Zero / ATSAMD series CPU watchdog support.
#include "utility/WatchdogSAMD.h"
typedef WatchdogSAMD WatchdogType;
#elif defined(__MK20DX128__) || defined(__MK20DX256__) || \
defined(__MK64FX512__) || defined(__MK66FX1M0__)
// Teensy 3.x watchdog support.
#include "utility/WatchdogKinetisK.h"
typedef WatchdogKinetisKseries WatchdogType;
#elif defined(__MKL26Z64__)
// Teensy LC watchdog support.
#include "utility/WatchdogKinetisL.h"
typedef WatchdogKinetisLseries WatchdogType;
#elif defined(NRF52_SERIES)
#include "utility/WatchdogNRF.h"
typedef WatchdogNRF WatchdogType;
#elif defined(ARDUINO_ARCH_ESP32)
#include "utility/WatchdogESP32.h"
typedef WatchdogESP32 WatchdogType;
#elif defined(ARDUINO_ARCH_ESP8266)
#include "utility/WatchdogESP8266.h"
typedef WatchdogESP8266 WatchdogType;
// Philhower BSP defines ARDUINO_ARCH_RP2040 for BOTH RP2040 and RP2350.
// RP2350 boards are distinguished by PICO_RP2350=1 / TARGET_RP2350.
#elif defined(ARDUINO_ARCH_RP2040) || defined(ARDUINO_ARCH_RP2350)
#if defined(PICO_RP2350) || defined(TARGET_RP2350) || \
defined(ARDUINO_ARCH_RP2350)
#include "utility/WatchdogRP2350/WatchdogRP2350.h"
typedef WatchdogRP2350 WatchdogType;
#else
#include "utility/WatchdogRP2040.h"
typedef WatchdogRP2040 WatchdogType;
#endif
#else
#error Unsupported platform for the Adafruit Watchdog library!
#endif
extern WatchdogType Watchdog;
#endif
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Adafruit Industries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,18 @@
# Adafruit SleepyDog Arduino Library [![Build Status](https://github.com/adafruit/Adafruit_SleepyDog/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_SleepyDog/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit_SleepyDog/html/index.html)
Arduino library to use the watchdog timer for system reset. Also provides simple API access for entering/exiting from low power sleep modes.
## Supported Platforms
| Support Level | Platform / Board Family | MCU(s) | Notes / Caveats |
|--------------|------------------------|--------|-----------------|
| **Full** | Raspberry Pi Pico | RP2040, RP2350 | — |
| **Full** | ESP32 Family | ESP32, ESP32-S2, ESP32-S3 | — |
| **Limited** | ESP8266 | ESP8266 | Software and hardware watchdog timers are fixed-interval and not programmable. See `utility/WatchdogESP8266.cpp`. |
| **Full** | Arduino Uno / ATmega328P Boards | ATmega328P | — |
| **Full** | Arduino Mega | ATmega2560, ATmega1280 | — |
| **Full** | Arduino Zero / Adafruit Feather M0 | ATSAMD21 | — |
| **Full** | Adafruit Feather M4 | ATSAMD51 | — |
| **Full** | Arduino Leonardo / 32u4 Boards | ATmega32u4 | USB Serial is disabled during sleep. Logic continues running but Serial appears frozen. Restore after wake with `USBDevice.attach();` and reconnect from host. |
| **Full** | ATtiny Series | ATtiny24/44/84, ATtiny25/45/85 | — |
| **Partial** | Teensy 3.x / LC | Teensy 3.x, LC | Watchdog supported; sleep not supported. |
@@ -0,0 +1,80 @@
// Adafruit Watchdog Library Basic Usage Example
//
// Simple example of how to use the watchdog library.
//
// Author: Tony DiCola
#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h> // Include TinyUSB Serial port
#endif
#include <Adafruit_SleepyDog.h>
void setup() {
Serial.begin(115200);
while (!Serial)
delay(10);
// wait for Arduino Serial Monitor (native USB boards)
Serial.println("Adafruit Watchdog Library Demo!");
Serial.println();
// First a normal example of using the watchdog timer.
// Enable the watchdog by calling Watchdog.enable() as below.
// This will turn on the watchdog timer with a ~4 second timeout
// before reseting the Arduino. The estimated actual milliseconds
// before reset (in milliseconds) is returned.
// Make sure to reset the watchdog before the countdown expires or
// the Arduino will reset!
int countdownMS = Watchdog.enable(4000);
Serial.print("Enabled the watchdog with max countdown of ");
Serial.print(countdownMS, DEC);
Serial.println(" milliseconds!");
Serial.println();
// Now loop a few times and periodically reset the watchdog.
Serial.println("Looping ten times while resetting the watchdog...");
for (int i = 1; i <= 10; ++i) {
Serial.print("Loop #");
Serial.println(i, DEC);
delay(1000);
// Reset watchdog with every loop to make sure the sketch keeps running.
// If you comment out this call watch what happens in about 4 iterations!
Watchdog.reset();
}
Serial.println();
// can not disable NRF or RP2040 wdt once enabled
#if !defined(NRF52_SERIES) && !defined(ARDUINO_ARCH_RP2040)
// Disable the watchdog entirely by calling Watchdog.disable();
Watchdog.disable();
#endif
// Finally demonstrate the watchdog resetting by enabling it for a shorter
// period of time and waiting a long time without a reset. Notice you can
// pass a _maximum_ countdown time (in milliseconds) to the enable call.
// The library will try to use that value as the countdown, but it might
// pick a smaller value if the hardware doesn't support it. The actual
// countdown value will be returned so you can see what it is.
countdownMS = Watchdog.enable(4000);
Serial.print("Get ready, the watchdog will reset in ");
Serial.print(countdownMS, DEC);
Serial.println(" milliseconds!");
Serial.println();
#ifndef ARDUINO_ARCH_ESP8266
delay(countdownMS + 1000);
#else
// Calls to delay() and yield() feed the ESP8266's
// hardware and software watchdog timers, delayMicroseconds does not.
delayMicroseconds(countdownMS * 1000);
#endif
// Execution will never get here because the watchdog resets the Arduino!
}
void loop() {
// We'll never actually get to the loop because the watchdog will reset in
// the setup function.
Serial.println("You shouldn't see this message.");
delay(1000);
}
@@ -0,0 +1,61 @@
// Adafruit Watchdog Library Sleep Example
//
// Simple example of how to do low power sleep with the watchdog timer.
//
// Author: Tony DiCola
#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h> // Include TinyUSB Serial port
#endif
#include <Adafruit_SleepyDog.h>
void setup() {
// For boards with "native" USB support (e.g. not using an FTDI chip or
// similar serial bridge), Serial connection may be lost on sleep/wake,
// and you might not see the "I'm awake" messages. Use the onboard LED
// as an alternate indicator -- the code turns it on when awake, off
// before going to sleep.
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); // Show we're awake
Serial.begin(115200);
while(!Serial); // wait for Arduino Serial Monitor (native USB boards)
Serial.println("Adafruit Watchdog Library Sleep Demo!");
Serial.println();
}
void loop() {
Serial.println("Going to sleep in one second...");
delay(1000);
// To enter low power sleep mode call Watchdog.sleep() like below
// and the watchdog will allow low power sleep for as long as possible.
// The actual amount of time spent in sleep will be returned (in
// milliseconds).
digitalWrite(LED_BUILTIN, LOW); // Show we're asleep
int sleepMS = Watchdog.sleep();
// Alternatively you can provide a millisecond value to specify
// how long you'd like the chip to sleep, but the hardware only
// supports a limited range of values so the actual sleep time might
// be smaller. The time spent in sleep will be returned (in
// milliseconds).
// int sleepMS = Watchdog.sleep(1000); // Sleep for up to 1 second.
// Code resumes here on wake.
digitalWrite(LED_BUILTIN, HIGH); // Show we're awake again
// 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
Serial.print("I'm awake now! I slept for ");
Serial.print(sleepMS, DEC);
Serial.println(" milliseconds.");
Serial.println();
}
@@ -0,0 +1,69 @@
// Adafruit Watchdog Library - RP2350 Pin/GPIO Sleep Example
//
// Simple example for using the Adafruit Watchdog Library to put the RP2350 to
// sleep until a GPIO pin changes state.
//
// Brent Rubell for Adafruit Industries
//
// NOTE: After uploading, physically unplug and replug the USB cable to
// reset the AON timer correctly before running.
#include <Adafruit_SleepyDog.h>
#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h>
#else
#include "USB.h"
#endif
#define WAKE_PIN 2 // GPIO pin to wake on, change as needed for your use case
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
Serial.begin(115200);
// while (!Serial);
Serial.println("Adafruit Watchdog Library - RP2350 Sleep Pin/GPIO Demo!");
Serial.println();
}
void loop() {
Serial.println("Going to sleep in 5 seconds...");
Serial.println("To wake up, connect GPIO pin 2 to 3.3V (for HIGH wake).");
delay(5000);
// Enter Sleep mode for 5 seconds
digitalWrite(LED_BUILTIN, LOW);
// Sleep until GPIO WAKE_PIN goes HIGH on a rising edge
Watchdog.goToSleepUntilPin(WAKE_PIN, true, true);
// Sleep until GPIO WAKE_PIN goes HIGH on a falling edge
// Watchdog.goToSleepUntilPin(WAKE_PIN, false, true);
// Sleep until GPIO WAKE_PIN goes LOW on a rising edge
// Watchdog.goToSleepUntilPin(WAKE_PIN, true, false);
// Sleep until GPIO WAKE_PIN goes LOW on a falling edge
// Watchdog.goToSleepUntilPin(WAKE_PIN, false, false);
// Re-enable clocks, generators, USB and resume execution
// NOTE: This MUST be called to properly resume from sleep!
Watchdog.resumeFromSleep();
#ifndef USE_TINYUSB
// Re-enable USB after sleep. Required for Windows where USB
// does not re-enumerate automatically. On macOS and Linux, USB is
// maintained across sleep cycles. If you use an external serial terminal
// (e.g. screen, minicom), comment out USB.disconnect() and USB.connect()
// to prevent the port from disconnecting on each wake cycle.
USB.disconnect();
delay(500); // Give host time to register disconnect before reconnecting
USB.connect();
#endif
// NOTE: We can not track sleep duration when waking from a pin because we use
// the crystal oscillator as the dormant clock source, so the AON timer we'd
// use to track sleep duration is turned off. If you need to track sleep
// duration, see the SleepRP2350Timer example which uses the AON timer to wake
// from sleep.
Serial.println("I'm awake now!");
}
@@ -0,0 +1,81 @@
// Adafruit Watchdog Library - RP2350 Timer Sleep Example
//
// Simple example for using the Adafruit Watchdog Library to put the RP2350 to
// sleep until the AON timer expires. NOTE: This example exposes both the Sleep
// State (6.5.2 in RP2350 Datasheet) and the Dormant State (6.5.3 in RP2350
// Datasheet) APIs.
//
// Brent Rubell for Adafruit Industries
//
// NOTE: After uploading, physically unplug and replug the USB cable to
// reset the AON timer correctly before running.
#include <Adafruit_SleepyDog.h>
#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h>
#else
#include "USB.h"
#endif
static bool awake;
// This function will be called when the device wakes from sleep
// You can add any custom behavior you want here
void cbWake() {
// Show we're awake again
digitalWrite(LED_BUILTIN, HIGH);
awake = true;
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
Serial.begin(115200);
// while(!Serial);
Serial.println("Adafruit Watchdog Library - RP2350 Sleep Timer Demo!");
Serial.println();
// When the device wakes back up, we'll call this callback function
// to indicate that we've resumed from sleep.
Watchdog.setWakeCb(cbWake);
}
void loop() {
Serial.println("Going to sleep in 5 seconds...");
delay(5000);
// Enter Sleep mode for 5 seconds
awake = false;
digitalWrite(LED_BUILTIN, LOW);
// Enter sleep state (6.5.2 in RP2350 Datasheet)
Watchdog.goToSleepUntil(5000);
// Uncomment the line below (and comment the line above) to enter Dormant
// State (6.5.3 in RP2350 Datasheet) instead of the Sleep State above
// Watchdog.goToSleepUntil(5000, true);
// Make sure we don't wake
while (!awake) {
Serial.println("Should be sleeping here...");
}
// Re-enable clocks, generators, USB and resume execution
// NOTE: This MUST be called to properly resume from sleep!
Watchdog.resumeFromSleep();
#ifndef USE_TINYUSB
// Re-enable USB after sleep. Required for Windows where USB
// does not re-enumerate automatically. On macOS and Linux, USB is
// maintained across sleep cycles. If you use an external serial terminal
// (e.g. screen, minicom), comment out USB.disconnect() and USB.connect()
// to prevent the port from disconnecting on each wake cycle.
USB.disconnect();
delay(500); // Give host time to register disconnect before reconnecting
USB.connect();
#endif
Serial.println("I'm awake now!");
Serial.print("Slept for approximately ");
long sleep_duration = Watchdog.getSleepDuration();
Serial.print(sleep_duration);
Serial.println(" milliseconds.");
}
@@ -0,0 +1,9 @@
name=Adafruit SleepyDog Library
version=1.8.4
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
sentence=Arduino library to use the watchdog timer for system reset and low power sleep.
paragraph=Arduino library to use the watchdog timer for system reset and low power sleep.
category=Other
url=https://github.com/adafruit/Adafruit_SleepyDog
architectures=avr,samd,nrf52,teensy,esp32,esp8266,rp2040,rp2350
@@ -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_
@@ -0,0 +1 @@
pico_simple_hardware_target(rosc)
@@ -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)
@@ -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