FreeRTOS的低功耗中的Tickless Idle Mode

低功耗支持-Tickless Idle Mode

低功耗简介

  • 开发过程中我们通常需要减少单片机功耗,针对FreeRTOS来说,通常使用Idle task hook来让单片机进入低功耗工作状态。这种简单的实现方式受限于一些场景,单片机某些情况下必须周期性退出和进入低功耗状态,从而处理时钟中断(SysTick),如果时钟中断频率过高,开启和关闭低功耗过程的功耗会比很多低功耗模式节省的电量还要多,除非使用最低功耗的模式。
  • 鉴于此,FreeRTOS的Tickless Idle Mode会在没有任务执行的情况下停止周期性的时钟中断,然后在时钟中断启动时对RTOS的tick计数器设定一个合适的值。

宏定义portSUPPRESS_TICKS_AND_SLEEP()

  • 一般形式:portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime)
    定义Tickless Idle 函数(以下简称功能函数)时需要在FreeRTOSConfig.h中,将configUSE_TICKLESS_IDLE宏定义为1,此时被定义的功能函数可以在具有Tickless Idle功能的接口使用;而将该宏定义为2时,在使能的同时,定义的Tickless Idle函数也可以提供给任何FreeRTOS接口使用。
  • 以下两个条件满足时,功能函数使能时,内核将调用portSUPPRESS_TICKS_AND_SLEEP()宏:

1.空闲任务是唯一能够执行的任务,因为其他任务都处于阻塞状态或挂起状态;
2.当FreeRTOSConfig.h中的configEXPECTED_IDLE_TIME_BEFORE_SLEEP被定义为n时,内核将至少经过n个时钟周期才会将任务从阻塞状态转换出来。

当一个任务被调度至就绪态前,portSUPPRESS_TICKS_AND_SLEEP()的参数值等于时钟周期总数,而在时钟中断被关闭的状态下,这个参数值即是单片机可以安全保持在睡眠状态的时间。

  • Note

如果在portSUPPRESS_TICKS_AND_SLEEP()函数中调用eTaskConfirmSleepModeStatus()时返回eNoTasksWaitingTimeout,那单片机的深度睡眠状态会存在不稳定性。被调用函数出现这种情况出现的原因可能是:

1.软件定时器未被使用,因此调度器不会执行定时器回调函数;
2.所有任务在portMAX_DELAY超时时,没有处于挂起或阻塞态,因此调度器没有可以调度的阻塞态任务。

为了避免竞争状态,调度器会在portSUPPRESS_TICKS_AND_SLEEP() 被调用时挂起,在该函数执行结束后恢复。这就保证了任务不会在单片机退出低功耗状态和调用执行portSUPPRESS_TICKS_AND_SLEEP()状态之间被执行。而且,创建这样的临界区是非常有用的,它处于tick源停止和MCU进入睡眠状态之间,在此期间应该调用eTaskConfirmSleepModeStatus()。
所有的GCC,IAR和Keil针对Cortex-M3和M4接口都提供了空闲函数portSUPPRESS_TICKS_AND_SLEEP()。

可以参考更多重要信息:Low Power Features For ARM Cortex-M MCUs

很多空闲函数一直在被逐步添加,同时,以下描述的钩子函数可以对任何接口提供tickless功能。

portSUPPRESS_TICKS_AND_SLEEP()

如果在FreeRTOS中,使用portSUPPRESS_TICKS_AND_SLEEP()时没有提供默认的实现代码,那么我们可以在FreeRTOSConfig.h中使用#define定义portSUPPRESS_TICKS_AND_SLEEP() ,然后在.c文件中编写函数代码。如果FreeRTOS中有提供默认实现的代码,可以通过相同的方式进行重载。
以下示例描述了如何使用portSUPPRESS_TICKS_AND_SLEEP() ,介绍内核维护时间和日历时间的一些误差,官方版本也在尽力尝试用更复杂的方法来减小这种误差。
以下调用的函数,只有vTaskStepTick()
eTaskConfirmSleepModeStatus()
是FreeRTOS的API。其他的关于时钟、省电等的详细实现需要自主实现。

/* 宏定义,参数是下次内核执行调度的时间。*/
#define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )
/*函数定义*/
void vApplicationSleep( TickType_t xExpectedIdleTime )
{
unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;
eSleepModeStatus eSleepStatus;
    /* 从时钟源获取时间,睡眠状态依然运行*/
    ulLowPowerTimeBeforeSleep = ulGetExternalTime();
    /* 关闭时钟中断 */
    prvStopTickInterruptTimer();
    /* 进入临界区,保证MCU唤醒 */
    disable_interrupts();
    /* 确保安全进入睡眠状态 */
    eSleepStatus = eTaskConfirmSleepModeStatus();
    if( eSleepStatus == eAbortSleep )
    {
        /*宏定义函数执行后,任务离开阻塞态,或者上下文被挂起,此时不能进入睡眠状态,重启时钟,并离开临界区*/
        prvStartTickInterruptTimer();
        enable_interrupts();
    }
    else
    {
        if( eSleepStatus == eNoTasksWaitingTimeout )
        {
            /* 不用配置中断唤醒MCU,采用Timeout的形式 */
            prvSleep();
        }
        else
        {
            /* 配置中断让单片机在内核下次被唤醒之前退出低功耗,该中断应当在MCU低功耗时能够被执行*/
            vSetWakeTimeInterrupt( xExpectedIdleTime );
            /* 进入低功耗模式 */
            prvSleep();
            /* 确定MCU进入低功耗模式的时间,如果唤醒方式是中断而不是vSetWakeTimeInterrupt(),
            则该时间应当小于xExpectedIdleTime。
            注意portSUPPRESS_TICKS_AND_SLEEP()被调用后调度器将被挂起,也就是其他任务在该函数
            执行完毕前不能运行。 */
            ulLowPowerTimeAfterSleep = ulGetExternalTime();

            /* 修正MCU低功耗模式下的内核计数器*/
            vTaskStepTick( ulLowPowerTimeAfterSleep - ulLowPowerTimeBeforeSleep );
        }

        /*退出临界区,最好在prvSleep()调用之后执行 */
        enable_interrupts();

        /*重启定时器,以便产生定时器中断*/
        prvStartTickInterruptTimer();
    }
}


原文地址:https://www.freertos.org/low-power-tickless-rtos.html

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值