一、工作模式
1.1、工作模式简介
- 运行模式:CPU运行,周边功能模块运行
- 休眠模式(Sleep Mode):CPU 停止运行,周边功能模块运行
- 深度休眠模式(Deep Sleep Mode):CPU 停止运行,高速时钟停止运行。
1.2、运行模式
系统在电源上电复位后,或从各低功耗模式唤醒后,微控制器 MCU 处于运行状态,各模块的运行
状态如图 运行模式下可运行模块一览所示。当 CPU不需继续运行时,可以利用多种低功耗模
式来节能。用户需要根据最低能耗、最快速启动时间、可用的唤醒源等条件,选定一个最佳的低功
耗模式。
运行模式下可运行模块一览
几种降低运行模式下芯片功耗的方法:
- 在运行模式下,通过对预分频寄存器(RCC_HCLKDIV, RCC_PCLKDIV)进行编程,可以降低任意
一个系统时钟(HCLK, PCLK)的速度。进入睡眠模式前,也可以利用预分频器来降低外设的时
钟。 - 在运行模式下,关闭不使用外设的时钟(RCC_HCLKEN, RCC_PCLKEN)来减少功耗。
- 使用深度休眠模式代替休眠模式,因为本产品的唤醒时间极短(~3us),亦可满足系统的实时
响应的需求。
1.3、 休眠模式(Sleep Mode)
使用WFI 指令可以进入休眠模式,休眠模式下,CPU 停止运行,但系统时钟、NVIC 中断处理以及
非HCLK 驱动的周边功能模块仍都可以工作。
系统进入休眠状态,不会改变端口状态,在进入休眠前请根据需要更改 IO 的状态为休眠模式下的
状态
1.3.1、如何进入休眠模式:
通过执行 WFI 指令进入睡眠状态。根据 Cortex®
-M0+系统控制寄存器中的 SLEEPONEXIT 位的值,
有两种选项可用于选择睡眠模式进入机制:
- SLEEP-NOW:如果SLEEPONEXIT=0,当WFI 或WFE 被执行时,微控制器立即进入睡眠模
式。 - SLEEP-ON-EXIT:如果 SLEEPONEXIT=1,系统从最低优先级的中断处理程序中退出时,微控
制器就立即进入睡眠模式。
1.3.2、 退出休眠模式:
如果执行WFI 指令进入睡眠模式,任意一个高优先级嵌套向量中断控制器响应的外设中断都能将
系统从睡眠模式唤醒。
休眠模式下可运行模块一览
使用注意:
-
SLEEP-ON-EXIT=1,执行完中断自动进入 Sleep,程序不需要写__wfi();
-
SLEEP-ON-EXIT=0,main()执行__wfi()后进入 Sleep,中断触发且执行完中断程序返回
main()后,执行WFI 指令后进入 Sleep。等待后续中断触发。 -
SLEEP-ON-EXIT 位不影响__wfi()指令的执行。SLEEP-ON-EXIT=0:main()执行__wfi()后进 入
Sleep,中断触发且执行完中断程序返回 main()后,继续往下执行; -
若在中断中进入 Sleep后,只有优先级高于此中断的中断才能唤醒,先执行高优先级,再执
行低优先级;优先级低于或等于当前中断的中断不能唤醒。
1.4、深度休眠模式(Deep Sleep Mode)
使用SLEEPDEEP 配合WFI 指令可以进入深度休眠模式,在深度休眠模式下,CPU 停止运行,高速
时钟关闭,低速时钟可配置是否运行,部分低功耗的周边模块可配置为是否运行,NVIC 中断处理
仍可以工作。
- 系统从高速时钟进入深度休眠模式,高速时钟自动关闭,低速时钟保持进入深度睡眠前的状 态。
- 系统从低速时钟进入深度休眠模式,低速时钟保持运行,除了低功耗模块可以运行,其他模 块自动关闭。
- 系统时钟切换时,所有时钟都不会自动关闭,需要根据功耗及系统需求软件关闭打开相应的 时钟。
- 系统进入深度休眠状态,不会改变端口状态,在进入深度休眠前根据需要更改 IO 的状态为 深度休眠模式下的状态。
1.4.1、进入深度休眠模式:
-
首先设置 Cortex®
-
-M0+系统控制寄存器中的SLEEPDEEP位,通过执行WFI指令进入深度睡眠状态。 根据 Cortex®
-
-M0+系统控制寄存器中的 SLEEPONEXIT 位的值,有两种选项可用于选择深度睡眠模 式进入机制:
-
SLEEP-NOW:如果SLEEPONEXIT=0,当WFI 或WFE 被执行时,微控制器立即进入睡眠模式。
-
SLEEP-ON-EXIT:如果 SLEEPONEXIT=1,系统从最低优先级的中断处理程序中退出时,微控制器 就立即进入睡眠模式。
1.4.2、退出深度休眠模式:
如果执行 WFI 指令进入睡眠模式,任意一个被嵌套向量中断控制器(NVIC)响应的外设中断(Deep
Sleep 模式下可运行的周边模块中断)都能将系统从睡眠模式唤醒
注:
- 在Deep Sleep 模式下,芯片重新复位后,可以通过 SWD 接口唤醒
- 只有在选择内部低速监控外部低速时钟功能时才能唤醒
二、Cortex®-M0+内核系统控制寄存器(SCR)
进入深度休眠后,唤醒后系统时钟有两种选择,默认使用进入深度休眠的时钟,配置寄存器
RCC_SYSCLKCR.WKBYHIRC 为 1 后不管进入深度休眠前是什么时钟,唤醒后都使用内部高速时钟
HIRC。如果进入深度睡眠前系统使用外部晶体振荡这样设置可以加速系统唤醒。
注:该部分资源来自于CX32L003官方用户手册。
三、 程序设计部分
3.1、进入睡眠模式
可以通过调用系统函数来进入睡眠模式
HAL_PWR_EnterSLEEPMode(PWR_SLEEPENTRY_WFI);
void HAL_PWR_EnterSLEEPMode(uint8_t SLEEPEntry)
{
/* Check the parameters */
assert_param(IS_PWR_SLEEP_ENTRY(SLEEPEntry));
/* Clear SLEEPDEEP bit of Cortex System Control Register */
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* Select SLEEP mode entry -------------------------------------------------*/
if(SLEEPEntry == PWR_SLEEPENTRY_WFI)
{
/* Request Wait For Interrupt */
__WFI();
}
else
{
/* Request Wait For Event */
__SEV();
__WFE();
__WFE();
}
}
由代码可知,是通过清除第三位为0,来使其进入睡眠模式
查询用户手册可知
3.2、从睡眠模式唤醒
通过官方手册可知,任意一个高优先级中断都可将其唤醒:
如果执行WFI 指令进入睡眠模式,任意一个高优先级嵌套向量中断控制器响应的外设中断都能将
系统从睡眠模式唤醒。
可参考官方代码AWK的sleep例程;
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock to HIRC 24MHz*/
SystemClock_Config();
GPIO_Init();
/* Configure uart1 for printf */
LogInit();
printf("AWK function example.\n");
printf("System wake up from sleep mode using auto wakeup timer, setting 8s wakeup interval.\n");
// BSP_LED_Init(LED1);
__HAL_RCC_AWK_CLK_ENABLE();
sAwkHandle.Instance = AWK;
sAwkHandle.Init.ClkSel = AWK_CLOCK_LIRC;
sAwkHandle.Init.ClkDiv = AWK_CLOCK_DIV_1024; // 32768Hz/1024 = 32Hz
sAwkHandle.Init.Period = 0; //初值 向上计数溢出 (256-0)/32 = 8S 初值避免接近250以上
HAL_AWK_Init(&sAwkHandle);
/* Start AWK module to wake up system from sleep mode later */
HAL_AWK_Start(&sAwkHandle);
HAL_NVIC_EnableIRQ(AWK_IRQn);
/* Suspend systemtick to avoid system to be waked up by systemtick */
HAL_SuspendTick();
while(1)
{
log_GPIO_DeInit();
HAL_PWR_EnterSLEEPMode(PWR_SLEEPENTRY_WFI);
log_GPIO_EnInit();
sleep_count++;
printf("System wake up from sleep mode count: %d \n",sleep_count);
}
}
通过设置溢出值,来产生一个唤醒中断,从而将设备从睡眠模式唤醒。
3.2、进入深度睡眠模式
//通过调用系统库函数可使其进入深度睡眠模式
HAL_PWR_EnterDEEPSLEEPMode(); // 进入深度睡眠
//系统库函数
void HAL_PWR_EnterDEEPSLEEPMode(void)
{
/* Set SLEEPDEEP bit of Cortex System Control Register */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
// __NOP();
// __NOP();
// __NOP();
/* This option is used to ensure that store operations are completed */
#if defined(__CC_ARM)
__force_stores();
#endif
/* Request Wait For Interrupt */
__WFI();
}
通过代码可以得知,通过设置位,再将一些指针变量保存后,通过调用WFI指令,使得CPU进入深度睡眠模式
通过代码
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define SCB_SCR_SLEEPONEXIT_Pos 1U /*!< SCB SCR: SLEEPONEXIT Position */
#define SCB_SCR_SLEEPONEXIT_Msk (1UL << SCB_SCR_SLEEPONEXIT_Pos) /*!< SCB SCR: SLEEPONEXIT Mask*/
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
可以看出,通过设置SCB->SCR寄存器的第二个寄存器位为1,即SLEEPONEXIT = 1,设置为系统从最低优先级的中断处理程序中退出时,微控制器就立即进入睡眠模式。
3.3、从深度睡眠模式唤醒
任意中断可唤醒
例如,RTC可在深度睡眠中运行,此时,可以通过一个RTC中断将CPU从深度睡眠模式唤醒
例程如下,部分参考官方手册:
#include "bsp_rtc.h"
#include "bsp_gpio.h"
#define TEST_RTC_CLOCK RTC_CLOCK_LIRC // RTC时钟选择
#define TEST_RTC_CYCLE_TIME HAL_RTC_ALARM2_10S // 周期中断 定时10S
RTC_TimeTypeDef sTime_current = {0};
RTC_DateTypeDef sDate_current = {0};
RTC_HandleTypeDef rtc_test = {0};
RTC_TimeTypeDef sTime_test = {0};
RTC_DateTypeDef sDate_test = {0};
uint32_t test_inflag = 0;
void RTCClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
if (TEST_RTC_CLOCK == RTC_CLOCK_LIRC)
{
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LIRC;
RCC_OscInitStruct.LIRCState = RCC_LIRC_ON;
RCC_OscInitStruct.LIRCCalibrationValue = RCC_LIRCCALIBRATION_32K;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
}
__HAL_RCC_RTC_CLK_ENABLE();
//__HAL_RCC_GPIOC_CLK_ENABLE();
}
void RTC_Init(void)
{
/*set init handle*/
rtc_test.Instance = RTC;
rtc_test.Init.ClockSource = TEST_RTC_CLOCK; // RTC时钟源
rtc_test.Init.HourFormat = RTC_HOURFORMAT_24; // 24小时制
rtc_test.Init.TimeAdjustMode = RTC_TIME_ADJUST_SEC30; // RTC 时钟调校 周期
rtc_test.Init.TimeTrim = 0X00; // 时钟补偿时间
rtc_test.State = HAL_RTC_STATE_RESET; // 使能RTC计数器
HAL_RTC_Init(&rtc_test);
/*set time and date */
sTime_test.Hours = 12;
sTime_test.Minutes = 45;
sTime_test.Seconds = 16;
sDate_test.Date = 6;
sDate_test.Month = 3;
sDate_test.Year = 2024;
sDate_test.WeekDay = RTC_WEEKDAY_SUNDAY;
HAL_RTC_SetTime_SetDate(&rtc_test, &sTime_test, rtc_test.Init.HourFormat, &sDate_test); // TRC 时间 日期设置
HAL_RTC_Alarm2_Config(&rtc_test, ENABLE, (HAL_RTCAlarm2TypeDef)TEST_RTC_CYCLE_TIME); // 周期闹钟2计数周期设定
/* Set interrupt priority and turn on interrupt*/
HAL_NVIC_SetPriority(RTC_IRQn, PRIORITY_LOW); // 中断优先级选择
HAL_RTC_Alarm_INT_Config(&rtc_test, RTC_ALARM_2, ENABLE); // 使能ALAM2周期中断
HAL_RTC_Alarm_Clear_Flag(RTC_ALARM_2); // 周期闹钟2中断原始状态被清除
HAL_NVIC_EnableIRQ(RTC_IRQn); // 使能 RTC_IRQn
printf("Enter Test RTC Calendar and ALARM2 10S \r\n");
}
void BSP_Rtc_Function(void)
{
if (test_inflag == 1)
{
test_inflag = 0;
log_GPIO_EnInit();
printf("Get RTC ALARM2 interrupt! \r\n");
HAL_RTC_GetTime_Date(&rtc_test, &sTime_current, &sDate_current); // 获取 RTC 时间
printf("NOW Time is:%d-%d-%d %d:%d:%d \r\n", sDate_current.Year, sDate_current.Month,
sDate_current.Date, sTime_current.Hours, sTime_current.Minutes, sTime_current.Seconds);
printf(" \r\n");
log_GPIO_DeInit();
HAL_PWR_EnterDEEPSLEEPMode();
}
}
void RTC_Irq_CallBack(void)
{
if (HAL_RTC_Alarm_Get_Flag(RTC_ALARM_2) == SET) // 获取周期闹钟2中断是否被激活
{
HAL_RTC_Alarm_Clear_Flag(RTC_ALARM_2); // 周期闹钟2中断原始状态被清除
test_inflag = 1;
}
}
通过在主函数调用BSP_Rtc_Function();每隔十秒可将其唤醒,并打印RTC时间,打印完成后,再通过调用深度睡眠函数, HAL_PWR_EnterDEEPSLEEPMode();可再次进入深度睡眠模式,效果如下所示:
修改代码,设计为,当电压低于4.4V时,开启低功耗模式,当重新上电时,关闭低功耗模式,并打印RTC时钟,此时监测电压为4v,
代码如下
oid BSP_Lvd_Mode_Function(void)
{
if (lvd_irq_mode == 1)
{
lvd_irq_mode = 0;
printf("deep sleep\r\n");
HAL_Delay(5); // 测试阶段观测打印进入低功耗模式
log_GPIO_DeInit(); // 关闭串口打印功能
sLvdHandle.Init.VoltageLevel = LVD_VOLLEVEL_4_0V;
sLvdHandle.Init.TriggerSel = LVD_TRIGGER_FALLING; // 下降沿触发使能(VDD从低于阈值电压变为高于阈值电压)
HAL_LVD_Init(&sLvdHandle); // 初始化会把寄存器清除
HAL_LVD_Enable_IT(&sLvdHandle);
HAL_PWR_EnterDEEPSLEEPMode(); // 进入深度睡眠模式
}
if (lvd_irq_mode == 2)//设备监测到电压大于4.0
{
lvd_irq_mode = 0;
rtc_print_flag = 1;
log_GPIO_EnInit();
printf("exit deep sleep mode! \r\n");
sLvdHandle.Init.VoltageLevel = LVD_VOLLEVEL_4_4V;
sLvdHandle.Init.TriggerSel = LVD_TRIGGER_RISING;
HAL_LVD_Init(&sLvdHandle);
HAL_LVD_Enable_IT(&sLvdHandle);
}
}
void BSP_Rtc_Function(void)
{
#if 0
if (test_inflag == 1)
{
test_inflag = 0;
log_GPIO_EnInit();
printf("Get RTC ALARM2 interrupt! \r\n");
HAL_RTC_GetTime_Date(&rtc_test, &sTime_current, &sDate_current); // 获取 RTC 时间
printf("NOW Time is:%d-%d-%d %d:%d:%d \r\n", sDate_current.Year, sDate_current.Month,
sDate_current.Date, sTime_current.Hours, sTime_current.Minutes, sTime_current.Seconds);
printf(" \r\n");
log_GPIO_DeInit();
HAL_PWR_EnterDEEPSLEEPMode();
}
#endif
if (rtc_print_flag == 1)
{
rtc_print_flag = 0;
HAL_RTC_GetTime_Date(&rtc_test, &sTime_current, &sDate_current); // 获取 RTC 时间
printf("NOW Time is:%d-%d-%d %d:%d:%d \r\n", sDate_current.Year, sDate_current.Month,
sDate_current.Date, sTime_current.Hours, sTime_current.Minutes, sTime_current.Seconds);
}
}
通过功耗仪表可以看到,处于深度睡眠模式下,只开启RTC和LVG电压监测时,功耗在2微安左右,与数据手册的功耗基本符合,也满足低功耗的条件
如图所示: