当前位置:网站首页>潘多拉 IOT 开发板学习(HAL 库)—— 实验7 窗口看门狗实验(学习笔记)
潘多拉 IOT 开发板学习(HAL 库)—— 实验7 窗口看门狗实验(学习笔记)
2022-07-05 12:41:00 【小辉_Super】
本文代码参考正点原子例程
之所以称为窗口就是因为其喂狗时间是一个有上下限的范围(窗口),你可以通过设定相关寄存器,设定其上限时间(下限固定)。喂狗的时间不能过早也不能过晚。
实验功能
main.c 中只有外设初始化的代码,喂狗代码被放在了看门狗中断中。窗口看门狗中断又叫 “提前唤醒中断”,当递减计数器等于窗口下限值 (0x40)时,触发一个 “提前唤醒中断(EWI)。
如果没有在中断中 “喂狗”,LED_R 将会不停闪烁,表明单片机在一直复位。
例程源码:(main.c)
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "wwdg.h"
/********************************************************************************* ___ _ _____ _____ _ _ _____ _____ _ __ / _ \ | | |_ _|| ___|| \ | ||_ _|| ___|| | / / / /_\ \| | | | | |__ | \| | | | | |__ | |/ / | _ || | | | | __| | . ` | | | | __| | \ | | | || |_____| |_ | |___ | |\ | | | | |___ | |\ \ \_| |_/\_____/\___/ \____/ \_| \_/ \_/ \____/ \_| \_/ * ****************************************************************************** * 正点原子 Pandora STM32L475 IoT开发板 实验7 * 窗口看门狗实验 HAL库版本 * 技术支持:www.openedv.com * 淘宝店铺:http://openedv.taobao.com * 关注微信公众平台微信号:"正点原子",免费获取STM32资料。 * 广州市星翼电子科技有限公司 * 作者:正点原子 @ALIENTEK * ******************************************************************************/
int main(void)
{
HAL_Init();
SystemClock_Config(); //初始化系统时钟为80M
delay_init(80); //初始化延时函数 80M系统时钟
uart_init(115200); //初始化串口,波特率为115200
LED_Init(); //初始化LED
LED_R(0);
delay_ms(300); //延时300ms再初始化看门狗,LED_R的变化"可见"
WWDG_Init(0X7F, 0X5F, WWDG_PRESCALER_8); //计数器值为7F,窗口寄存器为5F,分频数为8
while(1)
{
LED_R(1); //熄灭LED_R灯
}
}
代码剖析
HAL_Init()
HAL_Init() 定义如下:(具体实现的功能见注释)
HAL_StatusTypeDef HAL_Init(void)
{
HAL_StatusTypeDef status = HAL_OK;
/* 配置 Flash 预取,指令缓存,数据缓存 */
/* 默认配置为:预存取关闭 指令缓存和数据缓存开启 */
#if (INSTRUCTION_CACHE_ENABLE == 0) // Flash开启预存取配置,能加速CPU代码的执行
__HAL_FLASH_INSTRUCTION_CACHE_DISABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */
#if (DATA_CACHE_ENABLE == 0)
__HAL_FLASH_DATA_CACHE_DISABLE();
#endif /* DATA_CACHE_ENABLE */
#if (PREFETCH_ENABLE != 0)
__HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */
/* Set Interrupt Group Priority */
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2); // 配置 NVIC 优先级分组
/* Use SysTick as time base source and configure 1ms tick (default clock after Reset is MSI) */
if (HAL_InitTick(TICK_INT_PRIORITY) != HAL_OK) //初始化滴答定时器,时钟节拍设置为 1ms
{
status = HAL_ERROR;
}
else
{
/* Init the low level hardware */
HAL_MspInit(); // 低速的外设初始化,比如 GPIO、中断等的设置(使用 STM32CubeMx 生成代码时会将低速外设初始
// 代码当这类函数里,其他情况下可以忽略这个函数
}
/* Return function status */
return status;
}
HAL_InitTick()
滴答定时器时钟节拍初始化函数
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
HAL_StatusTypeDef status = HAL_OK;
/*Configure the SysTick to have interrupt in 1ms time basis*/
if (HAL_SYSTICK_Config(SystemCoreClock/1000UL) != 0U) // 系统时钟/1000,中断周期为 1ms
{
status = HAL_ERROR;
}
else
{
/*Configure the SysTick IRQ priority */
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority, 0); // 将滴答定时器的中断优先级设置为最高
}
/* Return function status */
return status;
}
SystemClock_Config()
SystemClock_Config()函数定义如下:(具体实现的功能见注释,仅供参考)
void SystemClock_Config(void)
{
HAL_StatusTypeDef ret = HAL_OK;
RCC_OscInitTypeDef RCC_OscInitStruct; // 定义振荡器初始化结构体变量
RCC_ClkInitTypeDef RCC_ClkInitStruct; // 定义时钟初始化结构体变量
__HAL_RCC_PWR_CLK_ENABLE(); // 使能电源控制时钟
/*Initializes the CPU, AHB and APB busses clocks*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 将 HSE(外部高速时钟)作为时钟源
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 开启 HSE
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 开启 PLL(锁相环)
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // 将 HSE 作为 PLL 的时钟源
RCC_OscInitStruct.PLL.PLLM = 1; // PLL-VCO 输入时钟分频系数,1 表示 2 分频(8 / 2 = 4M,本开发板外部晶振频率为 8MHz)
RCC_OscInitStruct.PLL.PLLN = 20; // PLL-VCO 输出时钟倍频系数,4 * 20 = 80M,即输出时钟频率为 80MHz
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7; // SAI 时钟的分频系数
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // SDMMC1, RNG 和 USB 的时钟分频系数
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // 主系统时钟的分频系数
ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); //初始化时钟配置
if(ret != HAL_OK) while(1);
/*Initializes the CPU, AHB and APB busses clocks*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 将所有时钟同时进行配置
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 将 PLL 作为系统时钟源
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 不分频
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 不分频
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 不分频
ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 配置时钟初始结构体变量,
//使用 Flash 延迟4,等待状态(延迟)的数量需要根据CPU时钟(HCLK)的频率和内部电压范围来选择,具体怎么
//选需要参考芯片手册
if(ret != HAL_OK) while(1);
/*Configure the main internal regulator output voltage*/
ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); //内部寄存器输出电压配置
// 下面是 HAL_PWREx_ControlVoltageScaling() 函数说明的部分内容:
//PWR_REGULATOR_VOLTAGE_SCALE1 Regulator voltage output range 1 mode, typical output voltage
// at 1.2 V, system frequency up to 80 MHz.
if(ret != HAL_OK) while(1);
}
delay_init()
滴答定时器已经在 HAL_Init() 中进行了初始化,下面这个函数实际上就是给 fac_us 赋了一个值(目前暂不涉及操作系统,其他代码暂时不去研究)。
static u32 fac_us = 0; //us延时倍乘数
/** * @brief 初始化延迟函数,SYSTICK的时钟固定为AHB时钟 * * @param SYSCLK 系统时钟频率 * * @return void */
void delay_init(u8 SYSCLK)
{
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
u32 reload;
#endif
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);//SysTick频率为HCLK
fac_us = SYSCLK; //不论是否使用OS,fac_us都需要使用
#if SYSTEM_SUPPORT_OS //如果需要支持OS.
reload = SYSCLK; //每秒钟的计数次数 单位为K
reload *= 1000000 / delay_ostickspersec; //根据delay_ostickspersec设定溢出时间
//reload为24位寄存器,最大值:16777216,在80M下,约209.7ms左右
fac_ms = 1000 / delay_ostickspersec; //代表OS可以延时的最少单位
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断
SysTick->LOAD = reload; //每1/OS_TICKS_PER_SEC秒中断一次
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
#else
#endif
}
LED_Init()
/** * @brief LED IO初始化函数 * * @param void * * @return void */
void LED_Init(void)
{
/* LED-B PE9 LED-G PE8 LED-R PE7 */
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOE_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9, GPIO_PIN_SET);
}
LED 操作函数
LED 的控制函数是宏函数,分别用到了 HAL_GPIO_WritePin() 和 HAL_GPIO_TogglePin() 两个库函数。
//RGB接口定义
#define LED_R(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_7,GPIO_PIN_RESET))
#define LED_R_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_7) //LED_R电平翻转
#define LED_G(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET))
#define LED_G_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_8) //LED_G电平翻转
#define LED_B(n) (n?HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOE,GPIO_PIN_9,GPIO_PIN_RESET))
#define LED_B_TogglePin HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_9) //LED_B电平翻转
delay_ms()
delay_ms() 里运行的是 delay_us(), delay_us()通过滴答定时器实现延时。上面的 delay_init() 已经将 fac_us 设置为了 80,滴答定时器计数 80 次需要用 10-6 秒(系统时钟为 80MHz),即 1us。
/** * @brief 延时毫秒(ms)函数 * * @param nms 需要延时多少毫秒 * * @return void */
void delay_ms(u16 nms)
{
u32 i;
for(i = 0; i < nms; i++) delay_us(1000);
}
/** * @brief 延时微秒(us)函数 * * @remark nus:0~190887435(最大值即2^32/[email protected]_us=22.5) * * @param nus 需要延时多少微秒 * * @return void */
void delay_us(u32 nus)
{
u32 ticks;
u32 told, tnow, tcnt = 0;
u32 reload = SysTick->LOAD; //LOAD的值
ticks = nus * fac_us; //需要的节拍数
told = SysTick->VAL; //刚进入时的计数器值
while(1)
{
tnow = SysTick->VAL;
if(tnow != told)
{
if(tnow < told)tcnt += told - tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
else tcnt += reload - tnow + told;
told = tnow;
if(tcnt >= ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
WWDG_Init()
窗口看门狗初始化函数,main() 函数中,该函数的参数分别是: 计数器值 0X7F(计数值取值要大于窗口值,它是一个 7bit 数据),窗口值 0x5F(窗口值可取范围是 0X40~0X7F),分频系数 WWDG_PRESCALER_8(对应的数值为 3)。这里的窗口指的是上窗口,下窗口固定为 0X40。
根据窗口看门狗的时间计算公式(见下图),再结合上面的参数,可以算出本实验看门狗超时时间为 4096*23*(63+1)]/48000=43.69ms

- PCLK 是 APB1 的时钟频率(48MHz),公式中是时间,且需要转换为 ms,所以对应的值为 1/48000,
- WDGTB 是分频系数(0~3),
- T[5:0] 是计数器的低 6 位,
这里有一点要注意,之所以要取计数器的低 6 位,是因为0X7F-0X40=0x3F(111111),即 “计数值 - 窗口下限” 的时间差最大为 0X3F,而且 0X40~0X7F 的第 7 位永远是 1,所以直接取低 6 位,就是 “计数值 - 窗口下限” 的时间差了。
WWDG_Handler.Init.EWIMode = WWDG_EWI_DISABLE;这一行我暂时不是很理解,关闭 EWI 模式,提前唤醒中断照样会触发。
/** * @brief 初始化窗口看门狗 * * @param tr T[6:0],计数器值 * @param wr W[6:0],窗口值 * @param fprer 分频系数(WDGTB),仅最低2位有效 * * @return void */
void WWDG_Init(u8 tr, u8 wr, u32 fprer)
{
WWDG_Handler.Instance = WWDG;
WWDG_Handler.Init.Prescaler = fprer; //设置分频系数
WWDG_Handler.Init.Window = wr; //设置窗口值
WWDG_Handler.Init.Counter = tr; //设置计数器值
WWDG_Handler.Init.EWIMode = WWDG_EWI_DISABLE;
HAL_WWDG_Init(&WWDG_Handler); //初始化WWDG
__HAL_WWDG_ENABLE_IT(&WWDG_Handler, WWDG_IT_EWI); //开启唤醒中断
}
下面这个函数是窗口看门狗的底层驱动初始化函数,HAL 库会自动调用这个函数。
/** * @brief WWDG底层驱动,时钟配置,中断配置 * 此函数会被HAL_WWDG_Init()调用 * * @param hwwdg 窗口看门狗句柄 * * @return void */
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{
__HAL_RCC_WWDG_CLK_ENABLE(); //使能窗口看门狗时钟
HAL_NVIC_SetPriority(WWDG_IRQn, 2, 3); //抢占优先级2,子优先级为3
HAL_NVIC_EnableIRQ(WWDG_IRQn); //使能窗口看门狗中断
}
中断服务函数
在底层中断服务函数中调用 HAL 库的中断服务函数。
/** * @brief 窗口看门狗中断服务函数 * * @param void * * @return void */
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&WWDG_Handler);//调用WWDG共用中断处理函数
}
HAL_WWDG_IRQHandler() 里会运行一些中断回调函数,其中就包括 HAL_WWDG_EarlyWakeupCallback() 提前唤醒中断回调函数,该函数中,主要完成了喂狗操作。
当计数值等于 0X40 (窗口下限)时,系统将会触发提前唤醒中断。
/** * @brief 中断服务函数处理过程 * 此函数会被HAL_WWDG_IRQHandler()调用 * * @param hwwdg 窗口看门狗句柄 * * @return void */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)
{
HAL_WWDG_Refresh(&WWDG_Handler);//更新窗口看门狗值
LED_B_TogglePin;
}
喂狗操作实际上是重新填充窗口看门狗的计数值。
/** * @brief Refresh the WWDG. * @param hwwdg pointer to a WWDG_HandleTypeDef structure that contains * the configuration information for the specified WWDG module. * @retval HAL status */
HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg)
{
/* Write to WWDG CR the WWDG Counter value to refresh with */
WRITE_REG(hwwdg->Instance->CR, (hwwdg->Init.Counter));
/* Return function status */
return HAL_OK;
}
边栏推荐
- Setting up sqli lab environment
- Kotlin process control and circulation
- What is the difference between Bi software in the domestic market
- 在家庭智能照明中应用的测距传感芯片4530A
- Lepton 无损压缩原理及性能分析
- ActiveMQ installation and deployment simple configuration (personal test)
- 以VMware创新之道,重塑多云产品力
- Transactions from January 6 to October 2022
- Oppo Xiaobu launched Obert, a large pre training model, and promoted to the top of kgclue
- Neural network of PRML reading notes (1)
猜你喜欢
随机推荐
【云原生】Nacos中的事件发布与订阅--观察者模式
Concurrent performance test of SAP Spartacus with JMeter
[cloud native] event publishing and subscription in Nacos -- observer mode
国内市场上的BI软件,到底有啥区别
Introduction to the principle of DNS
Taobao order amount check error, avoid capital loss API
实现 1~number 之间,所有数字的加和
Kotlin process control and circulation
RHCAS6
Add a new cloud disk to Huawei virtual machine
What if wechat is mistakenly sealed? Explain the underlying logic of wechat seal in detail
前几年外包干了四年,秋招感觉人生就这样了..
激动人心!2022开放原子全球开源峰会报名火热开启!
leetcode:221. 最大正方形【dp状态转移的精髓】
Using MySQL in docker
开发者,云原生数据库是未来吗?
Time conversion error
Lepton 无损压缩原理及性能分析
Super efficient! The secret of swagger Yapi
MySQL giant pit: update updates should be judged with caution by affecting the number of rows!!!









