连接软件:在 FreeRTOS 中实现无滴答模式 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
FreeRTOS 通过计数滴答来跟踪系统中经过的时间。主机 MCU 中可用的定时器之一生成的周期性中断例程内的滴答计数会增加。当 FreeRTOS 运行空闲任务挂钩时,微控制器可以进入低功耗模式。根据低功耗模式,可以禁用一个或多个外围设备,以节省尽可能多的能源。FreeRTOS 无滴答空闲模式允许在空闲期间停止滴答中断。停止滴答中断可使微控制器保持深度省电状态,直到唤醒事件发生。 应用程序需要配置模块(计时器、ADC 等),以便在执行下一个 FreeRTOS 任务之前唤醒微控制器。为此,在执行 vPortSuppressTicksAndSleep(启用无滴答空闲时由 FreeRTOS 调用的函数)期间,MCU 可以保持休眠状态的最大时间量作为输入参数传递,以便正确配置唤醒模块。一旦 MCU 唤醒并且 FreeRTOS 滴答中断重新启动,就必须恢复 MCU 睡眠期间丢失的滴答计数。 在连接软件 FreeRTOS 演示中,无滴答模式默认未启用。在这篇文章中,我们将 展示如何启用它。对于此示例,我们将使用 QN9080x 来演示实现。 低功耗 Freertos 无滴答 无滴答 以下文件中实施了更改: \框架\低功耗\源\QN908XC\PWR.c \框架\低功耗\接口\QN908XC\PWR_Interface.h \freertos\fsl_tickless_generic.h \source\common\ApplMain.c 以下文件已从项目中删除 fsl_tickless_qn_rtc.c PWR.C 和 PWR_Interface.h 此文件中的更改旨在让 QN9080 准备好使用 RTC 计时器唤醒。其他部件,如 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); //Tickless 在 PWR.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_接口.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 以下更改的目的是让系统做好准备,以恢复低功耗期间错过的滴答声。 在 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 振荡器保持活动状态。我们将更改连接软件电源库以使用深度睡眠模式 3(QN908x 的断电模式 0),以保持 32.768kHz 振荡器开启。这个改变是在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 Tick 间隔并使用以下公式计算: SYST_RNR = F_Systick_CLK(赫兹) * T_FreeRTOS_Ticks(毫秒) 其中: F_Systick_CLK(Hz) = AHB 或 SYST_CSR 选择的 32KHz T_FreeRTOS_Ticks(ms) = 滴答计数值。 FreeRTOS配置.h 最后,在 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) 测试无滴答 RTOS 为了测试是否成功添加了无滴答支持,我们实现了一个切换 LED 的示例应用程序。此应用程序配置一个 RTOS 定时器,每 500 毫秒切换一次 LED,并在空闲时间进入 DSM3 中的 MCU。电源分析演示就是为此目的而使用的。 电源分析.c 确保已包含以下头文件 #include "FreeRTOS.h"
#include "task.h" 创建一个 RTOS 任务,使 LED 每 500 毫秒闪烁一次。首先声明任务函数、任务ID和任务本身。 void vfnTaskLedBlinkTest(void* param); //New Task Definition
OSA_TASK_DEFINE(vfnTaskLedBlinkTest, 1, 1, 500, FALSE );
osaTaskId_t gAppTestTask1Id = 0; // TestTask1 Id 在BleApp_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 ,带有 功率测量工具。有了它,我们可以看到消耗的电流并证明实施情况符合预期。 配置功率测量工具 消耗电流 BLE软件 回复:连接软件:在 FreeRTOS 中实现无滴答模式 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
即使在无滴答模式或裸板模式下,电源配置文件也可以达到合理的功耗。 然而,有一个陷阱,需要小心。 如果从电源模式 PD1 恢复,在无滴答空闲任务中,在睡眠前已关闭 32K OSC, PWRLib_LPTMR_Stop将崩溃以操作断电外设。 如果电源模式为 PD0,则 OSC 工作时不会出现该问题,并且在睡眠期间保持通电。 如何修复: 判断从PD1还是PD0恢复。 回复:连接软件:在 FreeRTOS 中实现无滴答模式 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
温度传感器示例似乎在无滴答模式下工作。 与电源配置文件示例相比,睡眠消耗略高 回复:连接软件:在 FreeRTOS 中实现无滴答模式 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
哪个示例最适合用于测试无滴答 freertos 和低功耗操作? 回复:连接软件:在 FreeRTOS 中实现无滴答模式 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Hi: 最新的 SDK v2.2 已经支持 freertos 的 tickless 功能,需要在app_preinclude.h中启用以下宏 configUSE_TICKLESS_IDLE :为1 以下定义用于计算无滴消逝时间 /* RTC 在 PD1 中不运行,因此禁用 PWR 模块中的 RTC 相关代码 */ #定义 cPWR_EnableLpTmrRunning 1 #定义cPWR_EnablePD0RtcInterrupt 1 回复:连接软件:在 FreeRTOS 中实现无滴答模式 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
本指南是否可以更新为最新的 SDK?QN9080 的 SDK 中似乎实现了对无滴答模式的部分支持?
記事全体を表示