ZigBee開發(12)--協議棧工作原理
由於我的學習平臺是基於 TI 公司的,所以講述的當然也是 TI 的 Z-STACK。
協議棧下載連結 https://pan.baidu.com/s/1QCO1-E_UXsad__e3R8fQkw
TI 公司搭建了一個小型的作業系統(本質也是大型的程式),名叫 Z-stack。他們幫你考慮底層和網路層的內容,將複雜部分遮蔽掉。
讓使用者通過 API 函式就可以輕易用 ZigBee。這樣大家使用他們的產品也理所當然了,確實高明。
也就是說,協議棧是一個小作業系統。不要聽到是作業系統就感覺到很複雜。回想當初學習 51 微控制器時候是不是會用到定時器的功能?
我們會利用定時器計時,令 LED 一秒改變一次狀態。好,現在進一步,我們利用同一個定時器計時,令 LED1 一秒閃爍一次, LED2 二秒閃爍一次。這樣就有 2個任務了。
再進一步…有 n 個 LED,就有 n 個任務執行了。協議棧的最終工作原理也一樣。從它工作開始,定時器周而復始地計時,有傳送、接收…等任務要執行時就執行。這個方式稱為任務輪詢:
開啟協議棧資料夾 Texas Instruments\Projects\zstack 。裡面包含了 TI 公司的例程和工具。其中的功能我們會在用的的實驗裡講解。再開啟 Samples 資料夾:
Samples 資料夾裡面有三個例子: GenericApp、 SampleApp、 SimpleApp在這裡們選擇 SampleApp 對協議棧的工作流程進行講解。
開啟\SampleApp\CC2530DB 下工程檔案 SampleApp.eww。留意左邊的工程目錄, 我們暫時只需要關注 Zmain 資料夾和 App 資料夾。
任何程式都在 main 函式開始執行, Z-STACK 也不例外。開啟 Zmain.C, 找到 int main( void ) 函式。我們大概瀏覽一下 main 函式程式碼:
1 /********************************************************************* 2 * @fn main 3 * @brief First function called after startup. 4 * @return don't care 5 */ 6 int main( void ) 7{ 8 // Turn off interrupts 9 osal_int_disable( INTS_ALL ); ////關閉所有中斷 10 11 // Initialization for board related stuff such as LEDs 12 HAL_BOARD_INIT();//初始化系統時鐘 13 14 // Make sure supply voltage is high enough to run 15 zmain_vdd_check(); //檢查晶片電壓是否正常 16 17 // Initialize board I/O 18 InitBoard( OB_COLD ); //初始化 I/O , LED 、 Timer 等 19 20 // Initialze HAL drivers 21 HalDriverInit(); //初始化晶片各硬體模組 22 23 // Initialize NV System 24 osal_nv_init( NULL ); // 初始化 Flash 儲存器 25 26 // Initialize the MAC 27 ZMacInit(); //初始化 MAC 層 28 29 // Determine the extended address 30 zmain_ext_addr(); //確定 IEEE 64 位地址 31 32 #if defined ZCL_KEY_ESTABLISH 33 // Initialize the Certicom certificate information. 34 zmain_cert_init(); 35 #endif 36 37 // Initialize basic NV items 38 zgInit(); // 初始化非易失變數 39 40 #ifndef NONWK 41 // Since the AF isn't a task, call it's initialization routine 42 afInit(); 43 #endif 44 45 // Initialize the operating system 46 osal_init_system(); // 初始化作業系統 47 48 // Allow interrupts 49 osal_int_enable( INTS_ALL );// 使能全部中斷 50 51 // Final board initialization 52 InitBoard( OB_READY ); // 初始化按鍵 53 54 // Display information about this device 55 zmain_dev_info(); //顯示裝置資訊 56 57 /* Display the device info on the LCD */ 58 #ifdef LCD_SUPPORTED 59 zmain_lcd_init(); 60 #endif 61 62 #ifdef WDT_IN_PM1 63 /* If WDT is used, this is a good place to enable it. */ 64 WatchDogEnable( WDTIMX ); 65 #endif 66 67 osal_start_system(); // No Return from here 執行作業系統,進去後不會返回 68 69 return 0; // Shouldn't get here. 70 } // main()
看了上面的程式碼後,感覺很多函式不認識。不過,程式碼很有條理性,開始先執行初始化工作。包括硬體、網路層、任務等的初始化。
然後執行 osal_start_system();作業系統。進去後可不會回來了。在這裡, 重點了解 2 個函式:
c) 初始化作業系統
osal_init_system();
d) 執行作業系統
osal_start_system();
/************怎麼看?在函式名上單擊右鍵——go to definition of…,(或把游標放到函式上,按F12)便可以進入函式。 ********************/
osal_init_system(); 系統初始化函式,進入函式。發現裡面有 6個初始化函式,沒事,這裡只關心osalInitTasks();任務初始化函式。繼續由該函式進入。
看下面程式碼的註釋應該能有一些規律
/********************************************************************* * @fn osalInitTasks * * @brief This function invokes the initialization function for each task. * * @param void * * @return none */ void osalInitTasks( void ) { uint8 taskID = 0; // 分配記憶體,返回指向緩衝區的指標 tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); // 設定所分配的記憶體空間單元值為0 osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); // 任務優先順序由高向低依次排列,高優先順序對應taskID 的值反而小 macTaskInit( taskID++ ); //macTaskInit(0) ,使用者不需考慮 nwk_init( taskID++ ); //nwk_init(1),使用者不需考慮 Hal_Init( taskID++ ); //Hal_Init(2) ,使用者需考慮 #if defined( MT_TASK ) MT_TaskInit( taskID++ ); #endif APS_Init( taskID++ ); //APS_Init(3) ,使用者不需考慮 #if defined ( ZIGBEE_FRAGMENTATION ) APSF_Init( taskID++ ); #endif ZDApp_Init( taskID++ ); //ZDApp_Init(4) ,使用者需考慮 #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) ZDNwkMgr_Init( taskID++ ); #endif //使用者建立的任務 SampleApp_Init( taskID ); // SampleApp_Init _Init(5) ,使用者需考慮 }
可以這樣理解,函式對 taskID 個東西進行初始化,每初始化一個,taskID++。
註釋後面有些寫著使用者需要考慮,有些則寫著使用者不需考慮。沒錯,需要考慮的使用者可以根據自己的硬體平臺或者其他設定,而寫著不需考慮的也是不能修改的。
TI 公司出品協議棧已完成的東西。 SampleApp_Init(taskID );很重要,是應用協議棧例程的必需要函式,使用者通常在這裡初始化自己的東西。
至此, osal_init_system();大概瞭解完畢。
再來看第二個函式 osal_start_system();執行作業系統。同樣用 go todefinition 的方法進入該函式。 /********************************************************************* * @fn osal_start_system *
* @brief * * This function is the main loop function of the task system (if * ZBIT and UBIT are not defined). This Function doesn't return.
這個是任務系統輪詢的主要函式。他會查詢發生的事件然後呼叫相應的事件執行函式。如果沒有事件登記要發生,那麼就進入睡眠模式。這個函式是永遠不會返回的。 * * @param void * * @return none */ void osal_start_system( void ) { #if !defined ( ZBIT ) && !defined ( UBIT ) for(;;) // Forever Loop #endif { osal_run_system(); } } /********************************************************************* * @fn osal_run_system * * @brief * * This function will make one pass through the OSAL taskEvents table * and call the task_event_processor() function for the first task that * is found with at least one event pending. If there are no pending * events (all tasks), this function puts the processor into Sleep. * * @param void * * @return none */ void osal_run_system( void ) { uint8 idx = 0; osalTimeUpdate(); Hal_ProcessPoll(); do { if (tasksEvents[idx]) // Task is highest priority that is ready. { break; } } while (++idx < tasksCnt); if (idx < tasksCnt) { uint16 events; halIntState_t intState; HAL_ENTER_CRITICAL_SECTION(intState); events = tasksEvents[idx]; tasksEvents[idx] = 0; // Clear the Events for this task. HAL_EXIT_CRITICAL_SECTION(intState); activeTaskID = idx; events = (tasksArr[idx])( idx, events ); activeTaskID = TASK_NO_TASK; HAL_ENTER_CRITICAL_SECTION(intState); tasksEvents[idx] |= events; // Add back unprocessed events to the current task. HAL_EXIT_CRITICAL_SECTION(intState); } #if defined( POWER_SAVING ) else // Complete pass through all task events with no activity? { osal_pwrmgr_powerconserve(); // Put the processor/system into sleep } #endif /* Yield in case cooperative scheduling is being used. */ #if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0) { osal_task_yield(); } #endif }
進入 tasksEvents[idx]陣列定義,如圖 3.4H,發現恰好在剛剛 osalInitTasks( void )函式上面。而且 taskID 一一對應。這就是初始化與呼叫的關係。 taskID 把任務聯絡起來了。