1. 程式人生 > 實用技巧 >zigbee系統篇——ZStack系統抽象層詳解

zigbee系統篇——ZStack系統抽象層詳解

瞭解硬體和更多資料可點選:點選瞭解

新建一個物聯網行業交流學習QQ群,感興趣可加:928840648

=====CUT=====

*** 本章學習目的 ***

1)理解Z-Stack3.0系統抽象層(OSAL)。

2)掌握OSAL的使用。

4.1 OSAL的執行過程

OSAL也就是系統抽象層,其實並不是真正意義上的作業系統,不過實現了協議棧(ZStack)執行所必需的任務排程功能、記憶體管理、中斷管理等基本功能。為了方便學習,我們將工程進行的裁剪,去除文件和附件,只留下協議棧的元件和工程檔案:

本節課我們使用的和上節課一樣的工程進行講解(不同工程只是在應用層不一樣,工程的結構是一樣的):

開啟工程:

* 我們先進入ZMain.c這個檔案,並且找到main入口函式:

入口函式main的工作有兩個:初始化、系統輪詢(進行任務排程)。

  1. intmain(void)
  2. {
  3. //Turnoffinterrupts
  4. osal_int_disable(INTS_ALL); // 關閉所有中斷
  5. //InitializationforboardrelatedstuffsuchasLEDs
  6. HAL_BOARD_INIT(); // 初始化板載資源,比如PA、時鐘源等
  7. //Makesuresupplyvoltageishighenoughtorun
  8. zmain_vdd_check();// 檢測供電電壓是否可以支撐晶片正常執行
  9. //InitializeboardI/O
  10. InitBoard(OB_COLD);// 初始化板載I/O,比如按鍵配置為輸入
  11. //InitialzeHALdrivers
  12. HalDriverInit();// 初始化硬體適配層,比如串列埠、顯示器等
  13. //InitializeNVSystem
  14. osal_nv_init(NULL);// 初始化NV(晶片內部FLASH的一塊空間)
  15. //InitializetheMAC
  16. ZMacInit();// 初始化MAC層(資料鏈路層)
  17. //Determinetheextendedaddress
  18. zmain_ext_addr();// 確定晶片的實體地址
  19. #ifdefinedZCL_KEY_ESTABLISH
  20. //InitializetheCerticomcertificateinformation.
  21. zmain_cert_init();// 初始化認證資訊
  22. #endif
  23. //InitializebasicNVitems
  24. zgInit();// 初始化儲存在NV中的協議棧全域性資訊,如網路啟動方式等
  25. #ifndefNONWK
  26. //SincetheAFisn'tatask,callit'sinitializationroutine
  27. afInit();// 初始化AF(射頻)
  28. #endif
  29. //Initializetheoperatingsystem
  30. osal_init_system();// 初始化OSAL(作業系統抽象層)
  31. //Allowinterrupts
  32. osal_int_enable(INTS_ALL);// 使能所有中斷
  33. //Finalboardinitialization
  34. InitBoard(OB_READY);// 初始化板載IO資源,比如按鍵
  35. //Displayinformationaboutthisdevice
  36. zmain_dev_info();// 在顯示器上顯示裝置實體地址
  37. /*DisplaythedeviceinfoontheLCD*/
  38. #ifdefLCD_SUPPORTED
  39. zmain_lcd_init();// 在顯示器上顯示裝置資訊,比如製造商等
  40. #endif
  41. #ifdefWDT_IN_PM1
  42. /*IfWDTisused,thisisagoodplacetoenableit.*/
  43. WatchDogEnable(WDTIMX);// 啟動看門狗功能
  44. #endif
  45. /* 進入系統輪詢 */
  46. osal_start_system();//NoReturnfromhere
  47. return0;//Shouldn'tgethere.
  48. }//main()

重點的兩個函式:

//Initializetheoperatingsystem

osal_init_system();// 初始化OSAL(作業系統抽象層)

/* 進入系統輪詢 */

osal_start_system();//NoReturnfromhere

我們在“2. 作業系統的排程原理”中講到了系統輪詢,輪詢的是任務池;這兩個函式其實做的事情原理是一樣的。osal_init_system()初始化了任務池,osal_start_system()基於排程週期在任務池中進行任務排程!用IAR看程式碼可在函式名上單擊右鍵—— go to definition of…,便可以進入函式。

* osal_init_system()

  1. uint8osal_init_system(void)
  2. {
  3. #if!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS
  4. //InitializetheMemoryAllocationSystem
  5. osal_mem_init();//初始化記憶體堆疊,用於動態記憶體申請、釋放
  6. #endif/*!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS*/
  7. //Initializethemessagequeue
  8. osal_qHead=NULL;//初始化訊息佇列
  9. //Initializethetimers
  10. osalTimerInit();//初始化系統時鐘,系統輪詢週期的基礎
  11. //InitializethePowerManagementSystem
  12. osal_pwrmgr_init();//初始化電源管理,主要用於低功耗
  13. #ifdefUSE_ICALL
  14. /*Preparememoryspaceforserviceenrollment*/
  15. osal_prepare_svc_enroll();
  16. #endif/*USE_ICALL*/
  17. //Initializethesystemtasks.
  18. osalInitTasks();//初始化任務池
  19. #if!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS
  20. //Setupefficientsearchforthefirstfreeblockofheap.
  21. osal_mem_kick();
  22. #endif/*!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS*/
  23. #ifdefUSE_ICALL
  24. //InitializevariablesusedtotracktimingandprovideOSALtimerservice
  25. osal_last_timestamp=(uint_least32_t)ICall_getTicks();
  26. osal_tickperiod=(uint_least32_t)ICall_getTickPeriod();
  27. osal_max_msecs=(uint_least32_t)ICall_getMaxMSecs();
  28. /*Reduceceilingconsideringpotentiallatency*/
  29. osal_max_msecs-=2;
  30. #endif/*USE_ICALL*/
  31. return(SUCCESS);
  32. }

重要的函式: osalInitTasks();// 初始化任務池

我們知道ZStack框架上是分層的,比如HAL(硬體適配層)、OSAL(系統抽象層)、MAC(資料鏈路層)、MT(監視器)、APP(應用層)等等;這一點很重要,因為ZStack框架設計上每一層都是任務池中的任務,而且任務是具有優先順序的;系統在輪詢進行任務排程時,會掃描每一層,如果發現有任務並且任務已經到期就會被處理!

* osalInitTasks()

  1. voidosalInitTasks(void)
  2. {
  3. uint8taskID=0;
  4. /*申請任務池記憶體空間並進行初始化*/
  5. tasksEvents=(uint16*)osal_mem_alloc(sizeof(uint16)*tasksCnt);
  6. osal_memset(tasksEvents,0,(sizeof(uint16)*tasksCnt));
  7. /*下面是初始化各個層的任務,有些名稱不認識是因為和
  8. ZigBee協議有關,我們暫且忽略,後面篇章會講解到!
  9. */
  10. macTaskInit(taskID++);//初始化MAC(資料鏈路層)任務
  11. nwk_init(taskID++);//初始化網路層任務
  12. #if!defined(DISABLE_GREENPOWER_BASIC_PROXY)&&(ZG_BUILD_RTR_TYPE)
  13. gp_Init(taskID++);//初始化GP層任務
  14. #endif
  15. Hal_Init(taskID++);//初始化HAL(硬體適配層)任務
  16. #ifdefined(MT_TASK)
  17. MT_TaskInit(taskID++);//初始化MT(監視器)任務
  18. #endif
  19. APS_Init(taskID++);//初始化APS層任務
  20. #ifdefined(ZIGBEE_FRAGMENTATION)
  21. APSF_Init(taskID++);//初始化APSF層任務
  22. #endif
  23. ZDApp_Init(taskID++);//初始化ZDApp層任務
  24. #ifdefined(ZIGBEE_FREQ_AGILITY)||defined(ZIGBEE_PANID_CONFLICT)
  25. ZDNwkMgr_Init(taskID++);//初始化ZDNwkMgr層任務
  26. #endif
  27. //AddedtoincludeTouchLinkfunctionality
  28. #ifdefined(INTER_PAN)
  29. StubAPS_Init(taskID++);//初始化StubAPS層任務
  30. #endif
  31. //AddedtoincludeTouchLinkinitiatorfunctionality
  32. #ifdefined(BDB_TL_INITIATOR)
  33. touchLinkInitiator_Init(taskID++);//初始化touchLink任務
  34. #endif
  35. //AddedtoincludeTouchLinktargetfunctionality
  36. #ifdefined(BDB_TL_TARGET)
  37. touchLinkTarget_Init(taskID++);//初始化touchLink任務
  38. #endif
  39. zcl_Init(taskID++);//初始化ZCL任務
  40. bdb_Init(taskID++);//初始化BDB任務
  41. zclSampleSw_Init(taskID++);//初始化應用層任務
  42. #if(definedOTA_CLIENT)&&(OTA_CLIENT==TRUE)
  43. zclOTA_Init(taskID);//初始化OTA層任務
  44. #endif
  45. }

taskID其實就是任務的識別符號,系統輪詢也是根據taskID來的,taskID越小表示的是該任務的優先順序越高(因為系統更早的輪詢到該任務);實際上使用者的開發是集中在應用層上面的,比如這裡的zclSampleSw_Init函式初始化的就是應用層的任務,而且是針對智慧插座的應用,大家會發現除了不同應用的應用層初始化函式名稱不一樣之外,其他都是一樣的。接下來我們將進入到系統輪詢函式:

* osal_start_system() -> osal_run_system()

  1. voidosal_run_system(void)
  2. {
  3. uint8idx=0;
  4. /*更新時間,並整理出到期的任務,系統的時鐘週期是:320US*/
  5. osalTimeUpdate();
  6. Hal_ProcessPoll();//硬體適配層中斷查詢
  7. do{
  8. if(tasksEvents[idx])//檢視是否有任務需要處理
  9. {
  10. break;
  11. }
  12. }while(++idx<tasksCnt);//輪詢整個任務池
  13. if(idx<tasksCnt)//找到需要處理的任務
  14. {
  15. uint16events;
  16. halIntState_tintState;
  17. HAL_ENTER_CRITICAL_SECTION(intState);//關中斷
  18. events=tasksEvents[idx];//任務需要處理的所有事件
  19. tasksEvents[idx]=0;//CleartheEventsforthistask.
  20. HAL_EXIT_CRITICAL_SECTION(intState);//恢復中斷
  21. activeTaskID=idx;
  22. events=(tasksArr[idx])(idx,events);//處理任務中的事件
  23. activeTaskID=TASK_NO_TASK;
  24. HAL_ENTER_CRITICAL_SECTION(intState);//關中斷
  25. tasksEvents[idx]|=events;//儲存還沒被處理的事件到任務中
  26. HAL_EXIT_CRITICAL_SECTION(intState);//恢復中斷
  27. }
  28. #ifdefined(POWER_SAVING)&&!defined(USE_ICALL)
  29. else//Completepassthroughalltaskeventswithnoactivity?{
  30. osal_pwrmgr_powerconserve();//沒有任務需要處理則進入低功耗
  31. }
  32. #endif
  33. /*Yieldincasecooperativeschedulingisbeingused.*/
  34. #ifdefined(configUSE_PREEMPTION)&&(configUSE_PREEMPTION==0){
  35. osal_task_yield();
  36. }
  37. #endif

注:源程式不止上面這麼多的,為了簡化程式方便理解,我們刪除了那些沒有使用到的程式碼!!!“處理任務中的事件”會在下一節課講解到!

系統輪詢是輪詢每一層(任務),但是任務是由多個事件組成的,也就是說輪詢到需要處理的任務時,是需要處理該任務相應的已經到時間的事件,而有些事件還沒到時間所以不會被處理,等待下次時間到了再處理!

這裡需要重新提一下:ZStack每一層的任務實際上是可以包含多個事件的,比如我們應用層任務可以同時包含“開燈1”和“關燈2”、“獲取溫溼度”等事件,只不過不一定在同一時間被執行,比如“開燈1”執行後再過3秒才會執行“關燈2”這個動作!

4.2 ZStack應用層

我們“系統篇”的程式大部分是在應用層進行講解的,應用層:

我們首先進入到OSAL_SampleSw.c這個檔案中,這個檔案中只有一個函式:osalInitTasks(),這個函式在上一節課已經講過,功能是初始化任務池;這個檔案和OSAL關係密切,裡面有一個數組:

  1. constpTaskEventHandlerFntasksArr[]={
  2. macEventLoop,
  3. nwk_event_loop,
  4. #if!defined(DISABLE_GREENPOWER_BASIC_PROXY)&&(ZG_BUILD_RTR_TYPE)
  5. gp_event_loop,
  6. #endif
  7. Hal_ProcessEvent,
  8. #ifdefined(MT_TASK)
  9. MT_ProcessEvent,
  10. #endif
  11. APS_event_loop,
  12. #ifdefined(ZIGBEE_FRAGMENTATION)
  13. APSF_ProcessEvent,
  14. #endif
  15. ZDApp_event_loop,
  16. #ifdefined(ZIGBEE_FREQ_AGILITY)||defined(ZIGBEE_PANID_CONFLICT)
  17. ZDNwkMgr_event_loop,
  18. #endif
  19. //AddedtoincludeTouchLinkfunctionality
  20. #ifdefined(INTER_PAN)
  21. StubAPS_ProcessEvent,
  22. #endif
  23. //AddedtoincludeTouchLinkinitiatorfunctionality
  24. #ifdefined(BDB_TL_INITIATOR)
  25. touchLinkInitiator_event_loop,
  26. #endif
  27. //AddedtoincludeTouchLinktargetfunctionality
  28. #ifdefined(BDB_TL_TARGET)
  29. touchLinkTarget_event_loop,
  30. #endif
  31. zcl_event_loop,
  32. bdb_event_loop,
  33. zclSampleSw_event_loop,
  34. #if(definedOTA_CLIENT)&&(OTA_CLIENT==TRUE)
  35. zclOTA_event_loop
  36. #endif
  37. };

細心的同學可以發現,這裡面的程式和初始化任務池函式osalInitTasks()的內容是對應的,那麼這個陣列有什麼用呢?陣列中的成員名稱都帶有“event”這個關鍵字!其實這個陣列中存放的是函式,是每一層任務的事件處理函式,我們前面講過,任務包含多個事件,事件的處理函式就在這裡了!!

比如應用層任務的事件處理函式是:zclSampleSw_event_loop

接下來我們進入應用層最主要的檔案中:

這個檔案中我們可以找到兩個函式(240行和303行):

函式zclSampleSw_Init()是應用層(任務)的初始化函式,我們先前講過;而另一個函式zclSampleSw_event_loop()是應用層的事件處理函式,也就是說應用層有事件需要處理時,就會來到這個函式。到這裡我們就將OSAL和應用層的關係理清了!

事件的編碼方式:

我們可以看到應用層的事件處理函式(其它層的處理函式的引數是一樣的),uint16 zclSampleSw_event_loop( uint8 task_id, uint16 events ),其中函式引數 uint16 events說明事件是16位的變數所表示的,那是不是最多有65536個事件呢?答案是:“NO!”;這和事件的編碼方式密切相關!

ZStack的事件編碼方式採用“獨熱碼(one-hot code)”,“獨熱碼”直觀來說就是有多少個狀態就有多少位元位,比如uint16有16個位元位可以代表16種狀態。另外,ZStack事件分為“系統事件”和“使用者事件”兩種,uint16的最高位為1時表示“系統事件”,為0表示“使用者事件”,那麼剩下的15位就是15種事件了;總結一下就是:ZStack每一層最多可以有15個使用者事件!

uint16 events最高位為0才表示使用者事件,所以使用者事件有如下15種:

例:我們定義使用者事件可以這樣定義(在zcl_samplesw.h中進行定義):

#define USER_TEST_EVT 0x0001

下節課我們會講如何設定、執行一個事件!

4.3 ZStack事件的應用

* 定義使用者事件:

我們以應用層為例,在zcl_samplesw.h中定義一個自己的使用者事件:

* 處理使用者事件(在samplesw.c的函式zclSampleSw_event_loop中):

  1. uint16zclSampleSw_event_loop(uint8task_id,uint16events)
  2. {
  3. afIncomingMSGPacket_t*MSGpkt;
  4. (void)task_id;//Intentionallyunreferencedparameter
  5. /*SYS_EVENT_MSG0x8000表示系統事件,也就是說檢測uint16最高位*/
  6. if(events&SYS_EVENT_MSG)
  7. {
  8. .........//系統事件暫且不講解
  9. /*消除已經處理的事件然後返回未處理的事件!*/
  10. return(events^SYS_EVENT_MSG);
  11. }
  12. #ifZG_BUILD_ENDDEVICE_TYPE
  13. if(events&SAMPLEAPP_END_DEVICE_REJOIN_EVT)//使用者事件
  14. {
  15. bdb_ZedAttemptRecoverNwk();
  16. return(events^SAMPLEAPP_END_DEVICE_REJOIN_EVT);
  17. }
  18. #endif
  19. if(events&SAMPLEAPP_LCD_AUTO_UPDATE_EVT)//使用者事件
  20. {
  21. UI_UpdateLcd();
  22. return(events^SAMPLEAPP_LCD_AUTO_UPDATE_EVT);
  23. }
  24. if(events&SAMPLEAPP_KEY_AUTO_REPEAT_EVT)//使用者事件
  25. {
  26. UI_MainStateMachine(UI_KEY_AUTO_PRESSED);
  27. return(events^SAMPLEAPP_KEY_AUTO_REPEAT_EVT);
  28. }
  29. //TestEvent
  30. if(events&SAMPLEAPP_TEST_EVT)//使用者事件,自定義!
  31. {
  32. printf("HelloWorld!\r\n");
  33. return(events^SAMPLEAPP_TEST_EVT);
  34. }
  35. //Discardunknownevents
  36. return0;
  37. }

事件首先進行檢測,也就是events & ....,其實是通過檢測對應的位元位是否存在,如果存在表示有該事件,進行處理,處理完成後必須消除該事件所對應的位元位events ^ ....,然後返回其他未處理的事件。我們程式很簡單,只是通過printf("Hello World!\r\n");打印出資訊!

* 設定使用者事件:

設定使用者事件是有專門的API的,這個API是由OSAL所提供,因此首先我們要理解OSAL有哪些檔案,分別有什麼功能;OSAL除了做系統輪詢進行任務事件排程外,也提供比如記憶體管理、Flash管理、電源管理、系統定時器服務等等這些基本功能。

展開OSAL內容如下:

設定使用者事件的API在OSAL_Timers.h中:

uint8osal_start_timerEx(uint8task_id,uint16event_id,uint32timeout_value);

這個函式有三個引數:

task_id:任務ID,也就是具體那一層(任務)的識別符號。

event_id:事件ID,也就是任務中的哪一個事件。

timeout_value:多少毫秒後才處理這個事件。

如果我們希望3秒後處理我們自定義的事件,可以這樣呼叫API,呼叫的位置在應用層初始化函式(zclSampleSw_Init())的最後位置:

osal_start_timerEx(zclSampleSw_TaskID,SAMPLEAPP_TEST_EVT, 3000);

其中zclSampleSw_TaskID是一個全域性變數,儲存應用層的任務ID,儲存的過程是在應用層初始化函式一開始:

最終設定使用者事件的程式碼如下:

** 除錯模擬

點選”Make”對工程進行編譯:

然後通過模擬器連線開發板和電腦的USB口,然後將程式燒錄到開發板中並進入模擬中,調出“Terminal I/O”視窗,最後“全速執行程式”:

可以看到,3秒後系統輪詢發現應用層有任務需要處理,所以排程相應的任務處理函式(zclSampleSw_event_loop),任務處理函式中找到需要處理的事件進行處理(打印出“Hello World!”)。

本節課的內容雖然有點多,但卻非常重要,是我們後續課程的基礎,希望大家能夠掌握,為後面課程打下良好的基礎!!!

瞭解硬體和更多資料可點選:點選瞭解

新建一個物聯網行業交流學習QQ群,感興趣可加:928840648