在之前的低功耗软件设计中也提到过一部分的stm32降功耗的方法,freeRtos系统帮我们写好的一个睡眠模式tickless,当我们的系统进入空闲任务后,就会自动睡眠,来达到降功耗的目的,打开方式也比较简单,在FreeRtosConfig中将宏定义 configUSE_TICKLESS_IDLE 配置为 1。就是这么简单,我当前用的片子是stm32L452,初步使用了这个方法,在72Mhz主频下,功耗在2ma,如果你想验证,直接通过ST提供给我们的CubeMX生成一下代码就可以了,在低功耗测试中,我们经常要更改主频或者管脚,使用cubMX生成代码是真的很方便。
在这个试验中我们会遇到一个问题,就是CubeMx生成的代码,打开宏定义之后不能进入睡眠。这个原因是因为CubeMX自动生成了一个任务,导致我们进入空闲任务后仍然无法获取到空闲时间,导致无法休眠, defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);屏蔽掉这个任务就可以了。
上面我们说的都是tickless这个模式,但是作为低功耗产品来说,这个功耗还是很大怎么办,那么就要我们就要将MCU切到其他模式下,进一步的降低功耗。
说道这里就要说一下stm32的低功耗模式了,之前我们的stm32F系列四种模式,L系列为5种,L系列近几年发展很快,模式多,电源管理及低功耗外设,还有漏电流比F系列小很多。
每种模式肯定功耗不一样啊,但是要根据产品的需求来选择合适的模式。
上面这两个图比较重要,这样我们就可以知道哪种功耗最低,怎么进入模式,怎么退出模式,有没有唤醒延时,唤醒后,RAM数据是不是还存在等等,都是重要的评估标准。
有的模式任意中断都可以唤醒,有的则必须外部中断或者特定中断唤醒,有的无唤醒延时等。
不管哪种模式其实最终通过关闭相应的时钟来达到降低功耗的目的,mcu的主要功耗来源也是时钟,还有管脚上的一些漏电流。闲置的IO都设置为模拟输入配置,理论上可以达到IO零消耗。之前测试过,在F系列上将不用管脚配置一下,电流下降很多,L系列这方面做的很好了,即使不配置功耗也不是很高。
说道这里我们肯定要了解我们的MCU的时钟情况,CubeMx的作用就来了,以stm32L452RE为例:
通过上面的图我们可以很清晰的看到时钟来源,有外部的8Mhz晶振,和内部的HSI MSI LSI晶振,外部晶振肯定稳定性更高些,也是我们经常用到的,我们的低功耗应用中,MSI LSI HSI等几种将会经常被提及。
下面主要说两种模式,低功耗运行模式和低功耗stop2模式
低功耗运行模式首先在几种模式中功耗算低的,并且内核和外设都可以保持运行状态
Stop模式,内核停止,Vcore范围内的时钟停止,PLL,MSI,HSI,HSE都被禁止,SRAM和寄存器的值保留,功耗够低。
低功耗运行模式在freeRtos中的应用也很简单分为几部
#define configUSE_TICKLESS_IDLE 1 (使能低功耗模式)
#define configPRE_SLEEP_PROCESSING(x) OS_PreSleepProcessing(x) //睡眠入口
#define configPOST_SLEEP_PROCESSING(x) OS_PostSleepProcessing(x)//唤醒后操作
void OS_PreSleepProcessing(uint32_t *ulExpectedIdleTime)
{
*ulExpectedIdleTime = 0; //屏蔽掉默认的wfi指令执行方式
HAL_PWREx_EnableLowPowerRunMode();
}
void OS_PostSleepProcessing(uint32_t *ulExpectedIdleTime)
{
HAL_PWREx_DisableLowPowerRunMode();
}
低功耗运行模式就完了,比之前的tickless模式功耗要低一点点。
停机模式的使用耗费了很多的经历,过程中出现了很多的问题,比如说默认使用的HSE时钟,滴答定时器在stop模式唤醒后变慢。
最开始的时候我就是按照上面的步骤在入口函数中添加睡眠指令HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
在唤醒出口函数中添加对时钟重新的配置。
Stop模式后,PLL时钟被停掉了,我们开始的时候用的PLL时钟,stop模式唤醒后,默认的使用内部MSI时钟。
通过这个思路我开始用的HSE时钟,唤醒后我重新SystemClock_Config(),一下应该就可以了,但是还是慢,网上也都说需要重新配下时钟,但是没有人说怎么配置。。无语啊。
那么我换条思路,不唤醒后默认使用MSI时钟吗??那么我开始的时候就配置为MSI时钟,使用48Mhz,这样配置后,确实不变慢了,也足以说明,确实唤醒后使用的MSI时钟,但是这个时候出现了一个问题,我板卡重启后,板卡无法运行,只有debug下能运行,感觉像是只能RAM运行,falsh中找不到时钟配置似的,还有个问题就是停机模式下确实功耗降低了,但是在1ma左右,没有达到理想值ua级别。
这下回到的原点
freeRtos的时钟来源从时钟树上可以看到来自AHB,我们的时钟最开始配置的HSE,也就是滴答时钟应该也变慢了,唤醒中断本身就变慢了,我们重新配置时钟,然后又进入stop,所以感觉像是重配没有管用。那么我们将唤醒中断改为LPTIM或者RTC,这样我们就没有什么问题了。St官方给我们提供了参考历程,使用的RTC唤醒,他使用的stm32L476,经过芯片等配置的更改,可以跑到我的L452上了,测试确实是好用的,功耗在ua。唤醒后时钟没有问题。官方默认的时钟使用的MSI,应该考虑到切换时钟会存在时间的开销和电流的消耗。
官方的例程使用的是无系统的,我想使用系统,同样也是上面的操作,增加两个用户函数,在里面实现睡眠动作和唤醒后的操作。
void OS_PreSleepProcessing(uint32_t *ulExpectedIdleTime)
{
TickType_t tick;
*ulExpectedIdleTime = 0; //屏蔽掉默认的wfi指令执行方式
tick = prvGetExpectedIdleTime();//获取freertos空闲时间
HAL_RTCEx_DeactivateWakeUpTimer(&RTCHandle);//使用RTC唤醒
HAL_RTCEx_SetWakeUpTimer_IT(&RTCHandle,tick, RTC_WAKEUPCLOCK_RTCCLK_DIV16);//设置唤醒时间
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);//进入stop模式
}
void OS_PostSleepProcessing(uint32_t *ulExpectedIdleTime)
{
SYSCLKConfig_STOP(); //恢复PLL时钟
}
这样我们的睡眠和唤醒就正常了,我们使用 prvGetExpectedIdleTime(),动态获取空闲时间,避免我们在睡眠过程中,错过任务操作,睡眠和唤醒时间由实时系统说了算。
St的历程跑的很好,我们在移植的过程中仿佛并不是那么顺利,会遇到第一个问题。
if( HAL_RTC_Init(&RTCHandle) != HAL_OK)
{
/* Initialization Error */
}
移植到我们的板子后,RTC时钟初始化不通过,st历程好好的,其实st也做操作了
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
/*##-1- Configure the RTC clock source ######################################*/
/* -a- Enable LSI Oscillator */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
if(HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
while(1);
}
/* -b- Select LSI as RTC clock source */
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;
if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
{
while(1);
}
/* Enable RTC Clock */
__HAL_RCC_RTC_ENABLE();
/*##-3- Configure the NVIC for RTC Alarm ###################################*/
HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 0x0, 0);
HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
}
void HAL_RTC_MspDeInit(RTC_HandleTypeDef *hrtc)
{
/*##-1- Reset peripherals ##################################################*/
__HAL_RCC_RTC_DISABLE();
}
增加这两个函数后,我们就发现可以运行通过。但是又出现了一个问题
我调用了delay_ms的一个延时,却出现了问题,整个程序进入硬件中断。
在freeRtosConfig.h中添加这个宏定义即可解决。
#define INCLUDE_xTaskGetSchedulerState 1
其他的函数可以看一下我提供的源代码,这里不着重的说了。
在我们的实际应用中一定要尽量的使系统多处在空闲任务中,这样才能长时间进入模式
为了满足我们的项目需求,我们还可以通过标志位来切换到不同的模式中,例如:
网上搜索下载的文档及官方测试代码还有修改的代码,将全部开放下载,可以在下面链接中下载到。绝对超值。
上面的方法目前还没有长期测试,只为大家提供一点思路。如有问题可留言讨论。
注:
当你要包含新的hal库文件时,除了添加相应的.c文件,引用.h文件外,一般还要在stm32l4xx_hal_conf.h中打开相应的
宏定义。
另外使用低功耗的模式的时候,要注意FreeRTOSConfig.h文件中的宏定义,有的时候打开了不必要的宏定义,会使低功耗模式下,任务出现无法调度的情况。(低功耗模式正常,但是任务无法调度),同时系统时钟中断,RTC_WKUP_IRQHandler等中断的实现也要小心修改