一、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