zigbee系統篇——ZStack系統抽象層詳解
瞭解硬體和更多資料可點選:點選瞭解
新建一個物聯網行業交流學習QQ群,感興趣可加:928840648
=====CUT=====
*** 本章學習目的 ***
1)理解Z-Stack3.0系統抽象層(OSAL)。
2)掌握OSAL的使用。
4.1 OSAL的執行過程
OSAL也就是系統抽象層,其實並不是真正意義上的作業系統,不過實現了協議棧(ZStack)執行所必需的任務排程功能、記憶體管理、中斷管理等基本功能。為了方便學習,我們將工程進行的裁剪,去除文件和附件,只留下協議棧的元件和工程檔案:
本節課我們使用的和上節課一樣的工程進行講解(不同工程只是在應用層不一樣,工程的結構是一樣的):
開啟工程:
* 我們先進入ZMain.c這個檔案,並且找到main入口函式:
入口函式main的工作有兩個:初始化、系統輪詢(進行任務排程)。
- intmain(void)
- {
- //Turnoffinterrupts
- osal_int_disable(INTS_ALL); // 關閉所有中斷
- //InitializationforboardrelatedstuffsuchasLEDs
- HAL_BOARD_INIT(); // 初始化板載資源,比如PA、時鐘源等
- //Makesuresupplyvoltageishighenoughtorun
- zmain_vdd_check();// 檢測供電電壓是否可以支撐晶片正常執行
- //InitializeboardI/O
- InitBoard(OB_COLD);// 初始化板載I/O,比如按鍵配置為輸入
- //InitialzeHALdrivers
- HalDriverInit();// 初始化硬體適配層,比如串列埠、顯示器等
- //InitializeNVSystem
- osal_nv_init(NULL);// 初始化NV(晶片內部FLASH的一塊空間)
- //InitializetheMAC
- ZMacInit();// 初始化MAC層(資料鏈路層)
- //Determinetheextendedaddress
- zmain_ext_addr();// 確定晶片的實體地址
- #ifdefinedZCL_KEY_ESTABLISH
- //InitializetheCerticomcertificateinformation.
- zmain_cert_init();// 初始化認證資訊
- #endif
- //InitializebasicNVitems
- zgInit();// 初始化儲存在NV中的協議棧全域性資訊,如網路啟動方式等
- #ifndefNONWK
- //SincetheAFisn'tatask,callit'sinitializationroutine
- afInit();// 初始化AF(射頻)
- #endif
- //Initializetheoperatingsystem
- osal_init_system();// 初始化OSAL(作業系統抽象層)
- //Allowinterrupts
- osal_int_enable(INTS_ALL);// 使能所有中斷
- //Finalboardinitialization
- InitBoard(OB_READY);// 初始化板載IO資源,比如按鍵
- //Displayinformationaboutthisdevice
- zmain_dev_info();// 在顯示器上顯示裝置實體地址
- /*DisplaythedeviceinfoontheLCD*/
- #ifdefLCD_SUPPORTED
- zmain_lcd_init();// 在顯示器上顯示裝置資訊,比如製造商等
- #endif
- #ifdefWDT_IN_PM1
- /*IfWDTisused,thisisagoodplacetoenableit.*/
- WatchDogEnable(WDTIMX);// 啟動看門狗功能
- #endif
- /* 進入系統輪詢 */
- osal_start_system();//NoReturnfromhere
- return0;//Shouldn'tgethere.
- }//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():
- uint8osal_init_system(void)
- {
- #if!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS
- //InitializetheMemoryAllocationSystem
- osal_mem_init();//初始化記憶體堆疊,用於動態記憶體申請、釋放
- #endif/*!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS*/
- //Initializethemessagequeue
- osal_qHead=NULL;//初始化訊息佇列
- //Initializethetimers
- osalTimerInit();//初始化系統時鐘,系統輪詢週期的基礎
- //InitializethePowerManagementSystem
- osal_pwrmgr_init();//初始化電源管理,主要用於低功耗
- #ifdefUSE_ICALL
- /*Preparememoryspaceforserviceenrollment*/
- osal_prepare_svc_enroll();
- #endif/*USE_ICALL*/
- //Initializethesystemtasks.
- osalInitTasks();//初始化任務池
- #if!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS
- //Setupefficientsearchforthefirstfreeblockofheap.
- osal_mem_kick();
- #endif/*!definedUSE_ICALL&&!definedOSAL_PORT2TIRTOS*/
- #ifdefUSE_ICALL
- //InitializevariablesusedtotracktimingandprovideOSALtimerservice
- osal_last_timestamp=(uint_least32_t)ICall_getTicks();
- osal_tickperiod=(uint_least32_t)ICall_getTickPeriod();
- osal_max_msecs=(uint_least32_t)ICall_getMaxMSecs();
- /*Reduceceilingconsideringpotentiallatency*/
- osal_max_msecs-=2;
- #endif/*USE_ICALL*/
- return(SUCCESS);
- }
重要的函式: osalInitTasks();// 初始化任務池
我們知道ZStack框架上是分層的,比如HAL(硬體適配層)、OSAL(系統抽象層)、MAC(資料鏈路層)、MT(監視器)、APP(應用層)等等;這一點很重要,因為ZStack框架設計上每一層都是任務池中的任務,而且任務是具有優先順序的;系統在輪詢進行任務排程時,會掃描每一層,如果發現有任務並且任務已經到期就會被處理!
* osalInitTasks():
- voidosalInitTasks(void)
- {
- uint8taskID=0;
- /*申請任務池記憶體空間並進行初始化*/
- tasksEvents=(uint16*)osal_mem_alloc(sizeof(uint16)*tasksCnt);
- osal_memset(tasksEvents,0,(sizeof(uint16)*tasksCnt));
- /*下面是初始化各個層的任務,有些名稱不認識是因為和
- ZigBee協議有關,我們暫且忽略,後面篇章會講解到!
- */
- macTaskInit(taskID++);//初始化MAC(資料鏈路層)任務
- nwk_init(taskID++);//初始化網路層任務
- #if!defined(DISABLE_GREENPOWER_BASIC_PROXY)&&(ZG_BUILD_RTR_TYPE)
- gp_Init(taskID++);//初始化GP層任務
- #endif
- Hal_Init(taskID++);//初始化HAL(硬體適配層)任務
- #ifdefined(MT_TASK)
- MT_TaskInit(taskID++);//初始化MT(監視器)任務
- #endif
- APS_Init(taskID++);//初始化APS層任務
- #ifdefined(ZIGBEE_FRAGMENTATION)
- APSF_Init(taskID++);//初始化APSF層任務
- #endif
- ZDApp_Init(taskID++);//初始化ZDApp層任務
- #ifdefined(ZIGBEE_FREQ_AGILITY)||defined(ZIGBEE_PANID_CONFLICT)
- ZDNwkMgr_Init(taskID++);//初始化ZDNwkMgr層任務
- #endif
- //AddedtoincludeTouchLinkfunctionality
- #ifdefined(INTER_PAN)
- StubAPS_Init(taskID++);//初始化StubAPS層任務
- #endif
- //AddedtoincludeTouchLinkinitiatorfunctionality
- #ifdefined(BDB_TL_INITIATOR)
- touchLinkInitiator_Init(taskID++);//初始化touchLink任務
- #endif
- //AddedtoincludeTouchLinktargetfunctionality
- #ifdefined(BDB_TL_TARGET)
- touchLinkTarget_Init(taskID++);//初始化touchLink任務
- #endif
- zcl_Init(taskID++);//初始化ZCL任務
- bdb_Init(taskID++);//初始化BDB任務
- zclSampleSw_Init(taskID++);//初始化應用層任務
- #if(definedOTA_CLIENT)&&(OTA_CLIENT==TRUE)
- zclOTA_Init(taskID);//初始化OTA層任務
- #endif
- }
taskID其實就是任務的識別符號,系統輪詢也是根據taskID來的,taskID越小表示的是該任務的優先順序越高(因為系統更早的輪詢到該任務);實際上使用者的開發是集中在應用層上面的,比如這裡的zclSampleSw_Init函式初始化的就是應用層的任務,而且是針對智慧插座的應用,大家會發現除了不同應用的應用層初始化函式名稱不一樣之外,其他都是一樣的。接下來我們將進入到系統輪詢函式:
* osal_start_system() -> osal_run_system():
- voidosal_run_system(void)
- {
- uint8idx=0;
- /*更新時間,並整理出到期的任務,系統的時鐘週期是:320US*/
- osalTimeUpdate();
- Hal_ProcessPoll();//硬體適配層中斷查詢
- do{
- if(tasksEvents[idx])//檢視是否有任務需要處理
- {
- break;
- }
- }while(++idx<tasksCnt);//輪詢整個任務池
- if(idx<tasksCnt)//找到需要處理的任務
- {
- uint16events;
- halIntState_tintState;
- HAL_ENTER_CRITICAL_SECTION(intState);//關中斷
- events=tasksEvents[idx];//任務需要處理的所有事件
- tasksEvents[idx]=0;//CleartheEventsforthistask.
- HAL_EXIT_CRITICAL_SECTION(intState);//恢復中斷
- activeTaskID=idx;
- events=(tasksArr[idx])(idx,events);//處理任務中的事件
- activeTaskID=TASK_NO_TASK;
- HAL_ENTER_CRITICAL_SECTION(intState);//關中斷
- tasksEvents[idx]|=events;//儲存還沒被處理的事件到任務中
- HAL_EXIT_CRITICAL_SECTION(intState);//恢復中斷
- }
- #ifdefined(POWER_SAVING)&&!defined(USE_ICALL)
- else//Completepassthroughalltaskeventswithnoactivity?{
- osal_pwrmgr_powerconserve();//沒有任務需要處理則進入低功耗
- }
- #endif
- /*Yieldincasecooperativeschedulingisbeingused.*/
- #ifdefined(configUSE_PREEMPTION)&&(configUSE_PREEMPTION==0){
- osal_task_yield();
- }
- #endif
注:源程式不止上面這麼多的,為了簡化程式方便理解,我們刪除了那些沒有使用到的程式碼!!!“處理任務中的事件”會在下一節課講解到!
系統輪詢是輪詢每一層(任務),但是任務是由多個事件組成的,也就是說輪詢到需要處理的任務時,是需要處理該任務相應的已經到時間的事件,而有些事件還沒到時間所以不會被處理,等待下次時間到了再處理!
這裡需要重新提一下:ZStack每一層的任務實際上是可以包含多個事件的,比如我們應用層任務可以同時包含“開燈1”和“關燈2”、“獲取溫溼度”等事件,只不過不一定在同一時間被執行,比如“開燈1”執行後再過3秒才會執行“關燈2”這個動作!
4.2 ZStack應用層
我們“系統篇”的程式大部分是在應用層進行講解的,應用層:
我們首先進入到OSAL_SampleSw.c這個檔案中,這個檔案中只有一個函式:osalInitTasks(),這個函式在上一節課已經講過,功能是初始化任務池;這個檔案和OSAL關係密切,裡面有一個數組:
- constpTaskEventHandlerFntasksArr[]={
- macEventLoop,
- nwk_event_loop,
- #if!defined(DISABLE_GREENPOWER_BASIC_PROXY)&&(ZG_BUILD_RTR_TYPE)
- gp_event_loop,
- #endif
- Hal_ProcessEvent,
- #ifdefined(MT_TASK)
- MT_ProcessEvent,
- #endif
- APS_event_loop,
- #ifdefined(ZIGBEE_FRAGMENTATION)
- APSF_ProcessEvent,
- #endif
- ZDApp_event_loop,
- #ifdefined(ZIGBEE_FREQ_AGILITY)||defined(ZIGBEE_PANID_CONFLICT)
- ZDNwkMgr_event_loop,
- #endif
- //AddedtoincludeTouchLinkfunctionality
- #ifdefined(INTER_PAN)
- StubAPS_ProcessEvent,
- #endif
- //AddedtoincludeTouchLinkinitiatorfunctionality
- #ifdefined(BDB_TL_INITIATOR)
- touchLinkInitiator_event_loop,
- #endif
- //AddedtoincludeTouchLinktargetfunctionality
- #ifdefined(BDB_TL_TARGET)
- touchLinkTarget_event_loop,
- #endif
- zcl_event_loop,
- bdb_event_loop,
- zclSampleSw_event_loop,
- #if(definedOTA_CLIENT)&&(OTA_CLIENT==TRUE)
- zclOTA_event_loop
- #endif
- };
細心的同學可以發現,這裡面的程式和初始化任務池函式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中):
- uint16zclSampleSw_event_loop(uint8task_id,uint16events)
- {
- afIncomingMSGPacket_t*MSGpkt;
- (void)task_id;//Intentionallyunreferencedparameter
- /*SYS_EVENT_MSG:0x8000表示系統事件,也就是說檢測uint16最高位*/
- if(events&SYS_EVENT_MSG)
- {
- .........//系統事件暫且不講解
- /*消除已經處理的事件然後返回未處理的事件!*/
- return(events^SYS_EVENT_MSG);
- }
- #ifZG_BUILD_ENDDEVICE_TYPE
- if(events&SAMPLEAPP_END_DEVICE_REJOIN_EVT)//使用者事件
- {
- bdb_ZedAttemptRecoverNwk();
- return(events^SAMPLEAPP_END_DEVICE_REJOIN_EVT);
- }
- #endif
- if(events&SAMPLEAPP_LCD_AUTO_UPDATE_EVT)//使用者事件
- {
- UI_UpdateLcd();
- return(events^SAMPLEAPP_LCD_AUTO_UPDATE_EVT);
- }
- if(events&SAMPLEAPP_KEY_AUTO_REPEAT_EVT)//使用者事件
- {
- UI_MainStateMachine(UI_KEY_AUTO_PRESSED);
- return(events^SAMPLEAPP_KEY_AUTO_REPEAT_EVT);
- }
- //TestEvent
- if(events&SAMPLEAPP_TEST_EVT)//使用者事件,自定義!
- {
- printf("HelloWorld!\r\n");
- return(events^SAMPLEAPP_TEST_EVT);
- }
- //Discardunknownevents
- return0;
- }
事件首先進行檢測,也就是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