Thanks to visit codestin.com
Credit goes to github.com

Skip to content

rp2: Selectively leave the USB clocks enabled in light sleep. #15111

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

Merged

Conversation

projectgus
Copy link
Contributor

@projectgus projectgus commented May 24, 2024

Without this change going to lightsleep stops the USB peripheral clock, and can lead to either the device going into a weird state or the host deciding to issue a bus reset.

This change only keeps the USB peripheral clocks enabled if the USB device is currently active and a host has configured the device. Otherwise USB clocks are disabled same as before.

Increases power consumption in light sleep (measured at the USB port for a PICO board as 1.2-1.3mA without this change, and 3.4-3.5mA with this change). I think can argue that if you have a working active USB host connection, an extra 2mA is unlikely to be significant.

(EDIT: Above measurement was originally off by a factor of 10, corrected in comments.)

As discussed at https://github.com/orgs/micropython/discussions/14401

Tested with

  • The code supplied in the linked discussion, via mpremote run
  • mpremote a1 exec "import machine; machine.lightsleep(5000)" (plus some longer and shorter delays, up to 30 seconds).
  • Same command as above, via REPL (allows interactively sending bytes to the USB device to try and bring it back.)

Details

Logic analyser shows that without this change the USB Host continues sending SoF and other requests to the sleeping device, and it doesn't respond so every transfer is an error. On my system, the host seems to keep trying indefinitely. After waking, TinyUSB ends up in a bad state and doesn't set up any new transfers itself (i.e. the USB-CDC doesn't send any data to the host.) If using a REPL, pressing a key to send some data from the host to the device is enough to make it come back from this weird state and it seems to resume normal operations.

Behaviour seems to vary between host controllers - the discussion includes an example of the host deciding the device is no longer responding and resetting it. I wasn't able to reproduce this here, can only guess it depends on the host controller (and/or maybe the hub controller, especially for superspeed hubs.)

With this change applied, the USB device continues to respond to host transfers and (presumably) will even complete pending endpoint transfers. All other requests are NAKed while still asleep, but the interaction with the host seems to resume correctly on wake.

Alternatives

One alternative approach would be to disconnect the USB device pins when going into lightsleep. This would cause the host to definitely see the device as disconnected, rather than ending up in the current weird in-between states. Also no increase in power usage. If we do the planned work to support transparent USB serial disconnect/reconnect in mpremote then it will probably be possible for mpremote to cleanly recover the connection on wake, also.

I also did some experiments with getting TinyUSB and the host to recover from the weird in-between state, without the host needing to initiate an OUT transfer. This obviously doesn't help for hosts which trigger a full bus reset, but might help for some hosts. I had a little unreliable success with this, but it seems flaky.

This work was funded through GitHub sponsors.

Copy link

github-actions bot commented May 24, 2024

Code size report:

   bare-arm:    +0 +0.000% 
minimal x86:    +0 +0.000% 
   unix x64:    +0 +0.000% standard
      stm32:    +0 +0.000% PYBV10
     mimxrt:    +0 +0.000% TEENSY40
        rp2:   +48 +0.014% RPI_PICO
       samd:    +0 +0.000% ADAFRUIT_ITSYBITSY_M4_EXPRESS

Without this change going to lightsleep stops the USB peripheral clock, and
can lead to either the device going into a weird state or the host deciding
to issue a bus reset.

This change only keeps the USB peripheral clocks enabled if the USB device
is currently active and a host has configured the device.  This means the
USB device continues to respond to host transfers and (presumably) will
even complete pending endpoint transfers.  All other requests are NAKed
while still asleep, but the interaction with the host seems to resume
correctly on wake

Otherwise, if USB is not active or configured by a host, USB clocks are
disabled, the same as before.

With the change, one can issue a `machine.lightsleep(...)` with USB CDC
connected and the USB CDC remains connected during the sleep and resumes
when the lightsleep finishes.

Tested on a RPi Pico, the power consumption is:
- During normal idle at the REPL, about 15.3mA.
- During lightsleep, prior to this change, about 1.35mA.
- During lightsleep, with this change and USB CDC connected, about 3.7mA.

If power consumption should be as low as possible when USB is connected,
one can use `machine.USBDevice` to disable the USB before entering
lightsleep.

As discussed at https://github.com/orgs/micropython/discussions/14401

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <[email protected]>
@dpgeorge dpgeorge force-pushed the feature/rp2_usb_clocked_lightsleep branch from d6986d6 to a84c7a0 Compare June 3, 2024 06:11
@dpgeorge
Copy link
Member

dpgeorge commented Jun 3, 2024

This is a really great enhancement (and very simple)! It's not very user friendly that USB resumes in a broken state after lightsleep finishes.

Tested it and it works very well.

But I think you're current measurements are a factor of 10 too high. Idle REPL consumption is about 15mA, lightsleep is about 1.35mA without this PR, and about 3.67mA with this PR. IMO that's totally acceptable, a few extra mA to keep the USB alive.

If users really need to get lower power consumption with USB connected they can use machine.USBDevice to turn off the USB before entering lightsleep.

@dpgeorge dpgeorge merged commit a84c7a0 into micropython:master Jun 3, 2024
8 checks passed
@projectgus
Copy link
Contributor Author

projectgus commented Jun 3, 2024

But I think you're current measurements are a factor of 10 too high. Idle REPL consumption is about 15mA, lightsleep is about 1.35mA without this PR, and about 3.67mA with this PR. IMO that's totally acceptable, a few extra mA to keep the USB alive.

Oh you're right, that does sound more reasonable! Thanks.

EDIT: Yes I quickly checked and I definitely read off the value wrong.

@mungewell
Copy link
Contributor

@projectgus
I submitted a PR which exposes all the 'sleep_en[01]' bits to userland, so that I can tell Pico to keep the PIO clocks running.
#16183

How would you feel if I replaced the 'disable_usb' parameter, and included checking 'sleep_en[]' for the appropriate bits - thus keeping USB clock running.

@projectgus
Copy link
Contributor Author

@mungewell Thanks for the heads-up. I will aim to get back to you on your PR soon.

@mungewell
Copy link
Contributor

they can use machine.USBDevice to turn off the USB before entering lightsleep

Can someone please explain what command I can use to turn of the USB. I have enabled (and connected to) the UART REPL, the USB is still showing connected when I lsub on my Linux box.

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.

3 participants