【正点原子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

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法