rp2: Keep machine.RTC ticking while in lightsleep(). by projectgus · Pull Request #19694 · micropython/micropython · GitHub
Skip to content

rp2: Keep machine.RTC ticking while in lightsleep(). - #19694

Open
projectgus wants to merge 1 commit into
micropython:masterfrom
projectgus:bugfix/rp2_rtc_tick_lightsleep
Open

projectgus wants to merge 1 commit into
micropython:masterfrom
projectgus:bugfix/rp2_rtc_tick_lightsleep

Conversation

@projectgus

@projectgus projectgus commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #16519. This is a regression since v1.23 (exact commit unknown) - the RTC is frozen during lightsleep.

This work was funded through GitHub Sponsors.

Testing

  • Initial testing used the test case attached to the issue (thanks @madozu for this excellent reproducer).
  • A new unit test extmod_hardware/machine_rtc_sleep.py is added in this PR that fails on RPI_PICO_W and RPI_PICO2_W without this fix, and passes with this fix.

New test & other ports

I've added the test under extmod_hardware because in theory the behaviour of machine.RTC and machine.lightsleep are hardware agnostic.

In my quick testing, none of the other boards I tested could pass it:

  • esp32 port: Neither ESP32-S3 or ESP32-C6 wake after the timeout period without an external event. This is probably a bug, will try and investigate.
  • mimxrt port: machine.lightsleep() raises NotImplementedError. The method should probably be removed.
  • stm32: Boards with built-in USB (like pyboard) disconnect the USB port in lightsleep, so the test can't complete. Also tested a NUCLEO G4 board and found that it only intermittently woke from light sleep...

To avoid adding a known broken test on other ports I've added a check that currently skips unless the port is rp2, but there's a comment encouraging other ports to be added if/when they work.

Trade-offs and Alternatives

  • At some point it'd be nice to move to the pico-sdk low_power module to manage sleep, and I have a WIP branch for this. However, the "sleep for time" APIs in this module do not match the documented machine.lightsleep() behaviour - in "non-exclusive" mode the low_power APIs will wake to process interrupts but continue to go back to sleep until the timeout expires. By comparison, machine.lightsleep() is documented to return immediately after the next interrupt arrives. Arguably the pico-sdk low_power behaviour is more useful and more likely to be what someone expects, but I think it'd have to be something we change in MicroPython 2.0.

Generative AI

I did not use generative AI tools when creating this PR.

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

@projectgus
projectgus force-pushed the bugfix/rp2_rtc_tick_lightsleep branch from 39e6f4d to 50bed79 Compare September 10, 2026 02:43
Comment thread tests/extmod_hardware/machine_rtc_sleep.py Outdated
@github-actions

github-actions Bot commented Sep 10, 2026

Copy link
Copy Markdown

Code size report:

Reference:  shared/tinyusb: Add back runtime CDC config for older TinyUSB. [0414173]
Comparison: rp2: Keep machine.RTC ticking while in lightsleep(). [merge of ea6844a]
  mpy-cross:    +0 +0.000% 
   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
      esp32:    +0 +0.000% ESP32_GENERIC
     mimxrt:    +0 +0.000% TEENSY40
        rp2:    +8 +0.001% RPI_PICO_W
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS
  qemu rv32:    +0 +0.000% VIRT_RV32

@dpgeorge

Copy link
Copy Markdown
Member

Thanks for working on this! It's a nice and simple fix, easy to understand.

I tested with RPI_PICO2. In RISCV mode I find that the test passes on master, and with this PR. So that CPU is doing something differently...

On RPI_PICO2 in ARM mode, indeed the test added here fails on master, and passes with this PR.

@octoprobe-bot

octoprobe-bot commented Sep 10, 2026

Copy link
Copy Markdown

Octoprobe PR report

Test Tests
passed
Tests
skipped
Tests
xfailed
Tests
failed
format flash 4
run-tests.py 3847 450
run-tests.py --via-mpy --emit native 3803 494
run-tests.py --via-mpy 3847 450
run-perfbench.py 96
run-natmodtests.py 288 34 6
run-mpremote-tests.sh 36
run-tests.py --test-dirs=extmod_hardware 30 72 30
run-tests.py --test-dirs=extmod_hardware --emit-native 30 72 30
Total 11981 1572 66
Failures

Regression since v1.23 (exact commit unknown).

Adds a unit test that fails without this fix, and succeeds with it.
(Test is added in extmod_hardware but currently doesn't pass on any other
ports that I have access to...)

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
@projectgus
projectgus force-pushed the bugfix/rp2_rtc_tick_lightsleep branch from 50bed79 to ea6844a Compare September 10, 2026 04:07
@projectgus

Copy link
Copy Markdown
Contributor Author

Thanks for working on this! It's a nice and simple fix, easy to understand.

I tested with RPI_PICO2. In RISCV mode I find that the test passes on master, and with this PR. So that CPU is doing something differently...

On RPI_PICO2 in ARM mode, indeed the test added here fails on master, and passes with this PR.

Ah, thanks. I always forget about the RISCV mode, that's interesting.

@dpgeorge dpgeorge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good.

Would be nice to eventually get the new test running on other ports, but I suspect that's quite difficult especially for those with native USB.

@dpgeorge

Copy link
Copy Markdown
Member

By comparison, machine.lightsleep() is documented to return immediately after the next interrupt arrives. Arguably the pico-sdk low_power behaviour is more useful and more likely to be what someone expects, but I think it'd have to be something we change in MicroPython 2.0.

I think one big reason for the current behaviour (returning on interrupt) was so something like asyncio could call machine.lightsleep(ms) to reduce power while waiting for the next event.

Also, deepsleep(ms) can definitely wake earlier than ms if there's an external wake-up interrupt, eg pin change, and probably best to keep that behaviour.

@projectgus

Copy link
Copy Markdown
Contributor Author

I think one big reason for the current behaviour (returning on interrupt) was so something like asyncio could call machine.lightsleep(ms) to reduce power while waiting for the next event.

Good point. I guess one possibility would be to set a timer and call machine.idle() there instead? But I'm not necessarily advocating for it to change.

@Gadgetoid

Copy link
Copy Markdown
Contributor

Possibly related to this is that always-on timer continues to tick in some circumstances, but it unconditionally reset upon boot. See:

I have a downstream change that guards this with if (!aon_timer_is_running()) since it can affect our boards with an integrated battery which use reset as a feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RP2: machine.RTC().datetime() does not advance during lightsleep

4 participants