第一次写博客,感觉我这脑子越来越不好用,怕忘记了,所以以此记录。
1、STM32F10xxx进入和退出停止模式的条件
2、外部中断线描述
共有20个外部中断线,EXTI线0-15对应IO口Px0-Px15,另外四个EXTI线的连接方式如下:
● EXTI线16连接到PVD输出
● EXTI线17连接到RTC闹钟事件
● EXTI线18连接到USB唤醒事件
● EXTI线19连接到以太网唤醒事件(只适用于互联型产品)
3、基于Hal库的外部中断+RTC闹钟中断唤醒停止模式
以下例程实现了,按照工作时间+停止时间周期性的进入和退出停止模式。MCU为STM32F072。
3.1:RTC初始化
void MX_RTC_Init(void)
{
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};
RTC_AlarmTypeDef sAlarm = {0};
/** 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();
}
/* USER CODE BEGIN Check_RTC_BKUP */
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x0;
sTime.Minutes = 0x58;
sTime.Seconds = 0x0;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
sDate.WeekDay = RTC_WEEKDAY_MONDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 0x1;
sDate.Year = 0x0;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
/** Enable the Alarm A
*/
sAlarm.AlarmTime.Hours = 0x0;
sAlarm.AlarmTime.Minutes = 0x0;
sAlarm.AlarmTime.Seconds = 0x20;
sAlarm.AlarmTime.SubSeconds = 0x0;
sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY|RTC_ALARMMASK_HOURS;
sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
sAlarm.AlarmDateWeekDay = 0x1;
sAlarm.Alarm = RTC_ALARM_A;
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
}
使能RTC闹钟中断,闹钟设置了分秒匹配,不匹配日期星期小时。
3.2:退出和进入停止模式
//系统进入停止模式
void sys_enter_stop_mode(void)
{
// 使能PWR时钟
__HAL_RCC_PWR_CLK_ENABLE();
HAL_SuspendTick();
// 进入STOP模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}
void SYSCLKConfig_STOP(void)
{
/* 使能 HSE */
__HAL_RCC_HSE_CONFIG(RCC_HSE_ON);
/* 等待 HSE 准备就绪 */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET);
/* 使能 PLL */
__HAL_RCC_PLL_ENABLE();
/* 等待 PLL 准备就绪 */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_PLLRDY) == RESET)
{
}
/* 选择PLL作为系统时钟源 */
__HAL_RCC_SYSCLK_CONFIG(RCC_SYSCLKSOURCE_PLLCLK);
/* 等待PLL被选择为系统时钟源 */
while(__HAL_RCC_GET_SYSCLK_SOURCE() != 0x08)
{
}
}
//系统从停止模式恢复
void sys_resume(void)
{
SYSCLKConfig_STOP();
HAL_ResumeTick();
}
进入停止模式后,因为晶振关闭程序无法运行,当从停止模式恢复后,需要重新初始化系统时钟。恢复后程序会从停止的位置开始继续运行。
3.3:外部中断函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
if (RunPara.WorkMode == Stop){
if (RunPara.LocalConfigFlag == 0){
sys_resume();
}
RunPara.WorkMode = Work;
RunPara.AlarmIntReason = StopArrive;
}
else{
RunPara.AlarmIntReason = WakeArrive;
}
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_15){
RunPara.LocalConfigFlag = 1;
RunPara.ExitConfigTimes = ExitConfigTime;
if (RunPara.WorkMode == Stop){
sys_resume();
}
}
}
注意!!!:RTC闹钟中断一定要进入HAL_RTC_AlarmAEventCallback函数才能从低功耗模式唤醒,笔者之前设置了闹钟中断,但是进入的是RTC全局中断函数,在全局中断中判断闹钟中断源,导致死活唤醒不了。
3.4 闹钟设置
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN); //hal库读rtc时间,一定要再读日期,不然时间不更新
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
sAlarm.AlarmTime.Hours = sTime.Hours;
if (sTime.Minutes+TerUser.WorkTime<60){
sAlarm.AlarmTime.Minutes = sTime.Minutes+TerUser.WorkTime;
}
else{
sAlarm.AlarmTime.Minutes = sTime.Minutes+TerUser.WorkTime-60;
}
sAlarm.AlarmTime.Seconds = sTime.Seconds;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
不涉及时间进位,闹钟配置只能按分钟进行配置,范围为0-59分钟。
4、对于停止模式下喂狗的看法
独立看门狗使用的是LSI,所以停止模式下独立看门狗是一直运行的。并且独立看门狗,一旦打开,不可以停止,导致停止模式下无法喂狗。网上关于停止模式下独立看门狗喂狗,都是定期唤醒闹钟喂狗,但是不适用于本例程的应用逻辑。所以最后决定放弃独立看门狗,选择窗口看门狗,进入停止模式后,窗口看门狗也会停止,不需要对喂狗做任何额外的处理。不知道大家对低功耗模式下独立看门狗喂狗有什么好的方法,欢迎指教。