一、概述
本例程是用STOP休眠模式,用RTC的周期性自动唤醒功能来唤醒芯片。
根据手册的说明:这里有个可编程的可自动重装的向下计数器,按照相应的时钟频率赋予适当的值,每当向下计数到0时便产生一个唤醒标志,如果此时使能了相应的定时唤醒中断,它就可以把MCU从低功耗模式唤醒。需做如下四项基本的准备工作。
1、确定RTC时钟,即RTCCLK.可以是LSE、LSI、HSE/32其中一个。下面的例程中选用LSI.
2、为自动唤醒定时计数器选择合适的时钟源。
3、RTC时钟进来分频之后达到1秒(1Hz),每一次时间更新RTC时钟寄存器(RTC_TR、RTC_DR),我们读取的数字就会更改。如果配置了中断,相应事件的时候,中断也会响应。如果配置了闹钟,同样达到了闹钟设定的值也会响应闹钟。
默认设置
AsynchPrediv = 127;
SynchPrediv = 255;
4、做好RTC周期性定时唤醒的中断配置,即NVIC配置。RTC唤醒事件是连接到EXTI 17/19/20号线。
二、详细步骤
2-1 stm32MxCube 操作
注意配置NVIC(中断控制器),RTC中断的条件选择。
生成工程文件,即可。
2-2 代码解析 (以下部分同时为直接的代码移植做参考,因为stm32cubeMx前期代码生成后,再重新生成会很麻烦,只能直接通过代码操作了)重点内容
2-2-1 相关文件j及配置条件
stm32f0xx_hal_conf.h
#define HAL_RTC_MODULE_ENABLED //打开宏定义
驱动文件:低功耗相关 stm32f0xx_hal_pwr.c
RTC相关 stm32f0xx_hal_rtc.c stm32f0xx_hal_rtc_ex.c
MCU初始化 stm32f0xx_hal_msp.c
2-2-2 详细说明
如何进入睡眠模式通过执行 WFI(等待中断)或WFE(等待事件)指令进入睡眠状态。代码实现:
* @param Regulator: Specifies the regulator state in STOP mode. // 调压器模式选择,功耗会存在差异
* This parameter can be one of the following values:
* @arg PWR_MAINREGULATOR_ON: STOP mode with regulator ON
* @arg PWR_LOWPOWERREGULATOR_ON: STOP mode with low power regulator ON
* @param STOPEntry: specifies if STOP mode in entered with WFI or WFE instruction. // 进入睡眠状态进入机制,同时也表明了唤醒的选择方式
* This parameter can be one of the following values:
* @arg PWR_STOPENTRY_WFI:Enter STOP mode with WFI instruction
* @arg PWR_STOPENTRY_WFE: Enter STOP mode with WFE instruction
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI );
其他各种不同的模式可以自己研究。
如何退出睡眠模式
由于我们是采用指令WFI进入睡眠模式,那么任意一个被嵌套向量中断控制器NVIC响应的外设中断都能将系统从睡眠模式唤醒。并且该模式唤醒所需的时间最短,因为没有时间损失在中断的进入或退出上。
在RTX系统上,主要是周期性执行的系统滴答定时器中断会将系统从睡眠态唤醒,我们这里用的闹钟 Alarm A 来唤醒。
2-2-3 时钟初始化
void SystemClock_Config(void)
{
....// 省略
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_I2C1|RCC_PERIPHCLK_RTC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;*//cube时钟配置部分默认的时钟选择*
....// 省略
}
RTC初始化 static void MX_RTC_Init(void);
中断向量初始化 static void MX_NVIC_Init(void);
static void MX_RTC_Init(void)
{
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
RTC_AlarmTypeDef sAlarm;/**Initialize RTC Only
*/hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}/**Initialize RTC and set the Time and Date
*/sTime.Hours = 10;
sTime.Minutes = 12;
sTime.Seconds = 34;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}sDate.WeekDay = RTC_WEEKDAY_FRIDAY;
sDate.Month = RTC_MONTH_MAY;
sDate.Date = 19;
sDate.Year = 17;if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}/**Enable the Alarm A //允许RTC报警中断
*/sAlarm.AlarmTime.Hours = 0;
sAlarm.AlarmTime.Minutes = 0;
sAlarm.AlarmTime.Seconds = 10;
sAlarm.AlarmTime.SubSeconds = 0;
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY|RTC_ALARMMASK_HOURS |RTC_ALARMMASK_MINUTES;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
sAlarm.AlarmDateWeekDay = 1;
sAlarm.Alarm = RTC_ALARM_A;
if (HAL_RTC_SetAlarm(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}}
static void MX_NVIC_Init(void)
{/* RTC_IRQn interrupt configuration */
HAL_NVIC_SetPriority(RTC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(RTC_IRQn);
}void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) // 可以重新定义 闹钟中断回调函数,添加自己的代码辅助调试
{
/* NOTE : This function Should not be modified, when the callback is needed,
the HAL_RTC_AlarmAEventCallback could be implemented in the user file
*/
DBS("EventCallback");
}
读取时钟:
HAL_RTC_GetTime(&hrtc, &thisTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &thisData, RTC_FORMAT_BIN);
DBSTRLONG("_",thisData.Year);
DBSTRLONG("_",thisData.Month);
DBSTRLONG("_",thisData.WeekDay);
DBSTRLONG("_",thisTime.Hours);
DBSTRLONG("_",thisTime.Minutes);
DBSTRLONG("_",thisTime.Seconds);
RTC_AlarmTypeDef sAlarm;
HAL_RTC_GetAlarm(&hrtc,&sAlarm,RTC_ALARM_A,RTC_FORMAT_BIN); // 获取闹钟定时
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) // 可以重新定义 闹钟中断回调函数,添加自己的代码辅助调试
{
/* NOTE : This function Should not be modified, when the callback is needed,
the HAL_RTC_AlarmAEventCallback could be implemented in the user file
*/
DBS("EventCallback");
}
读取时钟:
HAL_RTC_GetTime(&hrtc, &thisTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &thisData, RTC_FORMAT_BIN);
DBSTRLONG("_",thisData.Year);
DBSTRLONG("_",thisData.Month);
DBSTRLONG("_",thisData.WeekDay);
DBSTRLONG("_",thisTime.Hours);
DBSTRLONG("_",thisTime.Minutes);
DBSTRLONG("_",thisTime.Seconds);
RTC_AlarmTypeDef sAlarm;
HAL_RTC_GetAlarm(&hrtc,&sAlarm,RTC_ALARM_A,RTC_FORMAT_BIN); // 获取闹钟定时
三、问题总结 【本次实验碰到的问题】
3-1
STM32F030用LSI作时钟源走时不准,40Khz配置,总会跳秒,要想准时,还是用外部LSE时钟,32768hz,会比较准。
3-2 sAlarm.AlarmMask =RTC_ALARMMASK_DATEWEEKDAY|RTC_ALARMMASK_HOURS |RTC_ALARMMASK_MINUTES ;
//注意屏蔽的对象,设置的日期,星期,时分是无效的,这时RTC闹钟 的秒匹配后触发闹钟中断。如果设置RTC_AlarmMask=RTC_AlarmMask_None;则为精确匹配,即闹钟不仅要求时分秒匹配还要匹配日期和星期,都匹配后触发闹钟中断)
起初因为没有注意到这一点,我设置10s 闹钟中断,当时只做了RTC_ALARMMASK_DATEWEEKDAY屏蔽 ,以至于耗费了好几天而不得结果。
3-3 测试低功耗时,最好通过任务,或者条件控制进入低功耗,否则,开机几秒就进入,那么下来,再烧写程序就麻烦了。