FreeRTOS的Tickless和STM32的STOP结合实现嵌入式低功耗 (amobbs.com 阿莫电子论坛)

本帖最后由 10xjzheng 于 2015-9-29 21:42 编辑

首先我们先来看下结果,如下图所示,我建立了两个LED翻转的任务,一个是200ms间隔,一个是500ms间隔,如第一二个波形所示,
第三波形如果是高电平,则处于STOP模式,如果是低电平,则处于run模式。

将波形进行放大,可以看到芯片及时地在任务1、2执行之前进行翻转。

首先我们来讨论下嵌入式系统的低功耗,简单地思路是干完活进入空闲任务的时候进入,然后醒来查看是否有任务需要执行,没有的话就继续进入空闲任务,这样子看来看来似乎还是可以的。
下面我们来看一个案例,只有一个任务,运行频率时1Hz,下面中我们可以看到在这1s内CPU不断地醒来,睡去,那么我们不禁回想,
既然已经知道任务地执行频率时1Hz,为什么还要频繁地查询?频繁地查询其实是很耗时的,因为进入和退出都需要一定的时间。

因此Tickless应运而生,在操作系统的管理之下,计算下一次应该醒来的准确时间,以实现长而少的睡眠-唤醒,而不是跟着tick的频
率无谓地醒来检查。正如我放上来的第一张图,可以看到需要翻转的时候,CPU就醒来才进行翻转。
接着我们需要结合CPU的低功耗模式来看看那种模式比较合适。

有三种模式,这三种模式工作的单元越来越少,而功耗也越来越低,首先standby不符合我的系统,醒来相当于复位。sleep和stop的
主要区别是stop模式下HSI、HSE时钟也会被关掉(这就是为什么系统醒来之后会好像慢了一样),stop模式的功耗也更低,粗略测
试时几mA,关掉HSI、HSE对我的系统没有什么影响,最多醒来的时候再配置一下。但是唤醒源不能再是任何的时钟了,只能是
EXTI Line,包括了RTC wake up定时器,于是我得首先将systick转变成RTC wake up timer,让它来担任其系统心跳的职责,修改的
步骤如下。
freeRTOS官方已经意识到担任心跳的定时器有可能限制于低功耗、在tickless模式溢出等因素不能进行工作,因此有文章介绍了怎么
修改为别的定时器来担任这个职责。
http://www.freertos.org/low-power-ARM-cortex-rtos.html
主要分为几个步骤:
1.配置新的定时器,然后将其覆盖掉其中的定时器配置。这里面有个很重要的点,将定时器的优先级配置为最低,至于为什么,见Cortex-M3中的图。

  1. #if configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0
  2. void vPortSetupTimerInterrupt( void )
  3. {
  4. /* Calculate the constants required to configure the tick interrupt. */
  5. #if configUSE_TICKLESS_IDLE == 1
  6. {
  7. ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
  8. xMaximumPossibleSuppressedTicks = (portMAX_16_BIT_NUMBER+1) / ulTimerCountsForOneTick;
  9. //                        ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
  10. ulStoppedTimerCompensation=0;
  11. }
  12. #endif /* configUSE_TICKLESS_IDLE */
  13. /* Configure RTC to interrupt at the requested rate. */
  14. RTC_Config();
  15. //                portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
  16. //                portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
  17. }
  18. #endif /* configOVERRIDE_DEFAULT_TICK_CONFIGURATION */

复制代码

2.删除掉Systick中断函数调用的osSystickHandler();,给RTC的wake up 定时器中断。
接着我们就要开始配置tickless模式了。
1.首先将configUSE_TICKLESS_IDLE这个宏置1 or 2,1表示使用systick,2表示使用另外的定时器。
这个宏为1,则使用官方在port.c中的移植。
这个宏为2,可以使用自己的一些移植。
实际上我是使能为1,使用非systick中断,但是对应修改官方移植好的函数。
2.配置下面几个宏

这个宏是你定时器计数的频率,我的RTC配置为2KHz,所以这里配置为2000
#define configCPU_CLOCK_HZ                        ( 2000 )
这个宏是心跳的频率,我设置为1000
#define configTICK_RATE_HZ                        ( ( TickType_t ) 1000 )
这几个设置的作用见前面的函数vPortSetupTimerInterrupt,这个函数中间还有变量ulStoppedTimerCompensation,这个变量是在
配置tickless的过程中暂时关掉定时器需要设置的偏移,至于多少要用逻辑分析仪测试一下,然后配置下就可以,上面直接配置为0。

2.修改函数vPortSuppressTicksAndSleep,这个函数就是系统调用进入stop模式之前的配置,这里涉及到一些定时器最大可以定时
的时间等等参数的配合。这个函数有一个参数,这个参数表示要延时的tick数,函数中我们可以将其转化为定时器要延时的时间个数。
这个函数在系统的管理之下,只有满足两个条件才运行。
1.首先所有的任务都是挂起或者阻塞的状态。
2.下一次系统应该醒来的时间应该大于configEXPECTED_IDLE_TIME_BEFORE_SLEEP,这个宏是用户可以定义的,默认配置为2。

  1. /*-----------------------------------------------------------*/
  2. #if configUSE_TICKLESS_IDLE == 1
  3. __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
  4. {
  5. uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements, ulSysTickCTRL;
  6. TickType_t xModifiableIdleTime;
  7. uint32_t i,j;
  8. /* 确保计算出来的不会超过xMaximumPossibleSuppressedTicks */
  9. if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
  10. {
  11. xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
  12. }
  13. //计算定时器需要重装的值
  14. ulReloadValue =  ( ulTimerCountsForOneTick * xExpectedIdleTime );
  15. //允许减去一些偏移,ulStoppedTimerCompensation在函数vPortSetupTimerInterrupt中可以进行配置
  16. //可以通过逻辑分析仪测量,单位跟定时器是一样的
  17. if( ulReloadValue > ulStoppedTimerCompensation )
  18. {
  19. ulReloadValue -= ulStoppedTimerCompensation;
  20. }
  21. /* 暂时关闭掉RTC定时器,配置RTC,这会导致系统时钟的一些漂移*/
  22. RTC_WakeUpCmd(DISABLE);
  23. //进入临界段
  24. __disable_irq();
  25. //最后检查下是否可以进入低功耗模式,因为有可能在这个配置过程中发生中断,导致某些任务就绪等等
  26. if( eTaskConfirmSleepModeStatus() == eAbortSleep )
  27. {
  28. //将RTC配置为原来的配置
  29. RTC_SetWakeUpCounter(ulTimerCountsForOneTick-1);
  30. //使能RTC
  31. RTC_WakeUpCmd(ENABLE);
  32. //退出临界段
  33. __enable_irq();
  34. }
  35. else
  36. {
  37. //重装值为前面计算好的
  38. RTC_SetWakeUpCounter(ulReloadValue);
  39. //使能RTC定时器
  40. RTC_WakeUpCmd(ENABLE);
  41. xModifiableIdleTime = xExpectedIdleTime;
  42. //进入低功耗之前需要做的配置,比如关闭外设时钟
  43. configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
  44. if( xModifiableIdleTime > 0 )
  45. {
  46. __dsb( portSY_FULL_READ_WRITE );
  47. //进入低功耗stop模式,在这里插入CPU进入低功耗的语句
  48. PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
  49. //                                __isb( portSY_FULL_READ_WRITE );
  50. }
  51. //退出低功耗模式应该做的事情
  52. configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
  53. //重新使能中断
  54. __enable_irq();
  55. //没有这个延时RTCWakeupFlag居然为0,我操,不知道为什么
  56. for(i=0;i<5;i++);
  57. printf("44:%d-\r\n",RTCWakeupFlag);
  58. //看下是不是tick中断到期,因为其他的中断也是可以唤醒CPU,执行到这里来的,计算重载值
  59. if( RTCWakeupFlag==SET )
  60. {
  61. uint32_t ulCalculatedLoadValue;
  62. //                                Uart_Sendbyte1('A');
  63. //这里省略精确的计算
  64. //                                //精确计算下一个需要延时的时间,一个tick需要延时的时间减去从长延时恢复之后到现在一共过了的时间
  65. //                                ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
  66. //                                //计算出来的越界了,可能是因为函数configPOST_SLEEP_PROCESSING运行太久了!
  67. //                                if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
  68. //                                {
  69. //                                        ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
  70. //                                }
  71. //计算修正的值
  72. ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
  73. }
  74. else  //不是tick中断,其他的中断
  75. {
  76. //                                Uart_Sendbyte1('B');
  77. //已经完成的定时器计数
  78. //                                ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - RTC_GetWakeUpCounter();
  79. //计算已经完成的延时的tick数,偏少
  80. //                                ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
  81. //这个是错误的,由于RTC没有
  82. ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
  83. //                                //小数部分
  84. //                                portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
  85. }
  86. printf("55:%d-\r\n",RTCWakeupFlag);
  87. //最后把计算值重装到寄存器
  88. RTC_SetWakeUpCounter( ulTimerCountsForOneTick - 1UL );
  89. portENTER_CRITICAL();
  90. {
  91. //修正Tick计数
  92. vTaskStepTick( ulCompleteTickPeriods );
  93. }
  94. portEXIT_CRITICAL();
  95. }
  96. }
  97. #endif /* #if configUSE_TICKLESS_IDLE */

复制代码

不足:
由于唤醒定时器位数是有限的,可能有的时候tickless要延时很久,而定时器无法延时比较久的时间。
这个当然是通过分次睡眠唤醒来解决咯。

问题:
如果只有tick中断还好,如果是其他的中断让CPU醒来呢?这个时候该怎么处理?
portSUPPRESS_TICKS_AND_SLEEP是在空闲任务中被调用的,发生外部中断之后,首先会执行中断服务函数,中断服务函数运行过
程中如果没有任务就绪的话,那么很简单,程序会回到进入睡眠的那个地方,在空闲任务中,又重新执行vPortSuppressTicksAndSleep
进入睡眠模式。在外部中断中也有可能有一些任务会被就绪,中断出来之后会先进行任务切换到高优先级任务,等到所有的任务都执行
完毕了,就进入空闲任务中,也就是我们之前进入睡眠模式的那个地方,空闲任务又重新执行了函数vPortSuppressTicksAndSleep进入
tickless。CPU执行了一大圈才回到进入到空闲任务中的睡眠模式的那个点,貌似没有问题。有几个小细节不知道大家有没有注意到,我
们的tick计数值还没有更新,tick定时器也没有重新配置,唤醒之后需要配置外设启动那些也没有做。因为之前我们假设了从哪里休眠就
从哪里唤醒,但是这次不是这样子的!这是需要注意的点,我现在想到的解决办法就是不在中断中让任务就绪。你可能会说,那延时的
那些任务不是也会影响到吗?no,no,no!如果没有进入休眠之前的那个点,那么tick数还没有更新呢。由tick管理的这些任务包括软
件定时器的回调函数那些都不会被就绪的,所以这个是没有问题的。

问题:
下面这两个在进入休眠的前后是干什么的呢?
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE );
见下面网友回复。

问题:
宏configPRE_SLEEP_PROCESSING、configPOST_SLEEP_PROCESSING给的参数xModifiableIdleTime是干什么的呢?
见下面网友回复。

tickless的一些资料:
http://mcuoneclipse.com/2013/07/06/low-power-with-freertos-tickless-idle-mode/
http://www.freertos.org/low-power-ARM-cortex-rtos.html
http://www.freertos.org/low-power-tickless-rtos.html

(0)

相关推荐

  • 实时操作系统的滴答Tick设置多少才合适?

    最近有读者问了这么一个问题:为啥RTOS的系统滴答(Tick)默认配置都是1000,我配置为100.10000,或者2000可以不? 相信很多初学者都有这个疑问,包括我初学RTOS也曾困惑滴答配置不同 ...

  • 嵌入式基础--毫秒级定时模块

    大家好,我是惊觉.失踪了三个月,我回来了.给大家带来一个好消息和一个坏消息.坏消息是,我尚未满血复活,Ardupilot第四篇将继续延期.好消息是,公众号恢复更新,先出一系列提升编码能力的文章. 全国 ...

  • 【华大测评】+华大HC32F460开发板之systick延时函数

    最近经过各种国产MCU的摧残,一开始我还觉得代码风格不是很好的华大460开发板代码,在我眼里又好了起来.之前时间在调试HC32L130,用了之后我对华大的MCU开始有了好的印象.最近又有点空了,决定再 ...

  • 基于STM32、FreeRTOS低功耗设计思路和原理

    如今电池供电的产品很多,电池供电通常设计到一个问题,那就是低功耗. 本文为大家讲讲基于STM32.FreeRTOS实现低功耗思想和原理. 嵌入式专栏 1 低功耗设计常规思路 应用中使用的 RTOS 一 ...

  • 【分享】增量式PID的stm32实现,整定过程 (amobbs.com 阿莫电子论坛)

    感谢大家最近的帮忙,让我顺利做完增量PID功能,虽然PID不是什么牛逼的东西,但是真心希望以后刚刚接触这块的人能尽快进入状态. 也下面我分享一下近期的这些工作吧.欢迎大家批评指点~ 首先说说增量式PI ...

  • 原来电子大佬阅读STM32参考手册的方法是这样的...

    <STM32F103xxx参考手册>不需要全部阅读--没有时间的.建议选读,但是前几章必读.存储器和总线架构.电源控制.备份寄存器.复位和时钟控制,通用和复用功能I/O,中断和时间等等前几 ...

  • STM32使用DMA接收串口数据

    STM32使用DMA接收串口数据

  • 关于在Ubuntu下开发STM32程序printf函数的重映射问题(SW4STM32)

    最近使用STM32CubeMX加Ubuntu18.0来进行STM32相关程序的开发,在使用串口打印的使用出现了如下问题: printf函数按照Keil中的方式进行映射后,在软件仍然无法正常使用Prin ...

  • STM32属于哈佛结构,还是冯诺依曼结构?

    现代的CPU基本上归为冯诺伊曼结构(也称普林斯顿结构)和哈佛结构. 冯洛伊曼结构就是我们所说的X86架构,而哈佛结构就是ARM架构.一个广泛用于桌面端(台式/笔记本/服务器/工作站等),一个雄踞移动领 ...

  • STM32:从入门到精通!

    为了学习单片机而去学习单片机的思路是不对的 你问:如何系统地入门学习stm32? 本身就是一个错误的问题 假如你会使用8051 会写C语言 那么STM32本身并不需要刻意的学习. 你要考虑的是 我可以 ...

  • freeRTOSConfig.h文件对FreeRTOS进行系统配置

    FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制.每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪定制FreeRTOS内核.这个配置文件是针 ...

  • µC/OS、FreeRTOS、RT-Thread、ThreadX开源协议的具体内容

    目前市面上的开源协议有很多种,比如:GPL.BSD.MIT.Mozilla.Apache 和 LGPL等. 选择RTOS,通常会考虑开源.市场占有率.配套资料和例程,以及配套组件等,市面上使用率较多的 ...