Zigbee節點只有終端裝置類型可以設定睡眠模式,要設定睡眠模式,在配置上先要確保:
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 )); }