pico-sdk icon indicating copy to clipboard operation
pico-sdk copied to clipboard

Watchdog Causes sleep_ms() to Hang When Debug Mode is Disabled (pause_on_debug= 1)

Open push-0x57df opened this issue 3 months ago • 8 comments

Issue Description:

When enabling the hardware watchdog with debug mode disabled (pause_on_debug parameter set to 1), the sleep_ms() function hangs indefinitely, causing system lockup. This issue occurs during debug.

Image

Reproduction Steps:

Create a new project from the blink example using VSCode Pico extension

Add watchdog_enable(100, 1); to main()

Compile in debug mode

Start debugging session

Program hangs at sleep_ms(LED_DELAY_MS); call

Code Example:


/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "pico/stdlib.h"

// Pico W devices use a GPIO on the WIFI chip for the LED,
// so when building for Pico W, CYW43_WL_GPIO_LED_PIN will be defined
#ifdef CYW43_WL_GPIO_LED_PIN
#include "pico/cyw43_arch.h"
#endif

#include "hardware/watchdog.h"

#ifndef LED_DELAY_MS
#define LED_DELAY_MS 250
#endif

// Perform initialisation
int pico_led_init(void) {
#if defined(PICO_DEFAULT_LED_PIN)
    // A device like Pico that uses a GPIO for the LED will define PICO_DEFAULT_LED_PIN
    // so we can use normal GPIO functionality to turn the led on and off
    gpio_init(PICO_DEFAULT_LED_PIN);
    gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);
    return PICO_OK;
#elif defined(CYW43_WL_GPIO_LED_PIN)
    // For Pico W devices we need to initialise the driver etc
    return cyw43_arch_init();
#endif
}

// Turn the led on or off
void pico_set_led(bool led_on) {
#if defined(PICO_DEFAULT_LED_PIN)
    // Just set the GPIO on or off
    gpio_put(PICO_DEFAULT_LED_PIN, led_on);
#elif defined(CYW43_WL_GPIO_LED_PIN)
    // Ask the wifi "driver" to set the GPIO on or off
    cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_on);
#endif
}

int main() {

    // Enable the watchdog, requiring the watchdog to be updated every 100ms or the chip will reboot
    // second arg is pause on debug which means the watchdog will pause when stepping through code
    watchdog_enable(100, 1);

    int rc = pico_led_init();
    hard_assert(rc == PICO_OK);
    while (true) {
        pico_set_led(true);
        sleep_ms(LED_DELAY_MS);
        pico_set_led(false);
        sleep_ms(LED_DELAY_MS);
    }
}

push-0x57df avatar Sep 16 '25 05:09 push-0x57df

The watchdog is behaving as expected here

  • You're enabling it with a 100ms delay, so you have to call watchdog_update every 100ms
  • Then you go to sleep for 250ms, so the watchdog reboots the chip as you didn't call watchdog_update
  • As the chip has been reset by the watchdog, the debugger is no longer connected correctly, so it looks like a hang at the sleep_ms call

The pause_on_debug parameter doesn't disable the watchdog when debugging, it just means that the watchdog is paused while the execution is paused (eg when waiting for you to step to the next line of code)

will-v-pi avatar Sep 16 '25 11:09 will-v-pi

The watchdog is behaving as expected here

  • You're enabling it with a 100ms delay, so you have to call watchdog_update every 100ms
  • Then you go to sleep for 250ms, so the watchdog reboots the chip as you didn't call watchdog_update
  • As the chip has been reset by the watchdog, the debugger is no longer connected correctly, so it looks like a hang at the sleep_ms call

The pause_on_debug parameter doesn't disable the watchdog when debugging, it just means that the watchdog is paused while the execution is paused (eg when waiting for you to step to the next line of code)

The second parameter 1 of watchdog_enable(100, 1); seems to be problematic. It is expected that in debug mode, watchdog_enable should not be reset. Please see the function prototype:

void watchdog_enable(uint32_t delay_ms, bool pause_on_debug)
Enable the watchdog

delay_ms – Number of milliseconds before watchdog will reboot without watchdog_update being called
pause_on_debug – If the watchdog should be paused when the debugger is stepping through code

If watchdog_start_tick value does not give a 1MHz clock to the watchdog system, then the delay_ms parameter will not be in milliseconds. See the datasheet for more details.

push-0x57df avatar Sep 16 '25 13:09 push-0x57df

pause_on_debug – If the watchdog should be paused when the debugger is stepping through code

I think it's reasonably clear that if you set pause_on_debug, then the watchdog will be paused only when the debugger is stepping through code - it won't be paused the rest of the time, so will still run when code is running

Feel free to raise a PR if you think it could be worded better

will-v-pi avatar Sep 16 '25 14:09 will-v-pi

pause_on_debug – If the watchdog should be paused when the debugger is stepping through code

I think it's reasonably clear that if you set pause_on_debug, then the watchdog will be paused only when the debugger is stepping through code - it won't be paused the rest of the time, so will still run when code is running

Feel free to raise a PR if you think it could be worded better

I do use breakpoints for single step debugging in debug mode, but it can be executed at breakpoint 1, but the program cannot execute to breakpoint 2 As shown:

Image

push-0x57df avatar Sep 16 '25 15:09 push-0x57df

I do use breakpoints for single step debugging in debug mode, but it can be executed at breakpoint 1, but the program cannot execute to breakpoint 2

Yes, this is expected behaviour, because the watchdog resets the chip before you reach breakpoint 2 - you need to call watchdog_update at least every 100ms, but that code is calling it every LED_DELAY_MS*2 ms, so not frequently enough

will-v-pi avatar Sep 16 '25 16:09 will-v-pi

@push-0x57df Your use of breakpoints implies that you're running the code, hitting the first breakpoint, and then pressing the "play" button and waiting for it to hit the second breakpoint? But when you click the "play" button you're no longer single-stepping through code, and so the watchdog is active again.

lurch avatar Sep 18 '25 08:09 lurch

@push-0x57df Your use of breakpoints implies that you're running the code, hitting the first breakpoint, and then pressing the "play" button and waiting for it to hit the second breakpoint? But when you click the "play" button you're no longer single-stepping through code, and so the watchdog is active again.

I set the second parameter to 1, the second parameter is pause_on_debug, just like this watchdog_enable(100, 1); but its performance did not achieve pause on debug

push-0x57df avatar Sep 18 '25 08:09 push-0x57df

To be more specific, "pause on debug" halts the timers only while the CPU is halted by the debugger. Not "when the debugger is connected". The result is that the timers will fire at the same virtual time as if the debugger never halted at all.

It's been clearly pointed out to you that your code is buggy, and a 100ms watchdog is guaranteed to fire if you sleep for 500ms. Nothing you do with the debugger or it's flags will change that.

In any case, it's very unusual to use ultra short timeouts in a watchdog timer. I've professionally used timeouts in the second or two range, because spurious unnecessary resets are often worse than waiting those two seconds to recover from a real problem. If I need to go shorter, then I'm also a lot more rigorous in preventing those crashes in the first place.

pelrun avatar Sep 20 '25 11:09 pelrun