raspberrypi / pico-extras

BSD 3-Clause "New" or "Revised" License
480 stars 120 forks source link

Pico doesn't wake up properly from Dormant/Sleep #41

Open RayNieport opened 2 years ago

RayNieport commented 2 years ago

Hi, thanks for providing this library.

I want to use dormant mode to reduce power consumption when not actively processing something. However this has several issues.

When the Pico wakes back up from IRQ it does not restart the clocks that were disabled by sleep_run_from_dormant_source()

I've attempted to re-enable all clocks using the following code, which is based on the recover_from_sleep() function found here. This article is also useful.

// Save existing clock states
uint scb_orig = scb_hw->scr;
uint clock0_orig = clocks_hw->sleep_en0;
uint clock1_orig = clocks_hw->sleep_en1;

// Go to sleep until we see a low level on SLEEP_PIN
sleep_run_from_xosc();    
sleep_goto_dormant_until_pin(SLEEP_PIN, 0, 0);

// Recover from sleep
rosc_write(&rosc_hw->ctrl, ROSC_CTRL_ENABLE_BITS);
scb_hw->scr = scb_orig;
clocks_hw->sleep_en0 = clock0_orig;
clocks_hw->sleep_en1 = clock1_orig;
clocks_init();
stdio_init_all();

While this does get all the clocks back up and running, there are still two issues that I'm aware of:

  1. The Pico cannot communicate via stdio_usb, only via stdio_uart. If UART is needed for purposes other than stdio this is a problem.
  2. The Pico can only enter dormant mode a handful of times before crashing/locking up.

I've looked around a bit to verify that I'm not the only one experiencing this issue.

RayNieport commented 2 years ago

These two issues appear to be somewhat connected. Removing pico_enable_stdio_usb(project 1) and pico_enable_stdio_uart(project 0) from CMakeLists.txt and commenting out all printf statements in my code alleviates the lockup issue I mentioned.

andrekuria commented 2 years ago

I have the same issue with the Pico where I can only enter picosleep mode a couple of times before it enters into sleep mode and doesn't IRQ back to running code. Have you gotten any success with it?

RayNieport commented 2 years ago

@kyrieandre No success yet. I might attempt to write my own sleep functionality since the pico-extras version seems to be having issues.

I've seen reports that disabling stdio might help the issue, but I need stdio for my use case.

RayNieport commented 2 years ago

@kyrieandre @ghubcoder @ms1963 I tried disabling stdio entirely just to test, but after about 30 minutes the Pico locked up again. Better, but I need this to run 24/7. Right now my test program simply blinks once per second for five seconds, then goes to sleep. If an interrupt occurs it wakes back up and continues blinking for another five seconds.

So every ~350 sleep cycles it freezes and won't wake back up.

RayNieport commented 2 years ago

I am using this in conjunction with FreeRTOS. I wonder if anyone else experiencing this issue is using FreeRTOS as well? I don't think it's a problem with the OS but it's a possibility.

kellyostrom commented 2 years ago

I'm seeing this issue as well. I'm not using FreeRTOS but I see the lockup consistently after about 6-10 sleep cycles before I see "PANIC: No user IRQs are available".

RayNieport commented 2 years ago

Not sure exactly what changed, but I'm no longer experiencing lockup after multiple sleep/wake cycles. I think this had to do with swapping to the official RP2040 FreeRTOS port (the guide I initially followed recommended using the generic ARM M0+ port).

However, I still have the issue with USB not working after the Pico wakes up. My goal is to output debug information via stdio_usb (all UART pins are used for other purposes in my project). I do run stdio_init_all() after waking up from dormant mode, but this doesn't seem to help.

callous4567 commented 1 year ago

Just to add a bit of input- if you use https://raspberrypi.github.io/pico-sdk-doxygen/group__pico__stdio__usb.html to re-initialize the USB stdio, you seem to be able to recover USB control over the Pico (at least using VSCode on Windows 10.) I've not checked about the clocks/etc, but this fixed my issue with not being able to re-use the Pico again without forcing bootsel + restarting it manually.

Edit: seems the Pico doesn't fully recover from dormancy though as others have pointed out... when I try to re-run the code set up to put it into dormancy and then wake, no dice unless I restart the Pico manually. Might just be how I've written but yeah... :(

mlepage commented 1 year ago

Any movement forward on this issue? It would be nice to have stable sleep/dormant with complete example code (restoring clocks, working USB serial, etc.)

I'm seeing this same issue with SLEEP (not DORMANT) mode. Without the aforementioned recover_from_sleep function, a second SLEEP does not awaken. However, even with clock restoration, I can only awaken 6 times before user IRQs are exhausted. I am not sure what is consuming user IRQs. If I mark them all unclaimed after awakening, I can maybe wake a few more times, but the Pico still crashes. I'm using USB serial comms.

techrah commented 1 year ago

If you look at the stdio_usb_init function in pico-sdk/src/rp2_common/pico_stdio_usb/stdio_usb.c, you'll see where it claims an IRQ:

#ifdef PICO_STDIO_USB_LOW_PRIORITY_IRQ
    user_irq_claim(PICO_STDIO_USB_LOW_PRIORITY_IRQ);
#else
    low_priority_irq_num = (uint8_t) user_irq_claim_unused(true);
#endif

But low_priority_irq_num is statically defined in that file so I don't think there's a way to find out what it is at runtime. Instead, you can add this to your CMakelists.txt file:

target_compile_definitions(myprog PRIVATE
    PICO_STDIO_USB_LOW_PRIORITY_IRQ=31)

PICO_STDIO_USB_LOW_PRIORITY_IRQ needs to be between 26 and 31 (inclusively) so that it passes this assertion:

static_assert(PICO_STDIO_USB_LOW_PRIORITY_IRQ >= NUM_IRQS - NUM_USER_IRQS, "");

Now you can unclaim it before calling stdio_init_all() or stdio_usb_init(), e.g.:

user_irq_unclaim(PICO_STDIO_USB_LOW_PRIORITY_IRQ);
clocks_init();
stdio_init_all();

This way you only use 1 IRQ and don't exhaust the available pool.

mlepage commented 1 year ago

That explains a lot. It would be helpful if this were placed in the documentation somewhere.

I was just unclaiming all the user IRQs (since I'm not myself using any). That didn't work, but I noticed the aforementioned recover_from_sleep function was re-initing stidio before I was doing that and re-initing myself, so I fixed that. I still got a crash after running a while, but then I removed all stdio/printf and it's been successfully running on two boards for 15 mins now (waking every 10s) so seems good. So while I'd still like to get USB stdio working with sleep (and a full example would really benefit the entire community), at least sleep itself seems finally to be working, after much work.

techrah commented 1 year ago

I played around with this some more and consistently reproduced the lockup. However, it turns out that there is no need to re-initialize stdio at all. Also, if you are using the dormant function (as opposed to the sleep function), there's no need to restore scb_hw->scr et al. because these are not changed when going dormant. The only thing missing from the hello_dormant example in pico-playground is that the clocks are not re-enabled when the system wakes up.

If using sleep_run_from_xosc() as is used in the example, the XOSC is automatically restarted upon wake-up but the ROSC needs to be re-enabled explicitly before calling clocks_init otherwise it will hang. I haven't tried to track down exactly where it's hanging or why but according to the Awaking the Raspberry Pico from deep sleep article, it might be when the system clock is being reconfigured.

If using sleep_run_from_rosc(), then the ROSC is automatically restarted on wake-up so calling just clocks_init works. The XOSC will then be restarted in clocks_init.

void rosc_enable(void)
{
    uint32_t tmp = rosc_hw->ctrl;
    tmp &= (~ROSC_CTRL_ENABLE_BITS);
    tmp |= (ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB);
    rosc_write(&rosc_hw->ctrl, tmp);
    // Wait for stable
    while ((rosc_hw->status & ROSC_STATUS_STABLE_BITS) != ROSC_STATUS_STABLE_BITS);
}

int main()
{
    .
    .
    .
    sleep_run_from_xosc();
    .
    .
    .
    // Go to sleep until we see a high edge on GPIO 10
    sleep_goto_dormant_until_edge_high(10);

    // back from dormant state
    rosc_enable();  // No need if using sleep_run_from_rosc
    clocks_init();

    uint i = 0;
    while (1) {
        printf("XOSC awake %d\n", i++);
    }
    .
    .
    .
}

While avoiding stdio_init_all will prevent the lockup issue, it unfortunately doesn't solve the USB connection from dropping 😞. It's pretty much a crap shoot as to whether you'll have USB output or not. I believe this is related to the USB clock being stopped but not really sure. For debugging purposes, you may be able to recover enough times when using USB but for the longer term, a UART solution may be more suitable.

I've not played around with the sleep functionality yet but based on the hello_sleep example, I see why the RTC clock was not stopped in sleep_run_from_dormant_source. However, it is not needed for dormant mode and seems like clocks are left running in dormant mode if not explicitly stopped.

2.16.5 In DORMANT mode (see Section 2.11.3) all of the on-chip clocks can be paused to save power. This is particularly useful in battery-powered applications.

However, even if left running, I don't think it can be used by the RTC anyway, if that's your chosen wake-up method.

If the RTC is being used to trigger wake-up then it must be clocked from an external source.

So it seems like it should be shut down with the rest of them, unless there's another reason to keep it running. Can the RP-2040 keep time while in dormant mode? 🤔 If so, that might be a reason.

Not sure why the peripheral clock is left running either 🤷‍♂️

mlepage commented 1 year ago

My Pico-based board with built-in LCD has been running for just about 24 hrs now on a 220 mAh battery, where before it only got just over 8 hrs, so it seems to be working. This is low power sleeping for 9s with LCD off, then waking and having LCD on for 1s (with just a regular sleep_ms), forever. I agree, I lost USB console not immediately but very quickly (in a minute or two).

mlepage commented 1 year ago

I got half a week life on battery, so low power sleep is working. I still haven't made it work with USB IO, it crashes/hangs pretty quickly. I will probably poke at it again, as it's one of my goals for this project, but for now I have to move on to other coding. It would be really helpful if the official examples were updated with all this information, and worked nicely out of the box.

mlepage commented 1 year ago

I tried again today (to use USB stdio while also sleeping, by unclaiming user IRQs and reinitializing stdio) and it wakes more than 6 times, but still crashes within about a minute. Not sure why.

One thing I can think of, is I am lazily unclaiming all the user IRQs upon waking (since I don't myself use any), but maybe something ELSE in the SDK doesn't like that.

Another thing I can think to try, is hooking up actual serial and looking for messages printed (panics etc.) to see what the real problem is, but I would have to go looking for a USB to serial cable and get that set up.

IgnacioEgea96 commented 1 year ago

I found a solution to the problem leaving the USB and USB PLL clocks always running. In the sleep function sleep_run_from_xosc():

 //CLK USB = 0MHz
#if !LIB_PICO_STDIO_USB
     clock_stop(clk_usb);
#endif
#if !LIB_PICO_STDIO_USB
     pll_deinit(pll_usb);
#endif

In the RTC sleep function sleep_goto_sleep_until():

// Turn off all clocks when in sleep mode except for RTC
#if LIB_PICO_STDIO_USB
    clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS|CLOCKS_SLEEP_EN0_CLK_SYS_PLL_USB_BITS;
    clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_USB_USBCTRL_BITS|CLOCKS_SLEEP_EN1_CLK_SYS_USBCTRL_BITS;
#else
    clocks_hw->sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS;
    clocks_hw->sleep_en1 = 0x0;
#endif

The function to reinit the clocks in the SDK shoud not reinit the USB and USB PLL clocks, make a new function identical to clocks_init() and comment the next section:

pll_init(pll_usb, 1, 1200 * MHZ, 5, 5);

and:

   // CLK USB = PLL USB (48MHz) / 1 = 48MHz
    clock_configure(clk_usb,
                    0, // No GLMUX
                    CLOCKS_CLK_USB_CTRL_AUXSRC_VALUE_CLKSRC_PLL_USB,
                    48 * MHZ,
                    48 * MHZ);

The function stdio_init_all(); should not be call every time the PICO wakes up. Replace the call to clocks_init() function with the new function.

cosmolabs-ru commented 1 year ago

Hello from 2023. I faced the same issue of Pico not waking up from neither Sleep nor Dormant. I digged tons of google & github, and all pages stated the same things as in ghubcoder's post. I tried literally everything I found related to sleep modes, tried to study registers and manual clocking settings, nothing worked. Desperate, I started to change random things.

In void sleep_run_from_dormant_source(), I commented out the last line: //setup_default_uart(); After this, my Pico wakes up from Dormant by pin. In Dormant, it draws 10 mA, vs 37 mA in active mode. Still too much, keep digging...

UPD: I am using an ST7789 display and it was parasite leakage thru its pins. I deinit'ed SPI and all involved GPIOs, and 10mA were gone. Also, before going to sleep, I am depowering the LCD with a MOSFET.