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

Skip to content

stm32/powerctrl: Add USB support to lightsleep. #8304

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

andrewleech
Copy link
Contributor

Currently on stm32, if usb cdc repl is in use when machine.lightsleep() is run, the reply locks up and never recovers.
The micro itself will wake up and continue on with the code, but no data can be read/written to USB.

The PC thinks the USB is still connected, however no data is transferred.

If the USB is unplugged / replugged the connection recovers - assuming the device is self-powered it continues working with a normal re-connection of the USB, a reboot is not required.

This PR fixes the issue by explicitely disconnecting / reconnecting the USB in code before and after the STOP call.

While it would be nicer to not need the PC connection to disconnect / reconnect I'm not sure this is possible.

@iabdalkader
Copy link
Contributor

This is a good fix to the USB issue in low-power. I see a different behavior with stm32 boards on Linux, the device disconnects and reconnects eventually, however it seems the OS thinks it's broken, and logs a lot of errors, like these:

[13891.998002] usb 4-2: reset full-speed USB device number 33 using xhci_hcd
[13892.129044] usb 4-2: device descriptor read/64, error -71
[13892.365742] usb 4-2: device descriptor read/64, error -71
....
[13895.324991] usb 4-2: New USB device found, idVendor=f055, idProduct=9800, bcdDevice= 2.00
[13895.325003] usb 4-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[13895.325006] usb 4-2: Product: Pyboard Virtual Comm Port in FS Mode
[13895.325009] usb 4-2: Manufacturer: MicroPython

With your patch the OS detects that the device is disconnected:

[14491.508322] usb 4-2: USB disconnect, device number 40
[14496.736220] usb 4-2: new full-speed USB device number 41 using xhci_hcd
[14496.911856] usb 4-2: New USB device found, idVendor=f055, idProduct=9800, bcdDevice= 2.00
[14496.911868] usb 4-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[14496.911871] usb 4-2: Product: Pyboard Virtual Comm Port in FS Mode
[14496.911874] usb 4-2: Manufacturer: MicroPython

@andrewleech
Copy link
Contributor Author

That's a shame it's not re-connecting in linux, could you test it a couple more times to be sure?
I'm running on windows currently and it generally worked, though one trial it stayed not-connected after wakeup.

Perhaps a more thorough reconfiguration of usb is warranted, though I was trying to avoid that.

@iabdalkader
Copy link
Contributor

That's a shame it's not re-connecting in linux

But it does reconnect, with or without this patch. However, with this patch it's more consistent and the OS detects the disconnection so it doesn't log any errors before reconnecting.

could you test it a couple more times to be sure?

I tested it a few times, on Linux, and it seems to be working great, much better than before for sure.

@andrewleech
Copy link
Contributor Author

Oh, I thought from your message that on linux it was stuck disconnected, but I looked again at the log messages and yes, that looks better; explicit disconnect then reconnect.

@dpgeorge
Copy link
Member

This looks like a good fix, because machine.lightsleep() has always been flaky with USB (on Linux at least).

But testing it now, it looks like Linux has improved the USB driver behaviour, because without this patch I'm able to run machine.lightsleep(5000) at the REPL and nothing goes wrong: it waits 5 seconds then the REPL is responsive again (and my terminal program stays alive the whole time). Kernel dmesg does not say anything about the device not being responsive. Everything looks perfectly fine.

Then testing with this PR, doing the machine.lightsleep(...) does explicitly disconnect the device and the terminal program dies.

So... not sure what to think about this. Maybe it's just the host USB driver that is the issue and now it's fixed (on Linux at least)? How do things behave on Windows?

@andrewleech
Copy link
Contributor Author

On windows for me the lightsleep() call has always caused the USB connection to stop responding. After sleep, I get no data in or out on repl. The device always appears to be working otherwise though, lights flash as expected / responds to buttons etc.
So lightsleep() has returned, but USB is locked up.

This has been a long term issue on a couple of projects of mine on Windows, I've had multiple stm32 micropython platforms with the issue, on different windows computers / versions.

That's interesting that Linux is working better for you, in that sense this patch is a regression in behaviour - I'd much prefer the existing connection continue.

I wonder if there's any signal that could be sent on USB to "ping" the computer. I've briefly looked into the USB suspend/resume signalling, though I think that's mostly supposed to be host initiated. Perhaps something like this could work better though.

@dpgeorge
Copy link
Member

Yes this PR is a regression for me, but not to the extent that it blocks this PR. The approach here might still be a good compromise.

But the fact that it works ok for me on Linux means the ST USB peripheral can handle STOP mode without any issue, in particular without re-enumerating.

Note that if the board is using an external PHY (eg HS PHY) then I think the link with the host PC is maintained during STOP mode, so this PR should perhaps only disconnect if the PHY is internal.

if (usb_connected) {
pyb_usb_dev_disconnect();
}
#endif
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be called at the very start of this function, before IRQs are disabled. It's a high level operation.

pyb_usb_dev_connect();
}
#endif

Copy link
Member

Choose a reason for hiding this comment

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

I think this should be called after IRQs are enabled, to mirror the disconnect.

@iabdalkader
Copy link
Contributor

iabdalkader commented Feb 17, 2022

Maybe it's just the host USB driver that is the issue and now it's fixed (on Linux at least)?

No not really, and I think the host driver is supposed to do this because the device just stops responding abruptly, so from the host's point of view the device could be malfunctioning. I'm now on Linux 5.16.3-arch1-1 and dmesg throws a lot of errors without this patch, but if the sleep duration is short the device wakes up in time to be recovered. However, if the sleep duration is longer (say 15 seconds) the kernel eventually gives up and will Not re-enumerate the port/device:

[ 1267.579489] usb 6-2: reset full-speed USB device number 10 using xhci_hcd
[ 1267.712684] usb 6-2: device descriptor read/64, error -71
....
[ 1272.119616] usb 6-2: Device not responding to setup address.
[ 1272.326169] usb 6-2: device not accepting address 14, error -71
[ 1272.326369] usb usb6-port2: unable to enumerate USB device  # <--- device will not re-enumerate.

With this patch, this doesn't happen on long sleep. When the device wakes up it just reconnects (note the timestamp)

[ 3126.041921] usb 6-2: USB disconnect, device number 17

[ 3140.699283] usb 6-2: new full-speed USB device number 18 using xhci_hcd
[ 3140.874529] usb 6-2: New USB device found, idVendor=f055, idProduct=9800, bcdDevice= 2.00
[ 3140.874542] usb 6-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 3140.874545] usb 6-2: Product: Pyboard Virtual Comm Port in FS Mode

Note that if the board is using an external PHY (eg HS PHY) then I think the link with the host PC is maintained during STOP mode, so this PR should perhaps only disconnect if the PHY is internal.

This is a very good point, I have a board with external HS phy, and in my code I have ST low-level ULPI code that I use to switch the phy to low-power mode on entry to stop mode, and out of low-power mode on exit from stop mode, so this patch is not needed for this board and similar boards with external phy which should do the same for the lowest current. So I think code in this patch (on entry/exit to stop mode) should just be in the board specific MICROPY_BOARD_ENTER_STOP and MICROPY_BOARD_EXIT_STOP macros, or maybe in new config macros MICROPY_BOARD_USB_STOP/START ?

Also worth mentioning, when switching the external phy to low-power/normal mode it seems to disconnect/reconnect the device, just like this patch does, which may indicate that this is the right way to do it.

H7 + HS Phy in low-power + stop mode, sleep 15 seconds:

[ 3928.154385] usb 4-1: USB disconnect, device number 2

[ 3943.701293] usb 4-1: new high-speed USB device number 3 using xhci_hcd
[ 3943.842729] usb 4-1: config 1 interface 0 altsetting 0 endpoint 0x82 has an invalid bInterval 32, changing to 9
[ 3943.842741] usb 4-1: config 1 interface 1 altsetting 0 bulk endpoint 0x1 has invalid maxpacket 64
[ 3943.842746] usb 4-1: config 1 interface 1 altsetting 0 bulk endpoint 0x81 has invalid maxpacket 64
[ 3943.844700] usb 4-1: New USB device found, idVendor=2341, idProduct=005b, bcdDevice= 2.00
[ 3943.844706] usb 4-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3

Side note, I see some USB config issues, not related to stop mode/low-power.

@andrewleech
Copy link
Contributor Author

I've found a number of online references to similar issues on a number of embedded devices, particularly for usb-cdc. The host always sends regular USB requests to a CDC device, of they're not ack/nak'd you run into trouble.

Suspend/resume is often used to trigger sleep on embedded devices, but can only been host initiated, not device.

Perhaps a possible better solution for lightsleep (stop1) would be to enable usb interrupt wake so the USB IRQ/connection is serviced. The lightsleep function can query the wake-up source (WUF: Wakeup flag) and if it was USB, go back to sleep for remainder of set time (if rtc is desired timeout) or until other external wake-up trigger etc.

@dpgeorge
Copy link
Member

So I think code in this patch (on entry/exit to stop mode) should just be in the board specific MICROPY_BOARD_ENTER_STOP and MICROPY_BOARD_EXIT_STOP macros, or maybe in new config macros MICROPY_BOARD_USB_STOP/START ?

The code might need to check at runtime if the USB has an external PHY or not, because there may be multiple USB ports active and only those with internal PHYs need to be disabled.

Perhaps a possible better solution for lightsleep (stop1) would be to enable usb interrupt wake so the USB IRQ/connection is serviced.

That sounds good but I don't think it works, because I think the USB wake interrupt is for when the host puts the device to sleep and then wakes it up again. I have never seen this interrupt fire (maybe because the USB devices we define don't indicate that they can go to sleep...).


But the bigger question is: why does machine.lightsleep() need to work when connected to USB? Usually low power mode is not needed when powered from a host. If you have a battery powered device that may have a USB connection you can already do this in Python:

def my_lightsleep(t):
    pyb.usb_mode(None)
    machine.lightsleep(t)
    pyb.usb_mode("VCP")

@iabdalkader
Copy link
Contributor

why does machine.lightsleep() need to work when connected to USB?

Mainly for development and testing, but a board that's connected to an SBC via USB maybe ?

@andrewleech
Copy link
Contributor Author

Others online have pointed to when running from a battery powered host, wanting to reduce power. It's also just a gotcha for new users - we ran into a problem with a project where our usb serial logging would just fail after a while, it took a long time before we found it was caused by lightsleep().

@andrewleech andrewleech force-pushed the stm32_lightsleep_usb branch from 3e8cf44 to 9cb3d30 Compare May 30, 2022 01:15
@andrewleech andrewleech force-pushed the stm32_lightsleep_usb branch from 9cb3d30 to e4ef97d Compare June 5, 2022 23:13
tannewt pushed a commit to tannewt/circuitpython that referenced this pull request Aug 24, 2023
Fixed compilation error when FF_MAX_SS != FF_MIN_SS
@dpgeorge
Copy link
Member

Coming back to this discussion after a few years (!!), the rp2 port handles this situation differently and keeps USB alive during lightsleep(). I think that's a good way to do it. Is that possible on stm32? See #15111.

@andrewleech
Copy link
Contributor Author

Coming back to this discussion after a few years (!!), the rp2 port handles this situation differently and keeps USB alive during lightsleep(). I think that's a good way to do it. Is that possible on stm32? See #15111.

Oh that is interesting, I'm not sure if some/all stm can support this but it's worth looking into certainly.

@dpgeorge
Copy link
Member

At the very least I think we should make the stm32 port match rp2 from a users perspective, in the sense that it should be possible to run machine.lightsleep(1000) from the USB REPL, and for the REPL to remain alive (over USB).

There's a lightsleep test for rp2 in tests/ports/rp2/rp2_lightsleep.py. We should get that working on stm32.

@andrewleech
Copy link
Contributor Author

andrewleech commented Aug 22, 2024

A quick read of the a basic F4 reference manual does not look overly promising.

In summary, USB (along with any other 48Mhz peripherals) is clocked by PLL48CLK which is derived from HSE or HSI.
lightsleep() is implemented with STOP mode, which is broadly defined in the reference manual as:

  • Stop mode (all clocks are stopped)

An alternative might be to check if USB is in use, if so swap out STOP mode for:

  • Sleep mode (Cortex®-M4 with FPU core stopped, peripherals kept running)

Which has two entry modes, basic WFI which I know is already used throughout the codebase, and WFE which reduces the range of wakeup triggers / events.

10.2.3 Wakeup event management
The STM32F4xx are able to handle external or internal events in order to wake up the core
(WFE). The wakeup event can be generated either by:

  • enabling an interrupt in the peripheral control register but not in the NVIC, and enabling
    the SEVONPEND bit in the Cortex®-M4 with FPU System Control register. When the
    MCU resumes from WFE, the peripheral interrupt pending bit and the peripheral NVIC
    IRQ channel pending bit (in the NVIC interrupt clear pending register) have to be
    cleared.
  • or configuring an external or internal EXTI line in event mode. When the CPU resumes
    from WFE, it is not necessary to clear the peripheral interrupt pending bit or the NVIC
    IRQ channel pending bit as the pending bit corresponding to the event line is not set.

I tried a quick / naieve hack

    // HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    __SEV();
    __WFE();

But not surprisingly

>>> machine.lightsleep(5000)

exits back to repl immediately. So as expected WFE would need some other careful management to disable/enable undesired wake triggers before/after the call.

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

Successfully merging this pull request may close these issues.

4 participants