Difference between revisions of "STM32 Watchdogs"

From Stm32World Wiki
Jump to navigation Jump to search
Line 25: Line 25:
  
 
Notice that by default the low speed oscillator is a simple built-in RC oscillator which is far less precise than a crystal would be.  It is therefore important to leave some room for drive.  If we set the reset time at 1.5 seconds, we should probably kick the watchdog once every second or so.
 
Notice that by default the low speed oscillator is a simple built-in RC oscillator which is far less precise than a crystal would be.  It is therefore important to leave some room for drive.  If we set the reset time at 1.5 seconds, we should probably kick the watchdog once every second or so.
 +
 +
=== Starting the watchdog ===
 +
 +
If configured through [[STM32CubeMX]] the generated code will automatically start the watchdog.
 +
 +
=== Kicking (resetting) the watchdog ===
 +
 +
After the watchdog has been started it is essentially that it is being reset periodically.  We could do that for example in our main loop like this:
 +
 +
<pre>
 +
    uint32_t wdg_reset_interval = 1000;
 +
 +
    uint32_t loop_cnt = 0, now = 0, next_tick = 1000, next_wdg = 0;
 +
 +
    while (1) {
 +
 +
        now = uwTick;
 +
 +
        if (now > next_wdg) {
 +
 +
            HAL_IWDG_Refresh(&hiwdg); // Kick the watchdog!
 +
 +
            next_wdg = now + wdg_reset_interval;
 +
        }
 +
 +
        if (now > next_tick) {
 +
 +
            printf("Tick %lu (loop = %lu)\n", now / 1000, loop_cnt);
 +
 +
            loop_cnt = 0;
 +
            next_tick = now + 1000;
 +
        }
 +
 +
        ++loop_cnt;
 +
 +
        /* USER CODE END WHILE */
 +
 +
        /* USER CODE BEGIN 3 */
 +
    }
 +
</pre>
  
 
== Window Watchdog (WWDG) ==
 
== Window Watchdog (WWDG) ==

Revision as of 03:15, 4 November 2024

Angry Watchdog.jpg

From the point of view of embedded systems, a watchdog is a device which monitors a system and is able to restart the system if or when something goes wrong. STM32 MCUs are equipped with two watchdogs: the Independent Watchdog (IWDG) and a Window Watchdog (WWDG). These watchdogs will be described in the following sections.

Independent Watchdog (IWDG)

The IWDG is, as the name implies, an independent device which watches over the MCU. It can be illustrated like this:

Watchdog.gif

In the STM32, the IWDG is built-into the MCU itself, but it is still a completely independent device. So much in fact that it is not possible to read the counter value during run-time.

The overall principle is that the IWDG is configured with a certain prescaler and a certain counter value. It will then be started and once running it will expect a "kick" (reset) sometime before the counter reaches zero. IF the counter reaches zero, the MCU will simply be reset (you can query the reset cause).

Calculating Time

The IWDG is clocked from the internal (or external) low speed timer. By default, without an external crystal, this is running at 32000 kHz. The formula to calculate the reset time is like this:

IWDG Formula.png

The counter is a 12-bit value, so possible to configure from 1 - 4096 and the prescaler can be configured to: 4, 8, 16, 32, 64, 128 or 256.

The calculation therefore is quite simple. If we want, for example, a reset time of 1.5 seconds, we can calculate the counter value like this:

C = 1.5 * 32000 / 16 = 3000

Notice that by default the low speed oscillator is a simple built-in RC oscillator which is far less precise than a crystal would be. It is therefore important to leave some room for drive. If we set the reset time at 1.5 seconds, we should probably kick the watchdog once every second or so.

Starting the watchdog

If configured through STM32CubeMX the generated code will automatically start the watchdog.

Kicking (resetting) the watchdog

After the watchdog has been started it is essentially that it is being reset periodically. We could do that for example in our main loop like this:

    uint32_t wdg_reset_interval = 1000;

    uint32_t loop_cnt = 0, now = 0, next_tick = 1000, next_wdg = 0;

    while (1) {

        now = uwTick;

        if (now > next_wdg) {

            HAL_IWDG_Refresh(&hiwdg); // Kick the watchdog!

            next_wdg = now + wdg_reset_interval;
        }

        if (now > next_tick) {

            printf("Tick %lu (loop = %lu)\n", now / 1000, loop_cnt);

            loop_cnt = 0;
            next_tick = now + 1000;
        }

        ++loop_cnt;

        /* USER CODE END WHILE */

        /* USER CODE BEGIN 3 */
    }

Window Watchdog (WWDG)

To be added

STM32 Reset Cause

When experimenting with watchdogs, it could be a good idea to add a bit of code to display/check the reset cause on restart. The following was lifted off of a Stackoverflow Reply:

/// @brief  Possible STM32 system reset causes
typedef enum reset_cause_e
{
    RESET_CAUSE_UNKNOWN = 0,
    RESET_CAUSE_LOW_POWER_RESET,
    RESET_CAUSE_WINDOW_WATCHDOG_RESET,
    RESET_CAUSE_INDEPENDENT_WATCHDOG_RESET,
    RESET_CAUSE_SOFTWARE_RESET,
    RESET_CAUSE_POWER_ON_POWER_DOWN_RESET,
    RESET_CAUSE_EXTERNAL_RESET_PIN_RESET,
    RESET_CAUSE_BROWNOUT_RESET,
} reset_cause_t;

/// @brief      Obtain the STM32 system reset cause
/// @param      None
/// @return     The system reset cause
reset_cause_t reset_cause_get(void)
{
    reset_cause_t reset_cause;

    if (__HAL_RCC_GET_FLAG(RCC_FLAG_LPWRRST))
            {
        reset_cause = RESET_CAUSE_LOW_POWER_RESET;
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST))
            {
        reset_cause = RESET_CAUSE_WINDOW_WATCHDOG_RESET;
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST))
            {
        reset_cause = RESET_CAUSE_INDEPENDENT_WATCHDOG_RESET;
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST))
            {
        // This reset is induced by calling the ARM CMSIS
        // `NVIC_SystemReset()` function!
        reset_cause = RESET_CAUSE_SOFTWARE_RESET;
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST))
            {
        reset_cause = RESET_CAUSE_POWER_ON_POWER_DOWN_RESET;
    }
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST))
            {
        reset_cause = RESET_CAUSE_EXTERNAL_RESET_PIN_RESET;
    }
    // Needs to come *after* checking the `RCC_FLAG_PORRST` flag in order to
    // ensure first that the reset cause is NOT a POR/PDR reset. See note
    // below.
    else if (__HAL_RCC_GET_FLAG(RCC_FLAG_BORRST))
            {
        reset_cause = RESET_CAUSE_BROWNOUT_RESET;
    }
    else
    {
        reset_cause = RESET_CAUSE_UNKNOWN;
    }

    // Clear all the reset flags or else they will remain set during future
    // resets until system power is fully removed.
    __HAL_RCC_CLEAR_RESET_FLAGS();

    return reset_cause;
}

// Note: any of the STM32 Hardware Abstraction Layer (HAL) Reset and Clock
// Controller (RCC) header files, such as
// "STM32Cube_FW_F7_V1.12.0/Drivers/STM32F7xx_HAL_Driver/Inc/stm32f7xx_hal_rcc.h",
// "STM32Cube_FW_F2_V1.7.0/Drivers/STM32F2xx_HAL_Driver/Inc/stm32f2xx_hal_rcc.h",
// etc., indicate that the brownout flag, `RCC_FLAG_BORRST`, will be set in
// the event of a "POR/PDR or BOR reset". This means that a Power-On Reset
// (POR), Power-Down Reset (PDR), OR Brownout Reset (BOR) will trip this flag.
// See the doxygen just above their definition for the
// `__HAL_RCC_GET_FLAG()` macro to see this:
//      "@arg RCC_FLAG_BORRST: POR/PDR or BOR reset." <== indicates the Brownout
//      Reset flag will *also* be set in the event of a POR/PDR.
// Therefore, you must check the Brownout Reset flag, `RCC_FLAG_BORRST`, *after*
// first checking the `RCC_FLAG_PORRST` flag in order to ensure first that the
// reset cause is NOT a POR/PDR reset.

/// @brief      Obtain the system reset cause as an ASCII-printable name string
///             from a reset cause type
/// @param[in]  reset_cause     The previously-obtained system reset cause
/// @return     A null-terminated ASCII name string describing the system
///             reset cause
const char* reset_cause_get_name(reset_cause_t reset_cause)
{
    const char *reset_cause_name = "TBD";

    switch (reset_cause)
    {
    case RESET_CAUSE_UNKNOWN:
        reset_cause_name = "UNKNOWN";
        break;
    case RESET_CAUSE_LOW_POWER_RESET:
        reset_cause_name = "LOW_POWER_RESET";
        break;
    case RESET_CAUSE_WINDOW_WATCHDOG_RESET:
        reset_cause_name = "WINDOW_WATCHDOG_RESET";
        break;
    case RESET_CAUSE_INDEPENDENT_WATCHDOG_RESET:
        reset_cause_name = "INDEPENDENT_WATCHDOG_RESET";
        break;
    case RESET_CAUSE_SOFTWARE_RESET:
        reset_cause_name = "SOFTWARE_RESET";
        break;
    case RESET_CAUSE_POWER_ON_POWER_DOWN_RESET:
        reset_cause_name = "POWER-ON_RESET (POR) / POWER-DOWN_RESET (PDR)";
        break;
    case RESET_CAUSE_EXTERNAL_RESET_PIN_RESET:
        reset_cause_name = "EXTERNAL_RESET_PIN_RESET";
        break;
    case RESET_CAUSE_BROWNOUT_RESET:
        reset_cause_name = "BROWNOUT_RESET (BOR)";
        break;
    }

    return reset_cause_name;
}

We can now check the reset cause during startup:

    reset_cause_t reset_cause = reset_cause_get();
    printf("The system reset cause is \"%s\"\n", reset_cause_get_name(reset_cause));

Miscellaneous Links