当前位置:网站首页>潘多拉 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;
}
边栏推荐
- 155. Minimum stack
- Docker configures redis and redis clusters
- 逆波兰表达式
- 使用 jMeter 对 SAP Spartacus 进行并发性能测试
- Common commands and basic operations of Apache Phoenix
- Lepton 无损压缩原理及性能分析
- 946. Verify stack sequence
- Didi open source Delta: AI developers can easily train natural language models
- 10 minute fitness method reading notes (3/5)
- Add a new cloud disk to Huawei virtual machine
猜你喜欢
SAP self-development records user login logs and other information
Transactions on December 23, 2021
RHCSA7
RHCSA5
【云原生】Nacos中的事件发布与订阅--观察者模式
Taobao product details API | get baby SKU, main map, evaluation and other API interfaces
研究:数据安全工具在 60% 的情况下无法抵御勒索软件
I'm doing open source in Didi
上午面了个腾讯拿 38K 出来的,让我见识到了基础的天花
stirring! 2022 open atom global open source summit registration is hot!
随机推荐
【Nacos云原生】阅读源码第一步,本地启动Nacos
946. 验证栈序列
HiEngine:可媲美本地的云原生内存数据库引擎
Introduction aux contrôles de la page dynamique SAP ui5
Alipay transfer system background or API interface to avoid pitfalls
Distance measuring sensor chip 4530a used in home intelligent lighting
CF:A. The Third Three Number Problem【关于我是位运算垃圾这个事情】
SAP SEGW 事物码里的 ABAP 类型和 EDM 类型映射的一个具体例子
SAP SEGW 事物码里的 Association 建模方式
Taobao flag insertion remarks | logistics delivery interface
Shi Zhenzhen's 2021 summary and 2022 outlook | colorful eggs at the end of the article
Transactions from December 27 to 28, 2021
Common commands and basic operations of Apache Phoenix
jxl笔记
简单上手的页面请求和解析案例
##无监控,不运维,以下是监控里常用的脚本监控
Simply take stock reading notes (4/8)
国内市场上的BI软件,到底有啥区别
以VMware创新之道,重塑多云产品力
滴滴开源DELTA:AI开发者可轻松训练自然语言模型