搜索
bottom↓
回复: 34

FreeRTOS的Tickless和STM32的STOP结合实现嵌入式低功耗

  [复制链接]

出0入10汤圆

发表于 2015-9-27 23:21:00 | 显示全部楼层 |阅读模式
本帖最后由 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.     //计算定时器需要重装的值
  15.                 ulReloadValue =  ( ulTimerCountsForOneTick * xExpectedIdleTime );
  16.                
  17.                 //允许减去一些偏移,ulStoppedTimerCompensation在函数vPortSetupTimerInterrupt中可以进行配置
  18.           //可以通过逻辑分析仪测量,单位跟定时器是一样的
  19.                 if( ulReloadValue > ulStoppedTimerCompensation )
  20.                 {
  21.                         ulReloadValue -= ulStoppedTimerCompensation;
  22.                 }

  23.     /* 暂时关闭掉RTC定时器,配置RTC,这会导致系统时钟的一些漂移*/
  24.     RTC_WakeUpCmd(DISABLE);

  25.                 //进入临界段
  26.                 __disable_irq();

  27.                 //最后检查下是否可以进入低功耗模式,因为有可能在这个配置过程中发生中断,导致某些任务就绪等等
  28.                 if( eTaskConfirmSleepModeStatus() == eAbortSleep )
  29.                 {
  30.       //将RTC配置为原来的配置
  31.                         RTC_SetWakeUpCounter(ulTimerCountsForOneTick-1);
  32.                        
  33.                         //使能RTC
  34.                         RTC_WakeUpCmd(ENABLE);

  35.                         //退出临界段
  36.                         __enable_irq();
  37.                 }
  38.                 else
  39.                 {
  40.       //重装值为前面计算好的
  41.       RTC_SetWakeUpCounter(ulReloadValue);

  42.                         //使能RTC定时器
  43.                         RTC_WakeUpCmd(ENABLE);

  44.                         xModifiableIdleTime = xExpectedIdleTime;
  45.                        
  46.                         //进入低功耗之前需要做的配置,比如关闭外设时钟
  47.                         configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
  48.                        
  49.                         if( xModifiableIdleTime > 0 )
  50.                         {
  51.                                 __dsb( portSY_FULL_READ_WRITE );
  52.                                 //进入低功耗stop模式,在这里插入CPU进入低功耗的语句
  53.                                 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI);
  54. //                                __isb( portSY_FULL_READ_WRITE );
  55.                         }

  56.                         //退出低功耗模式应该做的事情
  57.                         configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

  58.                         //重新使能中断
  59.                         __enable_irq();
  60.                         //没有这个延时RTCWakeupFlag居然为0,我操,不知道为什么
  61.       for(i=0;i<5;i++);
  62.                         printf("44:%d-\r\n",RTCWakeupFlag);

  63.                         //看下是不是tick中断到期,因为其他的中断也是可以唤醒CPU,执行到这里来的,计算重载值
  64.                         if( RTCWakeupFlag==SET )
  65.                         {
  66.                                 uint32_t ulCalculatedLoadValue;
  67.                        
  68. //                                Uart_Sendbyte1('A');

  69.                                 //这里省略精确的计算
  70. //                                //精确计算下一个需要延时的时间,一个tick需要延时的时间减去从长延时恢复之后到现在一共过了的时间
  71. //                                ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
  72. //                                //计算出来的越界了,可能是因为函数configPOST_SLEEP_PROCESSING运行太久了!
  73. //                                if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
  74. //                                {
  75. //                                        ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
  76. //                                }

  77.                                 //计算修正的值
  78.                                 ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
  79.                         }
  80.                         else  //不是tick中断,其他的中断
  81.                         {
  82. //                                Uart_Sendbyte1('B');
  83.                                
  84.                                 //已经完成的定时器计数
  85. //                                ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - RTC_GetWakeUpCounter();

  86.                                 //计算已经完成的延时的tick数,偏少
  87. //                                ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
  88.                                
  89.                                 //这个是错误的,由于RTC没有
  90.                                 ulCompleteTickPeriods = xExpectedIdleTime - 1UL;

  91. //                                //小数部分
  92. //                                portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
  93.                         }
  94.                         printf("55:%d-\r\n",RTCWakeupFlag);
  95.                         //最后把计算值重装到寄存器
  96.                         RTC_SetWakeUpCounter( ulTimerCountsForOneTick - 1UL );

  97.                         portENTER_CRITICAL();
  98.                         {
  99.                                 //修正Tick计数
  100.                                 vTaskStepTick( ulCompleteTickPeriods );
  101.                         }
  102.                         portEXIT_CRITICAL();
  103.                 }
  104.         }

  105. #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



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2015-9-28 09:19:41 | 显示全部楼层
首先,得找一个适合的低功耗定时器,且是脱离CPU可以独立运行的。

出0入0汤圆

发表于 2015-9-28 09:46:19 | 显示全部楼层
这个我也正准备搞。之前用这种方式在没RTOS的情况下也用上了。这次准备用FREERTOS现成的。

出0入8汤圆

发表于 2015-9-28 14:32:27 | 显示全部楼层
本帖最后由 security 于 2015-9-28 14:34 编辑
aozima 发表于 2015-9-28 09:19
首先,得找一个适合的低功耗定时器,且是脱离CPU可以独立运行的。


是的,如果想达到最佳的低功耗,必须具备一个 异步定时器,即在任何low power模式下,都可以运行的定时器,用来唤醒。
为什么SysTick无法胜任呢?
一般来说,如果直接依赖于SysTick定时器的话,对于ARM cortex-m系列,功耗无法到最低,
因为ARM一旦进入内核的最低功耗时,SysTick定时器是停止的,无法唤醒。

但我们仍可以继续以SysTick作为OS的时钟基准定时器,仅在必要时,才启用异步定时器
而不用直接舍弃SysTick定时器。
(在进入Tickless时,暂停SysTick,启动异步定时器,进入睡眠,唤醒后,再重新开启SysTick定时)

出0入0汤圆

发表于 2015-9-28 15:00:19 | 显示全部楼层
最主要的还是vPortSuppressTicksAndSleep这个函数。
要注意 有种情况可能会让低功耗定时器提前醒来(如,外部中断)

出0入0汤圆

发表于 2015-9-28 15:10:12 | 显示全部楼层
zhonghua_li 发表于 2015-9-28 15:00
最主要的还是vPortSuppressTicksAndSleep这个函数。
要注意 有种情况可能会让低功耗定时器提前醒来(如,外 ...

tick补偿是必须的

出0入10汤圆

 楼主| 发表于 2015-9-28 15:14:07 | 显示全部楼层
The embOS tickless low power support reduces the power consumption for e.g. battery powered devices. Instead of having a timer interrupt for each system tick the timer is reprogrammed to be able to spend as much time as possible in low power mode.
https://www.segger.com/tickless-support.html

出0入0汤圆

发表于 2015-9-28 15:21:56 | 显示全部楼层
谢谢分享,写的很详细,可以研究一下。

出0入8汤圆

发表于 2015-9-28 17:50:05 | 显示全部楼层
本帖最后由 security 于 2015-9-28 18:01 编辑

1、由于唤醒定时器位数是有限的,可能有的时候tickless要延时很久,而定时器无法延时比较久的时间。
-----------------------------------------------------------------------------------------------------------------------------------------
- 这个每次要进入sleep时,需要计算本次睡眠的时间,如果超过定时器的上限,则只睡眠定时器的时间。
- 唤醒后,如果没有其他事件的话,则重新再进入tilkless,类似于传统意义上的:数据包过长就要分包处理的机制一样。
- 依次循环,直到指定的睡眠时间耗尽,因此,这个不是问题。

2、问题:如果只有tick中断还好,如果是其他的中断让CPU醒来呢?这个时候该怎么处理
-----------------------------------------------------------------------------------------------------------------------------------------
- 我们需要处理两种唤醒情况,一种是异步定时器的唤醒,或者被其他中断唤醒(此时异步定时器必然未计数到位)
- 唤醒时,都需要做tick的同步,即将此次睡眠流逝的时间,转换为tick值,累加到OS的tick里面去
- 对于异步定时器事件,睡眠流逝的时间值就是我们设置的睡眠值
- 对于其他中断事件,说明异步定时器还未计数到位,此时,睡眠流逝的时间值就是异步定时器当前cnt值(假设cnt从0值开始)

出0入8汤圆

发表于 2015-9-29 09:51:27 | 显示全部楼层
本帖最后由 security 于 2015-9-29 09:53 编辑

问题:
下面这两个在进入休眠的前后是干什么的呢?
__dsb( portSY_FULL_READ_WRITE );
__isb( portSY_FULL_READ_WRITE );
尚未清楚。
-----------------------------------------------------------------------------------------------------------------------------------------
- 关于DSB和ISB,这两条命令你可以去看看权威指南
- 不过在这边的话,更重要的是休眠前的DSB,这个用于确保WFI执行前,所有的存储器访问动作(可能有缓存动作,即buffered write),都已经被真正completed,才进入休眠
- 这点典型如,进行休眠前,可能设置一些系统电源寄存器,我们要确保这些操作是完成的,如果不通过DSB,我们只好自己手动挡,写入之后,再去执行一次读取动作(当然要是volatile),手动确保写入是completed的
- ISB是清除指令流水线,确保指令的一致性,用在这边,其实可有可无。

问题:
宏configPRE_SLEEP_PROCESSING、configPOST_SLEEP_PROCESSING给的参数xModifiableIdleTime是干什么的呢?
尚未清楚。
-----------------------------------------------------------------------------------------------------------------------------------------
- 我这边没有FreeRTOS的官方原始版本,木有发现此宏的调用,不过还是特意去看了下原始版本,发现注释如下:
/* 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 );

- 你仔细看看前面的描述,就是说configPRE_SLEEP_PROCESSING宏,必然是一个简单的宏,而不会是函数宏
- configPRE_SLEEP_PROCESSING会改变参数的值,也就是这里的xModifiableIdleTime
- 当xModifiableIdleTime被设置为0值时,说明configPRE_SLEEP_PROCESSING自行实现了休眠的动作
- 此时接下去的分支,就不会执行(那是默认的休眠动作)
- 至此,你应该明白了。

出0入10汤圆

 楼主| 发表于 2015-9-29 11:40:02 | 显示全部楼层
security 发表于 2015-9-28 17:50
1、由于唤醒定时器位数是有限的,可能有的时候tickless要延时很久,而定时器无法延时比较久的时间。
------ ...

早上发现RTC居然没有计数值的一个寄存器喔,只有重载值的,这样子怎么解决外部中断来更新tick的问题?

出0入8汤圆

发表于 2015-9-29 12:01:39 | 显示全部楼层
10xjzheng 发表于 2015-9-29 11:40
早上发现RTC居然没有计数值的一个寄存器喔,只有重载值的,这样子怎么解决外部中断来更新tick的问题? ...

我这边也只讲机制,不细化策略
总之一句话,就是要想办法计算出本次实际睡眠的时间,更新到tick里面去。

出0入0汤圆

发表于 2015-9-29 14:14:22 | 显示全部楼层
看着好高端啊!

出0入10汤圆

 楼主| 发表于 2015-9-29 22:54:54 | 显示全部楼层
楼主位下面的程序部分改为如下所示。这样子改的原因是RTC没有计数值可以读取,为什么判断一个引脚的状态,这个引脚在进入休眠之前改为低,RTC中断之后改为高,因为变量作为标志位来判断RTC中断中置位了还是没有变化,不知道为什么,就用一个管脚来代替。这样子如果不是RTC中断,那么程序会一直等待到下一个RTC中断才继续执行!如果产生非RTC中断就会浪费了一些CPU不该运行时间,但是没有办法,RTC没有计数值可以读取。
  1. //                        //没有这个延时RTCWakeupFlag居然为0,我操,不知道为什么
  2. //      for(i=0;i<5;i++);
  3.                        
  4.       while(GPIO_ReadOutputDataBit(RTC_FLAG_PORT,RTC_FLAG_PIN)!=SET);
  5.                        
  6.       ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
  7.                        
  8.                        
  9. //                        //看下是不是tick中断到期,因为其他的中断也是可以唤醒CPU,执行到这里来的,计算重载值
  10. //                        if( RTCWakeupFlag==SET )
  11. //                        {
  12. //                                uint32_t ulCalculatedLoadValue;
  13. //                       
  14. //                                Uart_Sendbyte1('A');

  15. //                                //这里省略精确的计算
  16. ////                                //精确计算下一个需要延时的时间,一个tick需要延时的时间减去从长延时恢复之后到现在一共过了的时间
  17. ////                                ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
  18. ////                                //计算出来的越界了,可能是因为函数configPOST_SLEEP_PROCESSING运行太久了!
  19. ////                                if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
  20. ////                                {
  21. ////                                        ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
  22. ////                                }

  23. //                                //计算修正的值
  24. //                                ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
  25. //                        }
  26. //                        else  //不是tick中断,其他的中断
  27. //                        {
  28. //                                Uart_Sendbyte1('B');
  29. //                               
  30. //                                //已经完成的定时器计数
  31. ////                                ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - RTC_GetWakeUpCounter();

  32. ////                                //计算已经完成的延时的tick数,偏少
  33. ////                                ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;

  34. //                                //这个是错误的,由于RTC没有
  35. //                                ulCompleteTickPeriods = xExpectedIdleTime - 1UL;

  36. ////                                //小数部分
  37. ////                                portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
  38. //                        }


  39.                         //最后把计算值重装到寄存器
  40.                         RTC_SetWakeUpCounter( ulTimerCountsForOneTick - 1UL );
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入10汤圆

 楼主| 发表于 2015-9-29 23:18:38 | 显示全部楼层
思考了一天,算是暂时比较好地解决了这个问题。
arm和ST真的是没有诚意啊,arm搞个systick给操作系统却没有想到操作系统为了低功耗要求在低功耗模式下也可以运行,ST搞个RTC wake up timer却没有计数值,那让我们高tickless的怎么弄嘛!另外分频又是够坑爹,怎么分频出1000KHz这些!

出0入0汤圆

发表于 2015-9-30 08:59:51 | 显示全部楼层

出0入8汤圆

发表于 2015-9-30 10:16:34 | 显示全部楼层
10xjzheng 发表于 2015-9-29 23:18
思考了一天,算是暂时比较好地解决了这个问题。
arm和ST真的是没有诚意啊,arm搞个systick给操作系统却没有 ...

看LZ位的回答
-----------------------------------------------------------------------------------------------------------------------------------------
问题:
如果只有tick中断还好,如果是其他的中断让CPU醒来呢?这个时候该怎么处理?
-----------------------------------------------------------------------------------------------------------------------------------------
我自己是看晕掉了。

结合后续的改动,我就不明真相的说几点吧:
- 你将方案限制为ISR不允许任务就绪,这是不可取的,举个栗子,这个中断如果是紧急事件,触发一个semaphore之类的,
  这会拉低整个系统的实时性。

- STM32系列我没接触过,我粗略看了下F4的手册,对于RTC唤醒计数器寄存器貌似是可以读取的,当然我只是纸上谈兵
- 如果没有直接的计数器,通过前后RTC时间,也是可以算出的
- 如果这些都无法达标的话,那么你可能就要考虑不启用STOP模式,而是启用SLEEP模式
- SysTick在SLEEP模式下,应该能正常工作的,只是在ARM内核的DEEPSLEEP才无法工作。

总之,tickless是我们人为加上去的,程序的效果对外表现,要做到,逼近跟没有tickless一样,这样tickless才可以落地:
- 在任务idle时,进入idle线程处理
- 外部异步事件发生,如果使得高优先级任务就绪,就应抢占idle线程
- 没有外部异步事件,而是task_sleep耗尽,也会抢占idle线程,执行对应的线程

出0入10汤圆

 楼主| 发表于 2015-9-30 10:25:17 | 显示全部楼层

霸气,论坛元老了!

出0入10汤圆

 楼主| 发表于 2015-9-30 10:33:53 | 显示全部楼层
security 发表于 2015-9-30 10:16
看LZ位的回答
------------------------------------------------------------------------------------- ...

1.拉低系统的实时性是会的,我只能期望没有中断,但是现在也没有什么解决的办法,要么只能不用tickless了。
2.“RTC唤醒计数器寄存器貌似是可以读取的”,你可不可以稍微说下思路?我很仔细地翻了手册,还是发现不了好的思路。
3.将stop模式改为sleep模式也是一种思路,这样子systick就可以用了,但是功耗还不是很完美,毕竟stop才是更低,粗率测试好像是几mA。这种可能才是更好的办法,在功耗和实时性之间做一个平衡。

出0入8汤圆

发表于 2015-9-30 10:36:56 | 显示全部楼层
10xjzheng 发表于 2015-9-30 10:33
1.拉低系统的实时性是会的,我只能期望没有中断,但是现在也没有什么解决的办法,要么只能不用tickless了 ...

以下免责,我没接触过STM32
  1. /**
  2.   * @brief  Configures the RTC Wakeup counter.
  3.   * @note   The RTC WakeUp counter can only be written when the RTC WakeUp
  4.   *         is disabled (Use the RTC_WakeUpCmd(DISABLE)).        
  5.   * @param  RTC_WakeUpCounter: specifies the WakeUp counter.
  6.   *          This parameter can be a value from 0x0000 to 0xFFFF.
  7.   * @retval None
  8.   */
  9. void RTC_SetWakeUpCounter(uint32_t RTC_WakeUpCounter)
  10. {
  11.   /* Check the parameters */
  12.   assert_param(IS_RTC_WAKEUP_COUNTER(RTC_WakeUpCounter));
  13.   
  14.   /* Disable the write protection for RTC registers */
  15.   RTC->WPR = 0xCA;
  16.   RTC->WPR = 0x53;
  17.   
  18.   /* Configure the Wakeup Timer counter */
  19.   RTC->WUTR = (uint32_t)RTC_WakeUpCounter;
  20.   
  21.   /* Enable the write protection for RTC registers */
  22.   RTC->WPR = 0xFF;
  23. }

  24. /**
  25.   * @brief  Returns the RTC WakeUp timer counter value.
  26.   * @param  None
  27.   * @retval The RTC WakeUp Counter value.
  28.   */
  29. uint32_t RTC_GetWakeUpCounter(void)
  30. {
  31.   /* Get the counter value */
  32.   return ((uint32_t)(RTC->WUTR & RTC_WUTR_WUT));
  33. }
复制代码

出0入10汤圆

 楼主| 发表于 2015-9-30 10:40:34 | 显示全部楼层
security 发表于 2015-9-30 10:36
以下免责,我没接触过STM32

RTC_GetWakeUpCounter这个还是获取的reloader的值啊

出0入8汤圆

发表于 2015-9-30 10:44:41 | 显示全部楼层
本帖最后由 security 于 2015-9-30 10:53 编辑
10xjzheng 发表于 2015-9-30 10:40
RTC_GetWakeUpCounter这个还是获取的reloader的值啊


那只能另想法子了,可以参考我上面的回复了
或者
- tickless使用sleep模式,这时,系统的的外在表现,还是running的,只是在背后悄悄地自动省电节能
- 当真正没有任何事件活跃的话,我们主动让系统进入深度睡眠,即STM32的STOP模式,此时也无需定时唤醒
系统就一直睡下去,等到异步唤醒事件发生,重新醒过来。

出0入10汤圆

 楼主| 发表于 2015-9-30 11:03:22 | 显示全部楼层
security 发表于 2015-9-30 10:44
那只能另想法子了,可以参考我上面的回复了
或者
- tickless使用sleep模式,这时,系统的的外在表现,还 ...

我现在觉得还是改为sleep靠谱一点,置于“当真正没有任何事件活跃的话,我们主动让系统进入深度睡眠”,后面有应用是“真正没有任何事件活跃”再试试在stop和sleep之间切换。

出0入10汤圆

 楼主| 发表于 2015-9-30 12:28:45 | 显示全部楼层
我又来了,这下子正常了,用的是sleep模式。我什么也不想说了,直接上图。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2015-9-30 13:10:16 | 显示全部楼层
10xjzheng 发表于 2015-9-30 10:25
霸气,论坛元老了!

多灌水,你也可以。O(∩_∩)O哈哈~。。。

出0入0汤圆

发表于 2015-9-30 13:43:04 | 显示全部楼层
高手,佩服!谢谢分享!

出0入0汤圆

发表于 2015-9-30 14:06:11 | 显示全部楼层
学习学习,谢谢分享

出0入10汤圆

 楼主| 发表于 2015-9-30 16:49:44 | 显示全部楼层
为了减少tickless“分包”进入休眠状态,因为定时器的计数值有限,定时的时间有限。这里把systick的时钟频率降低了。

出0入0汤圆

发表于 2015-9-30 20:00:16 | 显示全部楼层
看起来挺有意思哦

出0入10汤圆

 楼主| 发表于 2015-10-22 00:18:27 | 显示全部楼层
后面我就在想,既然wakeup timer已经无法用了,那么我们是否可以试用下这样子的思路:
在run模式下,使用systick,然后在低功耗模式下使用RTC的闹钟来实现,RTC的闹钟是可以是秒以下级别的,可能RTC的闹钟也可以处理得比较长一些!RTC的闹钟同样是可以唤醒CPU的!

出0入0汤圆

发表于 2016-2-17 09:09:20 | 显示全部楼层
mark一下

出0入0汤圆

发表于 2016-2-17 09:52:13 | 显示全部楼层
MARK,正在找这方面的内容。

出0入0汤圆

发表于 2016-2-17 10:29:26 | 显示全部楼层
一直想研究下低功耗

出0入0汤圆

发表于 2016-6-16 15:47:27 | 显示全部楼层
本帖最后由 guo__qiu 于 2016-6-16 15:49 编辑
因为之前我们假设了从哪里休眠就
从哪里唤醒,但是这次不是这样子的!这是需要注意的点,我现在想到的解决办法就是不在中断中让任务就绪。

在调用portSUPPRESS_TICKS_AND_SLEEP()之前已经将调度器挂起了,如下:所以PendSV中不进行任务的调度

  1.         vTaskSuspendAll();
  2.         {
  3.                 /* Now the scheduler is suspended, the expected idle
  4.                 time can be sampled again, and this time its value can
  5.                 be used. */
  6.                 configASSERT( xNextTaskUnblockTime >= xTickCount );
  7.                 xExpectedIdleTime = prvGetExpectedIdleTime();

  8.                 if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
  9.                 {
  10.                         traceLOW_POWER_IDLE_BEGIN();
  11.                         portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
  12.                         traceLOW_POWER_IDLE_END();
  13.                 }
  14.                 else
  15.                 {
  16.                         mtCOVERAGE_TEST_MARKER();
  17.                 }
  18.         }
  19.         // 在这里才会进行调度
  20.         ( void ) xTaskResumeAll();
复制代码


出0入0汤圆

发表于 2016-9-30 11:34:08 | 显示全部楼层
进来学习一下低功耗
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-20 13:11

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表