[原创] 【ST NUCLEO-C031C6开发板测评】独立看门狗IWDG使用

HonestQiao   2024-2-13 23:03 楼主

看门狗是保障嵌入式系统正常运行必不可少的措施之一,能够在系统应为硬件设计缺陷、软件设计权限或者外部电磁干扰导致系统异常的情况下,及时复位系统正常运行。

从STM32C0系列MCU的系统架构图中可以了解到,系统提供了两个看门狗:

image.png 其分别为IWDG(独立看门狗)和WWDG(窗口看门狗)。

这篇帖子分享的是独立看门狗IWDG使用。

 

一、硬件了解

在系统手册中,有关于IWDG和WWDG具体功能说明:

image.png  

 

从其中可以了解到IWDG的框图:

image.png   

IWDG是由专门的低速总线进行驱动,即上图中的时钟频率为32kHz的LSI总线,它的时钟和系统主时钟是分离的,在系统主时钟出现故障的情况下,它仍然可以工作,从而可以额外为系统的正常运行提供一重保障。

IWDG一旦设置好以后,就能够独立于系统主程序之外运行,完全不会受到系统主程序的干扰制约。不过因为其驱动总线时钟频率仅为32K,所以适合对时间精度要求低的场合。

 

二、IWDG寄存器了解

要使用IWDG独立看门狗,主要就是操作其对应的寄存器。

从手册可以了解到,几个关键的寄存器:

1. 键寄存器:

image.png  

像写入不同的值,代表不同的功能:

  • 向该寄存器写入0xCCCC用于启动watchdog
  • 系统主程序部分,需要定时向该寄存器写入0xAAAA,否则当计数器为0时,watchdog会复位系统
  • 向该寄存器写入0x555,表示允许访问IWDG_PR和IWDG_RLR。这两个寄存器随后会说明

 

2. 预分频寄存器:

image.png    

其低三位,表示分频值。

 

3. 重装载寄存器

image.png  这个寄存器的值,表示IWDG的watchdog计数器重载时的值。一旦向IWDG_KR写入0xAAAA,则会将该值传送到watchdog计数器,进行计数。

 

4. IWDG窗口寄存器

image.png

三、喂狗

在主程序运行过程中,向键值寄存器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)设置。

 

根据上述公式,以及数据手册中的说明,具体的超时时间对照表如下:

image.png   有了上述的公示和对照表,就可以根据实际情况,合理的设置预分频系数和重载值,来实现实际需要的看门口超时时间。

 

五、zephyr中对IWDG的支持

在zephyr中,提供了对STM32C0的IWDG的支持,具体可见 zephyr/boards/arm/nucleo_c031c6/nucleo_c031c6.dts:

image.png  

image.png  

 

在代码中调用时,调用wdt_install_timeout()进行设置,实际调用iwdg_stm32_install_timeout进行处理,具体可见zdphyr/drivers/watchdog/wdt_iwdg_stm32.c: image.png  

 

在传入的config中,提供window.max设置,就会据此计算timeout,window.max * USEC_PER_MSEC (每秒的微秒数) 得到timeout(ms)。

然后调用iwdg_stm32_convert_timeout()自动计算需要进行设置的prescaler和reload进行设置。

image.png  

 

在程序中,使用如下的调用,进行具体的设置:

image.png   上述设置,对应超时时间为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,系统自动复位:

image.png  

 

如果在5s内按键,则进行喂狗,重新计数:

image.png  

 

七、总结

IWDG做为STM32C0提供的两种片内看门狗之一,其使用有优点也有缺点。

优点是,其驱动时钟独立于系统主时钟,在系统主时钟异常的情况下,依然可以发挥作用。

缺点是,时间精度较低,LSI的时钟频率为32kHz,且由于其独立于系统主时钟,所以计时器超时要触发复位操作,不会产生系统中断,在主程序中无法接收到中断信号,无法做现场处理,例如存储当前数据。

 

八、参考资料

回复评论 (2)

在系统主时钟异常的情况下,依然可以,这个好

点赞  2024-2-16 22:31
引用: Jacktang 发表于 2024-2-16 22:31 在系统主时钟异常的情况下,依然可以,这个好

是的,就相当于内置了一个外挂

点赞  2024-2-17 19:38
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复