在ST的单片机中,一般低功耗模式都有stop(停机)模式和standby(待机)模式两种,这篇博客主要是分享一下standby模式,并且通过RTC实时时钟的闹钟将单片机从低功耗模式中唤醒的方法。为了方便演示,实验流程是,通过串口命令来设置单片机进入低功耗模式,再通过RTC的闹钟将单片机从低功耗状态唤醒,进入正常模式。
使用CubeMX生成工程
1.根据使用的单片机来配置生成工程,我使用的是NUCLEO-F303RE来做实验
2.配置需要使用到的外设
![]()
首先要设置单片机的时钟源,在这里我是用的是内部高速时钟源和外部的低速时钟源。因为RTC需要使用一个低速时钟源。
![]()
激活RTC功能,并设置RTC的闹钟
![]()
第一步:配置数据格式为二进制格式(Binary data format),便于后期处理。
第二步:自动异步分频使能,这里可以自动将RTC的时钟源分频成1HZ
第三步:选择使能闹钟屏蔽标识,例如图中的参数设置,表示在秒相同的时候触发,也就是一分钟触发一次闹钟。同理,可以设置一秒触发,一天触发一次或者一周触发一次。
![]()
在这里我还设置了串口,用于在实验中显示输出。
![]()
最后使能相关的外设的中断。
3.设置好工程名称和保存位置,选择自己用的开发工具和版本,然后生成工程代码
代码处理
首先引用和定义一些必要的头文件和参数
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include “usart_user.h”
#include “string.h”
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
RTC_DateTypeDef gDateType; //获取RTC日期
RTC_TimeTypeDef gTimeType; //获取RTC时间
RTC_DateTypeDef sDateType; //设置RTC日期
RTC_TimeTypeDef sTimeType; //设置RTC时间
static char cmd_setTime[] = “AT+SETTIME”; //设置RTC时间命令
static char cmd_standby[] = “AT+STANDBY”; //进入standby模式命令
/* USER CODE END PTD */
main函数循环中用于处理命令的部分
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(USART_RX_Finish) //判断串口接收完成
{
USART_RX_Finish = 0; //清除串口接收完成标识
if(strncmp(USART_RX_BUF,cmd_setTime,strlen(cmd_setTime)) == 0) //判断命令,处理RTC时钟设置
{
sTimeType.Hours = (USART_RX_BUF[(strlen(cmd_setTime) + 1)]-‘0’)*10 +(USART_RX_BUF[(strlen(cmd_setTime) + 2)]-‘0’);
sTimeType.Minutes = (USART_RX_BUF[(strlen(cmd_setTime) + 4)]-‘0’)*10 +(USART_RX_BUF[(strlen(cmd_setTime) + 5)]-‘0’);
sTimeType.Seconds = (USART_RX_BUF[(strlen(cmd_setTime) + 7)]-‘0’)*10 +(USART_RX_BUF[(strlen(cmd_setTime) + 8)]-‘0’);
printf(“设置RTC时间为 %02d:%02d:%02d rn”,sTimeType.Hours,sTimeType.Minutes,sTimeType.Seconds);
if(HAL_RTC_SetTime(&hrtc,&sTimeType,RTC_FORMAT_BIN)!=HAL_OK) //设置RTC时间
{
printf(“HAL_RTC_SetTime ERR rn”);
}
printf(“设置RTC时间成功!rn”);
}
if(strncmp(USART_RX_BUF,cmd_standby,strlen(cmd_standby)) == 0) //判断命令,进入standby模式
{
printf(“Executing test (standby) rn”);
GPIO_AnalogState_Config(); //设置IO口为模拟输入状态
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnterSTANDBYMode(); //进入standby模式
printf(“进入待机模式失败rn”);
}
}
if((PWR-》CSR & PWR_CSR_WUF )== PWR_CSR_WUF) //清除闹钟中断的标识
{
PWR-》CR |= PWR_CR_CWUF;
}
HAL_Delay(200);
HAL_GPIO_TogglePin(LD2_GPIO_Port,LD2_Pin); //翻转LD2,显示单片机处于正常工作状态
}
/* USER CODE END 3 */
获取RTC时间和日期的函数,以及RTC的闹钟中断回调函数
/* USER CODE BEGIN 4 */
/*获取RTC的时钟和日期*/
void get_date (RTC_DateTypeDef *sDateStruc,RTC_TimeTypeDef *sTimeStruc)
{
if(HAL_RTC_GetTime(&hrtc,sTimeStruc,RTC_FORMAT_BIN) != HAL_OK)
{
printf(“HAL_RTC_GetTime ERR rn”);
}
if(HAL_RTC_GetDate(&hrtc,sDateStruc,RTC_FORMAT_BIN) != HAL_OK)
{
printf(“HAL_RTC_GetDate ERR rn”);
}
}
/*RTC闹钟的回调函数*/
void HAL_RTCEx_AlarmBEventCallback(RTC_HandleTypeDef *hrtc)
{
get_date(&gDateType,&gTimeType);
printf(“当前RTC时间为 %02d:%02d:%02d rn”,gTimeType.Hours,gTimeType.Minutes,gTimeType.Seconds);
}
/* USER CODE END 4 */
设置IO口为模拟输入状态的函数,这个函数主要是为了在低功耗状态下,尽量控制IO上不必要的漏电流产生。
/* USER CODE BEGIN 2 */
void GPIO_AnalogState_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/*Set all GPIO in analog state to reduce power consumption*/
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Pin = GPIO_PIN_All;
HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
HAL_GPIO_Init(GPIOC,&GPIO_InitStruct);
__HAL_RCC_GPIOA_CLK_DISABLE();
__HAL_RCC_GPIOB_CLK_DISABLE();
__HAL_RCC_GPIOC_CLK_DISABLE();
}
/* USER CODE END 2 */
到这里基本就已经实现了单片机的standby模式了,并且可以通过RTC的闹钟实现休眠一分钟后唤醒。但是每次唤醒后都会让RTC时钟的时间重置,所以最后我通过判断后备寄存器的方式,来保证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 */
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) != 0xA5A5) //判断后备寄存器是否被赋值
{
/* USER CODE END Check_RTC_BKUP */
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0;
sTime.Minutes = 0;
sTime.Seconds = 0;
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_MONDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 1;
sDate.Year = 0;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1, 0xA5A5); //为后备寄存器写入值
}
/** Enable the Alarm B
*/
sAlarm.AlarmTime.Hours = 0;
sAlarm.AlarmTime.Minutes = 0;
sAlarm.AlarmTime.Seconds = 0;
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_B;
if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler();
}
}
到这里,待机模式(standby)下RTC唤醒就已经实现了。
实验结果
![]()
在电路上,程序复位后,LD2会周期闪烁,然后再发送“AT+STANDBY”命令后,LD2熄灭,然后等待大概1分钟(时间和你发送进入待机模式的时间有关系),LD2重新开始周期闪烁。
经过万用表测量,最后单片机的待机功耗为5.8uA
总结
在利用RTC闹钟唤醒standby模式中,需要关注的问题有:
1.关于功耗控制,需要对不使用的IO口进行漏电流的控制,因此,最好将没有使用的IO口都配置为模拟输入状态。
2.利用RTC闹钟进行唤醒时,需要注意闹钟中断触发后,中断标识是否被清除,否则,standby模式很可能在进入后立马退出。
3.如果要在退出standby模式后,RTC时钟需要继续按照设定的时间计时,需要通过后备寄存器来判断单片机是否是第一次启动。