FreeRTOS 自定義Tickless的實(shí)現(xiàn)

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();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淘衙,隨后出現(xiàn)的幾起案子传藏,更是在濱河造成了極大的恐慌,老刑警劉巖彤守,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漩氨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡遗增,警方通過(guò)查閱死者的電腦和手機(jī)叫惊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)做修,“玉大人霍狰,你說(shuō)我怎么就攤上這事抡草。” “怎么了蔗坯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵康震,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我宾濒,道長(zhǎng)腿短,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任绘梦,我火速辦了婚禮橘忱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘卸奉。我一直安慰自己钝诚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布榄棵。 她就那樣靜靜地躺著凝颇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疹鳄。 梳的紋絲不亂的頭發(fā)上拧略,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音瘪弓,去河邊找鬼辑鲤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛杠茬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弛随,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼瓢喉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了舀透?” 一聲冷哼從身側(cè)響起栓票,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎愕够,沒(méi)想到半個(gè)月后走贪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惑芭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年坠狡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遂跟。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逃沿,死狀恐怖婴渡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凯亮,我是刑警寧澤边臼,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站假消,受9級(jí)特大地震影響柠并,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜富拗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一臼予、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧媒峡,春花似錦瘟栖、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至签餐,卻和暖如春寓涨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背氯檐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工戒良, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冠摄。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓糯崎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親河泳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沃呢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348