打印
[STM32F4]

STM32F4独立看门狗和窗口看门狗的配置、原理和使用

[复制链接]
92|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
前言
  本文详细介绍了独立看门狗和窗口看门狗的配置方法、内部原理和使用方法,对初学者有很大的帮助。全文九千五百多字,耗时一整天的时间。如果您对这一块知识存在一些疑惑,相信看完也会收获满满。

什么是看门狗
  说它是看门狗,是比喻说法。设想一下,你家养了一只大狼狗,拴在门旁边。你每天都需要去喂狗,要不然一旦超过了喂狗的时候,比如你一整天都不喂狗,它肯定大声地犬吠,搞得你不得安宁。是不是?它的大声犬吠会使得你原本的工作学习被打断,无法继续下去。所以你一旦养了狗,就需要去不间断的去喂狗。
  当然上面的说法希望你能理解看门狗的含义,没有要教大家怎么养狗的意思,哈哈哈……
  看门狗本质上就是递减计数器。一旦我们使用了看门狗,就必须间隔一段时间去喂狗。这在我们的程序中是写好的逻辑。当程序出现故障了,或者卡死了,那就肯定会影响喂狗的时间。看门狗超时之前还没喂狗,那么就会引发复位,程序重新开始运行。这样就增加了我们的代码的安全性。所以看门狗是属于功能安全范畴的。

独立看门狗IWDG
  独立看门狗,所谓“独立”,就是说,它完全独立于其他外设,就连时钟源都是自己使用一个内部的低速时钟,所以即便主频时钟出问题了,它也可以正常工作。

独立看门狗的原理
  独立看门狗是一个12位递减计数器,减到0就触发复位。先来看一下时钟:



  上图是STM32CubeIDE里面,关于时钟的配置部分截图。可以看到IWDG的时钟是由一个32KHz的内部低速时钟LSI提供的,而且没有“中间商”,你都不需要去使能这个时钟,它直接提供给看门狗。
  接下来我们谈一下分频的事。什么是分频?
  直白的说,就是输入的时钟太快了,我想把它的频率变慢了再使用!
  够直白,是吧,我希望大家都可以明白。
  那么分频之后的时钟频率,就是原时钟频率除以分频值。打比方,如果我选择4分频,那么IWDG实际使用的频率是32KHz/4=8KHz。
  那么周期是多少?1/8KHz=0.125ms。也就是说,时钟跳动一个周期用时0.125ms。那么下一个参数就可以讲下了。
重装载计数值是什么?就是计数器从什么数值开始递减计数。比如说按默认值4095来算,那么打开看门狗之后,不喂狗,多久就会触发复位?
  一个周期0.125ms,计数了4095个周期,那么算一下,二者相乘,结果是:511.875ms。也就是说,必须在511.875ms以内把狗喂了,要不然,复位伺候!
  那么我想把计时器溢出时间改为1秒,那怎么办?
灵活的调节这两个参数就可以了。明显,预分频值越大,时钟越慢,一个周期耗时越长,对我们增加溢出时间越有利。你看,上面的例子,已经把重装载值设置成最大了,溢出时间也就是半秒,达不到1秒的预期。那就把预分频值改大试试呗。比如预分频设置成64分频,重装载值改为500。计算一下,500*(64/32)=1000ms,也就是1秒。
  补充一下看门狗的喂狗时间(也就是看门狗溢出时间)的计算方式为:



其中:
Tout 为看门狗溢出时间(单位为 ms);
prer 为看门狗时钟预分频值(IWDG_PR 值),范围为 0~7;
rlr 为看门狗的重装载值(IWDG_RLR 的值);

  下面是IWDG的寄存器:

关键字寄存器IWDG_KR
这个寄存器可以实现启动看门狗、喂狗、使能对PR、RLR寄存器的访问等功能。



预分频器寄存器
这个寄存器设置预分频系数。



重载寄存器
这个寄存器设置计数器重载值。就是将这个寄存器的值重新加载到计数器的初值中。



状态寄存器
这个寄存器就是告诉我们PR和RLR寄存器的值是否“生效”。



独立看门狗的配置
  独立看门狗的配置简单,如下图:



  首先,在左边选择IWDG,之后可以打勾Activated,最后下面有两个配置参数。
  第一个:IWDG counter clock prescaler,即预分频系数,它有以下选项:



  也就是说,至少4分频,默认参数就是4,最大可以256分频。
  第二个参数是:IWDG down-counter reload value,即重装载值,这个参数可以手动输入。



  由此可以知道,最大就是0xFFF,换算成十进制就是4095,回头看一下配置界面,默认就是4095。默认就是最大值。
我们保留这些默认配置,就可以说配置结束了。

独立看门狗的使用
  前面我们配置完成了,那么就可以生成代码了。
在main函数中,初始化了独立看门狗。

MX_IWDG_Init();


  在函数内部,有如下的操作。



  其实函数的逻辑很清晰,不需要解释,都是我们配置的值。进HAL_IWDG_Init()函数看看。

HAL_StatusTypeDef HAL_IWDG_Init(IWDG_HandleTypeDef *hiwdg)
{
  uint32_t tickstart;

  if (hiwdg == NULL)
  {
    return HAL_ERROR;
  }

  /* Check the parameters */
  assert_param(IS_IWDG_ALL_INSTANCE(hiwdg->Instance));
  assert_param(IS_IWDG_PRESCALER(hiwdg->Init.Prescaler));
  assert_param(IS_IWDG_RELOAD(hiwdg->Init.Reload));
  /* Enable IWDG. LSI is turned on automatically */
  __HAL_IWDG_START(hiwdg);

  /* Enable write access to IWDG_PR and IWDG_RLR registers by writing 0x5555 in KR */
  IWDG_ENABLE_WRITE_ACCESS(hiwdg);

  /* Write to IWDG registers the Prescaler & Reload values to work with */
  hiwdg->Instance->PR = hiwdg->Init.Prescaler;
  hiwdg->Instance->RLR = hiwdg->Init.Reload;

  /* Check pending flag, if previous update not done, return timeout */
  tickstart = HAL_GetTick();

  /* Wait for register to be updated */
  while (hiwdg->Instance->SR != 0x00u)
  {
    if ((HAL_GetTick() - tickstart) > HAL_IWDG_DEFAULT_TIMEOUT)
    {
      return HAL_TIMEOUT;
    }
  }

  /* Reload IWDG counter with value defined in the reload register */
  __HAL_IWDG_RELOAD_COUNTER(hiwdg);

  /* Return function status */
  return HAL_OK;
}



  代码量不大,前面的断言就不看了。 __HAL_IWDG_START(hiwdg);
一进来直接就把IWDG使能了,还没设置内部参数呢,亲。可能是因为IWDG超时时间很长的缘故吧,所以他敢这么写。
/* Enable write access to IWDG_PR and IWDG_RLR registers by writing 0x5555 in KR */ IWDG_ENABLE_WRITE_ACCESS(hiwdg);
  这里先使能了对下面的寄存器的写操作。注释说的很明白,就是下面的IWDG_PR和IWDG_RLR寄存器不是直接可以写操作的,需要在KR寄存器里写0x5555,之后才能写。

hiwdg->Instance->PR = hiwdg->Init.Prescaler;
hiwdg->Instance->RLR = hiwdg->Init.Reload;
  这两行代码是对参数的设置。设置完还不行,必须等到参数已经“起效”了,这里说的比较直白。还有个寄存器,hiwdg->Instance->SR这个寄存器有两个位,都为0,就说明刚才设置进去的值“起效”了。



  最后,__HAL_IWDG_RELOAD_COUNTER(hiwdg);很贴心的喂了一次狗。
  接下来我们就要完善喂狗的逻辑了。
比如我们在主函数中设置每隔一段时间(不超过1秒)调用一下这个函数:

HAL_StatusTypeDef HAL_IWDG_Refresh(IWDG_HandleTypeDef *hiwdg)
{
  /* Reload IWDG counter with value defined in the reload register */
  __HAL_IWDG_RELOAD_COUNTER(hiwdg);

  /* Return function status */
  return HAL_OK;
}


  显然里面的代码和刚才初始化函数最后执行的一样。至此,独立看门狗讲解完毕。
  不过,从实际的使用出发,IWDG可以与定时器配合着,在定时器中断处理函数中去喂狗;或者在RTOS中,在一个周期任务中喂狗。

窗口看门狗WWDG
  窗口看门狗,顾名思义,就是在一个像窗口一样的时间范围内喂狗,不在这个时间窗口内喂狗,就会触发复位。说到底,跟独立看门狗一样,也是一个向下的计数器。

窗口看门狗的原理
  关于窗口看门狗,我们先看一张图,这张图看明白,对我们理解窗口看门狗至关重要。



  一眼望去,这不就是递减计数器吗?对,没错!减到0就复位?不是的!还没减到0就复位,具体是减到0x3F的时候就会触发复位。·这就是“窗口下限值”。也就是说,喂狗一定要赶在计数器递减到0x3F之前!那么“窗口上限值”在哪里?就是W[6:0]所决定的。W[6:0]是7位的窗口值,是我们用户可以自由设置的,但也有条件,要大于0x3F,否则没有窗口了。也要小于0x7F,因为它最大也就是这个数字了。
  那么回到图上,如果计数器的值大于W[6:0]的值,不允许喂狗,否则会引发复位!如果计数器的值在W[6:0]和0x3F之间,没错!可以喂狗,这也是正确的喂狗时机。一旦到了0x3F,那就复位伺候!
  那么图上的T6位怎么理解?T[6:0]是计数器的计数值,它在不断变化,当你把0x3F换成二进制,就知道这里的Bit6等于0,也就是T6位等于0了。而前一个数0x40,就是T6位等于1的最后时刻。
  你是否会问:“如果能在计数器减到0x40这个最后时刻触发中断,是不是我们可以在复位之前把狗喂了?”没错,WWDG的设计师也是这样认为。这叫Early wake-up interrupt,中文叫“提前唤醒中断”。



  只要我们将此位置位,即可使能中断,在中断处理函数中喂狗。所以你看出来区别了。IWDG没有中断,WWDG是有中断的。
下面顺便讲一下寄存器:







  需要强调一下,T[6:0]的数值是在自由运行状态的,即便我们禁止WWDG,它也会自动递减。找到参考手册原文与大家分享:





  关于WWDG,还有一幅图,一定要拿出来讲解一下,要不然还会感觉意犹未尽。



  首先,上面有W[6:0],是窗口值,下面有T[6:0],是计数值。二者通过比较器连接,输出的结果是T[6:0]大于W[6:0]吗?换句话说,高于窗口上限值了吗?如果高于了上限值,输出1。同时,如果这时候写了CR寄存器,也就是喂狗了,那么就会使得“与”门输出1。因为在窗口外喂狗了,这会引发复位的!下一步是一个“或”门,直接将结果输出到最终的“与门”。而另一个判断的条件是当前WWDG使能了吗?肯定使能了,是吧,我们都操作到现在了。看门狗都没使能就没得聊了。所以就引发了Reset。
  另一方面,T6连接了一根线并“取反”,连接到了“或门”,也就是说,T6一旦等于0,取反后得到1,就直接输出1到最终的”与门“,引发Reset。
  以上就是对这幅图的讲解,相信你看明白了。

最后说明一下WWDG的超时时间计算公式:



其中:
Twwdg: WWDG 超时时间(单位为 ms)
Fpclk1: APB1 的时钟频率(单位为 Khz)
WDGTB: WWDG 的预分频系数
T[5:0]:窗口看门狗的计数器低 6 位。

窗口看门狗的配置
  关于配置,稍稍比独立看门狗复杂一丢丢,但也很简单。



  左边选择WWDG,之后上面勾选Activated,下面设置参数。

  对于这些参数,简单介绍一下:
  WWDG counter clock prescaler:预分频值。由于WWDG的时钟源是PCLK1,就是下图中标出来的时钟源。



而我们需要在WWDG里面设置预分频值,前面讲过预分频的含义,就不赘述了。
  WWDG window value:窗口值。在WWDG中需要在窗口的上限和下限值之间去喂狗。而上限值就是这个窗口值,下限值是固定的,就是0x3F。所以窗口值要比0x3F大,比0x7F小。为啥要比0x7F小?因为W[0:7]最大也就是0x7F。
  WWDG free-running downcounter value:自由运行的递减计数器的初值。通常设置一个比较大的值,因为T[0:7]最大也即是0x7F,所以设置成0x7F。
  Early wakeup interrupt:是否使能提前唤醒中断。

  最后一个参数就是NVIC配置,使能NVIC中断。打勾使能即可。



窗口看门狗的使用
  代码生成之后,我们看到在main函数中,初始化阶段,调用了WWDG的初始化。

MX_WWDG_Init();


这个函数内部的操作如下图:



  同样,我们看一下HAL_WWDG_Init()函数的细节。

HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg)
{
  /* Check the WWDG handle allocation */
  if (hwwdg == NULL)
  {
    return HAL_ERROR;
  }

  /* Check the parameters */
  assert_param(IS_WWDG_ALL_INSTANCE(hwwdg->Instance));
  assert_param(IS_WWDG_PRESCALER(hwwdg->Init.Prescaler));
  assert_param(IS_WWDG_WINDOW(hwwdg->Init.Window));
  assert_param(IS_WWDG_COUNTER(hwwdg->Init.Counter));
  assert_param(IS_WWDG_EWI_MODE(hwwdg->Init.EWIMode));

#if (USE_HAL_WWDG_REGISTER_CALLBACKS == 1)
  /* Reset Callback pointers */
  if (hwwdg->EwiCallback == NULL)
  {
    hwwdg->EwiCallback = HAL_WWDG_EarlyWakeupCallback;
  }

  if (hwwdg->MspInitCallback == NULL)
  {
    hwwdg->MspInitCallback = HAL_WWDG_MspInit;
  }

  /* Init the low level hardware */
  hwwdg->MspInitCallback(hwwdg);
#else
  /* Init the low level hardware */
  HAL_WWDG_MspInit(hwwdg);
#endif

  /* Set WWDG Counter */
  WRITE_REG(hwwdg->Instance->CR, (WWDG_CR_WDGA | hwwdg->Init.Counter));

  /* Set WWDG Prescaler and Window */
  WRITE_REG(hwwdg->Instance->CFR, (hwwdg->Init.EWIMode | hwwdg->Init.Prescaler | hwwdg->Init.Window));

  /* Return function status */
  return HAL_OK;
}



  可以看到里面只做了两件事,就是写操作了两个寄存器。一个是CR,另一个是CFR。关于这两个寄存器,我们在原理部分有讲解,大家可以回顾一下。
  接下来我们要做的是:重新定义函数void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
这个函数是窗口看门狗的Early wake-up 中断触发之后,调用的喂狗函数。所以需要在这里喂狗。比如:

void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
        HAL_WWDG_Refresh(hwwdg);
}


  没错,里面唯一的一行代码就是喂狗的操作。

  接下来为了大家更深入的理解,我调试了一下,配合着寄存器的值,我们看一下在中断中喂狗的流程。



  程序继续运行,进入中断处理函数内部。







  没错,最后T[0:7]变成0x7F,说明我们喂狗成功了。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/smart_boy__/article/details/148478372

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

50

主题

147

帖子

0

粉丝