cubemx实现STM32的待机与唤醒,在待机过程中如何喂狗

前言:最近在学习原子的阿波罗,进行到待机实验,实验目的是摁下KEY_UP的时候就可以让MCU从待机模式唤醒了。而KEY1在按下的时候进入休眠。(验证过程比较繁琐,只看结论的小伙伴请找往下找STM32休眠时关闭看门狗的方案

STM32F429提供了三种低功耗模式,以达到不同层次的降低功耗的目的:

(1)睡眠模式(CM4内核停止工作,外设仍在运行)

(2)停止模式(所有时钟都停止)

(3)待机模式(所有时钟都停止,啥都不干了,就等唤醒了)

待机模式的目的主要是节省功耗,在此模式下最低只需要2.2uA电流(最低功耗模式)。期间MCU所有功能全部关闭。可以由WKUP引脚上升沿、RTC闹钟、RTC唤醒、RTC入侵事件、RTC时间戳、NRST引脚外部复位、IWDG复位,唤醒。从待机模式唤醒后的代码执行等同于复位后的执行。

目的:用自己的办法实现待机、唤醒实验。

设计:用KEY_1触发进入待机模式,用KEY_UP唤醒。

cubemx配置:将KEY_UP配置为系统唤醒、KEY1为中断模式,上拉,下降沿触发。开启中断,配置优先级

                      

摁下KEY_UP的时候就可以让MCU从待机模式唤醒了。而KEY1在按下的时候进入休眠。


生成代码,原子给出的进入休眠模式步骤:

(1)禁止所有RTC中断

(2)清零对应中断标志位

(3)清除PWR唤醒(WUF)标志

(4)重新使能RTC对应中断

(5)进入低功耗模式

原子代码如下:

//系统进入待机模式
void Sys_Enter_Standby(void)
{
    __HAL_RCC_AHB1_FORCE_RESET();       //复位所有IO口 
	
    while(WKUP_KD);                     //等待WK_UP按键松开(在有RTC中断时,必须等WK_UP松开再进入待机)
    __HAL_RCC_PWR_CLK_ENABLE();         //使能PWR时钟
    __HAL_RCC_BACKUPRESET_FORCE();      //复位备份区域
    HAL_PWR_EnableBkUpAccess();         //后备区域访问使能  
	
    //STM32F4,当开启了RTC相关中断后,必须先关闭RTC中断,再清中断标志位,然后重新设置
    //RTC中断,再进入待机模式才可以正常唤醒,否则会有问题.	
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
    __HAL_RTC_WRITEPROTECTION_DISABLE(&RTC_Handler);//关闭RTC写保护
    
    //关闭RTC相关中断,可能在RTC实验打开了
    __HAL_RTC_WAKEUPTIMER_DISABLE_IT(&RTC_Handler,RTC_IT_WUT);
    __HAL_RTC_TIMESTAMP_DISABLE_IT(&RTC_Handler,RTC_IT_TS);
    __HAL_RTC_ALARM_DISABLE_IT(&RTC_Handler,RTC_IT_ALRA|RTC_IT_ALRB);
    
    //清除RTC相关中断标志位
    __HAL_RTC_ALARM_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_ALRAF|RTC_FLAG_ALRBF);
    __HAL_RTC_TIMESTAMP_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_TSF); 
    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&RTC_Handler,RTC_FLAG_WUTF);
    
    __HAL_RCC_BACKUPRESET_RELEASE();                    //备份区域复位结束
    __HAL_RTC_WRITEPROTECTION_ENABLE(&RTC_Handler);     //使能RTC写保护
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);                  //清除Wake_UP标志
		
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);           //设置WKUP用于唤醒
    HAL_PWR_EnterSTANDBYMode();                         //进入待机模式     
}

然后,在main函数中对按键KEY_1查询,若按下则进入该函数,让MCU进入待机模式。然后用KEY_UP唤醒,可以立即唤醒。问题1:RTC时间为2000-00-00,每次唤醒后都是从这个时间开始刷新的?

也就是说RTC时间每次待机都被删除。因为RTC时间存放在备用区,有可能是代码中的复位备用区导致的。所以我屏蔽这句话,再尝试,发现时间就不会被清除了。

问题2:我在前一个实验中有打开RTC的唤醒,每秒唤醒一次,为什么休眠后一两秒都没有唤醒?

因为唤醒前把RTC唤醒中断给关了,而且休眠前也没有打开它。将RTC唤醒中断屏蔽,进入休眠后一秒被RTC唤醒了。

所以根据上面发现的两个问题,我重新整理了一下代码:

void Sys_Enter_Standby(void)
{
    __HAL_RCC_AHB1_FORCE_RESET();       //复位所有IO口 
	
    while(HAL_GPIO_ReadPin(KEY_UP_GPIO_Port,KEY_UP_Pin));                     //等待WK_UP按键松开(在有RTC中断时,必须等WK_UP松开再进入待机)
   
    __HAL_RCC_PWR_CLK_ENABLE();         //使能PWR时钟
//    __HAL_RCC_BACKUPRESET_FORCE();      //复位备份区域(该行会导致RTC时间清空)
    HAL_PWR_EnableBkUpAccess();         //后备区域访问使能  
	
    //STM32F4,当开启了RTC相关中断后,必须先关闭RTC中断,再清中断标志位,然后重新设置
    //RTC中断,再进入待机模式才可以正常唤醒,否则会有问题.	
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
    __HAL_RTC_WRITEPROTECTION_DISABLE(&hrtc);			//关闭RTC写保护
    
    //关闭RTC相关中断,可能在RTC实验打开了
//    __HAL_RTC_WAKEUPTIMER_DISABLE_IT(&hrtc,RTC_IT_WUT);//(该行会导致RTC的唤醒失败,导致死机看门狗触发而重启)
    __HAL_RTC_TIMESTAMP_DISABLE_IT(&hrtc,RTC_IT_TS);
    __HAL_RTC_ALARM_DISABLE_IT(&hrtc,RTC_IT_ALRA|RTC_IT_ALRB);
    
    //清除RTC相关中断标志位
    __HAL_RTC_ALARM_CLEAR_FLAG(&hrtc,RTC_FLAG_ALRAF|RTC_FLAG_ALRBF);
    __HAL_RTC_TIMESTAMP_CLEAR_FLAG(&hrtc,RTC_FLAG_TSF); 
    __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc,RTC_FLAG_WUTF);
    
//    __HAL_RCC_BACKUPRESET_RELEASE();                    //备份区域复位结束(该行会导致RTC时间清空)
    __HAL_RTC_WRITEPROTECTION_ENABLE(&hrtc);     		//使能RTC写保护
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);                  //清除Wake_UP标志
		
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);           //设置WKUP用于唤醒
    HAL_PWR_EnterSTANDBYMode();                         //进入待机模式    
	
}

问题3:在进入休眠模式后,按道理是除非我按下KEY_UP否则是不会被唤醒的,但是每次都是隔四秒后被唤醒?

起初是我尝试外部中断能不能唤醒,因为根据前面讲到,除非是WKUP或是RTC或是复位才可以唤醒。但是我发现进入休眠后用按键或是tpad都会在几秒钟后唤醒。后来才发现进入到休眠后大约四秒就一定会唤醒,这才想到有可能是看门狗的问题,因为我在前面的实验中看门狗设置的四秒不喂就触发。

我以为内核都关了狗就不用喂了,后来我才知道为啥IWDG叫做独立看门狗了,这狗很独立。

IWDG与内核是分开的,所以看门狗功能由 VDD 电压域供电,在停止模式和待机模式下仍能工作。

这篇文章写得不错:https://blog.csdn.net/weibo1230123/article/details/80705866 讲看门狗的

所以就是因为进入休眠模式了,没有喂狗动作,到了四秒就触发了看门狗导致重启。那为啥原子的没事儿呢?因为原子在那个工程中就没开看门狗。但是在我们实际应用中一定会用到看门狗的,这个非常常用。那么就引出问题4

问题4:如何在休眠模式中喂狗,或者关闭看门狗?

尝试关闭IWDG无效,因为我找了hal库,只有使能没有失能。后来四处百度,都说IWDG一旦开启就不能再关闭了。

STM32休眠时关闭看门狗的方案:

(1)采用调试模式关闭内核的功能来关闭看门狗计数(这个不理解,也没试过,您知道的话请留个言)

(2)休眠时采用时钟唤醒来喂狗后继续休眠(很折腾,但是能用,缺点是频繁重启MCU影响寿命)

(3)用基于系统时钟的窗口看门狗WWDG(好使,休眠前都不用去关,因为它属于内核管理,内核都关了,他也就不会被触发了)

(4)在RTC闹钟中喂狗(不靠谱,闹钟是最少一分钟,除非用到亚秒。不如用RTC唤醒喂狗呢)

(5)进入休眠前:复位并且不开启IWDG,再进入休眠。唤醒后开启看门狗。(该方案是我最满意的,因为它免去了(2)的麻烦,又还能继续使用IWDG)


我在实验第(2)个方案的时候,让RTC唤醒中断中喂狗,但是难点在于如何在需要休眠的时候被RTC唤醒又重新进入休眠模式。不能在RTC中断中进入休眠模式,因为在进入休眠模式前还会关闭RTC的所有中断。

我们需要想明白一点:RTC唤醒并不会决定MCU是应该休眠还是不休眠,它只负责喂狗。

其次:决定休眠的是KEY1和KEY_UP,也就是通过KEY1进入休眠后,KEY_UP不按下,程序始终会进入休眠。

那么看起来是需要一个标志位flag来做标志,比如按了KEY1就flag = 1;按了KEY_UP就flag = 0;在main()的while(1)中判断flag为1则进入休眠,否则跳过。

while(1)
{    
    _u8KeyStatus = KEY_Scan(0);
    if(_u8KeyStatus[KEY1])
    {
	u8StandbyFlag = 1;
    } 
    if(_u8KeyStatus[KEY_UP])
    {
	u8StandbyFlag = 0;
    } 
    if(u8StandbyFlag)
    {
	Sys_Enter_Standby();
    }
}
//RTC WAKE UP中断处理
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
	HAL_IWDG_Refresh(&hiwdg);

}

看起来没毛病,但是现实是按下KEY1后进入休眠模式后,触发RTC唤醒喂狗,之后就不会进入休眠模式了。

因为u8StandbyFlag是外部变量,存在RAM中,进入待机模式后就相当于断了MCU的电,RAM是掉电不保存的。也就是说RTC唤醒后u8StandbyFlag又为定义的初始值0了。那么我让u8StandbyFlag定义为1,效果就是MCU一直处于RTC唤醒-重启-休眠的循环中。

所以需要将u8StandbyFlag存放在掉电不丢数的地方,SDRAM可以,EEPROM可以,FLASH可以。这里我想用FLASH

往flash写东西前最好先看一眼map文件,写在代码后的扇区就可以

由map可以看见flash用到0x08009cd4,

由此选择ADDR_FLASH_SECTOR_3,作为存放u8StandbyFlag的地方。

main()
{
    //........各种初始化
    //从flash读待机标志
    u8StandbyFlag = STMFLASH_ReadWord(ADDR_FLASH_SECTOR_3);
    while(1)
    {
        _u8KeyStatus = KEY_Scan(0);
	if(_u8KeyStatus[KEY1])
	{
	    u8StandbyFlag = 1;
	    STMFLASH_WriteWord(ADDR_FLASH_SECTOR_3, (uint32_t)u8StandbyFlag);
	} 
	if(_u8KeyStatus[KEY_UP])
	{
	    u8StandbyFlag = 0;
	    STMFLASH_WriteWord(ADDR_FLASH_SECTOR_3, (uint32_t)u8StandbyFlag);
	} 
	if(u8StandbyFlag)
	{
	    Sys_Enter_Standby();
	}
        //喂狗
	HAL_IWDG_Refresh(&hiwdg);
	delay_ms(10);
    }
}
    

由于看门狗是4秒,那么RTC唤醒(唤醒中断中喂狗)周期定为3秒即可。

按照方案(2)下载后,现象为MCU正常启用,按KEY1进入待机模式,每过三秒,LED0快速闪烁一次,随后熄灭(进入待机)。可以说是成功的,唯一的不完美的地方在于,由于WKUP是有KEY_UP执行的,而该处是没有中断回调函数的,因此我们无从安放往flash写入不需要休眠的标志了。因此我仍旧采用在while(1)循环中去检测KEY_UP的电平状态,若被按下则执行。

但由于检测放在了while(1)中,而是否休眠的判断得放在判断KEY_UP的后面,故每次RTC唤醒后都一定会先执行完一遍初始化,包括LED、LCD等。这也是方案(2)的弊端,效率太低,很多不必要的操作不得不做。


方案(5)进入休眠前:复位并且不开启IWDG,再进入休眠。唤醒后开启看门狗。(该方案是我最满意的,因为它免去了(2)的麻烦,又还能继续使用IWDG)

所以分为几个步骤:1.在KEY1按下时写入flash u8StandbyFlag=1;重启一下重新初始化代码,不开启IWDG。2.在main的初始化的时候去读flash的u8StandbyFlag,若为1则表示需要待机,不开启IWDG,也不喂狗。3.在KEY_UP按下时写入flash u8StandbyFlag=0;重启一下重新初始化代码,开启IWDG,并喂狗。4.关闭RTC唤醒(不再需要它喂狗了)

有了这四个改动,只需要在待机前和唤醒后重启一下MCU即可,妈妈再也不用担心我的喂狗啦!

然并卵,效果可以看到在按下KEY1后重启了一下进入了待机模式,KEY_UP也会重启一下再正常。但是进入待机模式后,仍然会触发看门狗导致短暂重启一下。因为是独立看门狗,人家用的是VDD,MCU重启没有用,得VDD断一下才行,看门狗部分仍然还是开启状态!

然后我又在待机模式重启板子电源(包括J-LINK的供电),预期效果应该是看门狗的寄存器掉电复位了,也就是没有看门狗了。但是,板子仍然是那个现象,在待机模式中仍然会重启一下。咋回事儿!不应该啊,电都断了VDD没有了看门狗还行?那只有一种可能就是电池了。

随后我拔掉电池,再进入待机模式后,一切正常了,不会再触发看门狗了。那么也就是说看门狗用的是电池供的备用区域?讲不通啊。

随后我装上电池,再试一下,按道理说,应该会在唤醒的时候打开了看门狗,待机的时候没关住,会在待机的时候重启,但是事实再一次与想象不一致,很正常的不重启了。我无语了

后来我突然想到,在修改到第4个步骤的时候,我只是把RTC的唤醒的初始化屏蔽了,但是我并没有将它关闭,也就是说只要电池供电,除非我在代码中关闭RTC唤醒中断,它才会真的停掉。这就解释的通了,之前我看到的重启并不是看门狗的触发,而是RTC唤醒!只不过我判断flash后会再次进入待机模式而已!也就是说,MCU重启是可以关掉看门狗的,NVIC_SystemReset(); // 复位。这个函数会把MCU的电断掉重新上电。

我再把RTC的唤醒打开,再复现一下故障。可以的!其实就是我们在前面实验RTC唤醒的时候,让休眠前不关闭RTC唤醒中断,所以其实RTC唤醒在备用区有电的情况下是打开的。

结论:进入休眠前复位并且不开启IWDG,再进入休眠。唤醒后开启看门狗。该方案是可行的!


然后方案(3)用WWDG窗口看门狗我也实验过了,WWDG是内核控制的,所以进入待机也就顺带关闭了WWDG。只不过WWDG比IWDG要复杂一点点,这个我会用专门的一篇去介绍学习过程。

结论:用WWDG窗口看门狗的程序测试待机实验,是可行的,并且不用重启一下去关闭WWDG


总结:待机实验真的很不错,对RTC、IWDG、WWDG都有了更深层次的了解。我也是花费很多时间在这个实验上,看似简单,实则有很多细节是之前看不到的。所以我也强烈建议小伙伴们自己动手去做一遍,不要觉得小实验看看就可以了。

多多交流,欢迎您对我的文章做出评价,满意请按“1”,不满意请按“0”

发布了26 篇原创文章 · 获赞 0 · 访问量 2993

猜你喜欢

转载自blog.csdn.net/nianzhu2937/article/details/103679945