0
点赞
收藏
分享

微信扫一扫

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验

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


本章节我们来学习STM32MP1的窗口看门狗(WWDG)的使用方法,我们使用窗口看门狗的中断功能来喂狗,并通过LED灯来观察喂狗和复位情况。

本章将分为如下几个小节:

22.1、WWDG简介;

22.2、WWDG实验;


22.1 WWDG简介

22.1.1 STM32MP157看门狗

1. 看门狗介绍

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

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_回调函数


22.1.1. 1看门狗资源

2. 窗口看门狗框图

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

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_看门狗_02


22.1.1. 2 WWDG框图

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

信号名称

信号类型

说明

pclk

数字信号

APB1 总线时钟(PCLK1)

wwdg_out_rst

数字信号

WWDG1 复位信号输出

wwdg_it

数字信号

WWDG1 中断输出

22.1.1. 1 WWDG内部输入/输出信号

对框图的说明如下:

  1. pclkpclk时钟是由PCLK1提供的,最大可为104.5MHz,后面的分频系数(2WDGTB)由用户去配置,那么给WWDG时钟频率=PCLK1/(4096*2WDGTB)。
  2. T[6:0]控制寄存器的低7 位WWDG_CR[6:0]用于递减计数,也就是图中的计数器T[6:0],它的初始值用户可配置,可以配置这7位都为1,这个就是最大的初始值(0X7F)。递减计数器每计数一次的时间间隔=(4096*2WDGTB)/ Fpclk,如果要计数N次,则要用的时间为:N*(4096*2WDGTB)/ Fpclk。递减计数器最低可以递减到0X3F。
  3. W[6:0]配置寄存器的低7位WWDG_CFR[6:0]也就是图中的W[6:0],用于与控制寄存器的低7 位的计数器(T[6:0])的值进行比较,WWDG_CFR[6:0]的值也就是我们说的上限值,由用户设置,不能设置为0X40,最大可以设置等于递减计数器的初始值。
  4. T6位

就是控制寄存器的第6位,即递减计数器T[6:0]的最高位,当递减计数器从0X40递减到0X3F的时候就产生复位,而第6位从1变成0。0X40就是窗口的下限值,此值是固定的。

我们用一张图来说明WWDG的工作过程:

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_回调函数_03


22.1.1. 3工作示意图

上图中,窗口看门狗的递减计数器的初始值由用户配置,上限值用户通过设置WWDG_CFR的第0~6位的值来决定(A点),下限值是固定的,为0x40(B点)。寄存器WWDG_CR的第0~6位用来存储看门狗的计数器值,此值是可以更新的,在逐渐递减,如果在递减到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计数器刷新时间范围(介于最大值和最小值之间)。

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_寄存器_04


其中:

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,那么可以得到最小与最大超时时间,如下表所示:

WDGTB

最小超时(us)T[5:0]=0x00

最大超时(ms)T[5:0]=0x3F

0

39.20

2.51

1

78.39

5.02

2

156.78

10.03

3

313.57

20.07

4

627.14

40.14

5

1254.28

80.27

6

2508.56

160.55

7

5017.11

321.10

22.1.1. 2时钟下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,则计算出窗口值对应的时间为:

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_看门狗_05


在看门狗运行以后的第0ms~20.70ms之间喂狗的话,系统就复位。在看门狗运行后的第20.70ms~40.14ms之间喂狗的话,系统就不会复位,在40.14ms以后没有喂狗,系统就自动复位了。

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_看门狗_06


22.1.1. 4工作示意图

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内核。

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_回调函数_07


22.1.1. 5和MCU复位

22.1.2 WWDG寄存器

WWDG寄存器我们重点介绍以下3个:

1. 控制寄存器(WWDG_CR)

窗口看门狗的控制寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_看门狗_08


22.1.2. 1寄存器

该寄存器只有低八位有效,其中T[6:0]用来存储看门狗的计数器的值,随时更新的,每隔(4096×2^ WDGTB[2:0])PCLK个周期减1。当该计数器的值从0X40变为0X3F的时候,将产生看门狗复位。

WDGA位则是看门狗的激活位,该位由软件置1,启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。

2. 配置寄存器(WWDG_CFR)

配置寄存器描述如下图所示:

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_回调函数_09


22.1.2. 2寄存器

该寄存器中的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)

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_回调函数_10


22.1.2. 3寄存器

该寄存器用来记录当前是否有提前唤醒的标志。该寄存器仅有位0有效,其他都是保留位。当计数器值达到0x40时,此位由硬件置1。它必须通过软件写0来清除。对此位写1无效。即使中断未被使能,在计数器的值达到0X40的时候,此位也会被置1。

4. 时钟使能寄存器

另外,要用WWDG的话,得使能WWDG的时钟,WWDG挂在APB1上,所以要配置寄存器RCC_MC_APB1ENSETR的第28位,将其置1来开启WWDG时钟,在后面的实验中,STM32CubeIDE生成的初始化代码会自动为我们做好这一步操作。

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_看门狗_11


22.1.2. 4寄存器

5. 其它寄存器

除了上面几个重要的寄存器以外,还有:

WWDG硬件配置寄存器(WWDG_HWCFGR),用于配置看门狗时钟预分频为4096;WWDG版本注册寄存器(WWDG_VERR),用于记录IP版本主要修订信息;WWDG_SIDR和WWDG ID寄存器。这些寄存器我们用不到,可以不管。

22.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)​
/**​
注册用户WWDG回调函数,代替弱的预定义回调函数​
:WWDG句柄​
:要注册的回调的ID,此参数可以是下列值之一​
:提前唤醒中断回调ID​
:MspInit 回调ID​
:指向回调函数的指针​
状态​
*/​
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:WWDG句柄​
* @param:要注册的回调的ID,此参数可以是下列值之一​
* @arg @ref:提前唤醒中断回调ID​
* @arg @ref: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函数。

2. HAL库中的API函数

(1)HAL_WWDG_Init

  1. 函数功能:HAL_WWDG_Init函数根据关联到句柄的WWDG_InitTypeDef中的参数来初始化WWDG。
  2. 函数参数: hwwdg:指向WWDG_HandleTypeDef结构的指针,该结构包含指定WWDG的配置信息。
  3. 函数返回值:枚举型,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

  1. 函数功能:刷新WWDG
  2. 函数参数: hwwdg:指向WWDG_HandleTypeDef结构的指针,该结构包含指定WWDG的配置信息。
  3. 函数返回值:枚举型,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;​
}


  1. HAL_WWDG_Refresh函数很简单,调用WRITE_REG函数,将句柄中设置的计数器的初始值赋给WWDG_CR寄存器的低6位,从而实现刷新计时器。
    (3)HAL_WWDG_IRQHandler函数
  2. 函数功能:处理WWDG中断请求
  3. 函数参数: hwwdg:指向WWDG_HandleTypeDef结构的指针,该结构包含指定WWDG的配置信息。
  4. 函数返回值:枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(串口忙碌)、HAL_TIMEOUT(超时)
  5. 注意:如果开启了提前唤醒中断,当递减计数器达到值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函数的代码需要我们手动去实现。

22.2 WWDG实验

22.2.1 硬件设计

1. 例程功能

在程序中开启提前唤醒中断,并在回调函数中实现自动喂狗。程序运行后,先点亮LED0 100ms的时间,再初始化WWDG,然后再关闭LED0,当发生喂狗时,LED1会出现翻转,当发生MCU复位时,会看到LED0再次点亮。我们就是通过LED1灯的翻转来观察是否有在中断喂狗的。

2. 硬件资源

1)LED灯:

LED0

LED1

总线

PI0

PF3

AHB4

22.2.1. 1硬件资源表

2)窗口看门狗:位于APB1总线上。

3. 原理图

窗口看门狗属于STM32MP157的内部资源,只需要软件设置好即可正常工作。我们通过LED0和LED1来指示STM32MP157的复位情况和窗口看门狗的喂狗情况。

22.2.2 程序设计

本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\3、M4裸机驱动例程\库V1.2\实验11 窗口看门狗实验

1. 程序流程图

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_看门狗_12


22.2.2. 1程序流程图

2. 新建工程

在上一章节实验工程的基础上,我们在工程的Drivers\BSP下新建WWDG文件夹文件夹下新建wwdg.c和wwdg.h文件,此外,工程中要关联HAL库的stm32mp1xx_hal_wwdg.c文件

《STM32MP1 M4裸机HAL库开发指南》第二十二章 窗口门狗(WWDG)实验_寄存器_13


图22.2.2.2新建和关联文件

3. 添加用户代码

(1)wwdg.h文件

wwdg.h文件代码如下

#ifndef __WWDG_H​
#define __WWDG_H​
#include "./SYSTEM/sys/sys.h"​
void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer);​
#endif​
(2)wwdg.c文件​
wwdg.c文件代码如下:​
1 #include "./BSP/WWDG/wwdg.h"​
2 #include "./BSP/LED/led.h"​
3 ​
4 WWDG_HandleTypeDef g_wwdg_handle; /* 窗口看门狗句柄 */​
5 /**​
6 * @brief初始化窗口看门狗​
7 * @param计数器值​
8 * @param窗口值​
9 * @note分频系数(WDGTB),范围:0~7,表示2^WDGTB分频​
10一般PCLK1=104.5Mhz​
11溢出时间=(4096*2^fprer)*(tr-0X3F)/PCLK1​
12假设fprer=4,tr=7f,PCLK3=104.5Mhz​
13 则溢出时间=4096*16*64/104.5Mhz=40.13ms​
14 * @retval无​
15 */ ​
16 void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer)​
17 {​
18 g_wwdg_handle.Instance = WWDG1;​
19 g_wwdg_handle.Init.Prescaler = fprer; /* 设置分频系数 */​
20 g_wwdg_handle.Init.Window = wr; /* 设置窗口值 */​
21 g_wwdg_handle.Init.Counter = tr; /* 设置计数器值 */​
22 g_wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE;/* 使能窗口看门狗提前唤醒中断 */​
23 HAL_WWDG_Init(&g_wwdg_handle); /* 初始化WWDG */​
24 }​
25 /**​
26 * @brief底层驱动​
27 * @param窗口看门狗句柄​
28 * @note此函数会被HAL_WWDG_Init()调用​
29 * @retval无​
30 */​
31 void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)​
32 {​
33 __HAL_RCC_WWDG1_CLK_ENABLE(); /* 使能窗口看门狗时钟 */​
34 ​
35 HAL_NVIC_SetPriority(WWDG1_IRQn,2,3); /* 抢占优先级2,子优先级为3 */​
36 HAL_NVIC_EnableIRQ(WWDG1_IRQn); /* 使能窗口看门狗中断 */​
37 }​
38 /**​
39 * @brief窗口看门狗中断服务程序​
40 * @param无​
41 * @retval无​
42 */​
43 void WWDG1_IRQHandler(void)​
44 {​
45 HAL_WWDG_IRQHandler(&g_wwdg_handle);​
46 }​
47 /**​
48 * @brief窗口看门狗中断服务回调函数​
49 * @param无​
50 * @note此函数会被HAL_WWDG_IRQHandler()调用​
51 * @retval无​
52 */​
53 void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)​
54 {​
55 HAL_WWDG_Refresh(&g_wwdg_handle);/* 更新窗口看门狗值 */​
56 LED1_TOGGLE(); /* 绿灯闪烁 */​
57 }

第4行,定义一个窗口看门狗句柄g_wwdg_handle

第16~24行,设置窗口看门狗的分频系数fprer(分频值可以在调用函数的时候指定,很方便)、窗口值计数器初始值,并使能提前唤醒中断最后根据这些参数调用HAL_WWDG_Init函数完成窗口看门狗初始化,其中,HAL_WWDG_Init函数会调用HAL_WWDG_MspInit函数使能窗口看门狗时钟使能窗口看门狗中断以及设置抢占优先级为2和子优先级为3;

第43~46行,是窗口看门狗中断服务函数,其调用了中断请求函数HAL_WWDG_IRQHandler完成窗口看门狗的中断处理

第53~57行,窗口看门狗的回调函数。回调函数HAL_WWDG_EarlyWakeupCallback可以实现刷新计数器(喂狗)也就是,当递减计数器从初始值128递减到0X40(10进制为64)的时候就发生提前唤醒中断,我们就在中断中喂狗,所以系统就不会复位了,每次喂狗,LED1会翻转一次。每当计数器从128递减到64的时候,LED1翻转一次,然后中断里刷新计数器,计数器又重新从128开始递减,递减到了64的时候又开始喂狗。计数器从128递减到64的时候也就过了40.14ms,这个时间很短是吧,所以你会发现LED1灯在频繁翻转。如果程序设计有缺陷跑飞了,要在这么短的时间内去喂狗还是有些困难的,所以窗口看门狗常用于检测系统缺陷。

(3)main.c文件

main.c文件代码如下:

#include "./SYSTEM/sys/sys.h"​
#include "./SYSTEM/delay/delay.h"​
#include "./SYSTEM/usart/usart.h"​
#include "./BSP/LED/led.h"​
#include "./BSP/BEEP/beep.h"​
#include "./BSP/KEY/key.h"​
#include "./BSP/WWDG/wwdg.h"​
/**​
* @brief主函数​
* @param无​
* @retval无​
*/​
int main(void)​
{​
HAL_Init(); /* 初始化HAL库 */​
/* 初始化M4内核时钟,209M */​
if(IS_ENGINEERING_BOOT_MODE())​
{​
sys_stm32_clock_init(34, 2, 2, 17, 6826);​
}​
delay_init(209); /* 延时初始化 */​
led_init(); /* 初始化LED */​
LED0(0); /* 点亮LED0(红灯) */​
delay_ms(300); /* 延时100ms再初始化看门狗,LED0的变化"可见" */​
/* 计数器值为7f,窗口寄存器为5f,分频数为16 */ ​
wwdg_init(0x7F, 0x5F, WWDG_PRESCALER_16); ​
while(1) ​
{​
LED0(1); /* 关闭红灯 */​
}​
}

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

22.3 编译和测试

编译无报错后,运行可以发现LED0是先亮了一下然后关闭了,接着看到看到LED1在闪烁实验结果和我们程序设计一致


举报

相关推荐

0 条评论