Ruuvi Firmware – Part 15: Watchdog

Ruuvi Firmware series part 15 intro image

In this part of the tutorial we’ll setup a watchdog timer to reset our tag if the program “freezes”. Final code of this blog post can be downloaded at Ruuvi GitHub in the ruuviblog-branch, tag 3.15.0-alpha.

Please follow part 1 of the series for details on how to clone the repository and compile the code. Final hex of this tutorial can be downloaded from the Ruuvi Jenkins.

Watchdog Timer

“A watchdog timer (WDT) is a hardware timer that automatically generates a system reset if the main program neglects to periodically service it. It is often used to automatically reset an embedded device that hangs because of a software or hardware fault.” — MBED

As our program grows in complexity, so does the number of of bugs in the firmware. Even if we made perfect code some external influence such as a voltage droop or extreme Near Field Communication (NFC) field might cause our program to lock up.

Even worse than just not functioning, a tag which has frozen up might consume its battery within days. Watchdog is our last line of defence against such errors.

Watchdog Interface

Simple is good. When it comes to watchdog, we want to keep the complexity at the extreme minimum. We define two functions: Initialization and feed. Initialization takes the number of milliseconds before watchdog timer causes a reset as a parameter, and feed has no parameter at all — it only resets the watchdog timer.

/**
 * Initializes watchdog module.
 * After initialization watchdog must be fed at given interval or the program will reset.
 * There is not way to uninitialize the watchdog.
 * Consider bootloader watchdog interval on setup.
 *
 * parameter interval: how often the watchdog should be fed.
 *
 * Return RUUVI_DRIVER_SUCCESS on success, error code on failure.
 */
ruuvi_driver_status_t ruuvi_interface_watchdog_init(uint32_t interval);

/**
 * "Feed" the watchdog, resets the watchdog timer.
 * This must be called after watchdog initialization or the program will reset.
 */
ruuvi_driver_status_t ruuvi_interface_watchdog_feed(void);

View raw

Watchdog Driver

The implementation of interface functions is easy enough: wrap calls to the Nordic SDK.

nrf_drv_wdt_channel_id m_channel_id;

/**
 * @brief WDT events handler.
 */
void wdt_event_handler(void)
{
    // NOTE: The max amount of time we can spend in WDT interrupt is two cycles of 32768[Hz] clock
    // - after that, reset occurs
}

/**
 * Initializes watchdog module.
 * After initialization watchdog must be fed at given interval or the program will reset.
 * There is not way to uninitialize the watchdog.
 * Consider bootloader watchdog interval on setup.
 *
 * parameter interval: how often the watchdog should be fed.
 *
 * Return RUUVI_DRIVER_SUCCESS on success, error code on failure.
 */
ruuvi_driver_status_t ruuvi_interface_watchdog_init(uint32_t interval)
{
  uint32_t err_code = NRF_SUCCESS;
  nrf_drv_wdt_config_t config = NRF_DRV_WDT_DEAFULT_CONFIG;
  config.reload_value = interval;
  err_code = nrf_drv_wdt_init(&config, wdt_event_handler);
  err_code = nrf_drv_wdt_channel_alloc(&m_channel_id);
  nrf_drv_wdt_enable();
  return ruuvi_platform_to_ruuvi_error(&err_code);
}

/**
 * "Feed" the watchdog, resets the watchdog timer.
 * This must be called after watchdog initialization or the program will reset.
 */
ruuvi_driver_status_t ruuvi_interface_watchdog_feed(void)
{
  nrf_drv_wdt_channel_feed(m_channel_id);
  return RUUVI_DRIVER_SUCCESS;
}

View raw

One important gotcha is the interrupt priority level — if the program hangs in a higher or same priority than our watchdog interrupt the interrupt would not be executed. We’ll pick level 2, which is highest interrupt available for the application, and move the bluetooth radio activity interrupt down to level 3.

// <e> NRFX_WDT_ENABLED - nrfx_wdt - WDT peripheral driver
//==========================================================
#ifndef NRFX_WDT_ENABLED
#define NRFX_WDT_ENABLED 1
#endif
// <o> NRFX_WDT_CONFIG_BEHAVIOUR  - WDT behavior in CPU SLEEP or HALT mode

// <1=> Run in SLEEP, Pause in HALT
// <8=> Pause in SLEEP, Run in HALT
// <9=> Run in SLEEP and HALT
// <0=> Pause in SLEEP and HALT

#ifndef NRFX_WDT_CONFIG_BEHAVIOUR
#define NRFX_WDT_CONFIG_BEHAVIOUR 1
#endif

// Default WDT reset interval in ms
#ifndef NRFX_WDT_CONFIG_RELOAD_VALUE
#define NRFX_WDT_CONFIG_RELOAD_VALUE 12000
#endif

#ifndef NRFX_WDT_CONFIG_IRQ_PRIORITY
#define NRFX_WDT_CONFIG_IRQ_PRIORITY 2
#endif

View raw

Testing

We won’t be making a separate task_watchdog, instead we initialize the watchdog at boot by default. If we run unit tests the watchdog is initialized after the unit tests are complete as the tests can consume quite a long time.

For the purposes of the test we feed the watchdog on button press only — if the button is not pressed every 12 seconds the tag will reset.

FIFO reads from our previous version

We can see the FIFO reads from our previous version being executed, with last printed timestamp being at 11777 ms. Then the tag is reset and self-tests run again. This time we feed the watchdog twice, our tag does not reset until we let the watchdog time expire.

Power Profiling

In the last part of the series we were left with a bit high consumption of 29.4 μA, let’s see how our profile is affected by the watchdog timer.

Power profile affected by the watchdog timer

Consumption is now 29.7 μA, 0.3 μA higher than before. This is within margin of error for the measurement, in any case we can conclude that the increase in power consumption is minor.

Conclusion

We now have added watchdog functionality to our tags, further improving the reliability of RuuviTags in the field.

Stay tuned and follow @ojousima and @ruuvicom on Twitter for #FirmwareFriday posts!