HonestQiao 发表于 2024-2-13 23:03

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

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

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

<p>其分别为IWDG(独立看门狗)和WWDG(窗口看门狗)。</p>

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

<p>&nbsp;</p>

<p><strong>一、硬件了解</strong></p>

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

<p> &nbsp;</p>

<p>&nbsp;</p>

<p>从其中可以了解到IWDG的框图:</p>

<p> &nbsp;&nbsp;</p>

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

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

<p>&nbsp;</p>

<p><strong>二、IWDG寄存器了解</strong></p>

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

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

<p>1. 键寄存器:</p>

<p> &nbsp;</p>

<p>像写入不同的值,代表不同的功能:</p>

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

<p>&nbsp;</p>

<p>2. 预分频寄存器:</p>

<p> &nbsp;&nbsp;&nbsp;</p>

<p>其低三位,表示分频值。</p>

<p>&nbsp;</p>

<p>3. 重装载寄存器</p>

<p> &nbsp;这个寄存器的值,表示IWDG的watchdog计数器重载时的值。一旦向IWDG_KR写入0xAAAA,则会将该值传送到watchdog计数器,进行计数。</p>

<p>&nbsp;</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>&nbsp;</p>

<p><strong>四、IWDG超时时间</strong></p>

<p>IWDG计数器溢出时间计算公示为: Tout=((4*2^prer)*rlr)/32&nbsp;(ms)<br />
prer:预分频系数:0~7(只有低3位有效),由上述预分频寄存器(IWDG_PR)设置。<br />
rlr:重载值:0~4095(低11位有效),由上述重装载寄存器(IWDG_RLR)设置。</p>

<p>&nbsp;</p>

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

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

<p>&nbsp;</p>

<p><strong>五、zephyr中对IWDG的支持</strong></p>

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

<p> &nbsp;</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

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

<p>&nbsp;</p>

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

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

<p> &nbsp;</p>

<p>&nbsp;</p>

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

<p>&nbsp; 上述设置,对应超时时间为5s。</p>

<p>&nbsp;</p>

<p><strong>六、IWDG实际使用</strong></p>

<p>在 zephyr/samples/drivers/watchdog中,提供了看门狗的基础使用。</p>

<p>在此基础上,添加一个通过USR按键来喂狗的处理,具体的代码如下:</p>

<pre>
<code class="language-cpp">#include &lt;zephyr/kernel.h&gt;
#include &lt;zephyr/device.h&gt;
#include &lt;zephyr/drivers/watchdog.h&gt;
#include &lt;zephyr/sys/printk.h&gt;
#include &lt;stdbool.h&gt;

#include &lt;zephyr/drivers/gpio.h&gt;
#include &lt;zephyr/sys/util.h&gt;
#include &lt;inttypes.h&gt;

/*
* 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(&amp;button)) {
                printk("Error: button device %s is not ready\n",
                     button.port-&gt;name);
                return 0;
        }

        ret = gpio_pin_configure_dt(&amp;button, GPIO_INPUT);
        if (ret != 0) {
                printk("Error %d: failed to configure %s pin %d\n",
                     ret, button.port-&gt;name, button.pin);
                return 0;
        }

        ret = gpio_pin_interrupt_configure_dt(&amp;button,
                                              GPIO_INT_EDGE_TO_ACTIVE);
        if (ret != 0) {
                printk("Error %d: failed to configure interrupt on %s pin %d\n",
                        ret, button.port-&gt;name, button.pin);
                return 0;
        }

        gpio_init_callback(&amp;button_cb_data, button_pressed, BIT(button.pin));
        gpio_add_callback(button.port, &amp;button_cb_data);
        printk("Set up button at %s pin %d\n", button.port-&gt;name, button.pin);

        if (led.port &amp;&amp; !gpio_is_ready_dt(&amp;led)) {
                printk("Error %d: LED device %s is not ready; ignoring it\n",
                     ret, led.port-&gt;name);
                led.port = NULL;
        }
        if (led.port) {
                ret = gpio_pin_configure_dt(&amp;led, GPIO_OUTPUT);
                if (ret != 0) {
                        printk("Error %d: failed to configure LED device %s pin %d\n",
                             ret, led.port-&gt;name, led.pin);
                        led.port = NULL;
                } else {
                        printk("Set up LED at %s pin %d\n", led.port-&gt;name, led.pin);
                }
        }

        printk("Watchdog sample application\n");

        if (!device_is_ready(wdt)) {
                printk("%s: device not ready.\n", wdt-&gt;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, &amp;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, &amp;wdt_config);
        }
        if (wdt_channel_id &lt; 0) {
                printk("Watchdog install error\n");
                return 0;
        }

        err = wdt_setup(wdt, WDT_OPT);
        if (err &lt; 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 &lt; 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(&amp;button);

            if (val &gt;= 0) {
                gpio_pin_set_dt(&amp;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>也就是在按键时,调用&nbsp;wdt_feed()进行喂狗。</p>

<p>&nbsp;</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>&nbsp;</p>

<p>烧录完成,通过串口监控,不做任何操作的情况下,可以看到每隔5s,系统自动复位:</p>

<p> &nbsp;</p>

<p>&nbsp;</p>

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

<p> &nbsp;</p>

<p>&nbsp;</p>

<p><strong>七、总结</strong></p>

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

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

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

<p>&nbsp;</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&mdash;独立看门狗 &mdash; [野火]STM32库开发实战指南&mdash;&mdash;基于野火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 &middot; zephyrproject-rtos/zephyr (github.com)</a></li>
        <li><a href="https://docs.zephyrproject.org/latest/samples/drivers/watchdog/README.html">Watchdog &mdash; 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>

Jacktang 发表于 2024-2-16 22:31

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

HonestQiao 发表于 2024-2-17 19:38

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

<p>是的,就相当于内置了一个外挂</p>
页: [1]
查看完整版本: 【ST NUCLEO-C031C6开发板测评】独立看门狗IWDG使用