Skip to content

Add pico_low_power library#2852

Draft
will-v-pi wants to merge 124 commits intoraspberrypi:developfrom
will-v-pi:low-power-new
Draft

Add pico_low_power library#2852
will-v-pi wants to merge 124 commits intoraspberrypi:developfrom
will-v-pi:low-power-new

Conversation

@will-v-pi
Copy link
Copy Markdown
Contributor

@will-v-pi will-v-pi commented Mar 6, 2026

This adds a pico_low_power library, which allows going to sleep, dormant, and powman pstates.

Also adds bitsets and hardware_rosc, to support the low power stuff.

Includes a possible fix for #2420 to get the bazel rp2040 clang build working

Also requires raspberrypi/picotool#298 when putting persistent data in XIP SRAM

See low_power.h for documentation

will-v-pi added 15 commits March 6, 2026 15:29
Allows for much simpler custom linker scripts
…ript_var variables

Means that CMake doesn't need to know the default memory addresses for different platforms
…l files easier

Restructured so that it includes the platform-specific files before common ones, so common ones can be overridden
Use new include_linker_script_dir and use_linker_script_file functions to add the linker arguments
Breaking change for Bazel builds using different binary types, instead of setting PICO_DEFAULT_LINKER_SCRIPT to eg `//src/rp2_common/pico_crt0:no_flash_linker_script` it is now `//src/rp2_common/pico_standard_link:no_flash_linker_script`
Treat rp2040 layout (boot2 instead of embedded blocks) as the outlier

static inline generic_bitset_t *bitset_write_word(generic_bitset_t *bitset, uint word_num, uint32_t value) {
check_bitset(bitset);
if (word_num < bitset_word_size(bitset)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this raise an error if word_num >= bitset_word_size(bitset) ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a question for @kilograham, but given that the other functions don't throw any errors when you attempt to pass a value that is outside the range and just return 0/do nothing, I think this one should also not throw an error?

Comment thread src/common/pico_util/include/pico/util/bitset.h Outdated
Comment thread src/common/pico_util/include/pico/util/bitset.h Outdated
Comment thread src/common/pico_util/include/pico/util/bitset.h Outdated
Comment thread src/rp2_common/hardware_clocks/include/hardware/clocks.h Outdated
Comment thread src/rp2_common/hardware_irq/include/hardware/irq.h
Comment thread src/rp2_common/hardware_irq/irq.c Outdated
Comment thread src/rp2_common/hardware_powman/include/hardware/powman.h Outdated
Comment thread src/rp2_common/hardware_rosc/include/hardware/rosc.h Outdated
Comment thread src/rp2_common/hardware_rosc/include/hardware/rosc.h Outdated
Comment thread src/rp2_common/hardware_rosc/include/hardware/rosc.h Outdated
Comment thread src/rp2_common/hardware_rosc/include/hardware/rosc.h Outdated
Comment thread src/rp2_common/hardware_rosc/rosc.c Outdated
Comment thread src/rp2_common/hardware_rosc/rosc.c Outdated
Comment thread src/rp2_common/hardware_rosc/rosc.c Outdated
@kilograham
Copy link
Copy Markdown
Contributor

Although something currently seems to be broken with persistent data

EDIT: This appears to be a bug in the latest bitset changes from @kilograham - fixed_bitset_is_empty is returning true when there are bits set in the last (and only) word of the bitset

Oops can you add the failing case to the test and I’ll go fix I guess - or you can

Comment thread src/rp2_common/pico_low_power/low_power.c
Comment thread src/rp2_common/pico_low_power/low_power.c
Add new powman_timer_pause function which sets the timer to restart from the current time, rather than the previously set time

Use _pause instead of _stop in the powman functions that aren't supposed to change the time

Add PICO_POWMAN_CALIBRATE_LPOSC_FROM_OTP (default 1) to use OTP frequency value for the LPOSC frequency, otherwise (or if OTP ECC read is 0) uses the reset value of the registers

Only pause & start the timer when it's required due to changing the frequency in _set_1khz_tick_source_xxx_with_hz functions
pico_runtime_init now depends on hardware_rtc
Replace assertions with invalid_params_if or invalid_params_if_and_return, and fix invalid assertion that event happened when leaving dormant

Prevent races on entry to dormant & pstate by defining a minimum time allowed

Switch powman_timer back to XOSC after low_power_dormant_until_aon_timer
Add 4 and 77 sized bitsets
@will-v-pi
Copy link
Copy Markdown
Contributor Author

Although something currently seems to be broken with persistent data
EDIT: This appears to be a bug in the latest bitset changes from @kilograham - fixed_bitset_is_empty is returning true when there are bits set in the last (and only) word of the bitset

Oops can you add the failing case to the test and I’ll go fix I guess - or you can

Fixed, and added tests of more bitset sizes

Comment thread src/rp2_common/pico_low_power/low_power.c Outdated
Comment thread src/rp2_common/pico_low_power/low_power.c Outdated
Comment thread src/rp2_common/hardware_rosc/include/hardware/rosc.h Outdated
* \param exclusive Whether to only listen for the timer interrupt, or other interrupts.
* \return 0 on success, non-zero on error.
*/
int low_power_sleep_until_timer(timer_hw_t *timer, absolute_time_t until, const clock_dest_bitset_t *keep_enabled, bool exclusive);
Copy link
Copy Markdown
Contributor

@peterharperuk peterharperuk Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether we should make this use aon and do the conversion internally? It would avoid confusion with the API. We could then also add a helper, something like...

inline absolute_time_t low_power_delay_ms(uinnt32_t ms) {
    return delayed_by_ms(aon_timer_get_absolute_time(), ms);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think keeping a low_power_sleep_until_timer is good, so you can do sleeps with microsecond resolution - but could add a new low_power_sleep_until_aon_timer to sleep with millisecond resolution?

That helper sounds good, and maybe also a

inline bool low_power_start_aon_timer(uint64_t ms)

to handle the timespec stuff for you

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah and you definitely don't want to only have low_power_sleep_until_aon_timer on RP2040, as the resolution there is 1s

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah - yes, of course. I just think mixing the types of absolute delay is going to cause confusion. Maybe the sleep function should take an absolute delay

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could add a check whether the microseconds are 0 or not? For any times from AON timer they must be 0, and for system timers then will almost certainly be non-zero, so that check would guard the AON timer functions reasonably well.

I think I will also add low_power_sleep_for_ms (maybe low_power_sleep_for_us too), low_power_dormant_for_ms, and low_power_pstate_for_ms, so those variants will take a consistent delay

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could add a check whether the microseconds are 0 or not?

Ah yes - good idea!

Add low_power_xx_for_ms functions, which handle starting AON timer if necessary

Add low_power_set_external_clock_source function to avoid the need to have src_hz and gpio_pin in the arguments passed to low_power_dormant_until_aon_timer, and for RP2040 you just need to call low_power_set_external_clock_source beforehand (no-op on RP2350)

Add DORMANT_CLOCK_SOURCE_DEFAULT and DORMANT_CLOCK_HZ_DEFAULT, so you don't need `#if PICO_RP2040` in user code
@will-v-pi
Copy link
Copy Markdown
Contributor Author

Latest commit changes the signature for low_power_dormant_until_aon_timer to remove the src_hz and gpio_pin arguments. Those must now be passed to a separate function low_power_set_external_clock_source, which is ignored on non-RP2040 platforms, and for RP2040 it stores the values for use in low_power_dormant_until_aon_timer later.

I think this is cleaner than having unused src_hz and gpio_pin in the low_power_dormant_until_aon_timer signature, when those parameters are only used on RP2040.

Also adds low_power_xx_for_ms functions to just go to low power mode for a duration - these setup the AON timer for you, if it's not running already

@peterharperuk
Copy link
Copy Markdown
Contributor

Are you happy this is correct now? I can retest if you are.

Also increase powman_set_power_state timeout when waiting for POWMAN_STATE_WAITING_BITS, as it was sometimes timing out
This writes the calibrated frequency for the LPOSC to improve the accuracy, and switches it back to the default XOSC when waking up
It is unreliable, see RP2040-E10
@will-v-pi
Copy link
Copy Markdown
Contributor Author

Are you happy this is correct now? I can retest if you are.

Yes, I think I'm happy it's ready for retesting now

* \param ms The time in milliseconds to start the AON timer at.
* \return true on success, false on failure.
*/
static inline bool low_power_start_aon_timer_at_time_ms(uint64_t ms) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This resets the time if the aon timer is already running. So you'd always want to do this when using powman...

if (!aon_timer_is_running()) {
    low_power_start_aon_timer_at_time_ms(1776858754000);
}

Maybe the API should have a flag - bool reset_time_if_already_running (or something) which makes it do nothing if the aon timer is already running? or maybe just make it do nothing if already running..

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this one should reset the time, because you're explicitly specifying a time to start at - you might want to use it once the timer is already running to synchronise it with another clock source

But maybe low_power_start_aon_timer should check if aon_timer_is_running before resetting the time

*/
static inline bool low_power_start_aon_timer(void) {
struct timespec ts;
ms_to_timespec(to_ms_64_since_boot(get_absolute_time()), &ts);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a sensible value to set the epoch to? It would be better to set it to 1st Jan 1970 perhaps?

*
* \return true on success, false on failure.
*/
static inline bool low_power_start_aon_timer(void) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto about this resetting to clock if aon is already started

#if PICO_RP2040
int low_power_set_external_clock_source(uint src_hz, uint gpio_pin);
#else
static inline int low_power_set_external_clock_source(__unused uint src_hz, __unused uint gpio_pin) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether we should just not have this function in RP2350

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an @kilograham question - I'm happy to make it RP2040-only, but that would mean users have to put #if RP2040 in their code

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would call it like this...

low_power_set_external_clock_source(RTC_CLOCK_FREQ_HZ, RTC_CLOCK_SRC_GPIO_IN);

I don't think RTC_CLOCK_FREQ_HZ exists on RP2350 so it would have to be wrapped in an ifdef anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a DORMANT_CLOCK_HZ_DEFAULT define which you can use instead, that is defined as 0 on non-rp2040

Comment thread src/rp2_common/hardware_rtc/rtc.c Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants