应用FreeRTOS在MSP430上做项目已经很久了,如何配合MSP430这样的低功耗芯片让系统尽可能的工作在低功耗模式是本文的议题。
(本文原创,转载请注明出处RickLeaf CSDNhttp://blog.csdn.net/rickleaf)
一、IDLE线程作为低功耗入口
对于现在流行的大部分RTOS来说启动系统调度环境以后,都会创建出一个IDLE线程,而这个线程又往往运行在最低的优先级,这个线程让系统的调度环境继续工作。
在FreeRTOS的网站上会建议大家引用这个线程让系统进入MSP430的低功耗模式,事实上从官方拿到的代码就有下面这一句(本文以MSP430F5438A的主芯片为例子,代码跑在EXP-MSP5438A演示板上):
void vApplicationIdleHook( void ) { /* Called on each iteration of the idle task. In this case the idle task just enters a low power mode. */ __bis_SR_register( LPM3_bits + GIE ); }
__bis_SR_register( LPM3_bits + GIE );
这个语句会让MSP430进入LPM3模式,这个模式会关闭CPU,SCG1和SCG0,在进入这个模式以后PC指针会停到下一跳指令那里不动,只有在有中断唤醒以后CPU开始执行中断的代码,在中断的代码中加入下面的语句:
__bic_SR_register_on_exit( SCG1 + SCG0 + OSCOFF + CPUOFF );
中断返回以后CPU才会退出LPM3模式,从__bis_SR_register( LPM3_bits + GIE );之后的指令开始往后执行。
看到这里,或许我们已经觉得这个接口很简单哦。我曾经也是这么以为,直到我发现在这样进入LPM3以后,我的串口经常丢数,花费很多资源在校验重发上,直到我发现CPU的功耗一直很难满足硬件工程师的需求。让我们再看一下FreeRTOS的配置文件,或者说去了解你的系统的时钟滴答,下面是我的FreeRTOS的时钟滴答设置
#define configCPU_CLOCK_HZ ( 25000000UL ) #define configLFXT_CLOCK_HZ ( 32768L ) #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
可以看到我用一个低速的晶振把系统的工作时钟PLL到25M,并且我把FreeRTOS的时钟滴答设置成了1毫秒(1ms),现在我们设想一下如果我其它任务都已经挂起,只有我IDLE这个线程一直在工作,我们看一下系统的ticker中断
#pragma vector=configTICK_VECTOR __interrupt __raw void vTickISREntry( void ) { extern void vPortTickISR( void ); __bic_SR_register_on_exit( SCG1 + SCG0 + OSCOFF + CPUOFF ); vPortTickISR(); }
二、FreeRTOS Tickless低功耗模式在MSP430上的应用
在FreeRTOS网站上很容易找到一种低功耗的解决方案,并且大量的应用CortexM的内核上。所有tickless就是指,在系统进入低功耗模式以后,系统的ticker不会继续工作,听上去挺吓人吧,不是休眠是假死,因为没有心跳了。对于我来说,我只是找到一种方法让系统在没有一些event的时候系统能够停留在CPU的某个低功耗模式,当有中断发生以后再让系统的ticker继续工作。
其实,即使ticker不在,系统还是在运行的只是我们不希望在ticker每个中断都要让系统退出低功耗模式,只要在需要的时候才退出低功耗模式。而FreeRTOS的tickless功能本身就提供了这样的接口。
功能总是以耗费资源为代价的,就像如果我们想得到FreeRTOS的runtime states,我们必须要有一个额外的timer,我们为了实现tickless也必须有个额外的timer。大家可以先去看看FreeRTOS的官方文档。
http://www.freertos.org/low-power-tickless-rtos.html
当我们的系统进入IDLE以后,FreeRTOS内核会估算出下一个时间触发事件的到来的时刻,我们假设那个时刻和当前时刻的差值是T。
这时候我们配置要一个和FreeRTOS的ticker同样频率的timer, 并且希望这个tickless定制器在超时T时刻以后,唤醒并且让系统退出低功耗模式LPM3。然后我们停止系统ticker,系统就有T这么长的时间在LPM3中,等到tickless定时器唤醒并且退出LPM3以后,我们在重新启动ticker并且要把时间T的流逝告诉FreeRTOS,老大我们沉睡了1万年现在要到阿凡达了。
当然我们必须要考虑其它的中断唤醒并且退出LPM3的情况,假想一下沉睡的飞船也要能抵御敌人吧。所以如果其它中断唤醒系统并让系统退出LPM3以后,我们要告诉FreeRTOS你睡了几个月,需要紧急处理一些特别任务。
在FreeRTOS的配置文件中其实已经有了这样的接口,只是在MSP430上可能用的人不多。下面是内核配置:
#define configIDLE_SHOULD_YIELD 0 #define configUSE_TICKLESS_IDLE 1
</pre><pre name="code" class="cpp">#ifdef __ICC430__ extern void vApplicationSleep( uint32_t xExpectedIdleTime ); #endif /* __ICCARM__ */ #define portSUPPRESS_TICKS_AND_SLEEP( xIdleTime ) vApplicationSleep( xIdleTime )
/* FreeRTOS includes. */ #include "FreeRTOS.h" #include "task.h" /* Hardware includes. */ #include "msp430.h" volatile unsigned long ulTicklessCount = 0; volatile unsigned long ulExpectedIdleTime = 0; volatile unsigned char TickLessEnable = 0; /*-----------------------------------------------------------*/ void vConfigureTimerForTickless( void ) { const unsigned short usACLK_Frequency_Hz = 32768; /* Ensure the timer is stopped. */ TA0CTL = 0; /* Run the timer from the ACLK. */ TA0CTL = TASSEL_1; /* Clear everything to start with. */ TA0CTL |= TACLR; /* Set the compare match value according to the tick rate we want. */ TA0CCR0 = usACLK_Frequency_Hz / configTICK_RATE_HZ; /* Enable the interrupts. */ TA0CCTL0 = CCIE; /* Start up clean. */ TA0CTL |= TACLR; /* Up mode. */ TA0CTL |= MC_1; } /*-----------------------------------------------------------*/ unsigned long ulGetExternalTime(void) { return ulTicklessCount; } /*-----------------------------------------------------------*/ void vSetWakeTimeInterrupt(unsigned long xExpectedIdleTime) { ulExpectedIdleTime = xExpectedIdleTime; ulTicklessCount = 0; TickLessEnable = 1; } #pragma vector=TIMER0_A0_VECTOR __interrupt void prvTIMER0_A0_ISR( void ) { ulTicklessCount++; if((ulTicklessCount >= ulExpectedIdleTime) && TickLessEnable ) { __bic_SR_register_on_exit( SCG1 + SCG0 + OSCOFF + CPUOFF ); TickLessEnable = 0; } } void vApplicationSleep( TickType_t xExpectedIdleTime ) { unsigned long ulLowPowerTimeAfterSleep; eSleepModeStatus eSleepStatus; /* Read the current time from a time source that will remain operational while the microcontroller is in a low power state. */ //ulLowPowerTimeBeforeSleep = ulGetExternalTime(); /* Stop the timer that is generating the tick interrupt. */ TB0CTL &= ~MC_1; /* Enter a critical section that will not effect interrupts bringing the MCU out of sleep mode. */ __disable_interrupt(); /* Ensure it is still ok to enter the sleep mode. */ eSleepStatus = eTaskConfirmSleepModeStatus(); if( eSleepStatus == eAbortSleep ) { /* A task has been moved out of the Blocked state since this macro was executed, or a context siwth is being held pending. Do not enter a sleep state. Restart the tick and exit the critical section. */ TB0CTL |= MC_1; __enable_interrupt(); } else { if( eSleepStatus == eNoTasksWaitingTimeout ) { /* It is not necessary to configure an interrupt to bring the microcontroller out of its low power state at a fixed time in the future. */ __bis_SR_register( LPM3_bits + GIE ); } else { /* Configure an interrupt to bring the microcontroller out of its low power state at the time the kernel next needs to execute. The interrupt must be generated from a source that remains operational when the microcontroller is in a low power state. */ vSetWakeTimeInterrupt(xExpectedIdleTime); /* Enter the low power state. */ __bis_SR_register( LPM3_bits + GIE ); /* Determine how long the microcontroller was actually in a low power state for, which will be less than xExpectedIdleTime if the microcontroller was brought out of low power mode by an interrupt other than that configured by the vSetWakeTimeInterrupt() call. Note that the scheduler is suspended before portSUPPRESS_TICKS_AND_SLEEP() is called, and resumed when portSUPPRESS_TICKS_AND_SLEEP() returns. Therefore no other tasks will execute until this function completes. */ ulLowPowerTimeAfterSleep = ulGetExternalTime(); /* Correct the kernels tick count to account for the time the microcontroller spent in its low power state. */ vTaskStepTick( ulLowPowerTimeAfterSleep ); } __enable_interrupt(); TB0CTL |= MC_1; } }