Difference between revisions of "STM32 Watchdogs"
Line 10: | Line 10: | ||
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. | 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 [[]]). | + | 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 [[#STM32 Reset Cause|reset cause]]). |
=== Calculating Time === | === Calculating Time === |
Revision as of 03:11, 4 November 2024
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:
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:
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.
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));