Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add LowPower library #2620

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,8 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
rm -rf ~/.platformio/platforms/raspberrypi*
pio pkg install --global --platform https://github.com/maxgerhardt/platform-raspberrypi.git
pio pkg update --global --platform https://github.com/maxgerhardt/platform-raspberrypi.git
pio pkg install --global --tool symlink://.
cp -f /home/runner/work/arduino-pico/arduino-pico/tools/json/*.json /home/runner/.platformio/platforms/raspberrypi/boards/.
- name: Build Multicore Example
Expand Down Expand Up @@ -321,8 +321,8 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
rm -rf ~/.platformio/platforms/raspberrypi*
pio pkg install --global --platform https://github.com/maxgerhardt/platform-raspberrypi.git
pio pkg update --global --platform https://github.com/maxgerhardt/platform-raspberrypi.git
pio pkg install --global --tool symlink://.
cp -f /home/runner/work/arduino-pico/arduino-pico/tools/json/*.json /home/runner/.platformio/platforms/raspberrypi/boards/.
- name: Build Every Variant
Expand Down
81 changes: 81 additions & 0 deletions libraries/LowPower/LowPower.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2024 Maximilian Gerhardt
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "LowPower.h"
#include <hardware/structs/vreg_and_chip_reset.h>
#include <hardware/structs/syscfg.h>

LowPowerClass LowPower;

void LowPowerClass::setOscillatorType(dormant_source_t oscillator) {
this->oscillator = oscillator;
}

static uint32_t pinStatusToGPIOEvent(PinStatus status) {
switch (status) {
case HIGH: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS;
case LOW: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS;
case FALLING: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS;
case RISING: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS;
// Change activates on both a rising or a falling edge
case CHANGE:
return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS |
IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS;
default: return 0; // unreachable
}
}

void LowPowerClass::dormantUntilGPIO(pin_size_t wakeup_gpio, PinStatus wakeup_type){
sleep_run_from_dormant_source(this->oscillator);
uint32_t event = pinStatusToGPIOEvent(wakeup_type);

// turn of brown out detector (BOD), just pisses away power
vreg_and_chip_reset_hw->bod &= ~VREG_AND_CHIP_RESET_BOD_EN_BITS;
// set digital voltage to only 0.8V instead of 1.10V. burns less static power
vreg_and_chip_reset_hw->vreg &= ~VREG_AND_CHIP_RESET_VREG_VSEL_BITS;
// power down additional stuff
syscfg_hw->mempowerdown |=
SYSCFG_MEMPOWERDOWN_USB_BITS // USB Memory
;

sleep_goto_dormant_until_pin((uint) wakeup_gpio, event);

syscfg_hw->mempowerdown &=
~(SYSCFG_MEMPOWERDOWN_USB_BITS) // USB Memory
;
// We only reach the next line after waking up
vreg_and_chip_reset_hw->bod |= VREG_AND_CHIP_RESET_BOD_EN_BITS; // turn BOD back on
// default 1.10V again
vreg_and_chip_reset_hw->vreg |= VREG_AND_CHIP_RESET_VREG_VSEL_RESET << VREG_AND_CHIP_RESET_VREG_VSEL_LSB;

sleep_power_up();

// startup crystal oscillator (?)
#if (defined(PICO_RP2040) && (F_CPU != 125000000)) || (defined(PICO_RP2350) && (F_CPU != 150000000))
set_sys_clock_khz(F_CPU / 1000, true);
#endif
}

static void sleep_callback(void) { }

// For RP2040 this example needs an external clock fed into the GP20
// Note: Only GP20 and GP22 can be used for clock input, See the GPIO function table in the datasheet.
// You can use another Pico to generate this. See the clocks/hello_gpout example for more details.
// rp2040: clock_gpio_init(21, CLOCKS_CLK_GPOUT3_CTRL_AUXSRC_VALUE_CLK_RTC, 1); // 46875Hz can only export a clock on gpios 21,23,24,25 and only 21 is exposed by Pico
// RP2350 has an LPOSC it can use, so doesn't need this
// also need an initial value like
// struct timespec ts = { .tv_sec = 1723124088, .tv_nsec = 0 };
// aon_timer_start(&ts);
void LowPowerClass::dormantFor(uint32_t milliseconds) {
sleep_run_from_dormant_source(this->oscillator);
struct timespec ts = {};
aon_timer_get_time(&ts);
if (milliseconds >= 1000) {
ts.tv_sec += milliseconds / 1000;
milliseconds = milliseconds % 1000;
}
ts.tv_nsec += milliseconds * 1000ul * 1000ul;
sleep_goto_dormant_until(&ts, &sleep_callback);
}
36 changes: 36 additions & 0 deletions libraries/LowPower/LowPower.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2024 Maximilian Gerhardt
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include <Arduino.h>
#include "utility/sleep.h"

class LowPowerClass {
public:
/**
* Set the oscillator type that is shutdown during sleep and re-enabled after.
* The default is "crystal oscillator" (DORMANT_SOURCE_XOSC).
*/
void setOscillatorType(dormant_source_t oscillator);
/**
* Put the chip in "DORMANT" mode and wake up from a GPIO pin.
* The "wakeup" type can e.g. be "FALLING", so that a falling edge on that GPIO
* wakes the chip up again.
* This cannot be "CHANGE"
*/
void dormantUntilGPIO(pin_size_t wakeup_gpio, PinStatus wakeup_type);
/**
* Put the chip in "DORMANT" for a specified amount of time.
* Note that this does not work on RP2040 chips, unless you connect a 32.768kHz
* oscillator to specific pins. (TODO exact documentation)
*/
void dormantFor(uint32_t milliseconds);

private:
// sane default value, most boards run on the crystal oscillator
dormant_source_t oscillator = DORMANT_SOURCE_XOSC;
};

extern LowPowerClass LowPower;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <LowPower.h>

#define GPIO_EXIT_DORMANT_MODE 3 /* connect GP3 to GND once in DORMANT mode */

void setup() {
Serial1.setTX(12);
Serial1.setRX(13);
Serial1.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH);
pinMode(GPIO_EXIT_DORMANT_MODE, INPUT_PULLUP); // pull pin that will get us out of sleep mode
}

void loop() {
if (Serial1.available() > 0) {
(void) Serial1.read();
digitalWrite(LED_BUILTIN, LOW);
Serial1.end(); // disable the UART
pinMode(12, INPUT_PULLUP); // Serial TX
gpio_set_input_enabled(12, false);
pinMode(13, INPUT_PULLUP); // Serial RX
gpio_set_input_enabled(13, false);
pinMode(24, INPUT); // can measure VBUS on the pico
gpio_set_input_enabled(24, false);
pinMode(23, INPUT_PULLDOWN); // connected to PS of switching regulator; default pulldown
gpio_set_input_enabled(23, false);
pinMode(29, INPUT); // connected to Q1 / GPIO29_ADC3, which is a 3:1 voltage divider for VSYS
gpio_set_input_enabled(29, false);
// free standing / floating GPIOs
for(auto p : {0, 1, 2, /*3,*/ 4, 5, 6, 7, 8, 9, 10, 11, /*12, 13,*/ 14, 15, 16, 17, 18, 19, 20, 21, 22, /*23, 24,*/ 25, 26, 27, 28, 29}) {
pinMode(p, INPUT); // best performance!
//pinMode(p, INPUT_PULLDOWN);
//pinMode(p, INPUT_PULLUP);
//pinMode(p, OUTPUT); digitalWrite(p, HIGH); // drive HIGH
//pinMode(p, OUTPUT); digitalWrite(p, LOW); // drive LOW
gpio_set_input_enabled(p, false); // disable input gate
gpio_set_input_hysteresis_enabled(p, false); // additionally disable schmitt input gate
// ^-- makes no difference
}
gpio_set_input_enabled(30, false); // SWCLK
gpio_set_input_enabled(31, false); // SWD(IO)

// we will get out of sleep when an interrupt occurs.
// this will shutdown the crystal oscillator until an interrupt occurs.
LowPower.dormantUntilGPIO(GPIO_EXIT_DORMANT_MODE, FALLING);
// this will only be reached after wakeup.
// we don't actually know the time duration during which we were dormant.
// so, the absolute value of millis() will be messed up.
digitalWrite(LED_BUILTIN, HIGH);
Serial1.begin(115200); // start UART again
}
Serial1.println("hello, world");
digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);
delay(500);
}
10 changes: 10 additions & 0 deletions libraries/LowPower/library.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name=LowPower
version=1.0
author=Maximilian Gerhardt
maintainer=Maximilian Gerhardt <[email protected]>
sentence=Low Power support for RP2040 and RP2350
paragraph=
category=Data Storage
url=http://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
69 changes: 69 additions & 0 deletions libraries/LowPower/utility/rosc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#include "pico.h"

// For MHZ definitions etc
#include "hardware/clocks.h"
#include "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));
}
92 changes: 92 additions & 0 deletions libraries/LowPower/utility/rosc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#ifndef _HARDWARE_ROSC_H_
#define _HARDWARE_ROSC_H_

#include "pico.h"
#include "hardware/structs/rosc.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
Loading
Loading