1. STM32F105RBT6 的三种低功耗模式
1.1 sleep睡眠模式、stop停机模式、standby 待机模式
1.2 STM32中文参考手册有介绍STM32 低功耗模式的介绍
2. FreeRTOS 采用的是时间片轮转的抢占式任务调度机制,其低功耗设计思路一般是:
① 当运行空闲任务( IDLE任务)的时候就进入低功耗模式
② 在合适的时机,通过中断或者外部事件再唤醒MCU,退出低功耗模式
③ 对于STM32 系列单片机而言,systick 时间片如果设置的是1 ms,那么每隔1 ms 会将产生一个系统中断,可能会将MCU 从低功耗模式唤醒,如果MCU 频繁的进入、退出low power mode,MCU无法进入深度睡眠deep sleep,这是不合理的
3. FreeRTOS 给出了一种低功耗设计模式:Tickless Idle Mode
4. Tickless Idle Mode 的原理及实现
低功耗就是让单片机睡觉,只不过睡觉有很多种睡法,讲究人睡觉也讲究
4.1 情景分析
上图是任务调度示意图,横轴是时间轴,T1,T2,T3,T4是FreeRTOS 的时间片基准,FreeRTOS时间片一般是 1ms,产生一个systick 中断,有四个任务,分别是Task A、B、C、D
Task A、B、D 是周期性任务,例如三个LED灯,每隔100ms 亮一次
Task C 是突发性任务,例如,按键事件,按一下按键就产生一个外部事件
在两两任务之间有一个间隙,这个时候会运行空闲任务,Idle1,Idle2,Idle3,Idle4
4.1.1 Idle1 期间有一个systick 中断T1
运行完Task A后立马运行空闲任务,过了一会来了T1 中断,把MCU 又给唤醒了,然后又立马进入idle状态,过了一会后运行Task B,这个时候的T1 就是不合理的,就好像你在睡觉,然后我一脚把你踢醒,然后我跑了,你又接着睡,睡的很不爽
4.1.2 Idle2 期间可以一直睡觉,我不打你,你就可以睡的很爽了
4.1.3 Idle3 也可以一直睡觉,我也不打你,但是睡的时间太短了,就没必要睡了
干完task C,立马睡觉,然后又立马干task D,还没睡一会就又要起来干活(运行Task D),就像有点公司午睡只有半个小时一样,那还睡个毛,所以是否睡觉,什么时候睡觉,睡多久,睡多深,眯一会还是睡死,需要设计一套策略
4.1.4 和 Idle1 一样情况
5. Tickless Idle Mode 的软件设计原理
设计思想在于尽可能的让MCU 在运行空闲任务的时候进入低功耗模式,没事做就睡觉,节省粮食,少呼吸点空气吧你
① 合理的进入低功耗模式,避免频繁的在低功耗模式和运行模式之间进行切换,睡一下起来干活,睡一下起来干活很烦的,可以通过调整系统定时器中断触发时间长短来调整(systick中断),调一下systick 的值,然后测试一下功耗,调一下,测一下功耗。
Tickless:减少systick,原来会产生systick 的位置,现在不产生systick 中断,往后挪,增大时间片长度(或减小)
② 当MCU 被唤醒时,通过某种方式为系统时钟提供补偿,唤醒的两种方式:系统时钟中断,外部事件中断,可以通过运行在低功耗模式下的某种定时器来计算出MCU处于低功耗模式下的时间,在MCU唤醒后对系统时间进行软件补偿
③ 低功耗时间补偿策略,根据mcu低功耗特性和具体应用场景而定,可参考STM32 中文参考手册的低功耗模式章节
6. FreeRTOS 低功耗模式Tickless Mode 使能宏开关
置1 打开,置0 关闭
#define configUSE_TICKLESS_IDLE 1
7. FreeRTOS 创建空闲任务, 系统自动调度的,不需要人为调度
FreeRTOS 在启动任务调度的时候会创建一个空闲任务,没事干的时候就一直运行这个任务,摸鱼,老板来了就干活,老板一走就摸鱼任务
7.2 空闲任务宏定义 portTASK_FUNCTION
/*
* The Idle task.
*
* The portTASK_FUNCTION() macro is used to allow port/compiler specific language extensions. The equivalent prototype for this
function is:
*
* void prvIdleTask(void *pvParameters);
*
*/
static portTASK_FUNCTION(prvIdleTask, pvParameters)
{
(void)pvParameters;
/*This is the FreeRTOS idle task, which is created automatically when the scheduler is started*/
for(;;)
{
/* See if any tasks have deleted themselves - if so then the idle task is responsible for freeing the deleted
* task's TCB and stack
*/
prvCheckTasksWaitingTermination();
#if (0 == configUSE_PREEMPTION)
{
/* If we are not using preemption we keep forcing a task switch to see if any other task has become available. If we are using
* preemption we don't need to do this as any task becoming available will automatically get the processor anyway
*/
taskYIELD();
}
#endif /* configUSE_PREEMPTION */
#if ((1 == configUSE_PREEMPTION) && (1 == configIDLE_SHOULD_YIELD))
{
/* When using preemption tasks of equal priority will be timesliced. If a task that is sharing the idle priority is ready
* to run then the idle task should yield before the end of the timeslice.
* A critical region is not required here as we are just reading from the list, and an occasional incorrect value will not
* matter. If the ready list at the idle priority contains more than one task then a task other than the idle task is ready
* to execute.
*/
if (listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[tskIDLE_PRIORITY])) > (UBaseType_t)1)
{
taskYIELD();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ((configUSE_PREEMPTION == 1) && (configIDLE_SHOULD_YIELD == 1)) */
#if (1 == configUSE_IDLE_HOOK)
{
extern void vApplicationIdleHook(void);
/* Call the user defined function from within the idle task. This allows the application designer to add background
* functionality without the overhead of a separate task. NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
* CALL A FUNCTION THAT MIGHT BLOCK. */
vApplicationIdleHook();
}
#endif /* configUSE_IDLE_HOOK */
/* This conditional compilation should use inequality to 0, not equality to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP()
* is called when user defined low power mode implementations require configUSE_TICKLESS_IDLE to be set to a value other than 1.
*/
#if (configUSE_TICKLESS_IDLE != 0)
{
TickType_t xExpectedIdleTime;
/* It is not desirable to suspend then resume the scheduler on each iteration of the idle task. Therefore, a preliminary
* test of the expected idle time is performed without the scheduler suspended. The result here is not necessarily valid.
*/
xExpectedIdleTime = prvGetExpectedIdleTime();
if (xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP)
{
vTaskSuspendAll();
{
// Now the scheduler is suspended, the expected idle time can be sampled again, and this time its value can be used
configASSERT(xNextTaskUnblockTime >= xTickCount);
xExpectedIdleTime = prvGetExpectedIdleTime();
if (xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP)
{
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime);
traceLOW_POWER_IDLE_END();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(void) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_TICKLESS_IDLE */
}
}
8. 低功耗模式处理 vPortSuppressTicksAndSleep,需要根据MCU的低功耗模式编写代码vPortSuppressTicksAndSleep
#if configUSE_TICKLESS_IDLE == 1
__weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickCTRL;
TickType_t xModifiableIdleTime;
/* Make sure the SysTick reload value does not overflow the counter. */
if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
{
xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
}
/* Stop the SysTick momentarily. The time the SysTick is stopped for is accounted for as best it can be, but using the tickless mode will
inevitably result in some tiny drift of the time maintained by the kernel with respect to calendar time. */
portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
/* Calculate the reload value required to wait xExpectedIdleTime tick periods. -1 is used because this code will execute part way
through one of the tick periods. */
ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
if( ulReloadValue > ulStoppedTimerCompensation )
{
ulReloadValue -= ulStoppedTimerCompensation;
}
/* Enter a critical section but don't use the taskENTER_CRITICAL() method as that will mask interrupts that should exit sleep mode. */
__disable_irq();
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE );
/* If a context switch is pending or a task is waiting for the scheduler to be unsuspended then abandon the low power entry. */
if( eTaskConfirmSleepModeStatus() == eAbortSleep )
{
/* Restart from whatever is left in the count register to complete this tick period. */
portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
/* Restart SysTick. */
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
/* Reset the reload register to the value required for normal tick periods. */
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
/* Re-enable interrupts - see comments above __disable_irq() call above. */
__enable_irq();
}
else
{
/* Set the new reload value. */
portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
/* Clear the SysTick count flag and set the count value back to zero. */
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
/* Restart SysTick. */
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
/* Sleep until something happens. configPRE_SLEEP_PROCESSING() can set its parameter to 0 to indicate that its implementation contains
its own wait for interrupt or wait for event instruction, and so wfi should not be executed again. However, the original expected idle
time variable must remain unmodified, so a copy is taken. */
xModifiableIdleTime = xExpectedIdleTime;
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
if( xModifiableIdleTime > 0 )
{
__dsb( portSY_FULL_READ_WRITE );
__wfi();
__isb( portSY_FULL_READ_WRITE );
}
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
/* Stop SysTick. Again, the time the SysTick is stopped for is accounted for as best it can be, but using the tickless mode will
inevitably result in some tiny drift of the time maintained by the kernel with respect to calendar time. */
ulSysTickCTRL = portNVIC_SYSTICK_CTRL_REG;
portNVIC_SYSTICK_CTRL_REG = ( ulSysTickCTRL & ~portNVIC_SYSTICK_ENABLE_BIT );
/* Re-enable interrupts - see comments above __disable_irq() call above. */
__enable_irq();
if( ( ulSysTickCTRL & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
{
uint32_t ulCalculatedLoadValue;
/* The tick interrupt has already executed, and the SysTick count reloaded with ulReloadValue. Reset the
portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick period. */
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
/* Don't allow a tiny value, or values that have somehow underflowed because the post sleep hook did something that took too long. */
if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
{
ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
}
portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
/* The tick interrupt handler will already have pended the tick processing in the kernel. As the pending tick will be
processed as soon as this function exits, the tick value maintained by the tick is stepped forward by one less than the
time spent waiting. */
ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
}
else
{
/* Something other than the tick interrupt ended the sleep. Work out how long the sleep lasted rounded to complete tick
periods (not the ulReload value which accounted for part ticks). */
ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
/* How many complete tick periods passed while the processor was waiting? */
ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
/* The reload value is set to whatever fraction of a single tick period remains. */
portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
}
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
value. The critical section is used to ensure the tick interrupt can only execute once in the case that the reload register is near zero. */
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
portENTER_CRITICAL();
{
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
vTaskStepTick( ulCompleteTickPeriods );
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
}
portEXIT_CRITICAL();
}
}
#endif /* #if configUSE_TICKLESS_IDLE */
9. STM32Lxx 系列是专门为了RTOS 低功耗设计的
STM32L系列微控制器是意法半导体(STMicroelectronics)推出的一款超低功耗微控制器产品系列,旨在满足电池供电和节能应用的需求。该系列具有优异的功耗特性,并提供了多种低功耗模式和技术,以减少功耗并延长电池寿命。
STM32L系列的主要特点包括:
① 低功耗执行核心:采用ARM Cortex-M0+/M3/M4内核,具有高性能和低功耗的特点。
② 丰富的低功耗模式:包括低功耗运行模式、低功耗睡眠模式、低功耗停止模式等,可以根据需要选择不同的模式以达到最佳功耗效果。
③ 电源管理单元(PMU):提供了灵活的电源管理功能,包括自动电源切换、可选电源监测等。
④专用硬件加速器:部分型号提供了专用的硬件加速器,如AES加密、CRC校验等,可以提高性能并降低功耗。
⑤ 丰富的外设接口:包括UART、SPI、I2C、GPIO等常用外设接口,可满足不同应用的需求。
⑥ 多样化的封装选项:提供了不同的封装选项,以适应不同的应用场景和空间限制。
总之,STM32L系列是专门为低功耗要求而设计的微控制器产品系列,适用于电池供电和节能应用,比如便携设备、智能家居、传感器节点等。
10. 降低功耗的措施
为了节省功耗,可以将未使用的 GPIO 口设置为低功耗状态。以下是一些常见的方法:
① 输入模式:将未使用的 GPIO 口设置为输入模式,不使用推挽输出或开漏输出。这样可以减少功耗,因为输入模式下的电流消耗较低。
② 禁用上拉和下拉:如果未使用的 GPIO 口被设置为输入模式,确保禁用了上拉电阻和下拉电阻。禁用这些电阻可以降低静态电流消耗。
③ 关闭输入缓冲器:对于不需要读取外部信号的 GPIO 口,可以关闭输入缓冲器以减少功耗。这可以通过配置寄存器来实现,具体方法可能因芯片型号而有所不同。
④ 关闭输出驱动器:如果未使用的 GPIO 口被设置为输出模式,可以将输出驱动器关闭或调整为较低的驱动能力。这可以降低功耗并减少与其他电路的干扰。
⑤ 动态切换电源:如果可能,将未使用的 GPIO 口连接到外部电源开关或电源选择电路上。通过动态切换电源,可以完全断开或切换到更低功耗的电源,从而实现更高的功耗节省效果。
⑥ 将未使用的模块时钟关闭
⑦ 降低主频,需要综合考虑,主频降低,性能会下将,数据处理会变慢
⑧ 降低性能,这个需要综合考虑,需要在性能要求和功耗要求之间取得平衡
⑨ 选择不同的硬件,这个需要硬件攻城狮参与选型
每个芯片的 GPIO 管理方式和功耗优化方法可能会有所不同,查阅芯片制造商提供的技术参考手册和数据手册,以获得更详细和针对性的操作指南。