Skip to content

Commit a0810a1

Browse files
committed
Add first try of a low power library, work in progress
1 parent abc07ef commit a0810a1

File tree

8 files changed

+790
-0
lines changed

8 files changed

+790
-0
lines changed

libraries/LowPower/LowPower.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2024 Maximilian Gerhardt
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
#include "LowPower.h"
7+
#include <hardware/structs/vreg_and_chip_reset.h>
8+
#include <hardware/structs/syscfg.h>
9+
10+
LowPowerClass LowPower;
11+
12+
void LowPowerClass::setOscillatorType(dormant_source_t oscillator) {
13+
this->oscillator = oscillator;
14+
}
15+
16+
static uint32_t pinStatusToGPIOEvent(PinStatus status) {
17+
switch (status) {
18+
case HIGH: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_HIGH_BITS;
19+
case LOW: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_LEVEL_LOW_BITS;
20+
case FALLING: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS;
21+
case RISING: return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS;
22+
// Change activates on both a rising or a falling edge
23+
case CHANGE:
24+
return IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_HIGH_BITS |
25+
IO_BANK0_DORMANT_WAKE_INTE0_GPIO0_EDGE_LOW_BITS;
26+
default: return 0; // unreachable
27+
}
28+
}
29+
30+
void LowPowerClass::dormantUntilGPIO(pin_size_t wakeup_gpio, PinStatus wakeup_type){
31+
sleep_run_from_dormant_source(this->oscillator);
32+
uint32_t event = pinStatusToGPIOEvent(wakeup_type);
33+
34+
// turn of brown out detector (BOD), just pisses away power
35+
vreg_and_chip_reset_hw->bod &= ~VREG_AND_CHIP_RESET_BOD_EN_BITS;
36+
// set digital voltage to only 0.8V instead of 1.10V. burns less static power
37+
vreg_and_chip_reset_hw->vreg &= ~VREG_AND_CHIP_RESET_VREG_VSEL_BITS;
38+
// power down additional stuff
39+
syscfg_hw->mempowerdown |=
40+
SYSCFG_MEMPOWERDOWN_USB_BITS // USB Memory
41+
;
42+
43+
sleep_goto_dormant_until_pin((uint) wakeup_gpio, event);
44+
45+
syscfg_hw->mempowerdown &=
46+
~(SYSCFG_MEMPOWERDOWN_USB_BITS) // USB Memory
47+
;
48+
// We only reach the next line after waking up
49+
vreg_and_chip_reset_hw->bod |= VREG_AND_CHIP_RESET_BOD_EN_BITS; // turn BOD back on
50+
// default 1.10V again
51+
vreg_and_chip_reset_hw->vreg |= VREG_AND_CHIP_RESET_VREG_VSEL_RESET << VREG_AND_CHIP_RESET_VREG_VSEL_LSB;
52+
53+
sleep_power_up();
54+
55+
// startup crystal oscillator (?)
56+
#if (defined(PICO_RP2040) && (F_CPU != 125000000)) || (defined(PICO_RP2350) && (F_CPU != 150000000))
57+
set_sys_clock_khz(F_CPU / 1000, true);
58+
#endif
59+
}
60+
61+
static void sleep_callback(void) { }
62+
63+
// For RP2040 this example needs an external clock fed into the GP20
64+
// Note: Only GP20 and GP22 can be used for clock input, See the GPIO function table in the datasheet.
65+
// You can use another Pico to generate this. See the clocks/hello_gpout example for more details.
66+
// 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
67+
// RP2350 has an LPOSC it can use, so doesn't need this
68+
// also need an initial value like
69+
// struct timespec ts = { .tv_sec = 1723124088, .tv_nsec = 0 };
70+
// aon_timer_start(&ts);
71+
void LowPowerClass::dormantFor(uint32_t milliseconds) {
72+
sleep_run_from_dormant_source(this->oscillator);
73+
struct timespec ts = {};
74+
aon_timer_get_time(&ts);
75+
if (milliseconds >= 1000) {
76+
ts.tv_sec += milliseconds / 1000;
77+
milliseconds = milliseconds % 1000;
78+
}
79+
ts.tv_nsec += milliseconds * 1000ul * 1000ul;
80+
sleep_goto_dormant_until(&ts, &sleep_callback);
81+
}

libraries/LowPower/LowPower.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2024 Maximilian Gerhardt
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
#pragma once
7+
#include <Arduino.h>
8+
#include "utility/sleep.h"
9+
10+
class LowPowerClass {
11+
public:
12+
/**
13+
* Set the oscillator type that is shutdown during sleep and reenabled after.
14+
* The default is "crystal oscillator" (DORMANT_SOURCE_XOSC).
15+
*/
16+
void setOscillatorType(dormant_source_t oscillator);
17+
/**
18+
* Put the chip in "DORMANT" mode and wake up from a GPIO pin.
19+
* The "wakeup" type can e.g. be "FALLING", so that a falling edge on that GPIO
20+
* wakes the chip up again.
21+
* This cannot be "CHANGE"
22+
*/
23+
void dormantUntilGPIO(pin_size_t wakeup_gpio, PinStatus wakeup_type);
24+
/**
25+
* Put the chpi in "DORMANT" for a specified amount of time.
26+
* Note that this does not work on RP2040 chips, unless you connect a 32.768kHz
27+
* oscillator to specific pins. (TODO exact documentation)
28+
*/
29+
void dormantFor(uint32_t milliseconds);
30+
31+
private:
32+
// sane default value, most boards run on the crystal oscillator
33+
dormant_source_t oscillator = DORMANT_SOURCE_XOSC;
34+
};
35+
36+
extern LowPowerClass LowPower;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include <LowPower.h>
2+
3+
#define GPIO_EXIT_DORMANT_MODE 3 /* connect GP3 to GND once in DORMANT mode */
4+
5+
void setup() {
6+
Serial1.setTX(12);
7+
Serial1.setRX(13);
8+
Serial1.begin(115200);
9+
pinMode(LED_BUILTIN, OUTPUT);
10+
digitalWrite(LED_BUILTIN, HIGH);
11+
pinMode(GPIO_EXIT_DORMANT_MODE, INPUT_PULLUP); // pull pin that will get us out of sleep mode
12+
}
13+
14+
void loop() {
15+
if (Serial1.available() > 0) {
16+
(void) Serial1.read();
17+
digitalWrite(LED_BUILTIN, LOW);
18+
Serial1.end(); // disable the UART
19+
pinMode(12, INPUT_PULLUP); // Serial TX
20+
gpio_set_input_enabled(12, false);
21+
pinMode(13, INPUT_PULLUP); // Serial RX
22+
gpio_set_input_enabled(13, false);
23+
pinMode(24, INPUT); // can measure VBUS on the pico
24+
gpio_set_input_enabled(24, false);
25+
pinMode(23, INPUT_PULLDOWN); // connected to PS of switching regulator; default pulldown
26+
gpio_set_input_enabled(23, false);
27+
pinMode(29, INPUT); // connected to Q1 / GPIO29_ADC3, which is a 3:1 voltage divider for VSYS
28+
gpio_set_input_enabled(29, false);
29+
// free standing / floating GPIOs
30+
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}) {
31+
pinMode(p, INPUT); // best performance!
32+
//pinMode(p, INPUT_PULLDOWN);
33+
//pinMode(p, INPUT_PULLUP);
34+
//pinMode(p, OUTPUT); digitalWrite(p, HIGH); // drive HIGH
35+
//pinMode(p, OUTPUT); digitalWrite(p, LOW); // drive LOW
36+
gpio_set_input_enabled(p, false); // disable input gate
37+
gpio_set_input_hysteresis_enabled(p, false); // additionally disable schmitt input gate
38+
// ^-- makes no difference
39+
}
40+
gpio_set_input_enabled(30, false); // SWCLK
41+
gpio_set_input_enabled(31, false); // SWD(IO)
42+
43+
// we will get out of sleep when an interrupt occurs.
44+
// this will shutdown the crystal oscillator until an interrupt occurs.
45+
LowPower.dormantUntilGPIO(GPIO_EXIT_DORMANT_MODE, FALLING);
46+
// this will only be reached after wakeup.
47+
// we don't actually know the time duration during which we were dormant.
48+
// so, the absolute value of millis() will be messed up.
49+
digitalWrite(LED_BUILTIN, HIGH);
50+
Serial1.begin(115200); // start UART again
51+
}
52+
Serial1.println("hello, world");
53+
digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ^ 1);
54+
delay(500);
55+
}

libraries/LowPower/library.properties

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name=LowPower
2+
version=1.0
3+
author=Maximilian Gerhardt
4+
maintainer=Maximilian Gerhardt <[email protected]>
5+
sentence=Low Power support for RP2040 and RP2350
6+
paragraph=
7+
category=Data Storage
8+
url=http://github.com/earlephilhower/arduino-pico
9+
architectures=rp2040
10+
dot_a_linkage=true

libraries/LowPower/utility/rosc.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#include "pico.h"
8+
9+
// For MHZ definitions etc
10+
#include "hardware/clocks.h"
11+
#include "rosc.h"
12+
13+
// Given a ROSC delay stage code, return the next-numerically-higher code.
14+
// Top result bit is set when called on maximum ROSC code.
15+
uint32_t next_rosc_code(uint32_t code) {
16+
return ((code | 0x08888888u) + 1u) & 0xf7777777u;
17+
}
18+
19+
uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz) {
20+
// TODO: This could be a lot better
21+
rosc_set_div(1);
22+
for (uint32_t code = 0; code <= 0x77777777u; code = next_rosc_code(code)) {
23+
rosc_set_freq(code);
24+
uint rosc_mhz = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_ROSC_CLKSRC) / 1000;
25+
if ((rosc_mhz >= low_mhz) && (rosc_mhz <= high_mhz)) {
26+
return rosc_mhz;
27+
}
28+
}
29+
return 0;
30+
}
31+
32+
void rosc_set_div(uint32_t div) {
33+
assert(div <= 31 && div >= 1);
34+
rosc_write(&rosc_hw->div, ROSC_DIV_VALUE_PASS + div);
35+
}
36+
37+
void rosc_set_freq(uint32_t code) {
38+
rosc_write(&rosc_hw->freqa, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code & 0xffffu));
39+
rosc_write(&rosc_hw->freqb, (ROSC_FREQA_PASSWD_VALUE_PASS << ROSC_FREQA_PASSWD_LSB) | (code >> 16u));
40+
}
41+
42+
void rosc_set_range(uint range) {
43+
// Range should use enumvals from the headers and thus have the password correct
44+
rosc_write(&rosc_hw->ctrl, (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB) | range);
45+
}
46+
47+
void rosc_disable(void) {
48+
uint32_t tmp = rosc_hw->ctrl;
49+
tmp &= (~ROSC_CTRL_ENABLE_BITS);
50+
tmp |= (ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB);
51+
rosc_write(&rosc_hw->ctrl, tmp);
52+
// Wait for stable to go away
53+
while(rosc_hw->status & ROSC_STATUS_STABLE_BITS);
54+
}
55+
56+
void rosc_set_dormant(void) {
57+
// WARNING: This stops the rosc until woken up by an irq
58+
rosc_write(&rosc_hw->dormant, ROSC_DORMANT_VALUE_DORMANT);
59+
// Wait for it to become stable once woken up
60+
while(!(rosc_hw->status & ROSC_STATUS_STABLE_BITS));
61+
}
62+
63+
void rosc_enable(void) {
64+
//Re-enable the rosc
65+
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);
66+
67+
//Wait for it to become stable once restarted
68+
while (!(rosc_hw->status & ROSC_STATUS_STABLE_BITS));
69+
}

libraries/LowPower/utility/rosc.h

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3+
*
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
#ifndef _HARDWARE_ROSC_H_
8+
#define _HARDWARE_ROSC_H_
9+
10+
#include "pico.h"
11+
#include "hardware/structs/rosc.h"
12+
13+
#ifdef __cplusplus
14+
extern "C" {
15+
#endif
16+
17+
/** \file rosc.h
18+
* \defgroup hardware_rosc hardware_rosc
19+
*
20+
* Ring Oscillator (ROSC) API
21+
*
22+
* A Ring Oscillator is an on-chip oscillator that requires no external crystal. Instead, the output is generated from a series of
23+
* inverters that are chained together to create a feedback loop. RP2040 boots from the ring oscillator initially, meaning the
24+
* first stages of the bootrom, including booting from SPI flash, will be clocked by the ring oscillator. If your design has a
25+
* crystal oscillator, you’ll likely want to switch to this as your reference clock as soon as possible, because the frequency is
26+
* more accurate than the ring oscillator.
27+
*/
28+
29+
/*! \brief Set frequency of the Ring Oscillator
30+
* \ingroup hardware_rosc
31+
*
32+
* \param code The drive strengths. See the RP2040 datasheet for information on this value.
33+
*/
34+
void rosc_set_freq(uint32_t code);
35+
36+
/*! \brief Set range of the Ring Oscillator
37+
* \ingroup hardware_rosc
38+
*
39+
* Frequency range. Frequencies will vary with Process, Voltage & Temperature (PVT).
40+
* Clock output will not glitch when changing the range up one step at a time.
41+
*
42+
* \param range 0x01 Low, 0x02 Medium, 0x03 High, 0x04 Too High.
43+
*/
44+
void rosc_set_range(uint range);
45+
46+
/*! \brief Disable the Ring Oscillator
47+
* \ingroup hardware_rosc
48+
*
49+
*/
50+
void rosc_disable(void);
51+
52+
/*! \brief Put Ring Oscillator in to dormant mode.
53+
* \ingroup hardware_rosc
54+
*
55+
* The ROSC supports a dormant mode,which stops oscillation until woken up up by an asynchronous interrupt.
56+
* This can either come from the RTC, being clocked by an external clock, or a GPIO pin going high or low.
57+
* If no IRQ is configured before going into dormant mode the ROSC will never restart.
58+
*
59+
* PLLs should be stopped before selecting dormant mode.
60+
*/
61+
void rosc_set_dormant(void);
62+
63+
// FIXME: Add doxygen
64+
65+
uint32_t next_rosc_code(uint32_t code);
66+
67+
uint rosc_find_freq(uint32_t low_mhz, uint32_t high_mhz);
68+
69+
void rosc_set_div(uint32_t div);
70+
71+
inline static void rosc_clear_bad_write(void) {
72+
hw_clear_bits(&rosc_hw->status, ROSC_STATUS_BADWRITE_BITS);
73+
}
74+
75+
inline static bool rosc_write_okay(void) {
76+
return !(rosc_hw->status & ROSC_STATUS_BADWRITE_BITS);
77+
}
78+
79+
inline static void rosc_write(io_rw_32 *addr, uint32_t value) {
80+
rosc_clear_bad_write();
81+
assert(rosc_write_okay());
82+
*addr = value;
83+
assert(rosc_write_okay());
84+
};
85+
86+
void rosc_enable(void);
87+
88+
#ifdef __cplusplus
89+
}
90+
#endif
91+
92+
#endif

0 commit comments

Comments
 (0)