【ST NUCLEO-C031C6开发板测评】独立看门狗IWDG使用
<div class='showpostmsg'><p>看门狗是保障嵌入式系统正常运行必不可少的措施之一,能够在系统应为硬件设计缺陷、软件设计权限或者外部电磁干扰导致系统异常的情况下,及时复位系统正常运行。</p><p>从STM32C0系列MCU的系统架构图中可以了解到,系统提供了两个看门狗:</p>
<p>其分别为IWDG(独立看门狗)和WWDG(窗口看门狗)。</p>
<p>这篇帖子分享的是独立看门狗IWDG使用。</p>
<p> </p>
<p><strong>一、硬件了解</strong></p>
<p>在系统手册中,有关于IWDG和WWDG具体功能说明:</p>
<p> </p>
<p> </p>
<p>从其中可以了解到IWDG的框图:</p>
<p> </p>
<p>IWDG是由专门的低速总线进行驱动,即上图中的时钟频率为32kHz的LSI总线,它的时钟和系统主时钟是分离的,在系统主时钟出现故障的情况下,它仍然可以工作,从而可以额外为系统的正常运行提供一重保障。</p>
<p>IWDG一旦设置好以后,就能够独立于系统主程序之外运行,完全不会受到系统主程序的干扰制约。不过因为其驱动总线时钟频率仅为32K,所以适合对时间精度要求低的场合。</p>
<p> </p>
<p><strong>二、IWDG寄存器了解</strong></p>
<p>要使用IWDG独立看门狗,主要就是操作其对应的寄存器。</p>
<p>从手册可以了解到,几个关键的寄存器:</p>
<p>1. 键寄存器:</p>
<p> </p>
<p>像写入不同的值,代表不同的功能:</p>
<ul>
<li>向该寄存器写入0xCCCC用于启动watchdog</li>
<li>系统主程序部分,需要定时向该寄存器写入0xAAAA,否则当计数器为0时,watchdog会复位系统</li>
<li>向该寄存器写入0x555,表示允许访问IWDG_PR和IWDG_RLR。这两个寄存器随后会说明</li>
</ul>
<p> </p>
<p>2. 预分频寄存器:</p>
<p> </p>
<p>其低三位,表示分频值。</p>
<p> </p>
<p>3. 重装载寄存器</p>
<p> 这个寄存器的值,表示IWDG的watchdog计数器重载时的值。一旦向IWDG_KR写入0xAAAA,则会将该值传送到watchdog计数器,进行计数。</p>
<p> </p>
<p>4. IWDG窗口寄存器</p>
<p></p>
<p><strong>三、喂狗</strong></p>
<p>在主程序运行过程中,向键值寄存器IWDG_KR中写入0xAAAA, 自动重装载寄存器IWDG_RLR中的值就会重新加载到计数器,watchdog就会重新开始计数,从而避免产生看门狗复位操作。<br />
如果程序运行异常,就无法正常向键值寄存器IWDG_KR中写入0xAAAA,从而在watchdog超时时,系统自动复位。</p>
<p>向键值寄存器IWDG_KR中写入0xAAAA让计数器重载的的操作,就是俗称的喂狗。只有每间隔一定周期时间喂狗,watchdog才不会超时触发复位。如果主程序一直稳定运行,那么就能一直喂狗。</p>
<p> </p>
<p><strong>四、IWDG超时时间</strong></p>
<p>IWDG计数器溢出时间计算公示为: Tout=((4*2^prer)*rlr)/32 (ms)<br />
prer:预分频系数:0~7(只有低3位有效),由上述预分频寄存器(IWDG_PR)设置。<br />
rlr:重载值:0~4095(低11位有效),由上述重装载寄存器(IWDG_RLR)设置。</p>
<p> </p>
<p>根据上述公式,以及数据手册中的说明,具体的超时时间对照表如下:</p>
<p> 有了上述的公示和对照表,就可以根据实际情况,合理的设置预分频系数和重载值,来实现实际需要的看门口超时时间。</p>
<p> </p>
<p><strong>五、zephyr中对IWDG的支持</strong></p>
<p>在zephyr中,提供了对STM32C0的IWDG的支持,具体可见 zephyr/boards/arm/nucleo_c031c6/nucleo_c031c6.dts:</p>
<p> </p>
<p> </p>
<p> </p>
<p>在代码中调用时,调用wdt_install_timeout()进行设置,实际调用iwdg_stm32_install_timeout进行处理,具体可见zdphyr/drivers/watchdog/wdt_iwdg_stm32.c: </p>
<p> </p>
<p>在传入的config中,提供window.max设置,就会据此计算timeout,window.max * USEC_PER_MSEC (每秒的微秒数) 得到timeout(ms)。</p>
<p>然后调用iwdg_stm32_convert_timeout()自动计算需要进行设置的prescaler和reload进行设置。</p>
<p> </p>
<p> </p>
<p>在程序中,使用如下的调用,进行具体的设置:</p>
<p> 上述设置,对应超时时间为5s。</p>
<p> </p>
<p><strong>六、IWDG实际使用</strong></p>
<p>在 zephyr/samples/drivers/watchdog中,提供了看门狗的基础使用。</p>
<p>在此基础上,添加一个通过USR按键来喂狗的处理,具体的代码如下:</p>
<pre>
<code class="language-cpp">#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_WINDOW100U
#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_WINDOW1024U
#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_WINDOW5000U
#endif
#ifndef WDT_MIN_WINDOW
#define WDT_MIN_WINDOW0U
#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;
}
</code></pre>
<p>在上述代码中,使用了sw0设备,对应USR按键,使用了led0设备,用于在按键时点亮LD4。</p>
<p>看门口使用了watchdog0,对应了STM32C0的IWDG。</p>
<p>看门狗的超时设置由WDT_MAX_WINDOW定义为了5000U,对应最终的5s,也就是5000ms。</p>
<p>喂狗的部分,在如下代码中:</p>
<pre>
<code class="language-cpp">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;
}</code></pre>
<p>也就是在按键时,调用 wdt_feed()进行喂狗。</p>
<p> </p>
<p>代码编写完成,使用下面的指令进行编译:</p>
<pre>
<code class="language-bash">west build -b nucleo_c031c6 samples/drivers/watchdog</code></pre>
<p>然后使用west或者pydocd进行烧录:</p>
<pre>
<code># west烧录
west flash
# pyocd烧录
pyocd flash --erase chip --target STM32C031C6Ux build/zephyr/zephyr.hex</code></pre>
<p> </p>
<p>烧录完成,通过串口监控,不做任何操作的情况下,可以看到每隔5s,系统自动复位:</p>
<p> </p>
<p> </p>
<p>如果在5s内按键,则进行喂狗,重新计数:</p>
<p> </p>
<p> </p>
<p><strong>七、总结</strong></p>
<p>IWDG做为STM32C0提供的两种片内看门狗之一,其使用有优点也有缺点。</p>
<p>优点是,其驱动时钟独立于系统主时钟,在系统主时钟异常的情况下,依然可以发挥作用。</p>
<p>缺点是,时间精度较低,LSI的时钟频率为32kHz,且由于其独立于系统主时钟,所以计时器超时要触发复位操作,不会产生系统中断,在主程序中无法接收到中断信号,无法做现场处理,例如存储当前数据。</p>
<p> </p>
<p>八、参考资料</p>
<ul>
<li>STM32C0X1中文参考手册</li>
<li>STM32C0X1 Datasheet</li>
<li><a href="https://www.stmcu.com.cn/Designresource/list/STM32C0/document/datasheet" target="_blank">STM官网手册</a></li>
<li><a href="https://shequ.stmicroelectronics.cn/thread-634651-1-1.html">【经验分享】STM32F103:独立看门狗(IWDG)用法详解 (stmicroelectronics.cn)</a></li>
<li><a href="https://blog.csdn.net/DanielLee_ustb/article/details/39551583">STM32独立看门狗IWDG与窗口看门狗WWDG研究_stm32g030 iwdg触发次数是否有上限-CSDN博客</a></li>
<li><a href="https://blog.csdn.net/qq_38410730/article/details/79954662">【STM32】独立看门狗概述、寄存器、库函数(IWDG一般步骤)_stm32 看门狗寄存器-CSDN博客</a></li>
<li><a href="https://doc.embedfire.com/mcu/stm32/f103mini/std/zh/latest/book/IWDG.html">34. IWDG—独立看门狗 — [野火]STM32库开发实战指南——基于野火MINI开发板 文档 (embedfire.com)</a></li>
<li><a href="https://www.st.com/zh/microcontrollers-microprocessors/stm32c031c6.html">STM32C031C6 - 主流Arm Cortex-M0+ MCU,具有32 KB Flash存储器、12 KB RAM、48 MHz CPU、2x USART、定时器、ADC、通信接口,2V-3.6V - 意法半导体STMicroelectronics</a></li>
<li><a href="https://www.st.com/zh/evaluation-tools/nucleo-c031c6.html#st_key-benefits_sec-nav-tab">NUCLEO-C031C6 - 采用STM32C031C6 MCU的STM32 Nucleo-64开发板,支持Arduino和ST morpho连接 - 意法半导体STMicroelectronics</a></li>
<li><a href="https://github.com/zephyrproject-rtos/zephyr/blob/main/drivers/watchdog/wdt_iwdg_stm32.c">zephyr/drivers/watchdog/wdt_iwdg_stm32.c at main · zephyrproject-rtos/zephyr (github.com)</a></li>
<li><a href="https://docs.zephyrproject.org/latest/samples/drivers/watchdog/README.html">Watchdog — Zephyr Project Documentation</a></li>
</ul>
</div><script> var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;" style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
if(parseInt(discuz_uid)==0){
(function($){
var postHeight = getTextHeight(400);
$(".showpostmsg").html($(".showpostmsg").html());
$(".showpostmsg").after(loginstr);
$(".showpostmsg").css({height:postHeight,overflow:"hidden"});
})(jQuery);
} </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script> <p>在系统主时钟异常的情况下,依然可以,这个好</p>
Jacktang 发表于 2024-2-16 22:31
在系统主时钟异常的情况下,依然可以,这个好
<p>是的,就相当于内置了一个外挂</p>
页:
[1]