Difference between revisions of "STM32 Watchdogs"
(21 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | [[Category:STM32]][[Category:STM32 Development]][[Category:STM32CubeMX]][[Category:STM32CubeIde]][[Category:STM32 HAL]]{{metadesc|Description of the STM32 watchdogs}}[[File: | + | [[Category:STM32]][[Category:STM32 Development]][[Category:STM32CubeMX]][[Category:STM32CubeIde]][[Category:STM32 HAL]]{{metadesc|Description of the STM32 watchdogs}}[[File:Watson_the_watchdog.jpg|thumb|300px]] |
− | + | 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]] [[MCU]]s are equipped with two watchdogs: the Independent Watchdog (IWDG) and a Window Watchdog (WWDG). These watchdogs will be described in the following sections. | |
{{clear}} | {{clear}} | ||
== Independent Watchdog (IWDG) == | == Independent Watchdog (IWDG) == | ||
− | The IWDG is, as the name implies, an independent device which watches over the MCU. | + | The IWDG is, as the name implies, an independent device which watches over the MCU. It can be illustrated like this: |
[[File:watchdog.gif|400px]] | [[File:watchdog.gif|400px]] | ||
+ | |||
+ | 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 [[#STM32 Reset Cause|reset cause]]). | ||
+ | |||
+ | === Calculating Time === | ||
+ | |||
+ | The [[IWDG]] is clocked from the internal low speed oscillator (LSI), which is running at 32000 kHz. | ||
+ | |||
+ | [[File:IWDG Clock.png|800px]] | ||
+ | |||
+ | It is worth noticing that even if the external low speed oscillator (LSE) is enabled, the internal oscillator (LSI) is still used for the [[IWDG]]. | ||
+ | |||
+ | The formula to calculate the reset time is like this: | ||
+ | |||
+ | [[File:IWDG Formula.png|600px]] | ||
+ | |||
+ | 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. | ||
+ | |||
+ | <div class="res-img"> | ||
+ | [[File:LSI Specs.png|800px]] | ||
+ | </div> | ||
+ | |||
+ | Repeating the calculation for the two extremes: | ||
+ | |||
+ | Tmax = (16 * 3000) / 17000 = 2.82 | ||
+ | |||
+ | Tmin = (16 * 3000) / 47000 = 1.02 | ||
+ | |||
+ | 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) == | ||
+ | |||
+ | The Window Watchdog (WWDG) is somewhat different to the IWDG. In the [https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf reference manual] it is shown like this: | ||
+ | |||
+ | <div class="res-img"> | ||
+ | [[File:STM32F405 WWDG.png|800px]] | ||
+ | </div> | ||
+ | |||
+ | Firstly it is worth noticing that it is clocked from the PCLK1 bus - which by default is running at 42 MHz. This clock is then divided by a 7 bit pre-scaler and feeds a 7 bit downcounter. | ||
+ | |||
+ | == 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 [https://stackoverflow.com/questions/34196663/stm32-how-to-get-last-reset-status Stackoverflow Reply]: | ||
+ | |||
+ | <pre> | ||
+ | /// @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; | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | We can now check the reset cause during startup: | ||
+ | |||
+ | <pre> | ||
+ | reset_cause_t reset_cause = reset_cause_get(); | ||
+ | printf("The system reset cause is \"%s\"\n", reset_cause_get_name(reset_cause)); | ||
+ | </pre> | ||
+ | |||
+ | == Miscellaneous Links == | ||
+ | |||
+ | * [https://stackoverflow.com/questions/34196663/stm32-how-to-get-last-reset-status How to check STM32 reset cause] |
Latest revision as of 08:59, 13 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 low speed oscillator (LSI), which is running at 32000 kHz.
It is worth noticing that even if the external low speed oscillator (LSE) is enabled, the internal oscillator (LSI) is still used for the IWDG.
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.
Repeating the calculation for the two extremes:
Tmax = (16 * 3000) / 17000 = 2.82
Tmin = (16 * 3000) / 47000 = 1.02
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)
The Window Watchdog (WWDG) is somewhat different to the IWDG. In the reference manual it is shown like this:
Firstly it is worth noticing that it is clocked from the PCLK1 bus - which by default is running at 42 MHz. This clock is then divided by a 7 bit pre-scaler and feeds a 7 bit downcounter.
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));