1.f8wConfig.cfg文件中DRFD_RCVC_ALWAYS_ON定义为FALSE;
/****************************************
* The following are for End Devices only
***************************************/
-DRFD_RCVC_ALWAYS_ON=FALSE
2.在IAR的工程Options->C/C++Compiler->Preprocessor->Defined symbols中预编译POWER_SAVING;
除此之外,设备在运行过程中进入睡眠模式需要满足:
1.电源管理设备属性为PWRMGR_BATTERY;
2.所有任务都支持低功耗;
3.当前无事件需要处理;
满足以上条件后终端设备将进入睡眠状态。第三个条件实际上也暗示了终端设备的唤醒条件,进入睡眠模式需要保证当前无事件处理,假如在睡眠之前存在定时触发事件,那么当这个定时时间到后事件被设置,终端设备就要被唤醒去处理这个事件,这种机制也决定了Zigbee睡眠时间的长短是由事件的调度所决定的。
那Zigbee OSAL到底是怎样管理睡眠的呢?从ZMain.c中的main()函数中进入OSAL的主循环osal_start_system(),程序会在这个函数中一遍又一遍地轮询是否有事件需要处理,每次轮询时,当没有事件需要处理时并且定义了POWER_SAVING时则会调用osal_pwrmgr_powerconserve()考虑是否进入睡眠模式。
进入osal_pwrmgr_powerconserve()
发现这个函数可以调用 OSAL_SET_CPU_INTO_SLEEP( next )使设备进入睡眠模式,但需要满足两个条件:
1.pwrmgr_attribute.pwrmgr_device != PWRMGR_ALWAYS_ON;
2. pwrmgr_attribute.pwrmgr_task_state == 0
两个条件都涉及了一个pwrmgr_attribute_t类型的全局变量pwrmgr_attribute,这个变量是用于电源管理的。
/* This global variable stores the power management attributes.
*/
pwrmgr_attribute_t pwrmgr_attribute;
/* These attributes define sleep beheaver. The attributes can be changed
* for each sleep cycle or when the device characteristic change.
*/
typedef struct
{
uint16 pwrmgr_task_state; //任务状态
uint16 pwrmgr_next_timeout; //下一次超时时间
uint16 accumulated_sleep_time; //睡眠时间
uint8 pwrmgr_device; //电源管理设备属性,有PWRMGR_ALWAYS_ON和PWRMGR_BATTERY两种
} pwrmgr_attribute_t;
pwrmgr_attribute中包含了四个元素,pwrmgr_task_state记录了是否所有任务都支持低功耗,pwrmgr_device则是电源管理的设备属性,这两个正对应了最开始提到的设备要成功进入睡眠状态必须要满足的三个条件中的前两个:
1.电源管理设备属性为PWRMGR_BATTERY;
2.所有任务都支持低功耗;
3.当前无事件需要处理;
pwrmgr_sleep_time顾名思义是睡眠时间,pwrmgr_next_timeout是下次超时时间。
至此,显然如果不能同时满足pwrmgr_attribute.pwrmgr_device != PWRMGR_ALWAYS_ON和pwrmgr_attribute.pwrmgr_task_state == 0是无法调用 OSAL_SET_CPU_INTO_SLEEP( next )从而使设备进入睡眠模式的,那么全局变量pwrmgr_attribute在哪里进行了初始化呢?
进入ZMain()中main()函数的osal_init_system()发现有一个初始化电源配置的函数osal_pwrmgr_init()
uint8 osal_init_system( void )
{
// Initialize the Memory Allocation System
osal_mem_init();
// Initialize the message queue
osal_qHead = NULL;
// Initialize the timers
osalTimerInit();
// Initialize the Power Management System
osal_pwrmgr_init();
// Initialize the system tasks.
osalInitTasks();
// Setup efficient search for the first free block of heap.
osal_mem_kick();
return ( SUCCESS );
}
osal_pwrmgr_init()初始化了pwrmgr_attribute的两个元素
void osal_pwrmgr_init( void )
{
pwrmgr_attribute.pwrmgr_device = PWRMGR_ALWAYS_ON; // Default to no power conservation.
pwrmgr_attribute.pwrmgr_task_state = 0; // Cleared. All set to conserve
}
由于初始化pwrmgr_device = PWRMGR_ALWAYS_ON ,显然默认情况下是不支持进入睡眠模式的。好在pwrmgr_attribute是以全局变量的形式定义的,所以我们可以在应用层直接更改pwrmgr_device使其为PWRMGR_BATTERY。在OSAL_PwrMgr.c设置了可以直接更改pwrmgr_device的函数 osal_pwrmgr_device(),所以也可以通过在应用层调用osal_pwrmgr_device( PWRMGR_BATTERY )将电源管理设备属性更改为PWRMGR_BATTERY。
void osal_pwrmgr_device( uint8 pwrmgr_device )
{
pwrmgr_attribute.pwrmgr_device = pwrmgr_device;
}
OSAL.c中还定义了osal_pwrmgr_task_state( uint8 task_id, uint8 state ),该函数可以被任何任务调用来标记自己是否支持低功耗,支持则为PWRMGR_CONSERVE,不支持则为PWRMGR_HOLD。
#define PWRMGR_CONSERVE 0
#define PWRMGR_HOLD 1
/*********************************************************************
* @fn osal_pwrmgr_task_state
*
* @brief This function is called by each task to state whether or
* not this task wants to conserve power.
*
* @param task_id - calling task ID.
* state - whether the calling task wants to
* conserve power or not.
*
* @return SUCCESS if task complete
*/
uint8 osal_pwrmgr_task_state( uint8 task_id, uint8 state )
{
if ( task_id >= tasksCnt )
return ( INVALID_TASK );
if ( state == PWRMGR_CONSERVE )
{
// Clear the task state flag
pwrmgr_attribute.pwrmgr_task_state &= ~(1 << task_id );
}
else
{
// Set the task state flag
pwrmgr_attribute.pwrmgr_task_state |= (1 << task_id);
}
return ( SUCCESS );
}
回到OSAL_PwrMgr.c中的osal_pwrmgr_powerconserve(),当在应用层更改pwemgr_device为PWRMGR_BATTERY后且所有任务皆支持低功耗后,即满足
1.pwrmgr_attribute.pwrmgr_device != PWRMGR_ALWAYS_ON;
2. pwrmgr_attribute.pwrmgr_task_state == 0
void osal_pwrmgr_powerconserve( void )
{
uint16 next;
halIntState_t intState;
// Should we even look into power conservation
if ( pwrmgr_attribute.pwrmgr_device != PWRMGR_ALWAYS_ON )
{
// Are all tasks in agreement to conserve
if ( pwrmgr_attribute.pwrmgr_task_state == 0 )
{
// Hold off interrupts.
HAL_ENTER_CRITICAL_SECTION( intState );
// Get next time-out
next = osal_next_timeout();
// Re-enable interrupts.
HAL_EXIT_CRITICAL_SECTION( intState );
// Put the processor into sleep mode
OSAL_SET_CPU_INTO_SLEEP( next );
}
}
}
将调用 OSAL_SET_CPU_INTO_SLEEP( next ),OSAL_SET_CPU_INTO_SLEEP是宏,这里实际上就是调用halSleep(next),halSleep(next)中描述了设备睡眠的相关配置。
#define OSAL_SET_CPU_INTO_SLEEP(timeout) halSleep(timeout); /* Called from OSAL_PwrMgr */
void halSleep( uint16 osal_timeout )
{
uint32 timeout;
uint32 macTimeout = 0;
halAccumulatedSleepTime = 0;
/* get next OSAL timer expiration converted to 320 usec units */
timeout = HAL_SLEEP_MS_TO_320US(osal_timeout);
if (timeout == 0)
{
timeout = MAC_PwrNextTimeout();
}
else
{
/* get next MAC timer expiration */
macTimeout = MAC_PwrNextTimeout();
/* get lesser of two timeouts */
if ((macTimeout != 0) && (macTimeout < timeout))
{
timeout = macTimeout;
}
}
/* HAL_SLEEP_PM2 is entered only if the timeout is zero and
* the device is a stimulated device.
*/
halPwrMgtMode = (timeout == 0) ? HAL_SLEEP_DEEP : HAL_SLEEP_TIMER;
/* DEEP sleep can only be entered when zgPollRate == 0.
* This is to eliminate any possibility of entering PM3 between
* two network timers.
*/
#if ZG_BUILD_ENDDEVICE_TYPE && defined (NWK_AUTO_POLL)
if ((timeout > HAL_SLEEP_MS_TO_320US(PM_MIN_SLEEP_TIME)) ||
(timeout == 0 && zgPollRate == 0))
#else
if ((timeout > HAL_SLEEP_MS_TO_320US(PM_MIN_SLEEP_TIME)) ||
(timeout == 0))
#endif
{
halIntState_t ien0, ien1, ien2;
HAL_ASSERT(HAL_INTERRUPTS_ARE_ENABLED());
HAL_DISABLE_INTERRUPTS();
/* always use "deep sleep" to turn off radio VREG on CC2530 */
if (MAC_PwrOffReq(MAC_PWR_SLEEP_DEEP) == MAC_SUCCESS)
{
#if ((defined HAL_KEY) && (HAL_KEY == TRUE))
/* get peripherals ready for sleep */
HalKeyEnterSleep();
#endif
#ifdef HAL_SLEEP_DEBUG_LED
HAL_TURN_OFF_LED3();
#else
/* use this to turn LEDs off during sleep */
HalLedEnterSleep();
#endif
/* enable sleep timer interrupt */
if (timeout != 0)
{
if (timeout > HAL_SLEEP_MS_TO_320US( MAX_SLEEP_TIME ))
{
timeout -= HAL_SLEEP_MS_TO_320US( MAX_SLEEP_TIME );
halSleepSetTimer(HAL_SLEEP_MS_TO_320US( MAX_SLEEP_TIME ));
}
else
{
/* set sleep timer */
halSleepSetTimer(timeout);
}
/* set up sleep timer interrupt */
HAL_SLEEP_TIMER_CLEAR_INT();
HAL_SLEEP_TIMER_ENABLE_INT();
}
#ifdef HAL_SLEEP_DEBUG_LED
if (halPwrMgtMode == CC2530_PM1)
{
HAL_TURN_ON_LED1();
}
else
{
HAL_TURN_OFF_LED1();
}
#endif
/* save interrupt enable registers and disable all interrupts */
HAL_SLEEP_IE_BACKUP_AND_DISABLE(ien0, ien1, ien2);
HAL_ENABLE_INTERRUPTS();
/* set CC2530 power mode, interrupt is disabled after this function */
HAL_SLEEP_SET_POWER_MODE(halPwrMgtMode);
/* the interrupt is disabled - see halSetSleepMode() */
/* restore interrupt enable registers */
HAL_SLEEP_IE_RESTORE(ien0, ien1, ien2);
/* disable sleep timer interrupt */
HAL_SLEEP_TIMER_DISABLE_INT();
/* Calculate timer elasped */
halAccumulatedSleepTime += (HalTimerElapsed() / TICK_COUNT);
#ifdef HAL_SLEEP_DEBUG_LED
HAL_TURN_ON_LED3();
#else
/* use this to turn LEDs back on after sleep */
HalLedExitSleep();
#endif
#if ((defined HAL_KEY) && (HAL_KEY == TRUE))
/* handle peripherals */
(void)HalKeyExitSleep();
#endif
/* power on the MAC; blocks until completion */
MAC_PwrOnReq();
HAL_ENABLE_INTERRUPTS();
/* For CC2530, T2 interrupt won抰 be generated when the current count is greater than
* the comparator. The interrupt is only generated when the current count is equal to
* the comparator. When the CC2530 is waking up from sleep, there is a small window
* that the count may be grater than the comparator, therefore, missing the interrupt.
* This workaround will call the T2 ISR when the current T2 count is greater than the
* comparator. The problem only occurs when POWER_SAVING is turned on, i.e. the 32KHz
* drives the chip in sleep and SYNC start is used.
*/
macMcuTimer2OverflowWorkaround();
}
else
{
HAL_ENABLE_INTERRUPTS();
}
}
}
halSleep(next)的参数next是在osal_pwrmgr_powerconserve()确定,是应用层下一次定时器到的超时时间。
// Get next time-out
next = osal_next_timeout();
协议栈中有两类定时器,一类是CC2XXX中的定时器由硬件驱动计数;第二类是软件定时器,是由osal_start_timer()、osal_start_reload_timer等定时器设置函数添加到软定时器链表,再由系统时钟进行统一计数,也就是说这些软定时器是通过系统时钟来驱动的。而在OnBoard.h中定义了系统时钟的节拍为1ms,也就是说每过1ms系统时钟就驱动软件时间链表中的定时器减1,当有一个软件定时器计数减到0(也就是说超时了)就删除这个软件定时器并调用osal_set_event()设置相应的事件标志,通知系统需要处理是时候处理这个事件了。
/* OSAL timer defines */
#define TICK_TIME 1000 // Timer per tick - in micro-sec
而如前文提到的,有事件要处理时是不能睡眠的,所以进入睡眠之前必须要知道最近的一次定时器超时是长,并在其超时时醒来以及时处理相应的事件。halSleep( )先比较了应用层和MAC层下次超时时间的长短,并取了一个最小的作为睡眠时间,然后调用halSleepSetTimer()来配置睡眠定时器。
if (timeout != 0)
{
if (timeout > HAL_SLEEP_MS_TO_320US( MAX_SLEEP_TIME ))
{
timeout -= HAL_SLEEP_MS_TO_320US( MAX_SLEEP_TIME );
halSleepSetTimer(HAL_SLEEP_MS_TO_320US( MAX_SLEEP_TIME ));
}