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

Implement alarm-module for RP2350 #9965

Closed
wants to merge 6 commits into from
Closed

Conversation

bablokb
Copy link

@bablokb bablokb commented Jan 16, 2025

This PR implements the alarm-module for the RP2350 variant of the pico and fixes #9491.

Notes

  • The PR does not use the new power-domains for the RP2350, but uses plain old sleep/dormant modes. An implementation based on powman is something for a future PR.
  • This PR also reimplements light-sleep/deep-sleep for the RP2040. As @dhalbert stated in Third sleep option: RAM-preserving "deep" sleep #9521: "So redefining light sleep as RAM-preserving, but not activity-preserving, might be worthwhile". With this PR, light-sleep has power consumption down to deep-sleep levels but is RAM-preserving.
  • The implementation is smart enough to detect a serial connection. In this case, nothing changes to the current implementation (well, at least for the RP2040 - the behavior on RP2350 needs some more tests, but at least USB-serial does not disconnect)
  • Anybody who needs "light-sleep but keep everything running" can use what I call "slow-sleep" (see next section with results below). Which is already much better now compared to the current light-sleep implementation. This could be integrated with an option to light_sleep_until_alarms(maximize_power_save=True). But for real life use running on batteries, I cannot imagine that anybody really wants more than the absolute necessary stuff running during sleep.
  • This PR needs thorough testing, which I will continue to do (especially with peripherals and the w-variants of the boards, and with the RP2350B). Any additional tests are highly appreciated.

Light-Sleep/Deep-Sleep Current for RP2xxx

All measurements at 5V with Nordic PPKII powering via USB
(i.e. USB-serial not connected).

Notes:

  • sleep: time.sleep()
  • s-sleep ("slow sleep"): cpu.frequency = 25000000; time.sleep()
  • LS: light-sleep
  • DS: deep-sleep
  • TA: TimeAlarm
  • PA: PinAlarm

RP2040 (Pico)

type 9.2.1 9.2.2-PR
sleep 18.6mA 18.6mA
s-sleep 7.7mA 7.7mA
LS+TA 15.0mA 1.6mA
LS+PA 15.0mA 1.2mA
DS+TA 7.1mA 1.6mA
DS+PA 1.3mA 1.2mA

RP2350A (Pico2)

type 9.2.1 9.2.2-PR
sleep 15.1mA 15.1mA
s-sleep 5.7mA 5.7mA
LS+TA n.a. 1.7mA
LS+PA n.a. 1.7mA
DS+TA n.a. 1.7mA
DS+PA n.a. 1.7mA

@tannewt tannewt self-requested a review January 16, 2025 17:57
@tannewt
Copy link
Member

tannewt commented Jan 16, 2025

Thanks for doing this!

So this will disable some peripherals during light sleep now?

Is the RP2350 sleep current high because it doesn't use POWMAN to shutdown ram?

@bablokb
Copy link
Author

bablokb commented Jan 16, 2025

So this will disable some peripherals during light sleep now?

To be tested, but probably yes, due to the switch to dormant-state. For light-sleep with serial-connection, there should be no change.

Is the RP2350 sleep current high because it doesn't use POWMAN to shutdown ram?

I think so, dormant does nothing with other units than the cores.

@bablokb
Copy link
Author

bablokb commented Jan 18, 2025

Please mark this as a draft PR. The Pico-W (and probably the Pico2-W) need some more investigation. In my tests, the CYW43 did complain with messages like:

[CYW43] cyw43_kso_set(1): failed
F2 not ready
F2 not ready
[CYW43] STALL(0;129-129): timeout

There is a cyw43_enter_deep_sleep() function called from common_hal_alarm_enter_deep_sleep(), but I am missing the counterpart. Since this only sets WL_REG_ON to false, it might be simple.

Pinging @eightycc: any ideas?

@eightycc
Copy link

@bablokb The [CYW43] cyw43_kso_set(1): failed message may be occurring because simply turning off power to the WIFI radio as is done in cyw43_enter_deep_sleep() does not put the driver into a quiescent state. Similarly, properly powering the WIFI radio back up requires a sequence of operations involving an interrupt from the CYW43 that appears to be entirely missing.

I'll reproduce the failure here and get back with a more detailed analysis.

@dhalbert dhalbert marked this pull request as draft January 18, 2025 13:42
@eightycc
Copy link

The best source of technical specs for the CYW43439 that I've been able to find is the source code for Infineon's WHD driver: https://github.com/Infineon/wifi-host-driver.

The KSO (Keep SDIO On) bit managed by cyw43_kso_set() appears to affect operation only when the CYW43's interface is in SDIO mode. Why it's getting touched when the interface is in gSPI mode is a bit of a puzzler.

@eightycc
Copy link

In common_hal_alarm_set_deep_sleep_alarms() we're missing code for putting the CYW43439 into a quiescent state before pulling the plug on the WIFI radio by calling cyw43_enter_deep_sleep(). We're also missing code for bringing the CYW43439 back up after exiting deep sleep. That requires pretty much a complete re-initialization of the chip. There's no similar code in Micropython (i.e., powering down/up the WIFI radio), so we're on our own for an implementation.

The CYW43439 does expose power management controls, but the best I say for the documentation is that it's murky. There might be an alternative to pulling the plug there?

@bablokb
Copy link
Author

bablokb commented Jan 19, 2025

@eightycc thanks for your input. I will do some more analyses and tests next week. Maybe it makes more sense to put the driver into a quiescent state and leave the chip as is. Or I will try to reinitialize the chip. Although I don't really like the idea, because currently this is a lengthy process with a hard-coded delay of one whole second.

@eightycc
Copy link

Because common_hal_alarm_set_deep_sleep_alarms() with its call to cyw43_enter_deep_sleep() leaves the CYW43439 in an unrecoverable state (at least with the code as it is), I'd suggest removing the call to cyw43_enter_deep_sleep() and seeing what the power consumption hit is. Just a nit to pick, but the function as is includes a hard-coded pin number.

It may be worthwhile to look into the power management features of the chip exposed by cyw43_wifi_pm() in cyw43-driver. Presently bindings_cyw43_wifi_enforce_pm() in ports/raspberrypi/bindings/cyw43/__init__.c disables all power management features by default.

@bablokb
Copy link
Author

bablokb commented Jan 24, 2025

I added two small changes:

  • keep WLAN alive for light-sleep/deep-sleep when connected via USB
  • fix AON-wakeup for RP2350

The code from the PR is now also working for Pico-W and Pico2-W, but it is necessary that the user disables WLAN (wifi.radio.enabled = False) before switching to light-sleep (and "slow-sleep" also btw, since the CYW does not like changing clocks).

Looking at the measurements, the W variants draw a lot of current, probably more than what can be attributed to the CYW-chip. Light-sleep currently does not touch the CYW, so here is still some room for improvement. The goal would be to bring current draw down to deep-sleep level at least for the RP2350 (which would imply turning of the chip and reinitializing after wakeup).

Disabling the radio is currently a task for the user-code. This could be integrated into the firmware. Re-enabling could probably also be done, but re-connecting to the AP will always have to be done by the user (especially if turning off the chip during light-sleep works). This must be cleanly documented.

The measurements show considerable improvements for light-sleep and time-alarms (RP2040). The figures for the RP2350 are similar.

Pico-W (RP2040)

Test-Setups:

  • (a): import wifi and connected as documented
  • (b): wifi not imported
  • (A): import wifi, CYW43 shutdown, manual reconnect
  • (B): wifi not imported, CYW43 shutdown
type 9.2.3(a) 9.2.3(b) 9.2.3-PR(a) 9.2.3-PR(b) 9.2.3-PR(A) 9.2.3-PR(B)
sleep 63.2mA¹ 27.3mA 63.0mA 27.2mA 63.2mA¹ 27.5mA
s-sleep failed¹ failed¹
s-sleep 23.2mA² 13.5mA 23.2mA² 13.5mA 23.4mA² 13.6mA
LS+TA 59.8mA¹ failed¹
LS+TA 29.4mA² 22.1mA 21.4mA² 11.4mA 1.8mA³ 1.8mA
LS+PA 59.8mA¹ failed¹
LS+PA 29.7mA² 22.1mA 21.0mA² 11.0mA 1.3mA³ 1.3mA
DS+TA 6.0mA 6.1mA 1.8mA 1.8mA 1.8mA 1.8mA
DS+PA 1.4mA 1.4mA 1.3mA 1.3mA 1.3mA 1.3mA

¹ connected
² wifi.radio.enabled=False
³ reconnect from user-code necessary

(Pico2-W) RP2350A

Test-Setups:

  • (a): import wifi and connected as documented
  • (b): wifi not imported
  • (A): import wifi, CYW43 shutdown, manual reconnect
  • (B): wifi not imported, CYW43 shutdown
type 9.2.3(a) 9.2.3(b) 9.2.3-PR(a) 9.2.3-PR(b) 9.2.3-PR(A) 9.2.3-PR(B)
sleep 60.6mA¹ 20.1mA 60.2mA¹ 20.7mA 58.7mA¹ 18.9mA
s-sleep failed¹ failed¹
s-sleep 24.2mA² 11.3mA 24.2mA² 11.3mA 24.2mA² 14.8mA
LS+TA n.a. failed¹
LS+TA n.a. n.a. 21.3mA² 11.9mA 1.9mA³ 1.9mA
LS+PA n.a. failed¹
LS+PA n.a. n.a. 21.3mA² 11.9mA 1.9mA³ 1.9mA
DS+TA n,a. n.a. 1.9mA 1.8mA 1.9mA 1.9mA
DS+PA n.a. n.a. 1.9mA 1.8mA 1.9mA 1.9mA

¹ connected
² wifi.radio.enabled=False
³ reconnect from user-code necessary

@bablokb
Copy link
Author

bablokb commented Jan 28, 2025

@dhalbert: I am still working on this, trying to fix a pin-alarm related problem. I found this piece of code while searching for a solution (see: https://github.com/adafruit/circuitpython/blame/d965980a986c44c3048fd744e7112c5dda782288/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c#L31):

    if (_not_yet_deep_sleeping) {
        // Event went off prematurely, before we went to sleep, so set it again.
        gpio_set_irq_enabled(gpio, events, false);
    } else {
        // Went off during sleep.
        // Disable IRQ automatically.
        gpio_set_irq_enabled(gpio, events, false);
        gpio_set_dormant_irq_enabled(gpio, events, false);
    }

I don't understand the "if"-part of this. I think to "set it again" I would need both function-calls from the else-part but with true as last argument?!

Otherwise, I am making good progress. I managed to shutdown (and restart) the CYW-chip, so current in light-sleep is down to deep-sleep levels. I will update the PR as soon as I sorted out the last few problems.

@bablokb bablokb marked this pull request as ready for review February 1, 2025 09:39
@bablokb
Copy link
Author

bablokb commented Feb 1, 2025

This is now ready for review (and merging IMHO). I don't know why there is a complaint about a conflict in common-hal/alarm/__init__.c. Github marks a handful of lines as having a conflict although I change almost everything in the given file.

I also updated the measurements in the comments above. For the Pico-W for example a light-sleep time-alarm is now down from 60mA to below 2mA.

@dhalbert
Copy link
Collaborator

dhalbert commented Feb 1, 2025

I don't know why there is a complaint about a conflict in common-hal/alarm/__init__.c.

I changed that file in #10018, which was recently merged. Your PR is against an older commit of that file. So could you rebase or merge from upstream? That should clear the conflict, but check the changes. I think your rework will make that change moot, but try the test program in #10018. Thanks.

@bablokb
Copy link
Author

bablokb commented Feb 1, 2025

I actually integrated your recent change already. I will try a rebase and see if this resolves the issues.

@bablokb bablokb closed this Feb 1, 2025
@bablokb bablokb deleted the pico2_alarm branch February 1, 2025 14:29
@bablokb
Copy link
Author

bablokb commented Feb 1, 2025

Ok, seems I need a new PR since I could not push the rebased branch without deleting my upstream branch.

@eightycc
Copy link

eightycc commented Feb 1, 2025

@bablokb FYI, a git push --force will replace a branch on github and will automatically update any pending pull requests.

@tannewt
Copy link
Member

tannewt commented Feb 4, 2025

git push --force-with-lease will force but only if your local copy of the branch matches the remote one.

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.

Low power support for RP2350
4 participants