FreeRTOS的Tickless模式
注:以下內(nèi)容來(lái)自硬漢論壇的FreeRTOS教程信夫。
tickless 低功耗機(jī)制是當(dāng)前小型 RTOS 所采用的通用低功耗方法,比如 embOS,RTX 和 uCOS-III (類(lèi)似方法)都有這種機(jī)制幼苛。
FreeRTOS 的低功耗也是采用的這種方式倔喂,那么 tickless 又是怎樣一種模式呢雏蛮??jī)H從字母上看 tick 是滴答時(shí)鐘的意思恢氯,less 是 tick 的后綴魔眨,表示較少的,這里的含義可以表示為無(wú)滴答時(shí)鐘酿雪。 整體看這個(gè)字母就是表示滴答時(shí)鐘節(jié)拍停止運(yùn)行的情況。反映在 FreeRTOS 上侄刽,tickless 又是怎樣一種情況呢指黎?我們都知道,當(dāng)用戶(hù)任務(wù)都被掛起或者阻塞時(shí)州丹,最低優(yōu)先級(jí)的空閑任務(wù)會(huì)得到執(zhí)行醋安。 那么 STM32 支持的睡眠模式,停機(jī)模式就可以放在空閑任務(wù)里面實(shí)現(xiàn)墓毒。 為了實(shí)現(xiàn)低功耗最優(yōu)設(shè)計(jì)吓揪,我們還不能直接把睡眠或者停機(jī)模式直接放在空閑任務(wù)就可以了。 進(jìn)入空閑任務(wù)后所计,首先要計(jì)算可以執(zhí)行低功耗的最大時(shí)間柠辞,也就是求出下一個(gè)要執(zhí)行的高優(yōu)先級(jí)任務(wù)還剩多少時(shí)間。 然后就是把低功耗的喚醒時(shí)間設(shè)置為這個(gè)求出的時(shí)間主胧,到時(shí)間后系統(tǒng)會(huì)從低功耗模式被喚醒叭首,繼續(xù)執(zhí)行多任務(wù)。這個(gè)就是所謂的 tickless 模式踪栋。
對(duì)于 Cortex-M3 和 M4 內(nèi)核來(lái)說(shuō)焙格,F(xiàn)reeRTOS 已經(jīng)提供了 tickless 低功耗代碼的實(shí)現(xiàn),通過(guò)調(diào)用指令 WFI 實(shí)現(xiàn)睡眠模式夷都,具體代碼的實(shí)現(xiàn)就在 port.c 文件中眷唉,用戶(hù)只需在 FreeRTOSConfig.h 文件中配置宏定義 configUSE_TICKLESS_IDLE 為 1 即可。 如果配置此參數(shù)為 2,那么用戶(hù)可以自定義 tickless 低功耗模式的實(shí)現(xiàn)冬阳。 當(dāng)用戶(hù)將宏定義 configUSE_TICKLESS_IDLE 配置為 1 且系統(tǒng)運(yùn)行滿(mǎn)足以下兩個(gè)條件時(shí)蛤虐,系統(tǒng)內(nèi)核會(huì)自動(dòng)的調(diào)用低功耗宏定義函數(shù) portSUPPRESS_TICKS_AND_SLEEP()
實(shí)現(xiàn)自己的Tickless
本文以華大MCU HC32L190為例。系統(tǒng)默認(rèn)是使用Systick作為系統(tǒng)滴答時(shí)鐘摩泪,這里保持這個(gè)不變笆焰,使用一個(gè)低功耗定時(shí)器將芯片從深度睡眠模式中喚醒。
使能自定義Tickless
在FreeRTOSConfig.h
下定義如下的宏见坑,如果已存在就修改為2嚷掠。
#define configUSE_TICKLESS_IDLE 2
定義需要用的變量和宏
#if( configUSE_TICKLESS_IDLE == 2 )
#define configLPTIMER_CLOCK_HZ 32768U
/* The LPTIM is a 16-bit counter. */
#define portMAX_16_BIT_NUMBER ( 0xffffUL )
/*
* The number of LPTIM increments that make up one tick period.
*/
static uint32_t ulTimerCountsForOneTick = 0;
/*
* The maximum number of tick periods that can be suppressed is limited by the
* 16 bit resolution of the lowpower timer.
*/
static uint32_t xMaximumPossibleSuppressedTicks = 0;
#endif
在vPortSetupTimerInterrupt()中增加獲取定時(shí)相關(guān)參數(shù)的代碼,主要給后面計(jì)算定時(shí)多少時(shí)間用
__attribute__(( weak )) void vPortSetupTimerInterrupt( void )
{
/* Calculate the constants required to configure the tick interrupt. */
#if( configUSE_TICKLESS_IDLE == 1 )
{
ulTimerCountsForOneTick = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ );
xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR;
}
#endif /* configUSE_TICKLESS_IDLE */
#if( configUSE_TICKLESS_IDLE == 2 )
{
ulTimerCountsForOneTick = ( configLPTIMER_CLOCK_HZ / configTICK_RATE_HZ );
xMaximumPossibleSuppressedTicks = portMAX_16_BIT_NUMBER / ulTimerCountsForOneTick;
}
#endif /* configUSE_TICKLESS_IDLE */
/* Stop and reset the SysTick. */
portNVIC_SYSTICK_CTRL_REG = 0UL;
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
/* Configure SysTick to interrupt at the requested rate. */
portNVIC_SYSTICK_LOAD_REG = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT;
}
實(shí)現(xiàn)portSUPPRESS_TICKS_AND_SLEEP()
HC32L190的低功耗定時(shí)器為16位定時(shí)器荞驴,為了盡可能保持精度不皆,不進(jìn)行分頻,因此最大定時(shí)時(shí)長(zhǎng)為2秒熊楼。當(dāng)系統(tǒng)空閑時(shí)間大于2秒時(shí)霹娄,需要通過(guò)喚醒再休眠的方式實(shí)現(xiàn)在系統(tǒng)空閑時(shí)完全休眠。
#if configUSE_TICKLESS_IDLE == 2
#include "ddl.h"
#include "lptim.h"
#include "lpm.h"
static volatile bool lptim_timeout;
void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{
uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
TickType_t xModifiableIdleTime;
/* Make sure the LPTIM reload value does not overflow the counter. */
if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
{
xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
}
/* Stop the SysTick momentarily. The time the SysTick is stopped for
is accounted for as best it can be, but using the tickless mode will
inevitably result in some tiny drift of the time maintained by the
kernel with respect to calendar time. */
portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
/* Calculate the reload value required to wait xExpectedIdleTime
tick periods. -1 is used because this code will execute part way
through one of the tick periods. */
/* 定時(shí)時(shí)鐘周期數(shù)為(0XFFFF-ARR+0X0001) */
ulReloadValue = (uint16_t)(0x10000 - ulTimerCountsForOneTick * ( xExpectedIdleTime - 1 ));
/* Enter a critical section but don't use the taskENTER_CRITICAL()
method as that will mask interrupts that should exit sleep mode. */
// __asm volatile( "cpsid i" ::: "memory" );
// __asm volatile( "dsb" );
// __asm volatile( "isb" );
/* If a context switch is pending or a task is waiting for the scheduler
to be unsuspended then abandon the low power entry. */
if( eTaskConfirmSleepModeStatus() == eAbortSleep )
{
/* Restart from whatever is left in the count register to complete
this tick period. */
portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
/* Restart SysTick. */
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
/* Reset the reload register to the value required for normal tick
periods. */
portNVIC_SYSTICK_LOAD_REG = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
/* Re-enable interrupts - see comments above the cpsid instruction()
above. */
// __asm volatile( "cpsie i" ::: "memory" );
}
else
{
volatile uint32_t ulCompleteTickPeriods;
volatile uint32_t sleep_time;
stc_lptim_cfg_t lptim_cfg;
DDL_ZERO_STRUCT(lptim_cfg);
lptim_cfg.enTcksel = LptimRcl; // 選擇外部低速晶體振蕩器作為時(shí)鐘源
lptim_cfg.enCt = LptimTimerFun; // 定時(shí)器功能
lptim_cfg.enMd = LptimMode2; // 工作模式為模式2:自動(dòng)重裝載16位計(jì)數(shù)器/定時(shí)器
lptim_cfg.enPrs = LptimPrsDiv1; // Frcl
lptim_cfg.enGate = LptimGateLow; // 無(wú)門(mén)控鲫骗,TR=1時(shí)定時(shí)器工作
lptim_cfg.u16Arr = ulReloadValue;
Lptim_Init(M0P_LPTIMER0, &lptim_cfg);
lptim_timeout = false;
Lptim_Cmd(M0P_LPTIMER0, TRUE);
/* 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 )
{
__asm volatile( "dsb" ::: "memory" );
__asm volatile( "wfi" );
__asm volatile( "isb" );
}
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
/* Re-enable interrupts to allow the interrupt that brought the MCU
out of sleep mode to execute immediately. see comments above
__disable_interrupt() call above. */
// __asm volatile( "cpsie i" ::: "memory" );
// __asm volatile( "dsb" );
// __asm volatile( "isb" );
/* Disable interrupts again because the clock is about to be stopped
and interrupts that execute while the clock is stopped will increase
any slippage between the time maintained by the RTOS and calendar
time. */
// __asm volatile( "cpsid i" ::: "memory" );
// __asm volatile( "dsb" );
// __asm volatile( "isb" );
if (true == lptim_timeout)
{
ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
}
else
{
ulCompleteTickPeriods = (M0P_LPTIMER0->CNT - M0P_LPTIMER0->ARR) / ulTimerCountsForOneTick;
}
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
value. */
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
vTaskStepTick( ulCompleteTickPeriods );
portNVIC_SYSTICK_LOAD_REG = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
/* Exit with interrpts enabled. */
// __asm volatile( "cpsie i" ::: "memory" );
}
}
/*******************************************************************************
* 中斷服務(wù)函數(shù)
******************************************************************************/
/**
******************************************************************************
** \brief LPTIMER中斷服務(wù)函數(shù)
**
** \return 無(wú)
******************************************************************************/
void LpTim0_IRQHandler(void)
{
if (TRUE == Lptim_GetItStatus(M0P_LPTIMER0))
{
Lptim_ClrItStatus(M0P_LPTIMER0);//清除LPTimer的中斷標(biāo)志位
lptim_timeout = true;
}
}
#endif
注意:這里注釋掉了控制中斷的匯編代碼H堋!执泰!
休眠前后的處理
在FreeRTOSConfig.h
下定義如下的宏
extern uint32_t enter_deepsleep_handle(uint32_t tick);
extern void exit_deepsleep_handle(uint32_t tick);
#define configPRE_SLEEP_PROCESSING(tick) do {tick = enter_deepsleep_handle(tick);}while(0)
#define configPOST_SLEEP_PROCESSING(tick) exit_deepsleep_handle(tick)
在其他地方實(shí)現(xiàn)上面兩個(gè)函數(shù)枕磁,本文的例子中只使用了SPI這個(gè)外設(shè),因此休眠前關(guān)閉SPI术吝,并將相關(guān)的引腳配置為功耗最低的狀態(tài)计济,在休眠結(jié)束后再重新配置回來(lái)。
uint32_t enter_deepsleep_handle(uint32_t tick)
{
stc_gpio_cfg_t GpioInitStruct;
DDL_ZERO_STRUCT(GpioInitStruct);
/* GPIO Ports Clock Enable */
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
///< 不用的引腳全部配置成 數(shù)字端口
// DW用到的引腳都是數(shù)字引腳排苍,不需要處理這一步驟
///< 不用的引腳全部配置成 輸入IO
///< 不用的引腳全部配置成 使能下拉
GpioInitStruct.enDir = GpioDirIn;
GpioInitStruct.enPd = GpioPdEnable;
Gpio_Init(BMI_CSn_GPIO_Port, BMI_CSn_Pin, &GpioInitStruct);
Gpio_Init(BMI_SCK_GPIO_Port, BMI_SCK_Pin, &GpioInitStruct);
Gpio_Init(BMI_MISO_GPIO_Port, BMI_MISO_Pin, &GpioInitStruct);
Gpio_Init(BMI_MOSI_GPIO_Port, BMI_MOSI_Pin, &GpioInitStruct);
///<復(fù)位模塊
Reset_RstPeripheral0(ResetMskSpi0);
///< 關(guān)閉外設(shè)時(shí)鐘
Sysctrl_SetPeripheralGate(SysctrlPeripheralSpi0, FALSE);
/* 進(jìn)入深度休眠模式 */
Lpm_GotoDeepSleep(FALSE);
/* 喚醒后停止定時(shí)器 */
Lptim_Cmd(M0P_LPTIMER0, FALSE);
/* 返回0沦寂,不使用通用的休眠 */
return 0;
}
void exit_deepsleep_handle(uint32_t tick)
{
bmi_gpio_init();
bim_spi_init();
}