I’ve been meaning to look into the SparkFun Artemis’ Watchdog Timer (WDT) functionality for some time now, so I decided to spend the afternoon diving into what’s actually involved. The am_hal_wdt.h, am_hal_wdt.c files from the AmbiqSuite SDK were very useful in getting started, as was the watchdog.c example that’s provided. I didn’t find it to be overly complicated, though there were still a few roadblocks along the way.
The code I’ve included below is based on the watchdog.c example and demonstrates some of the basic functionality of the Artemis’ WDT.
While learning more about the Artemis’ WDT, some of the things of note I noted were:
The WDT runs off the Low Frequency RC Oscillator (LFRC) at a default value of 128 Hz, which provides a maximum interrupt and reset timeout of 2 seconds. However, the LFRC frequency can be decreased to 16 Hz (max timeout = ~16 s), 1 Hz (max timeout = 255 s) or 1/16 Hz (max timeout = 4080 s).
Similar to the Early Warning interrupt flag of the SAMD21 Watchdog Timer, you can program the Artemis’ WDT interrupt counter and reset counters independently to achieve different timeout periods. This is demonstrated in the code below (i.e. interrupt timeout occurs after 5 seconds and reset timeout occurs after 10 seconds).
The am_hal_wdt_config_t Watchdog timer configuration structure is where the WDT is configured. I’m not entirely sure why Ambiq indicates that the interrupt and reset counters (ui16InterruptCount & ui16ResetCount) are 16-bit unsigned integers, when they are in fact both 8-bit registers (INTVAL & RESVAL). When calculating your timeout periods, ensure that your “ticks” don’t exceed 255.
// Global variables
volatile uint8_t watchdogCounter = 0; // Watchdog interrupt counter
uint32_t resetStatus = 0; // Reset status register
// Watchdog timer configuration structure.
am_hal_wdt_config_t g_sWatchdogConfig = {
// Configuration values for generated watchdog timer event.
.ui32Config = AM_HAL_WDT_LFRC_CLK_16HZ | AM_HAL_WDT_ENABLE_RESET | AM_HAL_WDT_ENABLE_INTERRUPT,
// Number of watchdog timer ticks allowed before a watchdog interrupt event is generated.
.ui16InterruptCount = 80, // Set WDT interrupt timeout for 10 seconds (80 / 16 = 5).
// Number of watchdog timer ticks allowed before the watchdog will issue a system reset.
.ui16ResetCount = 240 // Set WDT reset timeout for 15 seconds (240 / 16 = 15).
};
void setup(void) {
Serial.begin(115200);
//while (!Serial);
delay(1000);
Serial.println("Artemis Watchdog Example");
Serial.print("Interrupt Count = "); Serial.print(g_sWatchdogConfig.ui16InterruptCount ); Serial.println(" ticks");
Serial.print("Reset Count = "); Serial.print(g_sWatchdogConfig.ui16ResetCount); Serial.println(" ticks");
// (Note: See am_hal_reset.h for RESET status structure)
am_hal_reset_status_t sStatus;
// Print out reset status register.
// (Note: Watch Dog Timer reset = 0x40)
am_hal_reset_status_get(&sStatus);
resetStatus = sStatus.eStatus;
char rStatus[3];
sprintf(rStatus, "Reset Status Register = 0x%x", resetStatus);
Serial.println(rStatus);
// Set the clock frequency.
am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_SYSCLK_MAX, 0);
// Set the default cache configuration
am_hal_cachectrl_config(&am_hal_cachectrl_defaults);
am_hal_cachectrl_enable();
// Configure the board for low power operation.
am_bsp_low_power_init();
// Clear reset status register for next time we reset.
am_hal_reset_control(AM_HAL_RESET_CONTROL_STATUSCLEAR, 0);
// LFRC must be turned on for this example as the watchdog only runs off of the LFRC.
am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_LFRC_START, 0);
// Configure the watchdog.
am_hal_wdt_init(&g_sWatchdogConfig);
// Enable the interrupt for the watchdog in the NVIC.
NVIC_EnableIRQ(WDT_IRQn);
am_hal_interrupt_master_enable();
// Enable the watchdog.
am_hal_wdt_start();
}
void loop(void) {
// Disable interrupts.
am_hal_interrupt_master_disable();
// Turn OFF the indicator LED.
digitalWrite(LED_BUILTIN, LOW);
// Go to deep sleep.
am_hal_sysctrl_sleep(AM_HAL_SYSCTRL_SLEEP_DEEP);
// An interrupt woke us up so now enable them and take it.
am_hal_interrupt_master_enable();
// Turn ON the indicator LED.
digitalWrite(LED_BUILTIN, HIGH);
Serial.println(watchdogCounter);
delay(10);
}
// Interrupt handler for the watchdog.
extern "C" void am_watchdog_isr(void) {
// Clear the watchdog interrupt.
am_hal_wdt_int_clear();
// Catch the first four watchdog interrupts, but let the fifth through untouched.
if ( watchdogCounter < 4 ) {
// Restart the watchdog.
am_hal_wdt_restart(); // "Pet" the dog.
}
else {
digitalWrite(LED_BUILTIN, HIGH); // Indicator that a reset will occur.
while(1);
}
// Increment the number of watchdog interrupts.
watchdogCounter++;
}
Keen to hear if anyone else has been working with the Artemis’ WDT! I’d really like to get it working with the low power examples that Nathan wrote, but that’s a challenge for another day.
Thanks a bunch for taking the time to detail that, it was a big help for me in getting the watchdog timer into my project and working nicely. I’ve incorporated a bastardised (sorry) version of your code and some more about making various types of reset (and finding out what they were) into an example which for the record is part of a pending pull request https://github.com/sparkfun/Arduino_Apollo3/pull/237
As such, I won’t include the code here, or at least not until any potential edits to the pull request are finalised.
But with regards to sleeping and the watchdog timer, I’m using a call to am_hal_wdt_halt() to disable the WDT just before going to sleep, and then re-intialising it on wake. Works great! I’m using a timer or GPIO interrupt to wake the processor as per the examples. I figured not much point in going for a WD interrupt while sleeping, especially since my sleep periods are longer than the WDT limits anyway.
Thanks for your reply! Great to see someone else working with the WDT.
I’m really keen to see a WDT library added to the Apollo Core. I had started writing one myself but unfortunately haven’t had the time to complete it.
For my real-world use cases, I’ll use the WDT alongside the RTC’s deep sleep to ensure that the system doesn’t freeze. I incorporate flags in the WDT and RTC ISRs and place all my code within nested if statements that checks for WDT or RTC interrupt triggers. Simple, but effective!
void loop() {
if (wdtFlag) {
if (rtcFlag) {
# Wake up and perform actions
rtcFlag = false;
}
wdtFlag = false;
}
goToSleep();
}
Ah, I hadn’t appreciated using the WDT against the possibility of a freeze in, or non-recovery from deep sleep. I use sleep as an “off” mode (albeit without powering down SRAM) with wake only triggered by a GPIO (“On” button) interrupt, as well as with the timer wake between logging data, and haven’t had any problems as far as I know (with millions of sleep recoveries, and 20+ Artemis-based logging devices, many deployed for months at a time).
Still, wouldn’t hurt to let the WDT wake the processor briefly every now and again. The reset elements of the new example in my pull request are the result of implementing reset recovery in my loggers - so if there’s a problem with initialising hardware or something (this issue explains how I was getting unrecoverable I2C bus crashes https://github.com/sparkfun/Arduino_Apollo3/issues/172), save the current state to flash/SD, do a software reset and then resume. I also added code to deliberately reset every 1000 log cycles just in case there’s something unforeseen happening, like a memory leak. The WDT would save that, might even be a better way of doing it, but I can’t be bothered changing for the moment.
While the WDT library will, for the most part, simply be a wrapper of the Ambiq HAL, I believe there’s a lot that can be done to reduce the complexity of configuring the Watchdog. This is the challenge I’m currently facing: how to intuitively allow the user to pass values to the Watchdog timer configuration structure. Thinking of my own needs, I would appreciate having full control over which WDT Clock Divider is used as well as the number of interrupts and reset ticks. Though, I realize there are issues with the periods not being accurate.
Please let me know if you have any experience with passing values into a structure within a library. I’m also planning on breaking down your WDT example into multiple examples for the WDT library.
Awesome work Adam, I will check it out tomorrow. Yep, break away at the example, that’s what it’s for.
I don’t have my head in the WDT space right now, but in general terms it’s always nice to have a simple version for users who are happy with the defaults and complex one for users who want to tweak parameters. (although complex users may be happy to play with HAL directly). But have you considered overloaded functions, or functions with default argument values?
I’ve been using this a bit lately with upgrading functions to take more parameters, or where I can rely on the defaults. You’re probably already aware of them, otherwise worth a bit of research.
Similar to what you suggested, the library has a default configuration for the WDT, which makes use of the HAL structure. I then wrote a number of additional functions that write directly to the registers to allow the user to configure the WDT. No structures, addresses or pointers needed!