FreeRTOS は、ティックをカウントすることで、システム内の経過時間を追跡します。ティック・カウントは、ホストMCUで使用可能なタイマーの1つによって生成される定期的な割り込みルーチン内で増加します。FreeRTOS がアイドルタスクフックを実行している場合、マイクロコントローラを低電力モードにすることができます。低電力モードに応じて、1つ以上の周辺機器を無効にして、可能な限り最大限のエネルギーを節約できます。FreeRTOS ティックレスアイドルモードでは、アイドル期間中のティック中断を停止できます。ティック割込みを停止すると、ウェイクアップ・イベントが発生するまで、マイクロコントローラは大幅な省電力状態を維持できます。
アプリケーションは、次のFreeRTOSタスクを実行する前にマイクロコントローラをウェイクアップするモジュール(タイマー、ADCなど)を構成する必要があります。そのため、tickless idleが有効な場合にFreeRTOSが呼び出す関数であるvPortSuppressTicksAndSleepの実行中に、ウェイクアップモジュールを適切に設定するために、MCUがスリープ状態を維持できる最大時間を入力パラメータとして渡します。MCUがウェイクアップし、FreeRTOSのティック割り込みが再開されると、MCUがスリープ状態の間に失われたティックカウントの数を元に戻す必要があります。
ティックレスモードは、接続ソフトウェア FreeRTOS デモではデフォルトでは有効になっていません。この投稿では、有効にする方法 説明します。この例では、QN9080x を使用して実装を示します。
低電力
FreeRTOS ティックレス
ダニ無し
変更は、次のファイルに実装されています。
\フレームワーク\LowPower\ソース\QN908XC\PWR.c
\framework\LowPower\Interface\QN908XC\PWR_Interface.h
\フリートス\fsl_tickless_generic.h
\ソース\共通\ApplMain.c
次のファイルがプロジェクトから削除されました
fsl_tickless_qn_rtc.c
このファイルの変更は、RTC タイマーを使用して QN9080 をウェイクアップするための準備を目的としています。MKW41Z などの他のパーツは、この目的のために他のモジュール (LPTMR など) を有効にする場合があり、このファイルを変更する必要がない場合があります。
*** PWR.c ***
RTC用のドライバを追加します。これは、QN908xをウェイクアップするために使用するタイマーです
/*Tickless: Add RTC driver for tickless support */
#include "fsl_rtc.h"ローカル変数の追加
uint64_t mLpmTotalSleepDuration; //Tickless
uint8_t mPWR_DeepSleepTimeUpdated = 0; //Tickless: Coexistence with TMR managerプライベート機能を追加する
uint32_t PWR_RTCGetMsTimeUntilNextTick (void); //Tickless
void PWR_RTCSetWakeupTimeMs (uint32_t wakeupTimeMs); //Tickless
void PWR_RTCWakeupStart (void); //TicklessPWR.C.で次の変更を行います。必要なすべての変更は、変更の開始位置で "Start" と、変更の終了位置で "End" というコメントとしてマークされます
#if (cPWR_UsePowerDownMode && (cPWR_EnableDeepSleepMode_1 || cPWR_EnableDeepSleepMode_2 || cPWR_EnableDeepSleepMode_3 || cPWR_EnableDeepSleepMode_4))
static void PWR_HandleDeepSleepMode_1_2_3_4(void)
{
#if cPWR_BLE_LL_Enable
uint8_t power_down_mode = 0xff;
bool_t enterLowPower = TRUE;
__disable_irq();
/****************START***********************************/
/*Tickless: Configure wakeup timer */
if(mPWR_DeepSleepTimeUpdated){
PWR_RTCSetWakeupTimeMs(mPWR_DeepSleepTimeMs);
mPWR_DeepSleepTimeUpdated = FALSE; // Coexistence with TMR Manager
}
PWR_RTCWakeupStart();
/*****************END**************************************/
PWRLib_ClearWakeupReason();
//Try to put BLE in deep sleep mode
power_down_mode = BLE_sleep();
if (power_down_mode < kPmPowerDown0)
{
enterLowPower = false; // BLE doesn't allow deep sleep
}
//no else - enterLowPower is already true
if(enterLowPower)
{
/****************START**************************/
uint32_t freeRunningRtcPriority;
/****************END****************************/
NVIC_ClearPendingIRQ(OSC_INT_LOW_IRQn);
NVIC_EnableIRQ(OSC_INT_LOW_IRQn);
while (SYSCON_SYS_STAT_OSC_EN_MASK & SYSCON->SYS_STAT) //wait for BLE to enter sleep
{
POWER_EnterSleep();
}
NVIC_DisableIRQ(OSC_INT_LOW_IRQn);
if(gpfPWR_LowPowerEnterCb != NULL)
{
gpfPWR_LowPowerEnterCb();
}
/* Disable SysTick counter and interrupt */
sysTickCtrl = SysTick->CTRL & (SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
SysTick->CTRL &= ~(SysTick_CTRL_ENABLE_Msk | SysTick_CTRL_TICKINT_Msk);
ICSR |= (1 << 25); // clear PendSysTick bit in ICSR, if set
/************************START***********************************/
NVIC_ClearPendingIRQ(RTC_FR_IRQn);
freeRunningRtcPriority = NVIC_GetPriority(RTC_FR_IRQn);
NVIC_SetPriority(RTC_FR_IRQn,0);
/***********************END***************************************/
POWER_EnterPowerDown(0); //Nighty night!
/************************START**********************************/
NVIC_SetPriority(RTC_FR_IRQn,freeRunningRtcPriority);
/************************END************************************/
if(gpfPWR_LowPowerExitCb != NULL)
{
gpfPWR_LowPowerExitCb();
}
/* Restore the state of SysTick */
SysTick->CTRL |= sysTickCtrl;
PWRLib_UpdateWakeupReason();
}
__enable_irq();
#else
PWRLib_ClearWakeupReason();
#endif /* cPWR_BLE_LL_Enable */
}
#endif /* (cPWR_UsePowerDownMode && cPWR_EnableDeepSleepMode_1) */
void PWR_SetDeepSleepTimeInMs(uint32_t deepSleepTimeMs)
{
#if (cPWR_UsePowerDownMode)
if(deepSleepTimeMs == 0)
{
return;
}
mPWR_DeepSleepTimeMs = deepSleepTimeMs;
/****************START******************/
mPWR_DeepSleepTimeUpdated = TRUE;
/****************END*********************/
#else
(void) deepSleepTimeMs;
#endif /* (cPWR_UsePowerDownMode) */
}ファイルの最後にある次の関数定義を追加/置換します
/*---------------------------------------------------------------------------
* Name: PWR_GetTotalSleepDurationMS
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/
uint32_t PWR_GetTotalSleepDurationMS(void)
{
uint32_t time;
uint32_t currentSleepTime;
OSA_InterruptDisable();
currentSleepTime = RTC_GetFreeRunningInterruptThreshold(RTC);
if(currentSleepTime >= mLpmTotalSleepDuration){
time = (currentSleepTime-mLpmTotalSleepDuration)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
}
else{
time = ((0x100000000-mLpmTotalSleepDuration)+currentSleepTime)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
}
OSA_InterruptEnable();
return time;
}
/*---------------------------------------------------------------------------
* Name: PWR_ResetTotalSleepDuration
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/
void PWR_ResetTotalSleepDuration(void)
{
OSA_InterruptDisable();
mLpmTotalSleepDuration = RTC_GetFreeRunningCount(RTC);
OSA_InterruptEnable();
}
/*---------------------------------------------------------------------------
* Name: PWR_RTCGetMsTimeUntilNextTick
* Description: -
* Parameters: -
* Return: Time until next tick in mS
*---------------------------------------------------------------------------*/
uint32_t PWR_RTCGetMsTimeUntilNextTick (void)
{
uint32_t time;
uint32_t currentRtcCounts, thresholdRtcCounts;
OSA_InterruptDisable();
currentRtcCounts = RTC_GetFreeRunningCount(RTC);
thresholdRtcCounts = RTC_GetFreeRunningResetThreshold(RTC);
if(thresholdRtcCounts > currentRtcCounts){
time = (thresholdRtcCounts-currentRtcCounts)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
}
else{
time = ((0x100000000-currentRtcCounts)+thresholdRtcCounts)*1000/CLOCK_GetFreq(kCLOCK_32KClk);
}
OSA_InterruptEnable();
return time;
}
/*---------------------------------------------------------------------------
* Name: PWR_RTCSetWakeupTimeMs
* Description: -
* Parameters: wakeupTimeMs: New wakeup time in milliseconds
* Return: -
*---------------------------------------------------------------------------*/
void PWR_RTCSetWakeupTimeMs (uint32_t wakeupTimeMs){
uint32_t wakeupTimeTicks;
uint32_t thresholdValue;
wakeupTimeTicks = (wakeupTimeMs*CLOCK_GetFreq(kCLOCK_32KClk))/1000;
thresholdValue = RTC_GetFreeRunningCount(RTC);
thresholdValue += wakeupTimeTicks;
RTC_SetFreeRunningInterruptThreshold(RTC, thresholdValue);
}
/*---------------------------------------------------------------------------
* Name: PWR_RTCWakeupStart
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/
void PWR_RTCWakeupStart (void){
if(!(RTC->CNT2_CTRL & RTC_CNT2_CTRL_CNT2_EN_MASK)){
RTC->CNT2_CTRL |= 0x52850000 | RTC_CNT2_CTRL_CNT2_EN_MASK | RTC_CNT2_CTRL_CNT2_WAKEUP_MASK | RTC_CNT2_CTRL_CNT2_INT_EN_MASK;
}
else{
RTC->CNT2_CTRL |= 0x52850000 | RTC_CNT2_CTRL_CNT2_WAKEUP_MASK | RTC_CNT2_CTRL_CNT2_INT_EN_MASK;
}
}
PWR_Interface.h ***
ファイルの末尾に次の関数宣言を追加します
/*---------------------------------------------------------------------------
* Name: PWR_GetTotalSleepDurationMS
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/
uint32_t PWR_GetTotalSleepDurationMS(void);
/*---------------------------------------------------------------------------
* Name: PWR_ResetTotalSleepDuration
* Description: -
* Parameters: -
* Return: -
*---------------------------------------------------------------------------*/
void PWR_ResetTotalSleepDuration(void);
#ifdef __cplusplus
}
#endif
#endif /* _PWR_INTERFACE_H_ */
次の変更は、低電力期間中に欠落したティックを回復するためにシステムを準備することを目的としています。
fsl_tickless_generic.h で次の変更を行います。必要なすべての変更は、変更の開始位置で "Start" と、変更の終了位置で "End" というコメントとしてマークされます
/* QN_RTC: The RTC free running is a 32-bit counter. */
#define portMAX_32_BIT_NUMBER (0xffffffffUL)
#define portRTC_CLK_HZ (0x8000UL)
/* A fiddle factor to estimate the number of SysTick counts that would have
occurred while the SysTick counter is stopped during tickless idle
calculations. */
#define portMISSED_COUNTS_FACTOR (45UL)
/*
* The number of SysTick increments that make up one tick period.
*/
/****************************START**************************/
#if configUSE_TICKLESS_IDLE == 1
static uint32_t ulTimerCountsForOneTick;
#endif /* configUSE_TICKLESS_IDLE */
/************************END*********************************/
/*
* Setup the timer to generate the tick interrupts.
*/
void vPortSetupTimerInterrupt(void);
#ifdef __cplusplus
}
#endif
#endif /* FSL_TICKLESS_GENERIC_H */
これがメインのアプリケーションファイルです。ここでは、適切なAPIを呼び出して、低電力モードでMCUに入り、ティックリカバリシーケンスを実行します。
必要なRTCおよびFreeRTOSヘッダーファイルを含める
/*Tickless: Include RTC and FreeRTOS header files */
#include "fsl_rtc.h"
#include "fsl_tickless_generic.h"
#include "FreeRTOS.h"
#include "task.h"QN9080には、いくつかの低電力モードが含まれています。スリープモードでは、ほとんどのモジュールがアクティブに保たれます。パワーダウンモードでは、ほとんどのモジュールがオフになりますが、必要に応じてMCUをウェイクアップするために、一部のモジュールをアクティブなままに構成できます。ティックレス FreeRTOS を使用すると、次の準備完了タスクが実行される前に、何らかのタイマーでウェイクアップする必要があります。QN908xの場合、このタイマーはRTCであり、32.768kHz発振器がアクティブ状態を維持する必要があります。コネクティビティソフトウェアのPower Libを、32.768kHzの発振器をオンに維持するディープスリープモード3(QN908xの場合はPower Downモード0)を使用するように変更します。この変更は、 main_task 関数に実装されています。
#if !defined(MULTICORE_BLACKBOX)
/* BLE Host Stack Init */
if (Ble_Initialize(App_GenericCallback) != gBleSuccess_c)
{
panic(0,0,0,0);
return;
}
#endif /* MULTICORE_BLACKBOX */
/*************** Start ****************/
#if (cPWR_UsePowerDownMode)
PWR_ChangeDeepSleepMode(3);
#endif
/*************** End ****************/
}
/* Call application task */
App_Thread( param );
}また、ティックレスFreeRTOSには、次のタスクを実行する前にMCUがスリープ状態を維持できるRTOSティックの量を入力パラメータとして取る特別なアイドル関数が必要です。次の変更により、ティックレス モードが有効になっている場合、接続ソフトウェアのデモで提供されるデフォルトのアイドル機能が無効になります。
/************************************************************************************
*************************************************************************************
* Private prototypes
*************************************************************************************
************************************************************************************/
#if (cPWR_UsePowerDownMode || gAppUseNvm_d)
#if (mAppIdleHook_c)
#define AppIdle_TaskInit()
#define App_Idle_Task()
#else
#if (!configUSE_TICKLESS_IDLE)
static osaStatus_t AppIdle_TaskInit(void);
static void App_Idle_Task(osaTaskParam_t argument);
#endif // configUSE_TICKLESS_IDLE
#endif
#endif/************************************************************************************
*************************************************************************************
* Private memory declarations
*************************************************************************************
************************************************************************************/
/******************************** Start ******************************/
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
/******************************** End ******************************/
#if (!mAppIdleHook_c)
OSA_TASK_DEFINE( App_Idle_Task, gAppIdleTaskPriority_c, 1, gAppIdleTaskStackSize_c, FALSE );
osaTaskId_t gAppIdleTaskId = 0;
#endif
#endif /* cPWR_UsePowerDownMode */#if !gUseHciTransportDownward_d
pfBLE_SignalFromISR = BLE_SignalFromISRCallback;
#endif /* !gUseHciTransportDownward_d */
/**************************** Start ************************/
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
/**************************** End ************************/
#if (!mAppIdleHook_c)
AppIdle_TaskInit();
#endif
#endif/***************************START**************************/
#if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE)
/******************************END***************************/
static void App_Idle(void)
{
PWRLib_WakeupReason_t wakeupReason;
if( PWR_CheckIfDeviceCanGoToSleep() )
{
/* Enter Low Power */
wakeupReason = PWR_EnterLowPower();
#if gFSCI_IncludeLpmCommands_c
/* Send Wake Up indication to FSCI */
FSCI_SendWakeUpIndication();
#endif
#if gKBD_KeysCount_c > 0
/* Woke up on Keyboard Press */
if(wakeupReason.Bits.FromKeyBoard)
{
KBD_SwitchPressedOnWakeUp();
PWR_DisallowDeviceToSleep();
}
#endif
}
else
{
/* Enter MCU Sleep */
PWR_EnterSleep();
}
}
#endif /* cPWR_UsePowerDownMode */
#if (mAppIdleHook_c)
void vApplicationIdleHook(void)
{
#if (gAppUseNvm_d)
NvIdle();
#endif
/*******************************START****************************/
#if (cPWR_UsePowerDownMode && !configUSE_TICKLESS_IDLE)
/*********************************END*******************************/
App_Idle();
#endif
}
#else /* mAppIdleHook_c */
/******************************* START ****************************/
#if ((cPWR_UsePowerDownMode || gAppUseNvm_d) && !configUSE_TICKLESS_IDLE)
/******************************* END ****************************/
static void App_Idle_Task(osaTaskParam_t argument)
{
while(1)
{
#if gAppUseNvm_d
NvIdle();
#endif
#if (cPWR_UsePowerDownMode)
App_Idle();
#endif
/* For BareMetal break the while(1) after 1 run */
if (gUseRtos_c == 0)
{
break;
}
}
}
デフォルトのアイドル機能を無効にしたら、特別なアイドル機能を実装する必要があります。ApplMain.c の末尾に次のコードを追加しますファイル。
/*Tickless: Implement Tickless Idle */
#if (cPWR_UsePowerDownMode && configUSE_TICKLESS_IDLE)
extern void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
{
uint32_t time_ms = xExpectedIdleTime * portTICK_PERIOD_MS;
uint32_t tmrMgrExpiryTimeMs;
ulTimerCountsForOneTick = 160000;//VALUE OF THE SYSTICK 10 ms
#if (cPWR_UsePowerDownMode)
PWRLib_WakeupReason_t wakeupReason;
//TMR_MGR: Get next timer manager expiry time
tmrMgrExpiryTimeMs = TMR_GetFirstExpireTime(gTmrAllTypes_c);
// TMR_MGR: Update RTC Threshold only if RTOS needs to wakeup earlier
if(time_ms<tmrMgrExpiryTimeMs){
PWR_SetDeepSleepTimeInMs(time_ms);
}
PWR_ResetTotalSleepDuration();
if( PWR_CheckIfDeviceCanGoToSleep() )
{
wakeupReason = PWR_EnterLowPower();
//Fix: All the tick recovery stuff should only happen if device entered in DSM
xExpectedIdleTime = PWR_GetTotalSleepDurationMS() / portTICK_PERIOD_MS; // Fix: ticks = time in mS asleep / mS per each tick (portTICK_PERIOD_MS)
/* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
value. The critical section is used to ensure the tick interrupt
can only execute once in the case that the reload register is near
zero. */
portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
portENTER_CRITICAL();
portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
vTaskStepTick( xExpectedIdleTime );
portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
portEXIT_CRITICAL();
#if gKBD_KeysCount_c > 0
/* Woke up on Keyboard Press */
if(wakeupReason.Bits.FromKeyBoard)
{
KBD_SwitchPressedOnWakeUp();
PWR_DisallowDeviceToSleep();
}
#endif
}
else
{
/* Enter MCU Sleep */
PWR_EnterSleep();
}
#endif /* cPWR_UsePowerDownMode */
}
#endif //cPWR_UsePowerDownMode && configUSE_TICKLESS_IDLE
前の関数から、ulTimerCountsForOneTick の値を使用して、ウェイクアップ後に RTOS ティック タイマーのカウントを復元します。この値は、FreeRTOSConfig.h で定義されている RTOS ティック間隔によって異なりますは、次の式を使用して計算されます。
SYST_RNR = F_Systick_CLK(Hz) * T_FreeRTOS_Ticks(ms)
ここで、
最後に、FreeRTOSConfig.h でファイルで、configUSE_TICKLESS_IDLE が 1 に設定されていることを確認します
* See http://www.freertos.org/a00110.html.
*----------------------------------------------------------*/
#define configUSE_PREEMPTION 1
#define configUSE_TICKLESS_IDLE 1 //<--- /***** Start *****/
#define configCPU_CLOCK_HZ (SystemCoreClock)
#define configTICK_RATE_HZ ((TickType_t)100)
#define configMAX_PRIORITIES (18)
#define configMINIMAL_STACK_SIZE ((unsigned short)90)ティックレス サポートが正常に追加されたかどうかをテストするために、LED を切り替えるサンプル アプリケーションが実装されています。このアプリケーションは、500mSごとにLEDを切り替え、アイドル時間中にDSM3にMCUに入るようにRTOSタイマを設定します。この目的のために、Power Profiling デモが使用されました。
次のヘッダー ファイルが含まれていることを確認します
#include "FreeRTOS.h"
#include "task.h"500mSごとにLEDを点滅させるRTOSタスクを作成します。まず、タスク関数、タスクID、およびタスク自体を宣言します。
void vfnTaskLedBlinkTest(void* param); //New Task Definition
OSA_TASK_DEFINE(vfnTaskLedBlinkTest, 1, 1, 500, FALSE );
osaTaskId_t gAppTestTask1Id = 0; // TestTask1 IdBleApp_Init関数内に新しいタスクを作成します
void BleApp_Init(void)
{
PWR_AllowDeviceToSleep();
mPowerState = 0; // Board starts with PD1 enabled
/******************* Start *****************/
gAppTestTask1Id = OSA_TaskCreate(OSA_TASK(vfnTaskLedBlinkTest), NULL); //Task Creation
/******************* End *****************/
}最後に、タスク関数定義をファイルの最後に追加します。
void vfnTaskLedBlinkTest(void* param)
{
uint16_t wTimeValue = 500; //500ms
while(1)
{
LED_BLUE_TOGGLE();
vTaskDelay(pdMS_TO_TICKS(wTimeValue));
}
}MCUXpresso IDE の電力消費は、 Power Measurement Tool を使用して監視できます。これにより、消費された電流を確認し、実装が期待どおりに機能していることを証明できます。
電力測定ツールを構成する
消費電流
電力プロファイルは、ティックレスモードやベアボードモードでも、妥当な消費電力に達する可能性があります。
ただし、1つのトラップがあり、注意が必要です。
電源モードPD1から回復すると、スリープ前に32K OSCを閉じたティックレスアイドルタスクで、電源 PWRLib_LPTMR_Stopない周辺機器を操作するためにクラッシュします。
電源モードがPD0の場合、OSCが動作し、スリープ中に電源が供給されたままになっている場合に問題は発生しませんでした。
修正方法:
PD1またはPD0からの回復を判断します。
温度センサーの例は、ティックレスモードで動作するようです。
ただし、パワープロファイルの例に比べてスリープ消費量がわずかに多くなります
ティックレスフリートスと低電力動作をテストするには、どの例を使用するのが最適ですか?
Hi:
最新のSDK v2.2はfreertosのticklessをサポートしており、app_preinclude.hで以下のマクロを有効にする必要があります
configUSE_TICKLESS_IDLE:为1
以下定义用于计算tickless消逝时间
/* PD1ではRTCが動作していないため、PWRモジュールでRTC関連コードを無効にします */
#define cPWR_EnableLpTmrRunning 1
#define cPWR_EnablePD0RtcInterrupt 1