本帖最后由 正点原子运营 于 2022-12-26 15:16 编辑
第十九章 窗口门狗(WWDG)实验
1)实验平台:正点原子MiniPro STM32H750开发板
2) 章节摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
6)MiniPro STM32H750技术交流QQ群:170313895
本章我们学习如何使用STM32H7的另外一个看门狗,窗口看门狗(以下简称WWDG)。我们将使用窗口看门狗的中断功能来喂狗,通过LED0和LED1提示程序的运行状态。 本章分为如下几个小节: 19.1 WWDG简介 19.2 硬件设计 19.3 程序设计 19.4 下载验证
19.1 WWDG简介窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。窗口看门狗跟独立看门狗一样,也是一个递减计数器,不同的是它们的复位条件不一样。窗口看门狗产生复位信号有两个条件: 1) 当递减计数器的数值从0x40减到0x3F时(T6位跳变到0)。 2) 当喂狗的时候如果计数器的值大于W[6:0]时,此数值在WWDG_CFR寄存器定义。 上述的两个条件详细解释是,当计数器的值减到0X40时还不喂狗的话,到下一个计数就会产生复位,这个值称为窗口的下限值,是固定的值,不能改变。这个跟独立看门狗类似,不同的是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值W[6:0]由用户设置。窗口看门狗计数器的上限值和下限值就是窗口的含义,喂狗也必须在窗口之内,否则就会复位。
19.1.1 WWDG框图下面先来学习WWDG框图,通过学习WWDG框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。 图19.1.1.1 WWDG框图
从WWDG框图整体认知就是,WWDG有一个输入(时钟pclk),经过一个除4096的分频器,再经过一个分频系数可选(1、2、4、8…128)的可编程预分频器提供时钟给一个7位递减计数器,这里有两个输出信号。具体如下表: 表19.1.1.1 WWDG内部输入/输出信号
这里需要注意的就是pclk时钟是由PCLK3提供,由前面十一章的内容知道,我们的例程在系统时钟设置函数中把PCLK3的时钟频率设置为120Mhz。 结合寄存器分析窗口看门狗的上限值和下限值。W[6:0] 是WWDG_CFR寄存器的低7位,用于与递减计数器比较的窗口值,也就是我们说的上限值,由用户设置。0x40就是下限值,递减计数器达到这个值就会产生复位。T6位就是WWDG_CR寄存器的位6,即递减计数器T[6:0]的最高位。他们的关系可以用图19.1.1.2来说明: 图19.1.1.2 窗口看门狗工作示意图
图19.1.2可以看出,递减计数器的值递减过程中,当T[6:0]>W[6:0]是不允许刷新T[6:0]的值,即不允许喂狗,否则会产生复位。只有在W[6:0]<T[6:0]< 0x3F这个时间可以喂狗,这就是喂狗的窗口时间。当T[6:0]=0x3F,即T6位为0这一刻,也会产生复位。 上限值W[6:0]是由用户自己设置,但是一定要确保大于0X40,否则就不存在上图的窗口了,下限值0x40是固定的,不可修改。 知道了窗口看门狗的工作原理,下面学习如何计算窗口看门狗的超时公式: Twwdg=(4096×2^WDGTB[2:0]×(T[5:0]+1)) /Fpclk3 其中: Twwdg:WWDG超时时间(单位为ms) Fpclk3:APB3的时钟频率(单位为Khz) WDGTB[2:0]:WWDG的预分频系数,0~7 T[5:0]:窗口看门狗的计数器低6位 根据以上公式,假设Fpclk3=120Mhz,那么可以得到最小-最大超时时间表如下表所示: 表19.1.1.2 120M时钟下窗口看门狗的最小最大超时表
19.1.2 WWDG寄存器WWDG只有3个寄存器,具体如下:
l 控制寄存器(WWDG_CR) 窗口看门狗的控制寄存器描述如图19.1.2.1所示: 图19.1.2.1 WWDG_CR寄存器
该寄存器只有低八位有效,其中T[6:0]用来存储看门狗的计数器的值,随时更新的,每隔(4096×2^ WDGTB[2:0])PCLK个周期减1。当该计数器的值从0X40变为0X3F的时候,将产生看门狗复位。 WDGA位则是看门狗的激活位,该位由软件置1,启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。
l 配置寄存器(WWDG_CFR) 配置寄存器描述如图19.1.2.2所示: 图19.1.2.2 WWDG_CFR寄存器
该寄存器中的EWI位是提前唤醒中断,如果该位置1,当递减计数器等于0x40时产生提前唤醒中断,我们就可以及时喂狗以避免WWDG复位。因此,我们一般都会用该位来设置中断,当窗口看门狗的计数器值减到0X40的时候,如果该位设置,并开启了中断,则会产生中断,我们可以在中断里面向WWDG_CR重新写入计数器的值,来达到喂狗的目的。注意这里在进入中断后,必须在不大于1个窗口看门狗计数周期的时间(在pclk3频率为120M且WDGTB[2:0]为0的条件下,该时间为34.13us)内重新写WWDG_CR,否则,看门狗将产生复位!
l 状态寄存器(WWDG_SR) 该寄存器用来记录当前是否有提前唤醒的标志。该寄存器仅有位0有效,其他都是保留位。当计数器值达到0x40时,此位由硬件置1。它必须通过软件写0来清除。对此位写1无效。即使中断未被使能,在计数器的值达到0X40的时候,此位也会被置1。
19.2 硬件设计
1. 例程功能先点亮LED0延时300ms后,初始化窗口看门狗,进入死循环,关闭LED0。然后等待窗口看门狗中断的到来,在中断里面,喂狗,并执行LED1的翻转操作。我们将通过LED0来指示STM32H7是否被复位了,如果被复位了就会点亮300ms。LED1用来指示中断喂狗,每次中断喂狗翻转一次。
2. 硬件资源1)RGB灯 RED :LED0 - PB4 GREEN :LED1 - PE6 2)窗口看门狗
3. 原理图窗口看门狗属于STM32H750的内部资源,只需要软件设置好即可正常工作。我们通过LED0和LED1来指示STM32H750的复位情况和窗口看门狗的喂狗情况。
19.3 程序设计
19.3.1 WWDG的HAL库驱动WWDG在HAL库中的驱动代码在stm32h7xx_hal_wwdg.c文件(及其头文件)中。
1. HAL_WWDG_Init函数 IWDG的初始化函数,其声明如下: - HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg);
复制代码l 函数描述: 用于初始化WWDG。 l 函数形参: 形参1是WWDG句柄,WWDG_HandleTypeDef结构体类型,其定义如下: - typedef struct
- {
- WWDG_TypeDef *Instance; /* WWDG寄存器基地址 */
- WWDG_InitTypeDef Init; /* WWDG初始化参数 */
- }WWDG_HandleTypeDef;
复制代码1)Instance:指向WWDG寄存器基地址。 2)Init:WWDG初始化结构体,用于配置计数器的相关参数。 - WWDG_InitTypeDef这个结构体类型定义如下:
- typedef struct
- {
- uint32_t Prescaler; /* 预分频系数 */
- uint32_t Window; /* 窗口值 */
- uint32_t Counter; /* 计数器值 */
- uint32_t EWIMode; /* 提前唤醒中断使能 */
- } WWDG_InitTypeDef;
复制代码1)Prescaler:预分频系数,WWDG_PRESCALER_1到WWDG_PRESCALER_128。 2)Window:窗口值,即上限值。 3)Counter:计数器值,用于保存要设置计数器的值。 4)EWIMode:提前唤醒中断使能。 l 函数返回值: HAL_StatusTypeDef枚举类型的值。
2. HAL_WWDG_Refresh函数 HAL_WWDG_Refresh函数是窗口看门狗的喂狗函数。其声明如下: - HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg);
复制代码l 函数描述: 该函数实际就是往CR寄存器重写Counter这个预先保存的计数器值。 l 函数形参: 形参1是WWDG_HandleTypeDef结构体指针类型的WWDG句柄。 l 函数返回值: HAL_StatusTypeDef枚举类型的值。
窗口看门狗配置步骤 1)使能WWDG时钟 WWDG不同于IWDG,IWDG有自己独立的32Khz时钟。而WWDG使用的是PCLK3的时钟,需要先使能时钟。方法是: - __HAL_RCC_WWDG1_CLK_ENABLE();
复制代码
2)设置窗口值,分频数和计数器初始值 在HAL库中,这三个值都是通过函数HAL_WWDG_Init来设置的,详见本例程源码。
3)开启WWDG 通过设置WWDG_CR寄存器的WDGA(bit7)位为1来实现开启窗口看门狗,同样是在HAL_WWDG_Init函数里面实现。
4)使能中断通道并配置优先级(如果开启了WWDG中断) WWDG的中断也是通过:HAL_NVIC_EnableIRQ函数使能,通过HAL_NVIC_SetPriority函数设置优先级。 HAL库同样为看门狗提供了MSP回调函数HAL_WWDG_MspInit,一般情况下,步骤1和步骤4的步骤,我们均放在该回调函数中。
5)编写中断服务函数 在最后,还是要编写窗口看门狗的中断服务函数,通过该函数来喂狗,喂狗要快,否则当窗口看门狗计数器值减到0X3F的时候,就会引起软复位了。在中断服务函数里面也要将状态寄存器的EWIF位清空。 窗口看门狗中断服务函数为:WWDG_IRQHandler,喂狗函数为:HAL_WWDG_Refresh。
6)重写窗口看门狗唤醒中断处理回调函数HAL_WWDG_EarlyWakeupCallback HAL库定义了一个中断处理共用函数HAL_WWDG_IRQHandler,我们在WWDG中断服务函数中会调用该函数。同时该函数会调用回调函数HAL_WWDG_EarlyWakeupCallback,提前唤醒中断逻辑(喂狗、闪灯)我们写在回调函数HAL_WWDG_EarlyWakeupCallback中。
19.3.2 程序流程图
图19.3.2.1 窗口看门狗实验程序流程图
19.3.3 程序解析
1. WWDG驱动代码这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。窗口看门狗(WWDG)驱动源码包括两个文件:wdg.c和wdg.h。 wdg.h头文件只有函数的声明,就不解释了。下面我们直接解析wdg.c的程序,先看WWDG的初始化函数,其定义如下: - /**
- *@brief 初始化窗口看门狗
- *@param tr: T[6:0],计数器值
- *@param tw: W[6:0],窗口值
- * @param fprer:分频系数(WDGTB),范围:WWDG_PRESCALER_1~WWDG_PRESCALER_128,
- * Fwwdg=PCLK3/(4096*2^fprer). 一般PCLK3=120Mhz
- * 溢出时间=(4096*2^fprer)*(tr-0X3F)/PCLK3
- * 假设fprer=4,tr=7f,PCLK3=120Mhz
- * 则溢出时间=4096*16*64/120Mhz=34.95ms
- *@retval 无
- */
- void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer)
- {
- g_wwdg_handle.Instance = WWDG1;
- g_wwdg_handle.Init.Prescaler = fprer; /* 设置分频系数 */
- g_wwdg_handle.Init.Window = wr; /* 设置窗口值 */
- g_wwdg_handle.Init.Counter = tr; /* 设置计数器值 */
- g_wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE; /* 使能窗口看门狗提前唤醒中断 */
- HAL_WWDG_Init(&g_wwdg_handle); /* 初始化WWDG */
- }
复制代码WWDG_Init是独立看门狗初始化函数,主要设置预分频数、窗口值和计数器的值,以及选择是否使能窗口看门狗提前唤醒中断。 因为用到中断,我们用HAL_WWDG_MspInit函数来编写窗口看门狗中断的初始化代码。当然大家也可以HAL_WWDG_MspInit函数的代码放到wwdg_init函数里面。这个初始化框架就是HAL库的特点。 - /**
- * @brief WWDGMSP回调
- *@param WWDG句柄
- *@note 此函数会被HAL_WWDG_Init()调用
- *@retval 无
- */
- voidHAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
- {
- __HAL_RCC_WWDG1_CLK_ENABLE(); /* 使能窗口看门狗时钟 */
- HAL_NVIC_SetPriority(WWDG_IRQn,2,3); /* 抢占优先级2,子优先级为3 */
- HAL_NVIC_EnableIRQ(WWDG_IRQn); /* 使能窗口看门狗中断 */
- }
复制代码HAL_WWDG_MspInit函数会被HAL_WWDG_Init函数调用。该函数使能窗口看门狗的时钟,并设置窗口看门狗中断的抢占优先级为2,响应优先级为3。 - /**
- *@brief 窗口看门狗中断服务程序
- *@param 无
- *@retval 无
- */
- voidWWDG_IRQHandler(void)
- {
- HAL_WWDG_IRQHandler(&g_wwdg_handle);
- }
复制代码WWDG_IRQHandler函数是窗口看门狗中断服务函数,而这个函数实际上就是调用HAL库的中断处理函数HAL_WWDG_IRQHandler。逻辑程序在下面的这个回调函数中: - /**
- * @brief 窗口看门狗喂狗提醒中断服务回调函数
- *@param wwdg句柄
- *@note 此函数会被HAL_WWDG_IRQHandler()调用
- *@retval 无
- */
- voidHAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)
- {
- HAL_WWDG_Refresh(&g_wwdg_handle);/* 更新窗口看门狗值 */
- LED1_TOGGLE(); /* LED1闪烁 */
- }
复制代码在回调函数内部调用HAL_WWDG_Refresh函数喂狗,并翻转LED1。
2. main.c代码在main.c里面编写如下代码: - int main(void)
- {
- sys_cache_enable(); /* 打开L1-Cache */
- HAL_Init(); /* 初始化HAL库 */
- sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
- delay_init(480); /* 延时初始化 */
- usart_init(115200); /* 串口初始化为115200 */
- 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函数里,先初始化系统和用户的外设代码,然后先点亮LED0,延时300ms后,初始化窗口看门狗,进入死循环,关闭LED0。 调用wwdg_init(0X7F,0X5F, WWDG_PRESCALER_16)这个语句,就设置计数器值为7f,窗口寄存器为5f,分频数为16,然后可由前面的公式得到Twwdg=4096×16×64/120000=34.95ms,即超时时间就是34.95ms。34.95ms进中断喂狗一次,并翻转LED1。
19.4 下载验证下载代码后,可以看到LED0亮了一下就熄灭,紧接着LED1开始不停的闪烁。每秒钟闪烁29次左右,说明程序在中断不停的喂狗,和我们预期的一致。 |