diff --git a/libraries/IWatchdog/README.md b/libraries/IWatchdog/README.md new file mode 100644 index 0000000000..65bd3cf887 --- /dev/null +++ b/libraries/IWatchdog/README.md @@ -0,0 +1,136 @@ +## IWatchdog Library V1.0.0 for stm32 Arduino core + +**Written by:** _Venelin Efremov_ and _Frederic Pillon_ + +### Requirement +* [Arduino_Core_STM32](https://github.com/stm32duino/Arduino_Core_STM32) version > 1.3.0 + +### What is the IWatchdog library. + +Th IWatchdog library provides an interface to the independent watchdog module (IWDG) inside STM32 chips. +The IWDG module is used in production systems to generate a reset signal to the CPU in case some +catastrophic event causes the software to become "stuck" or unresponsive. + +The IWDG module contains a count-down timer. The module would generate a reset condition when the timer +reaches zero. In normal operation mode the software running on the CPU would reload the timer periodically to +prevent the reset condition from happening. However if a software bug or other error causes the CPU to +execute a different code path for too long, the reload would not happen and the IWDG module would reset the CPU. + +### How to use it +The IWatchdog is a built-in library included with the STM32 core package. To add its functionality to your sketch +you will need to reference the library header file. It is done by adding: `#include ` + +```Arduino +#include + +void setup() { + ... + // Initialize the IWDG with 4 seconds timeout. + // This would cause a CPU reset if the IWDG timer + // is not reloaded in approximately 4 seconds. + IWatchdog.begin(4000000); +} + +void loop() { + ...your code here... + // make sure the code in this loop is executed in + // less than 2 seconds to leave 50% headroom for + // the timer reload. + IWatchdog.reload(); +} + +``` + +### Library functions + +#### Preinstantiate Object + +A default instance is available: `IWatchdog` + +```Arduino +IWatchdogClass IWatchdog = IWatchdogClass(); +``` + +#### Predefined values + + * Minimal timeout in microseconds: `IWDG_TIMEOUT_MIN` + * Maximal timeout in microseconds: `IWDG_TIMEOUT_MAX` + +#### `void begin(uint32_t timeout, uint32_t window = IWDG_TIMEOUT_MAX)` + +The `begin()` function would initialize the IWDG hardware block. + +The `timeout` parameter is in microseconds and set the timer reset timeout. +When the timer reaches zero the hardware block would generate a reset signal +for the CPU. + +When specifying timeout value plan to refresh the timer at least twice +as often. The `reload()` operation is not expensive. + +The downside of selecting a very large timeout value is that your system +may be left in a "stuck" state for too long, before the reset is +generated. + +Valid timeout values depends of the LSI clock. Typically, it is 32kH value are between +125µs and 32,768ms (~32.8 seconds). The precision depends of the timeout values: + + | timeout value range | timeout value precision | + | ------------------- |:-----------------------:| + | 125µs - 512ms | 125µs + | 513ms - 1024ms | 250µs + | 1025ms - 2048ms | 500µs + | 2049ms - 4096ms | 1ms + | 4097ms - 8192ms | 2ms + | 8193ms - 16384ms | 4ms + | 16385ms - 32768ms | 8ms + +The optional `window` parameter is in microseconds and must be less than `timeout`. +If the window option is enabled, the counter must be refreshed inside the window; +otherwise, a system reset is generated. + +**Note:** +Window feature is not available for all STM32 series. + +Calling the `begin()` method with value outside of the valid range +would return without initializing the watchdog timer. + +**WARNING:** +*Once started the IWDG timer can not be stopped. If you are +planning to debug the live system, the watchdog timer may cause the +system to be reset while you are stopped in the debugger. Also consider +the iwatchdog timer implications if you are designing a system which puts +the CPU in sleep mode.* + +#### `void reload()` + +The `reload()` method reloads the counter value. + +Once you have initialized the IWDG you **HAVE** to call `reload()` +periodically to prevent the CPU being reset. + +#### `void set(uint32_t timeout, uint32_t window = IWDG_TIMEOUT_MAX)` + +The `set()` method allows to set the timeout and window values. + +The `timeout` and optional `window` parameters are the same than `begin()` method. + +#### `void get(uint32_t* timeout, uint32_t* window = NULL)` + +The `get()` method allows to get the current timeout and window values +currently set. + +The `timeout` and optional `window` pointers to get values are in microseconds. + +#### `bool isEnabled()` + +The `isEnabled()` method returns status of the IWDG block. If enabled or not. + +#### `bool isReset(bool clear)` + +The `isReset()` method checks if the system has resumed from IWDG reset. + +The optional `clear` parameter allow to clear IWDG reset flag if true. Default: false. + +#### `void clearReset()` + +The `clearReset()` method clears IWDG reset flag. diff --git a/libraries/IWatchdog/examples/IWDG_Button/IWDG_Button.ino b/libraries/IWatchdog/examples/IWDG_Button/IWDG_Button.ino new file mode 100644 index 0000000000..51d69adafb --- /dev/null +++ b/libraries/IWatchdog/examples/IWDG_Button/IWDG_Button.ino @@ -0,0 +1,85 @@ +/* + IWatchdog + Button + + This example code is in the public domain. + + The code demonstrates the use of a independent watchdog timer. + The watchdog timer is initialized with timeout of 10 seconds. + Every time the button is pressed, the watchdog timer is reset. + If left unattended the system would reset itself about every 10 seconds. + + You would have to keep pressing the button frequently (< 10 seconds apart) + to prevent the system from reseting itself. + + You would recognize the reset condition when the LED blinks few times quickly. + + This is not a practical example, in real code you would reset the watchdog + timer in the main loop without requiring user input. + + The code is modified version of the code from: + http://www.arduino.cc/en/Tutorial/Button +*/ + +#include + +#ifdef USER_BTN +const int buttonPin = USER_BTN; +#else +const int buttonPin = 2; +#endif + +#ifdef LED_BUILTIN +const int ledPin = LED_BUILTIN; +#else +const int ledPin = 13; +#endif + +static int default_buttonState = LOW; + +void setup() { + pinMode(ledPin, OUTPUT); + pinMode(buttonPin, INPUT); + + if (IWatchdog.isReset(true)) { + // LED blinks to indicate reset + for (uint8_t idx = 0; idx < 5; idx++) { + digitalWrite(ledPin, HIGH); + delay(100); + digitalWrite(ledPin, LOW); + delay(100); + } + } + + // Read default state of the pushbutton + default_buttonState = digitalRead(buttonPin); + + // Init the watchdog timer with 10 seconds timeout + IWatchdog.begin(10000000); + // or with a 2 seconds window + // IWatchdog.begin(10000000, 2000000); + + if (!IWatchdog.isEnabled()) { + // LED blinks indefinitely + while (1) { + digitalWrite(ledPin, HIGH); + delay(500); + digitalWrite(ledPin, LOW); + delay(500); + } + } +} + +void loop() { + // Compare current button state of the pushbutton value: + if (digitalRead(buttonPin) == default_buttonState) { + digitalWrite(ledPin, LOW); + } else { + digitalWrite(ledPin, HIGH); + + // Uncomment to change timeout value to 6 seconds + //IWatchdog.set(6000000); + + // Reload the watchdog only when the button is pressed + IWatchdog.reload(); + } +} diff --git a/libraries/IWatchdog/keywords.txt b/libraries/IWatchdog/keywords.txt new file mode 100644 index 0000000000..2647aa1c88 --- /dev/null +++ b/libraries/IWatchdog/keywords.txt @@ -0,0 +1,29 @@ +####################################### +# Syntax Coloring Map For IWatchdog +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +IWatchdog KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +set KEYWORD2 +get KEYWORD2 +reload KEYWORD2 +isEnabled KEYWORD2 +isReset KEYWORD2 +clearReset KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +IWDG_TIMEOUT_MIN LITERAL1 +IWDG_TIMEOUT_MAX LITERAL1 +IS_IWDG_TIMEOUT LITERAL1 diff --git a/libraries/IWatchdog/library.properties b/libraries/IWatchdog/library.properties new file mode 100644 index 0000000000..1f45ef1d2d --- /dev/null +++ b/libraries/IWatchdog/library.properties @@ -0,0 +1,9 @@ +name=IWatchdog +version=1.0.0 +author=Venelin Efremov, Frederic Pillon +maintainer=stm32duino +sentence=Enables support for independent watchdog (IWDG) hardware on STM32 processors. +paragraph=Independent watchdog (IWDG) is a hardware timer on the chip which would generate a reset condition if the time is not reloaded withing the specified time. It is generally used in production systems to reset the system if the CPU becomes "stuck". +category=Timing +url= +architectures=stm32 diff --git a/libraries/IWatchdog/src/IWatchdog.cpp b/libraries/IWatchdog/src/IWatchdog.cpp new file mode 100644 index 0000000000..4316009f19 --- /dev/null +++ b/libraries/IWatchdog/src/IWatchdog.cpp @@ -0,0 +1,165 @@ +#include "IWatchdog.h" +#include "stm32yyxx_ll_iwdg.h" +#include "stm32yyxx_ll_rcc.h" + +// Initialize static variable +bool IWatchdogClass::_enabled = false; + +/** + * @brief Enable IWDG, must be called once + * @param timeout: value in microseconds + * @param window: optional value in microseconds + * Default: IWDG_TIMEOUT_MAX + * @retval None + */ +void IWatchdogClass::begin(uint32_t timeout, uint32_t window) +{ + if(!IS_IWDG_TIMEOUT(timeout)) { + return; + } + + // Enable the peripheral clock IWDG + LL_RCC_LSI_Enable(); + while (LL_RCC_LSI_IsReady() != 1) { + } + + // Enable the IWDG by writing 0x0000 CCCC in the IWDG_KR register + LL_IWDG_Enable(IWDG); + _enabled = true; + + set(timeout, window); +} + +/** + * @brief Set the timeout and window values + * @param timeout: value in microseconds + * @param window: optional value in microseconds + * Default: IWDG_TIMEOUT_MAX + * @retval None + */ +void IWatchdogClass::set(uint32_t timeout, uint32_t window) +{ + if((isEnabled()) && (!IS_IWDG_TIMEOUT(timeout))) { + return; + } + + // Compute the prescaler value + uint16_t div = 0; + uint8_t prescaler = 0; + uint32_t reload = 0; + + // Convert timeout to seconds + float t_sec = (float)timeout / 1000000 * LSI_VALUE; + + do { + div = 4 << prescaler; + prescaler++; + } while ((t_sec / div) > IWDG_RLR_RL); + + // 'prescaler' value is one of the LL_IWDG_PRESCALER_XX define + if(--prescaler > LL_IWDG_PRESCALER_256) { + return; + } + reload = (uint32_t)(t_sec / div) - 1; + + // Enable register access by writing 0x0000 5555 in the IWDG_KR register + LL_IWDG_EnableWriteAccess(IWDG); + // Write the IWDG prescaler by programming IWDG_PR from 0 to 7 + // LL_IWDG_PRESCALER_4 (0) is lowest divider + LL_IWDG_SetPrescaler(IWDG, (uint32_t)prescaler); + // Write the reload register (IWDG_RLR) + LL_IWDG_SetReloadCounter(IWDG, reload); + +#ifdef IWDG_WINR_WIN + if((window != IWDG_TIMEOUT_MAX) && + (LL_IWDG_GetWindow(IWDG) != IWDG_WINR_WIN)) { + if (window >= timeout) { + // Reset window value + reload = IWDG_WINR_WIN; + } else { + reload = (uint32_t)(((float)window / 1000000 * LSI_VALUE) / div) - 1; + } + LL_IWDG_SetWindow(IWDG, reload); + } +#else + UNUSED(window); +#endif + + // Wait for the registers to be updated (IWDG_SR = 0x0000 0000) + while (LL_IWDG_IsReady(IWDG) != 1) { + } + + // Refresh the counter value with IWDG_RLR (IWDG_KR = 0x0000 AAAA) + LL_IWDG_ReloadCounter(IWDG); +} + +/** + * @brief Get the current timeout and window values + * @param timeout: pointer to the get the value in microseconds + * @param window: optional pointer to the get the value in microseconds + * @retval None + */ +void IWatchdogClass::get(uint32_t* timeout, uint32_t* window) +{ + if(timeout != NULL) { + uint32_t prescaler = 0; + uint32_t reload = 0; + uint32_t win = 0; + float base = (1000000.0 / LSI_VALUE); + + while(LL_IWDG_IsActiveFlag_RVU(IWDG)); + reload = LL_IWDG_GetReloadCounter(IWDG); + + while(LL_IWDG_IsActiveFlag_PVU(IWDG)); + prescaler = LL_IWDG_GetPrescaler(IWDG); + + // Timeout given in microseconds + *timeout = (uint32_t)((4 << prescaler) * (reload + 1) * base); +#ifdef IWDG_WINR_WIN + if(window != NULL) { + while(LL_IWDG_IsActiveFlag_WVU(IWDG)); + win = LL_IWDG_GetWindow(IWDG); + *window = (uint32_t)((4 << prescaler) * (win + 1) * base); + } +#else + UNUSED(window); +#endif + } +} + +/** + * @brief Reload the counter value with IWDG_RLR (IWDG_KR = 0x0000 AAAA) + * @retval None + */ +void IWatchdogClass::reload(void) +{ + if(isEnabled()) { + LL_IWDG_ReloadCounter(IWDG); + } +} + +/** + * @brief Check if the system has resumed from IWDG reset + * @param clear: if true clear IWDG reset flag. Default false + * @retval return reset flag status + */ +bool IWatchdogClass::isReset(bool clear) +{ + bool status = LL_RCC_IsActiveFlag_IWDGRST(); + if (status && clear) { + clearReset(); + } + return status; +} + +/** + * @brief Clear IWDG reset flag + * @retval None + */ +void IWatchdogClass::clearReset(void) +{ + LL_RCC_ClearResetFlags(); +} + +// Preinstantiate Object +IWatchdogClass IWatchdog = IWatchdogClass(); diff --git a/libraries/IWatchdog/src/IWatchdog.h b/libraries/IWatchdog/src/IWatchdog.h new file mode 100644 index 0000000000..f3a475f53c --- /dev/null +++ b/libraries/IWatchdog/src/IWatchdog.h @@ -0,0 +1,30 @@ +#ifndef __IWATCHDOG_H__ +#define __IWATCHDOG_H__ + +#include "Arduino.h" + +// Minimal timeout in microseconds +#define IWDG_TIMEOUT_MIN ((4*1000000)/LSI_VALUE) +// Maximal timeout in microseconds +#define IWDG_TIMEOUT_MAX (((256*1000000)/LSI_VALUE)*IWDG_RLR_RL) + +#define IS_IWDG_TIMEOUT(X) (((X) >= IWDG_TIMEOUT_MIN) &&\ + ((X) <= IWDG_TIMEOUT_MAX)) + +class IWatchdogClass { + +public: + void begin(uint32_t timeout, uint32_t window = IWDG_TIMEOUT_MAX); + void set(uint32_t timeout, uint32_t window = IWDG_TIMEOUT_MAX); + void get(uint32_t* timeout, uint32_t* window = NULL); + void reload(void); + bool isEnabled(void) {return _enabled;}; + bool isReset(bool clear = false); + void clearReset(void); + +private: + static bool _enabled; +}; + +extern IWatchdogClass IWatchdog; +#endif /* __IWATCHDOG_H__ */