看门狗是保障嵌入式系统正常运行必不可少的措施之一,能够在系统应为硬件设计缺陷、软件设计权限或者外部电磁干扰导致系统异常的情况下,及时复位系统正常运行。
从STM32C0系列MCU的系统架构图中可以了解到,系统提供了两个看门狗:
其分别为IWDG(独立看门狗)和WWDG(窗口看门狗)。
这篇帖子分享的是独立看门狗IWDG使用。
一、硬件了解
在系统手册中,有关于IWDG和WWDG具体功能说明:
从其中可以了解到IWDG的框图:
IWDG是由专门的低速总线进行驱动,即上图中的时钟频率为32kHz的LSI总线,它的时钟和系统主时钟是分离的,在系统主时钟出现故障的情况下,它仍然可以工作,从而可以额外为系统的正常运行提供一重保障。
IWDG一旦设置好以后,就能够独立于系统主程序之外运行,完全不会受到系统主程序的干扰制约。不过因为其驱动总线时钟频率仅为32K,所以适合对时间精度要求低的场合。
二、IWDG寄存器了解
要使用IWDG独立看门狗,主要就是操作其对应的寄存器。
从手册可以了解到,几个关键的寄存器:
1. 键寄存器:
像写入不同的值,代表不同的功能:
- 向该寄存器写入0xCCCC用于启动watchdog
- 系统主程序部分,需要定时向该寄存器写入0xAAAA,否则当计数器为0时,watchdog会复位系统
- 向该寄存器写入0x555,表示允许访问IWDG_PR和IWDG_RLR。这两个寄存器随后会说明
2. 预分频寄存器:
其低三位,表示分频值。
3. 重装载寄存器
这个寄存器的值,表示IWDG的watchdog计数器重载时的值。一旦向IWDG_KR写入0xAAAA,则会将该值传送到watchdog计数器,进行计数。
4. IWDG窗口寄存器
三、喂狗
在主程序运行过程中,向键值寄存器IWDG_KR中写入0xAAAA, 自动重装载寄存器IWDG_RLR中的值就会重新加载到计数器,watchdog就会重新开始计数,从而避免产生看门狗复位操作。
如果程序运行异常,就无法正常向键值寄存器IWDG_KR中写入0xAAAA,从而在watchdog超时时,系统自动复位。
向键值寄存器IWDG_KR中写入0xAAAA让计数器重载的的操作,就是俗称的喂狗。只有每间隔一定周期时间喂狗,watchdog才不会超时触发复位。如果主程序一直稳定运行,那么就能一直喂狗。
四、IWDG超时时间
IWDG计数器溢出时间计算公示为: Tout=((4*2^prer)*rlr)/32 (ms)
prer:预分频系数:0~7(只有低3位有效),由上述预分频寄存器(IWDG_PR)设置。
rlr:重载值:0~4095(低11位有效),由上述重装载寄存器(IWDG_RLR)设置。
根据上述公式,以及数据手册中的说明,具体的超时时间对照表如下:
有了上述的公示和对照表,就可以根据实际情况,合理的设置预分频系数和重载值,来实现实际需要的看门口超时时间。
五、zephyr中对IWDG的支持
在zephyr中,提供了对STM32C0的IWDG的支持,具体可见 zephyr/boards/arm/nucleo_c031c6/nucleo_c031c6.dts:
在代码中调用时,调用wdt_install_timeout()进行设置,实际调用iwdg_stm32_install_timeout进行处理,具体可见zdphyr/drivers/watchdog/wdt_iwdg_stm32.c:
在传入的config中,提供window.max设置,就会据此计算timeout,window.max * USEC_PER_MSEC (每秒的微秒数) 得到timeout(ms)。
然后调用iwdg_stm32_convert_timeout()自动计算需要进行设置的prescaler和reload进行设置。
在程序中,使用如下的调用,进行具体的设置:
上述设置,对应超时时间为5s。
六、IWDG实际使用
在 zephyr/samples/drivers/watchdog中,提供了看门狗的基础使用。
在此基础上,添加一个通过USR按键来喂狗的处理,具体的代码如下:
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/sys/printk.h>
#include <stdbool.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/util.h>
#include <inttypes.h>
/*
* Get button configuration from the devicetree sw0 alias. This is mandatory.
*/
#define SW0_NODE DT_ALIAS(sw0)
#if !DT_NODE_HAS_STATUS(SW0_NODE, okay)
#error "Unsupported board: sw0 devicetree alias is not defined"
#endif
static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET_OR(SW0_NODE, gpios,
{0});
static struct gpio_callback button_cb_data;
/*
* The led0 devicetree alias is optional. If present, we'll use it
* to turn on the LED whenever the button is pressed.
*/
static struct gpio_dt_spec led = GPIO_DT_SPEC_GET_OR(DT_ALIAS(led0), gpios,
{0});
uint32_t counter = 0;
int wdt_channel_id;
const struct device *const wdt = DEVICE_DT_GET(DT_ALIAS(watchdog0));
// struct iwdg_stm32_data *data = IWDG_STM32_DATA(wdt);
void button_pressed(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32());
printk("Feeding watchdog...\n");
wdt_feed(wdt, wdt_channel_id);
counter = 0;
}
#define WDT_FEED_TRIES 5
/*
* To use this sample the devicetree's /aliases must have a 'watchdog0' property.
*/
#if DT_HAS_COMPAT_STATUS_OKAY(st_stm32_window_watchdog)
#define WDT_MAX_WINDOW 100U
#elif DT_HAS_COMPAT_STATUS_OKAY(nordic_nrf_wdt)
/* Nordic supports a callback, but it has 61.2 us to complete before
* the reset occurs, which is too short for this sample to do anything
* useful. Explicitly disallow use of the callback.
*/
#define WDT_ALLOW_CALLBACK 0
#elif DT_HAS_COMPAT_STATUS_OKAY(raspberrypi_pico_watchdog)
#define WDT_ALLOW_CALLBACK 0
#elif DT_HAS_COMPAT_STATUS_OKAY(gd_gd32_wwdgt)
#define WDT_MAX_WINDOW 24U
#define WDT_MIN_WINDOW 18U
#define WDG_FEED_INTERVAL 12U
#elif DT_HAS_COMPAT_STATUS_OKAY(intel_tco_wdt)
#define WDT_ALLOW_CALLBACK 0
#define WDT_MAX_WINDOW 3000U
#elif DT_HAS_COMPAT_STATUS_OKAY(nxp_fs26_wdog)
#define WDT_MAX_WINDOW 1024U
#define WDT_MIN_WINDOW 320U
#define WDT_OPT 0
#define WDG_FEED_INTERVAL (WDT_MIN_WINDOW + ((WDT_MAX_WINDOW - WDT_MIN_WINDOW) / 4))
#endif
#ifndef WDT_ALLOW_CALLBACK
#define WDT_ALLOW_CALLBACK 1
#endif
#ifndef WDT_MAX_WINDOW
#define WDT_MAX_WINDOW 5000U
#endif
#ifndef WDT_MIN_WINDOW
#define WDT_MIN_WINDOW 0U
#endif
#ifndef WDG_FEED_INTERVAL
#define WDG_FEED_INTERVAL 1000U
#endif
#ifndef WDT_OPT
#define WDT_OPT WDT_OPT_PAUSE_HALTED_BY_DBG
#endif
#if WDT_ALLOW_CALLBACK
static void wdt_callback(const struct device *wdt_dev, int channel_id)
{
static bool handled_event;
if (handled_event) {
return;
}
wdt_feed(wdt_dev, channel_id);
printk("Handled things..ready to reset\n");
handled_event = true;
}
#endif /* WDT_ALLOW_CALLBACK */
int main(void)
{
int err;
// int wdt_channel_id;
// const struct device *const wdt = DEVICE_DT_GET(DT_ALIAS(watchdog0));
int ret;
if (!gpio_is_ready_dt(&button)) {
printk("Error: button device %s is not ready\n",
button.port->name);
return 0;
}
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
printk("Error %d: failed to configure %s pin %d\n",
ret, button.port->name, button.pin);
return 0;
}
ret = gpio_pin_interrupt_configure_dt(&button,
GPIO_INT_EDGE_TO_ACTIVE);
if (ret != 0) {
printk("Error %d: failed to configure interrupt on %s pin %d\n",
ret, button.port->name, button.pin);
return 0;
}
gpio_init_callback(&button_cb_data, button_pressed, BIT(button.pin));
gpio_add_callback(button.port, &button_cb_data);
printk("Set up button at %s pin %d\n", button.port->name, button.pin);
if (led.port && !gpio_is_ready_dt(&led)) {
printk("Error %d: LED device %s is not ready; ignoring it\n",
ret, led.port->name);
led.port = NULL;
}
if (led.port) {
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT);
if (ret != 0) {
printk("Error %d: failed to configure LED device %s pin %d\n",
ret, led.port->name, led.pin);
led.port = NULL;
} else {
printk("Set up LED at %s pin %d\n", led.port->name, led.pin);
}
}
printk("Watchdog sample application\n");
if (!device_is_ready(wdt)) {
printk("%s: device not ready.\n", wdt->name);
return 0;
}
struct wdt_timeout_cfg wdt_config = {
/* Reset SoC when watchdog timer expires. */
.flags = WDT_FLAG_RESET_SOC,
/* Expire watchdog after max window */
.window.min = WDT_MIN_WINDOW,
.window.max = WDT_MAX_WINDOW,
};
#if WDT_ALLOW_CALLBACK
/* Set up watchdog callback. */
wdt_config.callback = wdt_callback;
printk("Attempting to test pre-reset callback\n");
#else /* WDT_ALLOW_CALLBACK */
printk("Callback in RESET_SOC disabled for this platform\n");
#endif /* WDT_ALLOW_CALLBACK */
wdt_channel_id = wdt_install_timeout(wdt, &wdt_config);
if (wdt_channel_id == -ENOTSUP) {
/* IWDG driver for STM32 doesn't support callback */
printk("Callback support rejected, continuing anyway\n");
wdt_config.callback = NULL;
wdt_channel_id = wdt_install_timeout(wdt, &wdt_config);
}
if (wdt_channel_id < 0) {
printk("Watchdog install error\n");
return 0;
}
err = wdt_setup(wdt, WDT_OPT);
if (err < 0) {
printk("Watchdog setup error\n");
return 0;
}
#if WDT_MIN_WINDOW != 0
/* Wait opening window. */
k_msleep(WDT_MIN_WINDOW);
#endif
#if 0
/* Feeding watchdog. */
printk("Feeding watchdog %d times\n", WDT_FEED_TRIES);
for (int i = 0; i < WDT_FEED_TRIES; ++i) {
printk("Feeding watchdog...\n");
wdt_feed(wdt, wdt_channel_id);
k_sleep(K_MSEC(WDG_FEED_INTERVAL));
}
#endif
/* Waiting for the SoC reset. */
printk("Waiting for reset...\n");
printk("Press the button to feed watchdog\n");
while (1) {
printk("counter is %d at %" PRIu32 "\n", counter, k_cycle_get_32());
counter++;
if (led.port) {
/* If we have an LED, match its state to the button's. */
int val = gpio_pin_get_dt(&button);
if (val >= 0) {
gpio_pin_set_dt(&led, val);
}
k_sleep(K_MSEC(WDG_FEED_INTERVAL));
}
k_yield();
}
return 0;
}
在上述代码中,使用了sw0设备,对应USR按键,使用了led0设备,用于在按键时点亮LD4。
看门口使用了watchdog0,对应了STM32C0的IWDG。
看门狗的超时设置由WDT_MAX_WINDOW定义为了5000U,对应最终的5s,也就是5000ms。
喂狗的部分,在如下代码中:
void button_pressed(const struct device *dev, struct gpio_callback *cb,
uint32_t pins)
{
printk("Button pressed at %" PRIu32 "\n", k_cycle_get_32());
printk("Feeding watchdog...\n");
wdt_feed(wdt, wdt_channel_id);
counter = 0;
}
也就是在按键时,调用 wdt_feed()进行喂狗。
代码编写完成,使用下面的指令进行编译:
west build -b nucleo_c031c6 samples/drivers/watchdog
然后使用west或者pydocd进行烧录:
# west烧录
west flash
# pyocd烧录
pyocd flash --erase chip --target STM32C031C6Ux build/zephyr/zephyr.hex
烧录完成,通过串口监控,不做任何操作的情况下,可以看到每隔5s,系统自动复位:
如果在5s内按键,则进行喂狗,重新计数:
七、总结
IWDG做为STM32C0提供的两种片内看门狗之一,其使用有优点也有缺点。
优点是,其驱动时钟独立于系统主时钟,在系统主时钟异常的情况下,依然可以发挥作用。
缺点是,时间精度较低,LSI的时钟频率为32kHz,且由于其独立于系统主时钟,所以计时器超时要触发复位操作,不会产生系统中断,在主程序中无法接收到中断信号,无法做现场处理,例如存储当前数据。
八、参考资料