`简介
随着物联网(IoT)的兴起,产品对功耗的需求越来越强烈。作为数据采集的传感器节点通常需要在电池供电时长期工作,而作为联网的SOC也需要有快速的响应功能和较低的功耗。IoT 产品的功耗来源可以分成2部分,一部分是 MCU 内部的功耗,一部分是板载其他外设的功耗。而在 MCU 内的功耗又可以分成CPU 的功耗和片内外设的功耗。为了实现低功耗的功能,我们需要对它们进行合适的管理。
在该文档中,基于MM32 MCU 特性,适配RT-Thread 强大的应用调度功能,对功耗做优良的管理。
MM32 Series MCU 低功耗有三种模式,分别为:
Sleep/Stop/Standby,其中Sleep可以被任意中断唤醒,故在进入Sleep前需要关闭Sys
tick。Stop/Standby则不需要考虑这种问题。程序的移植 Low-power modes有三种,Sleep mode、Stop mode、Standby mode,实现如下 :void pwr_control(rt_uint8_t NewState)
{
/* Check the parameters */
assert_param(IS_POWERMODE_STATE(NewState));
if(PM_SLEEP_MODE_STANDBY == NewState)
{
// PWR CLOCK
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// RCC->APB2RSTR|=0X01FC;//REAST IO
//Enable to wake up pin function
PWR_WakeUpPinCmd(ENABLE);
//Enter standby mode
PWR_EnterSTANDBYMode();
}
else if(PM_SLEEP_MODE_DEEP == NewState)
{
/* enter Stop mode */
sysTick_Cmd(DISABLE);
RCC_APB1PeriphClockCmd(RCC_APB1ENR_PWREN, ENABLE);
PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
/* exit Sleep mode */
/* HSE和HSI已关闭需重启开启 */
sysTick_init();
SYSCLKConfig_START();
}
else
{
/* enter Sleep mode */
/* SysTick_Handler() 会自增rt_tick,需要DISABLE。否则无法进入睡眠 */
sysTick_Cmd(DISABLE);
__WFI();
/* exit Sleep mode */
/* 单纯使用sysTick_Cmd(ENABLE)不能正常,rt_thread_delay()也会有影响 */
sysTick_init();
}
}
在RT-Thread任务运行中使MCU进入低功耗状态,由于系统有个默认的工作模式,是IDLE空闲模式,这个模式下会一直调用sleep这个函数,导致无法进入低功耗模式,故需要修改成NONE模式,同时在进入前使系统进入临界状态。进入睡眠后使用K1(外部中断PA0)唤醒,在EXTI0中断中使任务退出临界状态,执行正常的任务。因为RT-Thread使用到systick,在Sleep mode模式时,是无法进入低功耗状态的,因此需要临时关闭Systick,唤醒后再次启用。在实验中我们使用外部中断模式唤醒,故进行如下配置:
// This function is EXTI0 1 Handler
void EXTI0_1_IRQHandler(void)
{
EXTI_ClearFlag(EXTI_Line0);
rt_exit_critical();
}
// NVIC EXTI Config
static void EXTI_NVIC_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
//ENABLE External interrupt
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// EXTI Config
static void EXTI_Config(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
//PA.0 use EXTI line 0
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
EXTI_StructInit(&EXTI_InitStructure);
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
// EXTI Config
static void EXTI_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA , ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //set as pull down input
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
// External interrupt wake-up standby initialization
void WKUP_STOP_Init(void)
{
// EXTI SYSTEM CLOLK ENABLE
EXTI_NVIC_Init();
EXTI_Config();
EXTI_GPIO_Config();
}
为了便于观察,我们建立两个线程,线程中进行LED闪烁,用来判断任务是否正常运行,代码如下:/* 线程1入口函数 */
static void thread1_entry(void *param)
{
while (1)
{
LED2_TOGGLE();
LED1_TOGGLE();
rt_thread_delay(1000);
}
}
unsigned char flash_buff[40];
/* 线程2入口函数 */
static void thread2_entry(void *param)
{
while (1)
{
LED4_TOGGLE();
LED3_TOGGLE();
rt_thread_delay(1000);
}
}
int event_simple_init()
{
/* 创建线程1 */
tid1 = rt_thread_create("t1",
thread1_entry, RT_NULL, /* 线程入口是thread1_entry, 入口参数是RT_NULL */
THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid1 != RT_NULL)
rt_thread_startup(tid1);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED);
/* 创建线程2 */
tid2 = rt_thread_create("t2",
thread2_entry, RT_NULL, /* 线程入口是thread2_entry, 入口参数是RT_NULL */
THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE);
if (tid2 != RT_NULL)
rt_thread_startup(tid2);
else
tc_stat(TC_STAT_END | TC_STAT_FAILED);
return 0;
}
int rt_application_init()
{
u16 i;
LED_Init();
for(i = 0; i < 10; i++) {
LED1_TOGGLE();
LED2_TOGGLE();
LED3_TOGGLE();
LED4_TOGGLE();
deleyNop(1000);
}
rt_enter_critical();
WKUP_STOP_Init();
pwr_control(PM_SLEEP_MODE_STANDBY);
event_simple_init();
}
考虑到芯片进入低功耗模式时,有些时候不易擦除,故在进入低功耗之前最好做一个比较大的延时。同时在进入之前,先使系统进入临界区。进入临界区后调度器将被上锁。在系统锁住调度器的期间,系统依然响应中断,如果中断唤醒了的更高优先级线程,调度器并不会立刻执行它,直到调用解锁调度器函数才尝试进行下一次调度。需要注意的是在唤醒后需要先执行退出临界区操作,当系统退出临界区的时候,系统会计算当前是否有更高优先级的线程就绪,如果有比当前线程更高优先级的线程就绪,将切换到这个高优先级线程中执行;如果无更高优先级线程就绪,将继续执行当前任务。
实验
按下Miniboard的K1键后工程开始运行,可以看到LED闪烁,表示唤醒开始工作。
`