車乾的ZigBee學習筆記六、ZigBee協議棧常用函數

一、ZigBee協議棧
1、什麼是ZigBee協議棧?

協議是一系列的通信標準,通信雙方需要共同按照這一標準進行正常的數據發射和接收。

協議棧是協議的具體實現形式,就是協議棧是協議和用戶之間的一個接口,開發人員通過使用協議棧來使用這個協議的,進而實現無線數據收發。

ZigBee 的協議分爲兩部分
IEEE 802.15.4 定義了 PHY(物理層)和 MAC(介質訪問層)技術規範;
ZigBee聯盟定義了 NWK(網絡層)APS(應用程序支持子層)、APL(應用層)技術規範。

ZigBee協議棧就是將各個層定義的協議都集合在一直,以函數的形式實現,並給用戶提供 API(應用層),用戶可以直接調用。

2、ZigBee協議棧怎麼用,用到什麼地步?

(1)協議棧是協議的實現,可以理解爲代碼,函數庫,供上層應用調用,協議較底下的層與
應用是相互獨立的。

(2)商業化的協議棧就是給你寫好了底層的代碼,符合協議標準,提供給你一個功能模塊給你調用。

(3)我們只需要想好應用邏輯,數據的傳輸、存儲,處理;系統設備之間的通信順序。

(4)當我們的應用需要數據通信時,調用組網函數給你組建你想要的網絡;

(5)當你想從一個設備發數據到另一個設備時,調用無線數據發送函數;接收端就調用接收函數;

(6)當設備閒置時,就調用睡眠函數;工作的時候就調用喚醒函數。

所以當我們做具體應用時,不需要關心協議棧是怎麼寫的,裏面的每條代碼是什麼意思。除非你要做協議研究。每個廠商的協議棧有區別,也就是函數名稱和參數可能有區別,這個要看具體的例子、說明文檔。

3、無線數據通信一般步驟
1、組網:調用協議棧的組網函數、加入網絡函數,實現網絡的建立與節點的加入。
2、發送:發送節點調用協議棧的無線數據發送函數,實現無線數據發送。
3、接收:接收節點調用協議棧的無線數據接收函數,實現無線數據接收。
例:無線發送函數
1、afStatus_t AF_DataRequest( afAddrType_t *dstAddr,
2、 endPointDesc_t *srcEP,
3. uint16 cID,
4. uint16 len, //發送數據的長度
5. uint8 *buf, //只想存放發送數據的緩衝區的指針
6. uint8 *transID,
7. uint8 options,
8. uint8 radius )
我們理解了函數中的8個參數的含義,就可以使用這個函數進行無線數據通信

我一直不太理解,也沒有能力理解調用協議後硬件的工作。但是隻要我們調用函數後,ZigBee協議棧會將所需要的工作做好,我們只需要調用相應的API函數(預定義函數)即可。

二、無線控制LED
ZigBee協議棧例程
1、main函數

int mian(void)
{
	osal_int_disable(INTS_ALL);  //關閉所有中斷
	HAL_BOARD_INIT();   //初始化系統時鐘
	 zmain_vdd_check(); //檢查芯片電壓是否正常
	InitBoard( OB_COLD ); //初始化 I/O ,LED 、Timer 等
	HalDriverInit(); //初始化芯片各硬件模塊
	osal_nv_init( NULL ); //初始化 Flash 存儲器
	ZMacInit(); //初始化 MAC 層
	zmain_ext_addr(); //確定 IEEE 64 位地址
	zgInit(); //初始化非易失變量
	
	#ifndef NONWK
		// 因爲AF不是任務,所以調用它的初始化例程
	afInit();
	#endif
	
	osal_init_system(); //初始化操作系統
	osal_int_enable( INTS_ALL ); //使能全部中斷
	InitBoard( OB_READY ); //最終板載初始化
	zmain_dev_info(); //顯示設備信息
	
	#ifdef LCD_SUPPORTED
	zmain_lcd_init(); //初始化 LCD
	#endif
	
	#ifdef WDT_IN_PM1
		 如果使用WDT,這是啓用它的好地方。 
	WatchDogEnable( WDTIMX );
	#endif
	
	osal_start_system();// No Return from here 執行操作系統,進去後不會返回
	 return 0; //無返回值
}

main函數先執行初始化工作,包括硬件、網絡層、任務等初始化。然後進行osal_start_system();操作系統,進去後無法返回
在這裏有兩個重要的函數
初始化操作系統 osal_init_system();
運行操作系統 osal_start_system();
我們先看osal_init_system();系統初始化函數,
osal_init_system();包含6個初始化函數,在這裏我們只看osalInitTasks();任務初始化函數

2、初始化操作系統 osal_init_system();——osalInitTasks();任務初始化函數
函數代碼如下:

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_TASK 則調用 MT_TaskInit()
	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()是我們應用協議棧的必要函數,用戶通常在這裏初始化自己的東西。

3、 osal_start_system();運行操作系統

void osal_start_system( void )
{
	#if !defined ( ZBIT ) && !defined ( UBIT )
	for(;;) // 死循環
	#endif
	{
		uint8 idx = 0;
		osalTimeUpdate(); //掃描哪個事件被觸發了,然後置相應的標誌位 
		Hal_ProcessPoll(); //輪詢 TIMER 與 UART
		do {
			if (tasksEvents[idx]) // T務是準備好的最高優先級
			{
				break; //得到待處理的最高優先級任務索引號 idx
			}
		} while (++idx < tasksCnt);
		if (idx < tasksCnt)
		{
			uint16 events;
			halIntState_t intState;
			HAL_ENTER_CRITICAL_SECTION(intState);// 進入臨界區,保護
			events = tasksEvents[idx]; //提取需要處理的任務中的事件
			tasksEvents[idx] = 0; //清除本次任務的事件
			HAL_EXIT_CRITICAL_SECTION(intState); // 退出臨界區
			events = (tasksArr[idx])( idx, events );//通過指針調用任務處理函數,關鍵
			HAL_ENTER_CRITICAL_SECTION(intState); //進入臨界區
			tasksEvents[idx] |= events; // 保存未處理的事件 將未處理的事件返回到當前任務。
			HAL_EXIT_CRITICAL_SECTION(intState); // 退出臨界區
		}
		#if defined( POWER_SAVING )
		else // 完全通過所有沒有活動的任務事件?
		{
			osal_pwrmgr_powerconserve(); // 使處理器/系統進入休眠狀態
		}
		#endif
	}
}

events = tasksEvents[idx];進tasksEvents[idx]數組定義,發現恰好是osalInitTasks()函數裏面分配空間初始化過的 tasksEvents。而且 taskID 一一對應。這就是初始化與調用的關係。taskID 把任務聯繫起來了。

4、SampleApp_Init()用戶應用任務初始化函數

void SampleApp_Init( uint8 task_id )
{
	SampleApp_TaskID = task_id;//osal 分配的任務 ID 隨着用戶添加任務的增多而改變 
 	SampleApp_NwkState = DEV_INIT; /*設備狀態設定爲 ZDO 層中定義的初始化狀態 
初始化應用設備的網絡類型,設備類型的改變都要產生一個事件—ZDO_STATE_CHANGE,從字
面理解爲 ZDO 狀態發生了改變。所以在設備初始化的時候一定要把它初始化爲什麼狀態都沒
有。那麼它就要去檢測整個環境,看是否能重新建立或者加入存在的網絡。但是有一種情況
例外,就是當 NV_RESTORE 被設置的候(NV_RESTORE 是把信息保存在非易失存儲器中),那麼
當設備斷電或者某種意外重啓時,由於網絡狀態存儲在非易失存儲器中,那麼此時就只需要
恢復其網絡狀態,而不需要重新建立或者加入網絡了.**這裏需要設置 NV_RESTORE 宏定義**。 */
 	SampleApp_TransID = 0; //消息發送 ID(多消息時有順序之分)
#if defined ( BUILD_ALL_DEVICES )
	if ( readCoordinatorJumper() )
 		zgDeviceLogicalType = ZG_DEVICETYPE_COORDINATOR;
 	else
 		zgDeviceLogicalType = ZG_DEVICETYPE_ROUTER;
#endif // BUILD_ALL_DEVICES
/*該段的意思是,如果設置了 HOLD_AUTO_START 宏定義,將會在啓動芯片的時候會暫停啓動
流程,只有外部觸發以後纔會啓動芯片。其實就是需要一個按鈕觸發它的啓動流程。 */
#if defined ( HOLD_AUTO_START )
 	ZDOInitDevice(0);
#endif 
 //設置發送數據的方式和目的地址尋址模式 
 //發送模式:廣播發送 
	SampleApp_Periodic_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast; //廣播
 	SampleApp_Periodic_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; //指定端點號
  	SampleApp_Periodic_DstAddr.addr.shortAddr = 0xFFFF;//指定目的網絡地址爲廣播地址
 //發送模式:組播發送 Setup for the flash command's destination address - Group 1 
 	SampleApp_Flash_DstAddr.addrMode = (afAddrMode_t)afAddrGroup; //組尋址
	SampleApp_Flash_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; //指定端點號 
 	SampleApp_Flash_DstAddr.addr.shortAddr = SAMPLEAPP_FLASH_GROUP; //組號 0x0001
 //定義本設備用來通信的 APS 層端點描述符 
 	SampleApp_epDesc.endPoint = SAMPLEAPP_ENDPOINT;
 	SampleApp_epDesc.task_id = &SampleApp_TaskID; //SampleApp 描述符的任務 ID
 	SampleApp_epDesc.simpleDesc  = (SimpleDescriptionFormat_t*)&SampleApp_SimpleDesc;
 	//SampleApp 簡單描述符
 	SampleApp_epDesc.latencyReq = noLatencyReqs; //延時策略
/*向 AF 層登記描述符, 登記 endpoint description 到 AF,要對該應用進行初始化並在 AF
進行登記,告訴應用層有這麼一個 EP 已經開通可以使用,那麼下層要是有關於該應用的信
息或者應用要對下層做哪些操作,就自動得到下層的配合 */
	afRegister( &SampleApp_epDesc );
 // 登記所有的按鍵事件 
 	RegisterForKeys( SampleApp_TaskID );
 // 默認情況下,所有設備都在第一組中啓動
 	SampleApp_Group.ID = 0x0001; //組號
 	osal_memcpy( SampleApp_Group.name, "Group 1", 7 ); //設定組名
 	aps_AddGroup( SAMPLEAPP_ENDPOINT, &SampleApp_Group ); //把該組登記添加到 APS 中
#if defined ( LCD_SUPPORTED )
 	HalLcdWriteString( "SampleApp", HAL_LCD_LINE_1 ); //如果支持 LCD,顯示提示信息
#endif
}

5、SampleApp_ProcessEvent() 用戶應用任務的事件處理函數

uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events )
{
	afIncomingMSGPacket_t *MSGpkt;
	(void)task_id;  // 有意未引用的參數
 	if ( events & SYS_EVENT_MSG ) //接收系統消息再進行判斷 
 	{
	//接收屬於本應用任務 SampleApp 的消息,以 SampleApp_TaskID 標記
		MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
	 	while ( MSGpkt )
	 	{
 			switch ( MSGpkt->hdr.event )
 			{
		 	// 當按下密鑰時收到
 				case KEY_CHANGE:	//按鍵事件
 					SampleApp_HandleKeys( ((keyChange_t *)MSGpkt)->state,((keyChange_t*)MSGpkt)->keys );
 					break;  // 在接收到此端點的消息(OTA)時收到
				case AF_INCOMING_MSG_CMD: //接收數據事件,調用函數 AF_DataRequest()接收數據
 					SampleApp_MessageMSGCB( MSGpkt ); //調用回調函數對收到的數據進行處理 
 					break;// 每當設備在網絡中更改狀態時接收。
 				case ZDO_STATE_CHANGE: /*只要網絡狀態發生改變,就通過 ZDO_STATE_CHANGE 事
件通知所有的任務。同時完成對協調器,路由器,終端的設置 */
 					SampleApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
 					//if ( (SampleApp_NwkState == DEV_ZB_COORD) /*實驗中協調器只接收數據所以取消發送事件*/
 					if ( (SampleApp_NwkState == DEV_ROUTER) || (SampleApp_NwkState == DEV_END_DEVICE) )
 					{
 					//這個定時器只是爲發送週期信息開啓的,設備啓動初始化後從這裏開始觸發第一個週期信息的發送,然後周而復始下去。
						osal_start_timerEx( SampleApp_TaskID,
 						SAMPLEAPP_SEND_PERIODIC_MSG_EVT,
 						SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT );
 					}
 					else
 					{
 						// 設備不再存在於網絡中。
 					}
 					break;
 				default:
 					break;
 			}
 		// 釋放內存 //事件處理完了,釋放消息佔用的內存
 			osal_msg_deallocate( (uint8 *)MSGpkt );
 		//指針指向下一個放在緩衝區的待處理的事件,返回 while ( MSGpkt )重新處理事件,直到緩衝區沒有等待處理事件爲止
 			MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
 		}
 		// return unprocessed events //返回未處理的事件
 		return (events ^ SYS_EVENT_MSG);
	}
	 // 發送消息-此事件由計時器生成。
 	// (setup in SampleApp_Init()).
 	if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT )
 	{
	 //處理週期性事件,利用 SampleApp_SendPeriodicMessage()處理完當前的週期性事件,然後啓動定時器開啓下一個週期性事情,這樣一種循環下去,也即是上面說的週期性事件了,可以做爲傳感器定時採集、上傳任務
 		SampleApp_SendPeriodicMessage();
 		// 在正常時間內再次發送消息的設置(+一點抖動)
		osal_start_timerEx(SampleApp_TaskID,SAMPLEAPP_SEND_PERIODIC_MSG_EVT,(SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT + (osal_rand() & 0x00FF)) );
 		// return unprocessed events 返回未處理的事件
 		return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT);
 	}
 	// 丟棄未知事件
 	return 0;
}

6、分析接收數據函數SampleApp_MessageMSGCB

//接收數據,參數爲接收到的數據
void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
	uint16 flashTime;
 	byte buf[3]; 
 	switch ( pkt->clusterId ) //判斷簇 ID
 	{
 		case SAMPLEAPP_PERIODIC_CLUSTERID: //收到廣播數據
 			osal_memset(buf, 0 , 3);
 			osal_memcpy(buf, pkt->cmd.Data, 2); //複製數據到緩衝區中
 			if(buf[0]=='D' && buf[1]=='1') //判斷收到的數據是否爲“D1” 
 			{
 				HalLedBlink(HAL_LED_1, 0, 50, 500); //如果是則 Led1 間隔 500ms 閃爍
#if defined(ZDO_COORDINATOR) //協調器收到"D1"後,返回"D1"給終端,讓終端 Led1 也閃爍
 					SampleApp_SendPeriodicMessage();
#endif
 			}
 			else
 			{
 				HalLedSet(HAL_LED_1, HAL_LED_MODE_ON); 
 			}
 			break;
 		case SAMPLEAPP_FLASH_CLUSTERID: //收到組播數據
 			flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
 			HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
 			break;
 	}
}

7、分析發送週期信息 SampleApp_SendPeriodicMessage()

void SampleApp_SendPeriodicMessage( void )
{
 	byte SendData[3]="D1";
// 調用 AF_DataRequest 將數據無線廣播出去
 	if( AF_DataRequest( &SampleApp_Periodic_DstAddr, &SampleApp_epDesc,
 						SAMPLEAPP_PERIODIC_CLUSTERID,
 						2,
 						SendData,
 						&SampleApp_TransID,
 						AF_DISCV_ROUTE,
 						AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
 	{
 	}
 	else
 	{
 		HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);
 // 請求發送時發生錯誤
 	}
}

8、AF_DataRequest 發送函數

AF_DataRequest( &SampleApp_Periodic_DstAddr, //發送目的地址+端點地址和傳送模式
&SampleApp_epDesc, //源(答覆或確認)終端的描述(比如操作系統中任務 ID 等)源 EP
SAMPLEAPP_PERIODIC_CLUSTERID, //被 Profile 指定的有效的集羣號
2, // 發送數據長度
SendData,// 發送數據緩衝區
&SampleApp_TransID, // 任務 ID 號
AF_DISCV_ROUTE, // 有效位掩碼的發送選項
AF_DEFAULT_RADIUS ) //傳送跳數,通常設置爲 AF_DEFAULT_RADIUS
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章