功耗节省简介

在空闲任务中放置处理器的低功耗模式代码是常用的节省功耗的一种方式。功耗节省可以通过这种简单的方式实现,但是受限于需要周期性的退出并且重新进入低功耗模式去处理节拍中断。但是如果节拍中断的频率很高,不断的退出和重新进入低功耗模式可能会潜在的导致功耗节省达不到预期效果。

freeRTOS的低节拍空闲模式可以停止周期性的节拍中断,在节拍中断重新开始时修正RTOS的节拍计数值。

停止节拍中断允许处理器进入一个更深层次的低功耗模式,直到其他中断发生或者RTOS内核将一个任务转换为就绪态。

portSUPPRESS_TICKS_AND_SLEEP() 宏

portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime )

configUSE_TICKLESS_IDLE设置为1,将使用内建的低节拍空闲函数,设置为2,使用用户自定义的函数。

当低节拍空闲函数可用时,内核会在满足如下条件时调用 portSUPPRESS_TICKS_AND_SLEEP() 宏:

  1. 空闲任务是当前唯一可执行任务,其他任务均处在阻塞态。
  2. 至少需要过n个完整的节拍周期,在此期间没有任务进入就绪态。n的值通过configEXPECTED_IDLE_TIME_BEFORE_SLEEP定义

portSUPPRESS_TICKS_AND_SLEEP宏的参数值等于任务进入就绪态之前的总的节拍周期数。在这个参数值的时间内,处理器将安全的处在深睡眠状态(节拍中断关闭)。

注意: 如果在portSUPPRESS_TICKS_AND_SLEEP()中调用eTaskConfirmSleepModeStatus(),并且其返回值为eNoTasksWaitingTimeout则,处理器可以无限期的处在深睡眠状态,因为此时没有任务在等待执行。只有在如下条件为真是,eTaskConfirmSleepModeStatus()的返回值才会是eNoTasksWaitingTimeout:

  1. 软件定时器没有使用,调度器不需要去执行定时器回调函数
  2. 所有的任务都处在无限期的挂起或者阻塞态,因此调度器不需要去将任务转换为就绪态。

portSUPPRESS_TICKS_AND_SLEEP()调用或者结束之前,为了防止与RTOS的调度器产生条件竞争,调度器处在挂起的状态下。这可以有效保证应用任务不会再处理器从低功耗模式退出和portSUPPRESS_TICKS_AND_SLEEP()执行完成之前就执行。进一步的,有必要在portSUPPRESS_TICKS_AND_SLEEP()中创建一个小的临界代码段,这个临界代码段位于节拍中断停止和处理器进入低功耗模式之前,eTaskConfirmSleepModeStatus()应该在这个临界段中调用,去检查当前的任务状态。

所有的GCC、IAR、Keil、ARM Cotex-M3/4分支提供默认的portSUPPRESS_TICKS_AND_SLEEP()实现。一些使用ARM Cotex M系列处理器的重要信息可以参阅 ,这些和具体处理器相关的应用信息可能不会去翻译,除非将来我自己使用到这些处理器

默认的portSUPPRESS_TICKS_AND_SLEEP()实现会在将来逐步加入到所有支持的分支中,同时,下面描述的功能可以用在所有的分支,用以极速低节拍功能:

实现portSUPPRESS_TICKS_AND_SLEEP()

如果你使用的处理器没有提供默认的portSUPPRESS_TICKS_AND_SLEEP(),你可以在FreeRTOSConfig.h中自己去实现这个宏。当然如果默认宏达不到你的目的,你也可以重写这个默认宏。

看个例子:

  1. /* 首先定义portSUPPRESS_TICKS_AND_SLEEP()宏,参数是内核下次需要执行的等待时间 */
  2. #define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )
  3. /* 定义与处理器相关的睡眠函数 */
  4. void vApplicationSleep( TickType_t xExpectedIdleTime )
  5. {
  6. unsigned long ulLowPowerTimeBeforeSleep, ulLowPowerTimeAfterSleep;
  7. eSleepModeStatus eSleepStatus;
  8. /* 记录进入低功耗之前的时间 */
  9. ulLowPowerTimeBeforeSleep = ulGetExternalTime();
  10. /* 停止产生节拍中断的定时器 */
  11. prvStopTickInterruptTimer();
  12. /* 进入临界代码段,前提是不会影响唤醒 */
  13. disable_interrupts();
  14. /* 再次确认是否满足睡眠条件. */
  15. eSleepStatus = eTaskConfirmSleepModeStatus();
  16. if( eSleepStatus == eAbortSleep )
  17. {
  18. /* 有任务从阻塞态移出或者等待上下文切换,不能进入睡眠,
  19. 重启产生节拍中断的定时器和使能中断. */
  20. prvStartTickInterruptTimer();
  21. enable_interrupts();
  22. }
  23. else
  24. {
  25. if( eSleepStatus == eNoTasksWaitingTimeout )
  26. {
  27. /* 没有配置将处理器从低功耗模式唤醒的中断的必要,直接进入睡眠 */
  28. prvSleep();
  29. }
  30. else
  31. {
  32. /* 在内核下次需要执行的时间,配置唤醒源将处理器唤醒,例如RTC。 */
  33. vSetWakeTimeInterrupt( xExpectedIdleTime );
  34. /* 进入睡眠状态. */
  35. prvSleep();
  36. /* 用来计算处理器处在睡眠状态的时间,后面修正节拍中断的计数值会用到 */
  37. ulLowPowerTimeAfterSleep = ulGetExternalTime();
  38. /* 修正节拍中断计数值,这样的处理显然是不是很精确的,官方也在试图实现一个更精确的时间修正. */
  39. vTaskStepTick( ulLowPowerTimeAfterSleep ulLowPowerTimeBeforeSleep );
  40. }
  41. /* 退出临界段 */
  42. enable_interrupts();
  43. /* 重启节拍中断定时器*/
  44. prvStartTickInterruptTimer();
  45. }
  46. }