【正点原子MP157连载】第十五章 窗口门狗(WWDG)实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_stm32mp157看门狗-程序员宅基地

技术标签: stm32  LINUX  arm  单片机  

1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614
在这里插入图片描述

第十五章 窗口门狗(WWDG)实验

本章节我们来学习STM32MP1的窗口看门狗(WWDG)的使用方法,我们使用窗口看门狗的中断功能来喂狗,并通过LED灯来观察喂狗和复位情况。
本章将分为如下几个小节:
15.1、WWDG简介;
15.2、WWDG实验;
15.1 WWDG简介
15.1.1 STM32MP157看门狗

  1. 看门狗介绍
    STM32MP157有3个看门狗,其中两个独立看门狗(IWDG1和IWDG2)给MPU使用,另一个窗口看门狗(WWDG1)给MCU使用。IWDG看门狗有独立时钟,由LSI驱动,即使主时钟发生故障它也能保持活动状态,而WWDG的时钟由APB1时钟分频后得到时钟驱动,最大为104.5MHz。
    在这里插入图片描述

图15.1.1. 1 STM32MP157看门狗资源
2. 窗口看门狗框图
窗口看门狗(WWDG)跟独立看门狗(IWDG1)一样,是一个递减计数器。独立看门狗计数器递减为0的时候将产生复位,如果在递减到0之前进行喂狗则不会产生复位。窗口看门狗就不一样了,窗口看门狗递减到0X40的时候如果不喂狗,到下一个计数0X3F的时候就会产生复位,如果要喂狗,也要在一定的时间范围内喂狗才不会导致复位,我们用一个图来说明窗口看门狗的工作过程:
在这里插入图片描述

图15.1.1. 2 WWDG框图
从WWDG框图中我们可以知道,WWDG有一个输入时钟(时钟pclk),经过一个4096的分频器,再经过一个分频系数(2 WDGTB)可编程的预分频器以后才给一个7位递减计数器提供时钟,然后递减计数器开始工作。递减计数器按照这个时钟频率,从初始值往下逐一递减,每递减一次就用了一个固定的时钟周期,从而实现递减计时。我们来看看框图中的几个信号,如下表所示:
在这里插入图片描述

表15.1.1. 1 WWDG内部输入/输出信号
对框图的说明如下:
●pclk
pclk时钟是由PCLK1提供的,最大可为104.5MHz,后面的分频系数(2WDGTB)由用户去配置,那么给WWDG时钟频率=PCLK1/(40962WDGTB)。
●T[6:0]
控制寄存器的低7 位WWDG_CR[6:0]用于递减计数,也就是图中的计数器T[6:0],它的初始值用户可配置,可以配置这7位都为1,这个就是最大的初始值(0X7F)。递减计数器每计数一次的时间间隔=(4096
2WDGTB)/ Fpclk,如果要计数N次,则要用的时间为:N*(4096*2WDGTB)/ Fpclk。递减计数器最低可以递减到0X3F。
●W[6:0]
配置寄存器的低7位WWDG_CFR[6:0]也就是图中的W[6:0],用于与控制寄存器的低7 位的计数器(T[6:0])的值进行比较,WWDG_CFR[6:0]的值也就是我们说的上限值,由用户设置,不能设置为0X40,最大可以设置等于递减计数器的初始值。
●T6位
就是控制寄存器的第6位,即递减计数器T[6:0]的最高位,当递减计数器从0X40递减到0X3F的时候就产生复位,而第6位从1变成0。0X40就是窗口的下限值,此值是固定的。
我们用一张图来说明WWDG的工作过程:
在这里插入图片描述

图15.1.1. 3 WWDG工作示意图
上图中,窗口看门狗的递减计数器的初始值由用户配置,上限值用户通过设置WWDG_CFR的第06位的值来决定(A点),下限值是固定的,为0x40(B点)。寄存器WWDG_CR的第06位用来存储看门狗的计数器值,此值是可以更新的,在逐渐递减,如果在递减到A之前进行喂狗的话,系统就复位,如果递减到A和B之间进行喂狗的话,系统不会复位,如果递减到B还不喂狗,如果有使能了提前唤醒中断,则进入此中断,如果没有使能提前唤醒中断,当递减计数器到了下一个计数0X3F(即T6位为0这一刻)的时候系统就复位了。之所以形象地称为窗口,就是前面说的,其喂狗时间是一个有上下限的范围(窗口),喂狗的时间不能过早也不能过晚。
(1)STM32MP157的窗口看门狗在以下两种情况之一会产生复位:
①当喂狗的时候,如果递减计数器的值大于设定数值WWDG_CFR[6:0](上限值)时;
②当递减计数器的数值从 0x40 减到 0x3F 时(T6位跳变为0时)。
(2)这里有几点要注意的:
①喂狗是指对递减计数器进行清零(重新装载计数器的值,也叫刷新窗口),让计时器重新开始递减计数。
②设置的窗口上限值必须大于下窗口值0x40(最大可以设为0X7F),否则窗口看门狗就没有窗口了。
③独立看门狗没有中断,超时直接复位。而窗口看门狗有中断,例如窗口看门狗的提前唤醒中断(EWI),如果启动了窗口看门狗并且允许提前唤醒中断,当递减计数器等于0x40时则触发提前唤醒中断,此中断可以用于喂狗以避免复位或者做复位前的一些函数操作,例如备份重要的数据、告警提示等等。
④一旦启用看门狗,除系统重置外,不能禁用WWDG。
(3)窗口看门狗的超时公式
知道了窗口看门狗的工作原理,下面学习如何计算窗口看门狗的超时公式。这里的超时(或者说溢出时间)是说:看门狗计数器从初始值递减到0X3F的时候经过了多长的时间,也就是WWDG计数器刷新时间范围(介于最大值和最小值之间)。

其中:
TWWDG:WWDG超时时间(单位为ms);
Fpclk1:APB1的时钟频率(单位为Khz);
WDGTB[2:0]:WWDG的预分频系数,用户配置,可以选0~7,如果选为0,20=1,如果选为7,则27=128。
T[5:0]:窗口看门狗的计数器的低5位,由用户配置,最小为0x00,最大为0x3F,配置的越大,超时时间越长。
根据以上公式,假设Fpclk1=104.5Mhz,那么可以得到最小与最大超时时间,如下表所示:
在这里插入图片描述

表15.1.1. 2 104.5MHz时钟下WWDG的最小最大超时表
我们后面的实验参数设置为:
PCLK1为104.5MHz;WDGTB=4;WWDG_CR[6:0]初始值=T[5:0]=0x3F;窗口值WWDG_CFR[6:0]= W[6:0]=0X57。即WDGTB分频系数为4,计时器初始值为128,窗口值为95。那么最大超时时间为40.14ms,计时器从128开始逐一递减,窗口值为95,则计算出窗口值对应的时间为:

在看门狗运行以后的第0ms20.70ms之间喂狗的话,系统就复位。在看门狗运行后的第20.70ms40.14ms之间喂狗的话,系统就不会复位,在40.14ms以后没有喂狗,系统就自动复位了。
在这里插入图片描述

图15.1.1. 4 WWDG工作示意图
3. 为什么要WWDG
窗口看门狗可以检查程序是否按预定逻辑运行,可以用于检测系统故障和检测软件错误。通过计算程序正常运行的时间来设置刷新看门狗的窗口阈值,这样可以保证在规定的时间范围内刷新看门狗。如果程序跑飞了,想要在规定的时间范围内喂狗是很困难的,程序跑飞导致没有及时喂狗,系统就复位了,我们就可以检测出程序出现异常了。
4. MCU的复位源
MCU的可能复位源有4个:系统复位、由MPU产生的复位、WWDG1复位(wwdg1_out_rst)、由MCU本身产生的复位。WWDG1以APB1时钟作为时钟,并提供复位和提前唤醒中断信号,此信号可以被MPU(GIC)和MCU(NVIC)中断控制器接收,发生超时(wwdg1_out_rst信号)时,WWDG1还会对MCU产生复位,如果WWDG1模块复位了MCU,该信号也被路由到EXTI,以唤醒MPU内核。
在这里插入图片描述

图15.1.1. 5 WWDG和MCU复位
15.1.2 WWDG寄存器
WWDG寄存器,我们重点介绍以下3个:

  1. 控制寄存器(WWDG_CR)
    窗口看门狗的控制寄存器描述如下图所示:
    在这里插入图片描述

图15.1.2. 1 WWDG_CR寄存器
该寄存器只有低八位有效,其中T[6:0]用来存储看门狗的计数器的值,随时更新的,每隔(4096×2^ WDGTB[2:0])PCLK个周期减1。当该计数器的值从0X40变为0X3F的时候,将产生看门狗复位。
WDGA位则是看门狗的激活位,该位由软件置1,启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。
2. 配置寄存器(WWDG_CFR)
配置寄存器描述如下图所示:
在这里插入图片描述

图15.1.2. 2 WWDG_CFR寄存器
该寄存器中的EWI位是提前唤醒中断,如果该位置1,当递减计数器等于0x40时产生提前唤醒中断,我们就可以及时喂狗以避免WWDG复位,且该中断仅在复位后由硬件清除。因此,我们一般都会用该位来设置中断,当窗口看门狗的计数器值减到0X40的时候,如果该位设置,并开启了中断,则会产生中断,我们可以在中断里面向WWDG_CR重新写入计数器的值,来达到喂狗的目的。注意这里在进入中断后,必须在不大于1个窗口看门狗计数周期的时间(在pclk3频率为120M且WDGTB[2:0]为0的条件下,该时间为34.13us)内重新写WWDG_CR,否则,看门狗将产生复位!
W[6:0] 为7 位窗口值,包含用于与递减计数器进行比较的窗口值。
WDGTB[2:0] 为定时器时基,可按如下方式修改预分频器的时基:
000:CK 计数器时钟 (PCLK div 4096) 分频器 1
001:CK 计数器时钟 (PCLK div 4096) 分频器 2
010:CK 计数器时钟 (PCLK div 4096) 分频器 4
011:CK 计数器时钟 (PCLK div 4096) 分频器 8
100:CK 计数器时钟 (PCLK div 4096) 分频器 16
101:CK 计数器时钟 (PCLK div 4096) 分频器 32
110:CK 计数器时钟 (PCLK div 4096) 分频器 64
111:CK 计数器时钟 (PCLK div 4096) 分频器 128
3. 状态寄存器(WWDG_SR)
在这里插入图片描述

图15.1.2. 3 WWDG_SR寄存器
该寄存器用来记录当前是否有提前唤醒的标志。该寄存器仅有位0有效,其他都是保留位。当计数器值达到0x40时,此位由硬件置1。它必须通过软件写0来清除。对此位写1无效。即使中断未被使能,在计数器的值达到0X40的时候,此位也会被置1。
4. 时钟使能寄存器
另外,要用WWDG的话,得使能WWDG的时钟,WWDG挂在APB1上,所以要配置寄存器RCC_MC_APB1ENSETR的第28位,将其置1来开启WWDG时钟,在后面的实验中,STM32CubeIDE生成的初始化代码会自动为我们做好这一步操作。
在这里插入图片描述

图15.1.2. 4 RCC_MC_APB1ENSETR寄存器
5. 其它寄存器
除了上面几个重要的寄存器以外,还有:
WWDG硬件配置寄存器(WWDG_HWCFGR),用于配置看门狗时钟预分频为4096;WWDG版本注册寄存器(WWDG_VERR),用于记录IP版本主要修订信息;WWDG_SIDR和WWDG ID寄存器。这些寄存器我们用不到,可以不管。
15.1.3 WWDG的HAL库驱动
WWDG的HAL库驱动代码在stm32mp1xx_hal_wwdg.c和stm32mp1xx_hal_wwdg.h文件中。

  1. 结构体和句柄
    (1)WWDG_InitTypeDef结构体
    WWDG_InitTypeDef结构体在stm32mp1xx_hal_wwdg.h文件中有定义,主要就是用于配置WWDG的分频值、窗口值、递减计数器初始值以及是否开启提前唤醒中断。通过对此结构体成员赋值就可以完成WWDG这些参数的初始化配置。
typedef struct
{
    
  uint32_t Prescaler;  					/* 指定WWDG的预分频器值 */
/* 指定要与递减计数器进行比较的WWDG窗口值,此窗口值必须在数字0x40和0x7F之间 */
  uint32_t Window;    
/* 指定WWDG递减计数器的初始值,此初始值必须是0x40和0x7F之间的数字 */
  uint32_t Counter;       
/* 指定是否使用提前唤醒中断 */
  uint32_t EWIMode ;      
} WWDG_InitTypeDef;

(2)WWDG_TypeDef
WWDG_TypeDef结构体在stm32mp157axx_cm4.h文件中有定义,主要是定义WWDG寄存器的偏移地址。在前面我们也多次强调过,外设的寄存器都在stm32mp157axx_cm4.h文件中通过结构体封装好了。WWDG涉及的寄存器不多,如下:

typedef struct
{
    
  __IO uint32_t CR;      			/* WWDG控制寄存器,地址偏移量:0x00 */
  __IO uint32_t CFR;     			/* WWDG配置寄存器,地址偏移量:0x04 */
  __IO uint32_t SR;      			/* WWDG状态寄存器,地址偏移量:0x08 */
  uint32_t  RESERVED1[249];   	/* 保留,0x0C-0x3EC */
  __IO uint32_t HWCFGR; 			/* WWDG硬件配置寄存器,地址偏移量:0x3F0 */
  __IO uint32_t VERR;    			/* WWDG版本寄存器,地址偏移量:0x3F4 */
  __IO uint32_t IPIDR;   			/* WWDG标识寄存器,地址偏移量:0x3F8 */
  __IO uint32_t SIDR;    			/* WWDG大小ID寄存器,地址偏移量:0x3FC */
} WWDG_TypeDef;

(3)WWDG_HandleTypeDef句柄定义
WWDG_HandleTypeDef句柄在stm32mp1xx_hal_wwdg.h文件中有定义,HAL库中通过句柄操作来操作外设。

#if (USE_HAL_WWDG_REGISTER_CALLBACKS == 1)
typedef struct __WWDG_HandleTypeDef
#else
typedef struct
#endif
{
    
  WWDG_TypeDef  *Instance;  				/* 寄存器基地址 */
  WWDG_InitTypeDef  Init;   				/* WWDG所需参数 */
#if (USE_HAL_WWDG_REGISTER_CALLBACKS == 1)
/* WWDG提前唤醒中断回调函数 */
  void (* EwiCallback)(struct __WWDG_HandleTypeDef *hwwdg);
/* WWDG Msp初始化回调函数 */
  void (* MspInitCallback)(struct __WWDG_HandleTypeDef *hwwdg);
#endif
} WWDG_HandleTypeDef;
这里提一下USE_HAL_WWDG_REGISTER_CALLBACKS这个宏,此宏定义默认为0,在stm32mp1xx_hal_conf.h文件中有定义。

/* 这是可以使用寄存器回调的模块列表 */

#define USE_HAL_ADC_REGISTER_CALLBACKS    0u
#define USE_HAL_CEC_REGISTER_CALLBACKS    0u
#define USE_HAL_DAC_REGISTER_CALLBACKS    0u
#define USE_HAL_I2C_REGISTER_CALLBACKS    0u
#define USE_HAL_RNG_REGISTER_CALLBACKS    0u
#define USE_HAL_SPI_REGISTER_CALLBACKS    0u
#define USE_HAL_UART_REGISTER_CALLBACKS   0u
#define USE_HAL_USART_REGISTER_CALLBACKS  0u
#define USE_HAL_WWDG_REGISTER_CALLBACKS   0u
如果这些宏定义为0,则默认使用HAL库中的回调函数,HAL库中默认的回调函数基本上都是弱定义的,所以用户可以重新定义一个同名的回调函数。那如果这些宏定义为1的话,则用户可以自己注册回调函数,原来弱定义的回调函数就被用户注册的回调函数替代了。用户注册WWDG回调函数部分在stm32mp1xx_hal_wwdg.h文件中有,如下:
#if (USE_HAL_WWDG_REGISTER_CALLBACKS == 1)
/**
  * @brief  注册用户WWDG回调函数,代替弱的预定义回调函数
  * @param  hwwdg:WWDG句柄
  * @param  CallbackID:要注册的回调的ID,此参数可以是下列值之一
  *           @arg @ref HAL_WWDG_EWI_CB_ID:提前唤醒中断回调ID
  *           @arg @ref HAL_WWDG_MSPINIT_CB_ID :MspInit 回调ID
  * @param pCallback:指向回调函数的指针
  * @retval 状态
  */
HAL_StatusTypeDef HAL_WWDG_RegisterCallback(WWDG_HandleTypeDef *hwwdg, HAL_WWDG_CallbackIDTypeDef CallbackID, pWWDG_CallbackTypeDef pCallback)
{
    
  HAL_StatusTypeDef status = HAL_OK;
  if(pCallback == NULL)
  {
    
    status = HAL_ERROR;
  }
  else
  {
    
    switch(CallbackID)
    {
    
      case HAL_WWDG_EWI_CB_ID:
        hwwdg->EwiCallback = pCallback;
        break;
      case HAL_WWDG_MSPINIT_CB_ID:
        hwwdg->MspInitCallback = pCallback;
        break;
      default:
        status = HAL_ERROR;
        break;
    }
  }

  return status;
}

/**
  * @brief  取消注册WWDG回调函数,WWDG回调被重定向到弱(收费)预定义的回调函数。
  * @param  hwwdg:WWDG句柄
  * @param   CallbackID:要注册的回调的ID,此参数可以是下列值之一
  *           @arg @ref HAL_WWDG_EWI_CB_ID:提前唤醒中断回调ID
  *           @arg @ref HAL_WWDG_MSPINIT_CB_ID :MspInit 回调ID
  * @retval status
  */
HAL_StatusTypeDef HAL_WWDG_UnRegisterCallback(WWDG_HandleTypeDef *hwwdg, HAL_WWDG_CallbackIDTypeDef CallbackID)
{
    
  HAL_StatusTypeDef status = HAL_OK;
  switch(CallbackID)
  {
    
    case HAL_WWDG_EWI_CB_ID:
      hwwdg->EwiCallback = HAL_WWDG_EarlyWakeupCallback;
      break;
    case HAL_WWDG_MSPINIT_CB_ID:
      hwwdg->MspInitCallback = HAL_WWDG_MspInit;
      break;
    default:
      status = HAL_ERROR;
      break;
  }
  return status;
}
#endif
其中HAL_WWDG_RegisterCallback是用于注册用户指定的回调函数的代码,我们就关注两行红色的代码,pCallback是一个指针,指向用户自定义的回调函数。此函数的代码也就是通过CallbackID来注册或者说指定用户自定义的WWDG的提前唤醒中断回调函数以及MspInit 回调函数,HAL库里的弱定义的回调函数就不用了(或者说被用户指定的回调函数替代了)。
HAL_WWDG_UnRegisterCallback函数表示取消注册,我们也是看上面两行标红的代码,函数指针指向了HAL库里的回调函数,也就是使用HAL库里的回调函数,不使用用户自定义的回调函数。取消注册就是注销用户自定义的回调函数。
我们后面的实验就默认使用HAL库里弱定义的回调函数,大家想自定义回调函数也是可以的。下面我们来看看HAL库中的API函数。
  1. HAL库中的API函数
    (1)HAL_WWDG_Init
    ●函数功能:HAL_WWDG_Init函数根据关联到句柄的WWDG_InitTypeDef中的参数来初始化WWDG。
    ●函数参数:
    hwwdg:指向WWDG_HandleTypeDef结构的指针,该结构包含指定WWDG的配置信息。
    ●函数返回值:枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(串口忙碌)、HAL_TIMEOUT(超时)
1   HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg)
2   {
    
3     /* 检查WWDG句柄分配 */
4     if (hwwdg == NULL)
5     {
    
6       return HAL_ERROR;
7     }
8     /* 检查参数 */
9     assert_param(IS_WWDG_ALL_INSTANCE(hwwdg->Instance));
10    assert_param(IS_WWDG_PRESCALER(hwwdg->Init.Prescaler));
11    assert_param(IS_WWDG_WINDOW(hwwdg->Init.Window));
12    assert_param(IS_WWDG_COUNTER(hwwdg->Init.Counter));
13    assert_param(IS_WWDG_EWI_MODE(hwwdg->Init.EWIMode));
14
15  #if (USE_HAL_WWDG_REGISTER_CALLBACKS == 1)
16    /* 重置回调指针 */
17    if(hwwdg->EwiCallback == NULL)
18    {
    
19      hwwdg->EwiCallback = HAL_WWDG_EarlyWakeupCallback;
20    }
21    if(hwwdg->MspInitCallback == NULL)
22    {
    
23      hwwdg->MspInitCallback = HAL_WWDG_MspInit;
24    }
25    /* 初始化底层硬件 */
26    hwwdg->MspInitCallback(hwwdg);
27  #else
28    /* 初始化底层硬件 */
29    HAL_WWDG_MspInit(hwwdg);
30  #endif
31    /* 设置WWDG计数器值 */
32    WRITE_REG(hwwdg->Instance->CR, (WWDG_CR_WDGA | 																		hwwdg->Init.Counter));
33    /* 设置WWDG预分频器和窗口 */
34    WRITE_REG(hwwdg->Instance->CFR, (hwwdg->Init.EWIMode | 									hwwdg->Init.Prescaler | hwwdg->Init.Window));
35    /* 返回功能状态 */
36    return HAL_OK;
37  }

(2)HAL_WWDG_Refresh
●函数功能:刷新WWDG
●函数参数:
hwwdg:指向WWDG_HandleTypeDef结构的指针,该结构包含指定WWDG的配置信息。
●函数返回值:枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(串口忙碌)、HAL_TIMEOUT(超时)
HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef hwwdg)
{
/
将WWDG计数器值写入WWDG CR寄存器以刷新 */
WRITE_REG(hwwdg->Instance->CR, (hwwdg->Init.Counter));
return HAL_OK;
}
HAL_WWDG_Refresh函数很简单,调用WRITE_REG函数,将句柄中设置的计数器的初始值赋给WWDG_CR寄存器的低6位,从而实现刷新计时器。
(3)HAL_WWDG_IRQHandler函数
●函数功能:处理WWDG中断请求
●函数参数:
hwwdg:指向WWDG_HandleTypeDef结构的指针,该结构包含指定WWDG的配置信息。
●函数返回值:枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(串口忙碌)、HAL_TIMEOUT(超时)
●注意:如果开启了提前唤醒中断,当递减计数器达到值0x40时可以产生提前唤醒中断,如果特定的安全操作或必须在实际复位产生之前执行数据记录,则可以使用提前唤醒中断(EWI)

1   void HAL_WWDG_IRQHandler(WWDG_HandleTypeDef *hwwdg)
2   {
    
3     /* 检查是否启用了提前唤醒中断 */
4     if (__HAL_WWDG_GET_IT_SOURCE(hwwdg, WWDG_IT_EWI) != RESET)
5     {
    
6       /* 检查是否发生了WWDG提前唤醒中断 */
7       if (__HAL_WWDG_GET_FLAG(hwwdg, WWDG_FLAG_EWIF) != RESET)
8       {
    
9         /* 清除WWDG提前唤醒标志 */
10        __HAL_WWDG_CLEAR_FLAG(hwwdg, WWDG_FLAG_EWIF);
11
12  #if (USE_HAL_WWDG_REGISTER_CALLBACKS == 1)
13        /* 提前唤醒注册回调函数 */
14        hwwdg->EwiCallback(hwwdg);
15  #else
16        /* 提前唤醒回调函数 */
17        HAL_WWDG_EarlyWakeupCallback(hwwdg);
18  #endif
19      }
20    }
21  }
HAL_WWDG_IRQHandler处理中断请求函数也是比较简单。
第4行,程序通过读取WWDG_CFR寄存器的EWI位来判断是否已经开启提前唤醒中断,如果已经开启提前唤醒中断,则通过第7行,读取WWDG_SR寄存器的提前唤醒中断标志EWIF来判断是否有提前唤醒中断发生,如果有发生提前唤醒中断,则先清除提前唤醒中断在去调用提前唤醒中断回调函数来做相应的处理。在外部中断实验章节,我们讲过,发生中断以后,进入中断处理函数以后记得清理中断标志位,如果不清理,则会导致执行完回调函数以后还会重新进入中断,导致程序卡在中断中,无法返回主函数。
第12~18行,如果用户有自定义回调函数,则执行用户定义的提前唤醒中断回调函数,如果用户没有自定义,则默认执行HAL库里的回调函数。

(4)其它函数
此外还有HAL_WWDG_MspInit初始化函数和HAL_WWDG_EarlyWakeupCallback提前唤醒中断回调函数,这两个函数都是弱定义的。STM32CubeIDE会自动为我们生成一个新的HAL_WWDG_MspInit函数,用于开启HSEM和设置中断优先级分组。而HAL_WWDG_EarlyWakeupCallback函数的代码需要我们手动去实现。
15.2 WWDG实验
15.2.1 硬件设计

  1. 例程功能
    在程序中开启提前唤醒中断,并在回调函数中实现自动喂狗。程序运行后,先点亮LED0 100ms的时间,再初始化WWDG,然后再关闭LED0,当发生喂狗时,LED1会出现翻转,当发生MCU复位时,会看到LED0再次点亮。我们就是通过LED1灯的翻转来观察是否有在中断喂狗的。
  2. 硬件资源
    1)LED灯:
    LED0 LED1 总线
    PI0 PF3 AHB4
    表15.2.1. 1 硬件资源表
    2)窗口看门狗:位于APB1总线上。
  3. 原理图
    窗口看门狗属于STM32MP157的内部资源,只需要软件设置好即可正常工作。我们通过LED0和LED1来指示STM32MP157的复位情况和窗口看门狗的喂狗情况。
    15.2.2 软件设计
    本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 8 WWDG。
  4. 程序流程图
    在这里插入图片描述

图15.2.2. 1程序流程图
2. 生成工程
(1)配置LED灯引脚
按照跑马灯实验配置PI0和PF3为推挽输出、上拉、高速模式,User Label保持和之前设置的一致,因为我们后面会使用前面实验的led.h和led.c文件。当然,也可以直接在跑马灯实验的工程上直接配置WWDG,省去重新创建工程的麻烦。
在这里插入图片描述

图15.2.2. 2配置LED引脚
(2)配置WWDG
打开左边的System CoreWWDG1,配置WWDG参数,我们就按照前面说的,配置WDGTB分频值为16,计时器初始值为128,窗口值为95,并使能提前唤醒中断。
在这里插入图片描述

图15.2.2. 3配置WWDG参数
在NVIC Settings处使能看门狗中断,此时中断抢占优先级和子优先级均为0:
在这里插入图片描述

图15.2.2. 4使能WWDG中断
0优先级是最高的,而且系统级别的中断以及滴答定时器中断的优先级也是0,所以我们到NVIC设置处配置看门狗中断优先级,设置中断优先级分组为2,抢占优先级和子优先级均为3,设置为其它优先级也是可以的,我们在前面的外部中断实验章节有分析过。
在这里插入图片描述

图15.2.2. 5配置WWDG中断优先级
(3)时钟和工程配置
时钟的话,我们就配置PCLK1为104.5MHz,采用内部时钟或者外部时钟都可以,如果采用外部时钟,可以参考前面时钟系统章节来配置,这里我们采用默认的HSI,配置如下:
在这里插入图片描述

图15.2.2. 6配置PCLK1时钟为104.5MHz
配置完时钟,返回Project Manager配置界面,勾选Generate peripheral initialization as a pair of ".c/.h’ files per peripheral选项,这样可以独立生成对应外设的初始化.h和.c 文件(这么做也是为了不让外设的初始化代码生成在main.c文件中,方便查看,也避免main.c文件变得臃肿):
在这里插入图片描述

图15.2.2. 7配置生成独立的初始化代码
(4)生成初始化代码
按下“Ctrl+S”保存配置,生成工程,如下图,可以看到在工程中多了一个gpio.c文件和wwdg.c文件。gpio.c文件我们前面有介绍过,主要是完成gpio的初始化。wwdg.c主要是完成WWDG的初始化。
在这里插入图片描述

图15.2.2. 8生成工程
3. 添加用户代码
(1)添加跑马灯驱动
将跑马灯实验创建的BSP文件夹拷贝到Src文件夹下,如下图:
在这里插入图片描述

图15.2.2. 9添加LED初始化代码
(2)在main.c中添加如下代码:

led_init();						/* 关闭 LED0和LED1 */
LED0(0);  							/* 打开 LED0 */
HAL_Delay(100);
/* while循环中添加如下代码 */
LED0(1);							/* 关闭 LED0 */

图15.2.2. 10 main.c中添加逻辑代码
(3)在led.c.c文件中添加代码如下

#include "./Include/led.h"
#include "wwdg.h"
void led_init(void)
{
    
    LED0(1);    		/* 关闭 LED0 */
    LED1(1);   		 	/* 关闭LED1 */
}
/**
 * @brief       窗口看门狗中断服务回调函数
 * @param       无
 * @note        此函数会被HAL_WWDG_IRQHandler()调用
 * @retval      无
 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
    
    HAL_WWDG_Refresh(&hwwdg1);	/* 刷新窗口看门狗值 */
    LED1_TOGGLE();					/* LED1灯翻转 */
}

在这里插入图片描述

图15.2.2. 11自定义回调函数
其中在led_init函数中,先将LED1关闭了,我们先添加代码,测试现象,后面我们再体分析代码。
4. 编译和测试
保存修改后点击工具栏的小锤子进行编译,编译无报错后,按照第4.1.6小节连接好开发板和ST-Link,进入Debug模式。进入Debug以后,点击继续运行按钮来运行调试,可以看到开发板底板的LED0 灯先是亮了一下就灭了,然后接着就是LED1在不停闪烁,说明程序在喂狗。
5. 工程代码分析
gpio.c文件我们就不分析了。下面我们来看看其它文件:
(1)wwdg.c文件
wwdg.c文件的内容如下:

1   /** 
2    * @brief    初始化窗口看门狗
3    * @note     fprer:分频系数(WDGTB),范围:0~7,表示2^WDGTB分频
4    *           Fwwdg=PCLK1(APB1)/(4096*2^fprer). 一般PCLK1=104.5Mhz
5    *           溢出时间=(4096*2^fprer)*(T[6:0]-0X3F)/PCLK1
6    *           假设fprer=4,T[6:0]=0X7f,W[6:0]=0X5F,PCLK1=104.5Mhz
7    *           则溢出时间=4096*16*64/104.5Mhz=40.14ms
8    * @retval   无
9    */ 
10  #include "wwdg.h"
11
12  WWDG_HandleTypeDef hwwdg1;						/* 窗口看门狗句柄 */
13
14  /* WWDG1初始化功能 */
15  void MX_WWDG1_Init(void)
16  {
    
17    hwwdg1.Instance = WWDG1;						/* 操作WWDG1 */
18    hwwdg1.Init.Prescaler = WWDG_PRESCALER_16;	/* 设置分频系数WDGTB */
19    hwwdg1.Init.Window = 95;						/* 设置窗口值 */
20    hwwdg1.Init.Counter = 127;						/* 设置计数器值 */
21    /* 使能窗口看门狗提前唤醒中断 */
22    hwwdg1.Init.EWIMode = WWDG_EWI_ENABLE;
23    if (HAL_WWDG_Init(&hwwdg1) != HAL_OK)		/* 初始化WWDG */
24    {
    
25      Error_Handler();
26    }
27
28  }
29  /**
30   * @brief       WWDG底层驱动
31   * @param       wwdgHandle:窗口看门狗句柄
32   * @note        此函数会被HAL_WWDG_Init()调用
33   * @retval      无
34   */
35  void HAL_WWDG_MspInit(WWDG_HandleTypeDef* wwdgHandle)
36  {
    
37    if(wwdgHandle->Instance==WWDG1)
38    {
    
39      /* WWDG1时钟使能 */
40      __HAL_RCC_WWDG1_CLK_ENABLE();
41      /* WWDG1中断优先级初始化 */
42      HAL_NVIC_SetPriority(WWDG1_IRQn, 3, 3);
43      /* WWDG1中断使能 */
44      HAL_NVIC_EnableIRQ(WWDG1_IRQn);
45    }
46  }

第12行,定义一个句柄hwwdg1;
第17~22行,将设置的窗口值、分频系数WDGTB、计数器初始值、提前唤醒中断使能赋值给句柄hwwdg1;
第23~26行,通过句柄赋值实现WWDG初始化,初始化后的WWDG,窗口值为95,分频系数WDGTB为4,计数器初始值为128。
第40行,开启WWDG时钟,WWDG挂在APB1总线上,通过将APB1外设使能设置寄存器RCC_MC_APB1ENSETR的第28位WWDG1EN置1来开启WWDG的时钟。
第42~44行,设置WWDG的抢占优先级和子优先级为3,并使能WWDG,WWDG1_IRQn是个中断号,在stm32mp157dxx_cm4.h文件中有定义,我们之前也多次讲过中断号和中断向量表以及中断服务函数的关系,这里就不再讲解了。
(2)stm32mp1xx_hal_msp.c文件
stm32mp1xx_hal_msp.c文件下就一个HAL_MspInit函数,主要是开启HSEM,设置中断优先级分组为2。HAL_MspInit函数最后会被HAL_Init函数调用,完成HAL库的初始化。
#include “main.h”
/* 初始化全局MSP /
void HAL_MspInit(void)
{
/
开启HSEMEN外设时钟 /
__HAL_RCC_HSEM_CLK_ENABLE();
/
设置中断优先级分组为2 /
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
}
(3)stm32mp1xx_it.c文件
stm32mp1xx_it.c文件我们前面也是多次介绍过,这里只贴出和WWDG相关的代码:
/
中断服务函数 */
void WWDG1_IRQHandler(void)
{
/*中断请求函数 */
HAL_WWDG_IRQHandler(&hwwdg1);
}
我们在前面API函数分析部分有介绍此函数,中断服务函数中通过调用中断请求函数来完成中断,而中断请求函数中调用了回调函数HAL_WWDG_EarlyWakeupCallback,此回调函数使用的是HAL库里默认的弱定义函数,需要我们自己定义一个回调函数来实现中断的处理工作。
(4)led.c文件
led.c文件是从前面的跑马灯实验拷贝过来用的,我们在led.c文件中定义了回调函数HAL_WWDG_EarlyWakeupCallback以实现刷新计数器(喂狗),当然回调函数也可以定义在其它地方。也就是,当递减计数器从初始值128递减到0X40(10进制为64)的时候就发生提前唤醒中断,我们就在中断中喂狗,所以系统就不会复位了,每次喂狗,LED1会翻转一次。每当计数器从128递减到64的时候,LED1翻转一次,然后中断里刷新计数器,计数器又重新从128开始递减,递减到了64的时候又开始喂狗。计数器从128递减到64的时候也就过了40.14ms,这个时间很短是吧,所以你会发现LED1灯在频繁翻转。如果程序设计有缺陷跑飞了,要在这么短的时间内去喂狗还是有些困难的,所以窗口看门狗常用于检测系统缺陷。
(5)main.c文件
main.c文件的时钟初始化代码我们在前面的时钟系统章节有分析过,这里就不粘贴出来了。

1   #include "main.h"
2   #include "wwdg.h"
3   #include "gpio.h"
4 
5   /* USER CODE BEGIN Includes */
6   #include "./BSP/Include/led.h"
7   /* USER CODE END Includes */
8 
9   void SystemClock_Config(void);
10
11  int main(void)
12  {
    
13    HAL_Init();
14
15    if(IS_ENGINEERING_BOOT_MODE())
16    {
    
17      /* 初始化系统时钟 */
18      SystemClock_Config();
19    }
20
21    /* 初始化已经配置的GPIO */
22    MX_GPIO_Init();
23    led_init();				/* 关闭 LED0和LED1 */
24    LED0(0);  				/* 打开 LED0 */
25    HAL_Delay(100);			/* 先延时100ms再打开WWDG,让LED0的变化"可见" */
26    MX_WWDG1_Init();		/* 初始化WWDG */
27    /* USER CODE BEGIN 2 */
28
29    while (1)
30    {
    
31      /* USER CODE BEGIN 3 */
32        LED0(1);				/* 关闭 LED0 */
33    }
34    /* USER CODE END 3 */
35  }

main.c的代码比较简单,也就是先关闭LED1,再打开LED0,然后再初始化WWDG,再在while循环中将LED0关闭。所以实验中会看到,LED0是先亮了一下,当程序进入主函数以后LED0就关闭了,接着就是发生提前唤醒中断,中断中会喂狗,然后就看到LED1在闪烁。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_55796564/article/details/123176040

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签