花了好久寫的...感覺還不錯的呢...如果看,請細看...Mua~
Z-Stack協議棧基礎和數據傳輸實驗
一、實驗目的
終端節點將數據無線發送到協調器,協調器通過串口將數據發送到PC端,並在屏幕上顯示出來。串口優化把有線串口傳輸改為無線藍牙傳輸。
二、實驗平台
硬件:2個zigbee節點,1個編譯器,1根方口轉USB數據線,一個藍牙模塊
軟件:實驗基於SampleApp工程進行。
三、實驗步驟
- 串口初始化代碼
- 發送部分代碼
- 接收部分代碼
四、協議棧基礎
做實驗之前先了解一點關於協議棧的基礎知識吧~
什么是協議棧?我們知道使用Zigbee一般都要進行組網、傳輸數據。可想而知其中的代碼數量是非常龐大的,如果我們每次使用zigbee都需要自己寫所以代碼的話,會非常麻煩。因此就有了協議棧。可以說它是一個小型的操作系統,把很多通信、組網之類的代碼都封裝起來了。我們要做的只是通過調用函數來實現我們的目的。
來看一下協議棧的工作流程圖(圖1)。然后我會對照流程圖對協議棧進行簡單的分析。

圖1
我們就從流程圖的“開始”開始分析吧~
打開工程文件SampleApp,main函數是程序執行的開始,我們要先找到它。Main函數在ZMAin文件夾的ZMain.c下,打開它,找到main函數。
1 int main( void ) 2 3 { 4 5 // Turn off interrupts 6 7 //關閉所有中斷 8 9 osal_int_disable( INTS_ALL ); 10 11 12 13 // Initialization for board related stuff such as LEDs 14 15 //初始化系統時鍾 16 17 HAL_BOARD_INIT(); 18 19 20 21 // Make sure supply voltage is high enough to run 22 23 //檢測芯片電壓是否正常 24 25 zmain_vdd_check(); 26 27 28 29 // Initialize board I/O 30 31 //初始化外設 32 33 InitBoard( OB_COLD ); 34 35 36 37 // Initialze HAL drivers 38 39 //初始化芯片各硬件模塊 40 41 HalDriverInit(); 42 43 44 45 // Initialize NV System 46 47 //初始化flash存儲器 48 49 osal_nv_init( NULL ); 50 51 52 53 // Initialize the MAC 54 55 //初始化MAC層 56 57 ZMacInit(); 58 59 60 61 // Determine the extended address 62 63 //確定IEEE 64位地址 64 65 zmain_ext_addr(); 66 67 68 69 #if defined ZCL_KEY_ESTABLISH 70 71 //Initialize the Certicom certificate information. 72 73 zmain_cert_init(); 74 75 #endif 76 77 78 79 // Initialize basic NV items 80 81 //初始化非易失變量 82 83 zgInit(); 84 85 86 87 #ifndef NONWK 88 89 // Since the AF isn't a task, call it's initialization routine 90 91 afInit(); 92 93 #endif 94 95 96 97 // Initialize the operating system 98 99 //初始化操作系統***********************************初始化重點 100 101 osal_init_system(); 102 103 104 105 // Allow interrupts 106 107 //允許中斷使能 108 109 osal_int_enable( INTS_ALL ); 110 111 112 113 // Final board initialization 114 115 //初始化按鍵 116 117 InitBoard( OB_READY ); 118 119 120 121 // Display information about this device 122 123 //顯示設備信息 124 125 zmain_dev_info(); 126 127 128 129 /* Display the device info on the LCD */ 130 131 #ifdef LCD_SUPPORTED 132 133 zmain_lcd_init(); 134 135 #endif 136 137 138 139 #ifdef WDT_IN_PM1 140 141 /* If WDT is used, this is a good place to enable it. */ 142 143 WatchDogEnable( WDTIMX ); 144 145 #endif 146 147 148 149 // No Return from here 150 151 // 執行操作系統,進去后不會返回************************運行重點 152 153 osal_start_system(); 154 155 156 157 return 0; // Shouldn't get here. 158 159 }
瀏覽一下main函數可以看到一開始都是各種初始化函數,即對應流程圖中的“各種初始化函數”。初始化中我們需要注意的是“osal_init_system();”初始化操作系統函數。等一下會對它進行說明。繼續看下去,“osal_start_system();”這是執行操作系統函數,對應流程中的“運行操作系統”。注意這個函數進去之后是不會再返回的。總結main函數就是初始化和執行操作系統兩個部分。
我們再來分析一下“osal_init_system();”這個函數,它的功能是初始化操作系統。我們go to definition看一下這個函數的代碼。
1 uint8 osal_init_system( void ) 2 3 { 4 5 // Initialize the Memory Allocation System 6 7 // 初始化內存分配系統 8 9 osal_mem_init(); 10 11 12 13 // Initialize the message queue 14 15 // 初始化消息隊列 16 17 osal_qHead = NULL; 18 19 20 21 // Initialize the timers 22 23 // 初始化定時器 24 25 osalTimerInit(); 26 27 28 29 // Initialize the Power Management System 30 31 // 初始化電源管理系統 32 33 osal_pwrmgr_init(); 34 35 36 37 // Initialize the system tasks. 38 39 // 初始化系統任務**********************************重點 40 41 osalInitTasks(); 42 43 44 45 // Setup efficient search for the first free block of heap. 46 47 osal_mem_kick(); 48 49 50 51 return ( SUCCESS ); 52 53 }
瀏覽這個函數我們可以看到其中依舊是各種初始化函數。重點觀察“osalInitTasks();”這個函數,函數功能是初始化任務系統,繼續go to definition,查看該函數。
1 void osalInitTasks( void ) 2 3 { 4 5 uint8 taskID = 0; 6 7 8 9 //分配緩沖區內存,返回指針 10 11 tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt); 12 13 //設置所分配的內存空間單元值為0 14 15 osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt)); 16 17 18 19 // 任務優先級由高到低依次排列,高優先級對應taskID小 20 21 macTaskInit( taskID++ ); //0 不用考慮 22 23 nwk_init( taskID++ ); //1 | 24 25 Hal_Init( taskID++ ); //2 | 26 27 #if defined( MT_TASK ) 28 29 MT_TaskInit( taskID++ ); 30 31 #endif 32 33 APS_Init( taskID++ ); //3 | 34 35 #if defined ( ZIGBEE_FRAGMENTATION ) 36 37 APSF_Init( taskID++ ); 38 39 #endif 40 41 ZDApp_Init( taskID++ ); //4 需要考慮 42 43 #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) 44 45 ZDNwkMgr_Init( taskID++ ); 46 47 #endif 48 49 SampleApp_Init( taskID ); //5 需要考慮*************************重點 50 51 //通常在這里初始化自己的東西 52 53 }
通過注釋我們可以知道這個函數也是拿來初始化的,可以里面的代碼有點難以理解......這里我們需要先知道一點,后面會提到,這里先說明下。額,因為這個是我自己的理解,所以部分描述起來可能不是很專業,能懂這個意思就好了,以后專業起來了再回來修改......協議棧采用任務機制,然后使用輪詢的方式處理任務。就是說在空閑的時候它從優先級高的任務開始,一個個檢查是否有任務要處理,有則處理這個任務,沒有則繼續循環檢測。
好嘞~就是這樣!那么再來看這個函數,它的作用就是按“任務”的優先級給它們發一個ID號,發的同時呢又對這個任務進行初始化。需要注意的是任務優先級越高,它的ID號越小!然后上面那些我們全都不用考慮,需要考慮的是最后兩個函數(原來我們能操作的優先級最低呀......)。嗯...感覺go to definition好久了...就不繼續看下去啦,之后再詳細解讀這兩個函數吧~
這樣子初始化的函數算是解釋完了,我們回到main函數,繼續看下一個函數“osal_start_system();”執行操作系統函數!來來來,繼續go to definition找到它本尊。
1 void osal_start_system( void ) 2 3 { 4 5 #if !defined ( ZBIT ) && !defined ( UBIT ) 6 7 for(;;) // Forever Loop 8 9 #endif 10 11 { 12 13 osal_run_system(); 14 15 } 16 17 }
嗯哼,找到“osal_run_system();”我們繼續......
1 void osal_run_system( void ) 2 3 { 4 5 uint8 idx = 0; 6 7 8 9 osalTimeUpdate(); //掃描哪個事件被觸發了,置相應標志位 10 11 Hal_ProcessPoll(); 12 13 14 15 //從0(最高優先級)開始檢索是否有任務需要處理 16 17 //有則立即跳出循環,沒有等到檢索完再跳出循環 18 19 do { 20 21 if (tasksEvents[idx]) // Task is highest priority that is ready. 22 23 { 24 25 break; 26 27 } 28 29 } while (++idx < tasksCnt); 30 31 32 33 //分析索引號,對任務進行處理,沒有任務就跳過 34 35 if (idx < tasksCnt) 36 37 { 38 39 uint16 events; 40 41 halIntState_t intState; 42 43 44 45 HAL_ENTER_CRITICAL_SECTION(intState); //進入臨界區,保護 46 47 events = tasksEvents[idx]; //提取任務 48 49 tasksEvents[idx] = 0; // Clear the Events for this task.清除任務 50 51 HAL_EXIT_CRITICAL_SECTION(intState); //退出臨界區 52 53 54 55 activeTaskID = idx; 56 57 events = (tasksArr[idx])( idx, events ); //通過指針調用任務處理函數********重點 58 59 activeTaskID = TASK_NO_TASK; 60 61 62 63 HAL_ENTER_CRITICAL_SECTION(intState); //進入臨界區 64 65 tasksEvents[idx] |= events; // Add back unprocessed events to the current task. 66 67 //保存未處理事件 68 69 HAL_EXIT_CRITICAL_SECTION(intState); //退出臨界區 70 71 } 72 73 #if defined( POWER_SAVING ) 74 75 else // Complete pass through all task events with no activity? 76 77 { 78 79 osal_pwrmgr_powerconserve(); // Put the processor/system into sleep 80 81 } 82 83 #endif 84 85 86 87 /* Yield in case cooperative scheduling is being used. */ 88 89 #if defined (configUSE_PREEMPTION) && (configUSE_PREEMPTION == 0) 90 91 { 92 93 osal_task_yield(); 94 95 } 96 97 #endif 98 99 }
這里就是我之前說的輪詢的地方啦~這里就說下我的理解吧......但是不確定對不對......大致思想應該是對的......
先把工作分成兩部分,一部分是任務請求,有任務請求了就把相應標志位置1。另一部分就是我們看到的這個函數。在函數開頭讀一下任務請求的寄存器(也許不是寄存器,就那個意思),然后從最高優先級依次檢索是不是有任務請求。只要有任務請求,就進入處理任務請求部分(就是“if (idx < tasksCnt)”這個if語句里面的內容),沒有則繼續循環。處理任務請求部分中需要注意兩點:1. 它在把高優先級任務處理完之后會繼續檢測是否還有任務請求,直到把使用任務請求處理完畢。2. 處理完一個任務之后它會清除該任務的標志位。
咳,不知道你們有沒有看懂......然后這里面的重點函數呢就是“events = (tasksArr[idx])( idx, events );”這一句。先看一看tasksArr[]這個數組的定義。
1 const pTaskEventHandlerFn tasksArr[] = { 2 3 macEventLoop, 4 5 nwk_event_loop, 6 7 Hal_ProcessEvent, 8 9 #if defined( MT_TASK ) 10 11 MT_ProcessEvent, 12 13 #endif 14 15 APS_event_loop, 16 17 #if defined ( ZIGBEE_FRAGMENTATION ) 18 19 APSF_ProcessEvent, 20 21 #endif 22 23 ZDApp_event_loop, 24 25 #if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT ) 26 27 ZDNwkMgr_event_loop, 28 29 #endif 30 31 SampleApp_ProcessEvent 32 33 };
有沒有發現它和函數osalInitTasks();在同一個文件里面!有沒有發現它就在osalInitTasks()這個函數的上面!再仔細看一看,有沒有發現它定義成員變量名的順序和下面初始化函數的順序是一樣的!這樣說估計還是雲里霧里的吧...(因為寫到這里我也還是沒有完全懂...)我再說明白點......之前不是說每個任務都有一個ID號嘛,優先級從0開始的,而數組里面第一位的索引號也是0,就是說任務ID號和數組索引號相對應,那么利用任務ID號就可以在數組里面找到相應的任務。還有很神奇的一點,至少我是怎么覺得的...之前看這行代碼感覺非常難以理解......“events = (tasksArr[idx])( idx, events ); ”。剛剛想明白的,原來那個數組的類型是一個函數!也就是說通過任務ID找到相應進行任務處理的函數!這樣你們有沒有明白?不懂留言......有人看嗎?笑......
終於,協議棧的分析工作完成了......會不會覺得很亂?看看下面的流程圖再來回顧整理一下吧~

圖2
五、實驗過程和說明
1. 串口初始化代碼
①在協議棧中,用戶自己添加代碼的地方基本為App這個文件夾。打開其中的文件SampleApp.c,在INCLUDES部分添加代碼
#include "MT_UART.h"

圖3
操作說明:協議棧中關於串口封裝的文件有兩個,一個是HAL->Target->CC2530EB->Drivers->hal_uart.c,另一個是MT->MT_UART.c。這兩個文件有什么區別呢?打開文件研究一下。首先,在MT_UART.c中有include “hal_uart.h”,所以寫頭文件只要寫”MT_UART.h”即可。然后,仔細看代碼,分析hal_uart.c這個文件只要是對不同串口類型的相應操作進行選擇,MT_UART.c這個文件則是對任意串口的操作。就是說MT_UART.c這個文件更底層一點。
②同樣SampleApp.c文件中,找到函數void SampleApp_Init( uint8 task_id ),在其中加入串口初始化代碼
/************串口初始化******************/ MT_UartInit(); //串口初始化 MT_UartRegisterTaskID(task_id); //登記任務號
圖4
操作說明:串口初始化就不說了。登記任務號就是把串口事件通過task_id登記在SampleApp_Init();中。之前我們有提到說SampleApp_Init();函數很重要,就是分配ID號,它還是優先級最低的那個。把這個函數的ID號給串口就是告訴串口我是在這個函數里面初始化的,相應的我的任務優先級是最低的......
③更改串口初始化配置。
在上圖所示的MT_UartInit();處go to definition,進入MT_UartInit()函數(如圖5)。找到其中的MT_UART_DEFAULT_BAUDRATE,go to definition后將波特率設置為115200(如圖6)。
回到圖3位置,找到MT_UART_DEFAULT_OVERFLOW,go to definition將參數設為FALSE(如圖7)。

圖5

圖6

圖7
操作說明:修改波特率就不解釋啦。
#define MT_UART_DEFAULT_OVERFLOW FALSE
這行代碼是打開串口流控的意思。因為我們串口通訊是兩根線的,必須把它關閉。
2. 發送部分代碼
①打開SampleApp.c,找到SampleApp事件處理函數SampleApp_ProcessEvent()。
補充一點,我們可以在SampleApp下添加自己的事件,每個事件有自己的事件號。事件號是16位的,但是每個事件號只允許占16位中的1位,也就是說最多有16個事件。
我們先瀏覽一下代碼,大致功能是分析傳遞進來的事件號,觸發相應事件。感覺這個和任務號處理模式還是挺像的。我們需要關注的是“系統消息事件”被觸發之后。即 “if ( events & SYS_EVENT_MSG )”語句之后的部分。先看第一行:
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( SampleApp_TaskID );
這一行代碼實現功能是獲取系統消息數據。我們可以自己去查看里面的定義。afIncomingMSGPacket是包含整個消息內容的結構體類型。
之后的選擇語句則是根據消息中的信息對數據進行相應處理。我們需要關注如下代碼(圖8)

圖8
弄了半天,原來還在初始化......意思是網絡狀態發送變化時(其實就是打開網絡),就對數據發送進行初始化。看下這三個參數,第一個是任務號,不重復啦。第二個是事件號,這個也說過啦,每個事件只占1位哦!第三個是設置時間,就是規定你多久發一次信息!這里我們預設SAMPLEAPP_SEND_PERIODIC_MSG_TIMEOUT = 5000,這個值可以自行修改,數值單位是毫秒,就是說這個程序是5秒發送一次數據。
②設置發送內容,自動周期性發送。在同一個函數下找到如下代碼(圖9)

圖9
如果觸發周期性數據發送部分(就是說5秒過去了,要發送信息了),就執行SampleApp_SendPeriodicMessage()這個函數。這個函數是重點哦,里面放我們需要發送的數據。繼續go to definition......
找到該函數后對該函數做如下修改(如圖10)。

圖10
我們來看一下AF_DataRequest()這個函數,通過上下文我們就可以知道這個函數一定就是決定發送數據內容的啦。我們需要關注的是其中第3、4、5個參數,第3個參數的作用是和接收方建立聯系,這里定義SAMPLEAPP_PERIODIC_CLUSTERID=1,如果協調器收到一個數據包,獲取里面的這個標號,為1則證明這個數據包是以周期性廣播方式進來的。第4個參數表示發送數據的長度,第5個參數為需要發送的數據的指針。
進行到這里發送數據部分已經結束啦~~~
3. 接收部分代碼
在SampleApp.c下找到函數“void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )”,在“case SAMPLEAPP_PERIODIC_CLUSTERID:”下面一行添加代碼(如圖11):
HalUARTWrite(0, "I get data!\n", 12); HalUARTWrite(0, &pkt->cmd.Data[0], pkt->cmd.DataLength); HalUARTWrite(0, "\n", 1);

圖11
操作說明:我們先看一下這個條件語句“case SAMPLEAPP_PERIODIC_CLUSTERID:”。這個就是在發送部分設置的表示周期性發送數據的編號。看吧,這里就用上了~
在添加代碼的這個地方,我們可以對收到的數據進行處理(不局限於串口發送)。這里的三行代碼都是串口發送的,不再多說什么啦。
重點看一下“afIncomingMSGPacket_t *pkt”。所有的數據和信息都在函數傳進來的afIncomingMSGPacket里面,查看這個定義
1 typedef struct 2 3 { 4 5 osal_event_hdr_t hdr; /* OSAL Message header */ 6 7 uint16 groupId; /* Message's group ID - 0 if not set */ 8 9 uint16 clusterId; /* Message's cluster ID */ 10 11 afAddrType_t srcAddr; /* Source Address, if endpoint is STUBAPS_INTER_PAN_EP, 12 13 it's an InterPAN message */ 14 15 uint16 macDestAddr; /* MAC header destination short address */ 16 17 uint8 endPoint; /* destination endpoint */ 18 19 uint8 wasBroadcast; /* TRUE if network destination was a broadcast address */ 20 21 uint8 LinkQuality; /* The link quality of the received data frame */ 22 23 uint8 correlation; /* The raw correlation value of the received data frame */ 24 25 int8 rssi; /* The received RF power in units dBm */ 26 27 uint8 SecurityUse; /* deprecated */ 28 29 uint32 timestamp; /* receipt timestamp from MAC */ 30 31 uint8 nwkSeqNum; /* network header frame sequence number */ 32 33 afMSGCommandFormat_t cmd; /* Application Data */ 34 35 } afIncomingMSGPacket_t;
它是一個結構體,里面包含了數據包的所以內容,這里就不說啦,想知道的自己翻譯下注釋吧~我們重點關注其中的afMSGCommandFormat_t cmd。查看它的定義
typedef struct { uint8 TransSeqNumber; uint16 DataLength; // Number of bytes in TransData uint8 *Data; } afMSGCommandFormat_t;
哦哦,這個里面就有我們傳送的數據內容啦!其中的DataLength就是數據長度,Data就是數據內容的指針啦。
我們再看回上去,HalUARTWrite(0, &pkt->cmd.Data[0], pkt->cmd.DataLength);這個代碼就是把收到的數據發送給串口啦
4. 程序燒錄
這里有個注意事項!下載的時候需要選擇模式!如圖12,CoordinatorEB模式下載到協調器(連接電腦的那個!),EndDeviceEB模式下載到終端模塊!

圖12
至此,無線數據傳輸實驗就結束啦~
沒有記住?沒關系!下面就是總結回顧整個流程啦~
六、流程分析

圖13
看着這個流程圖有沒有覺得更加方便理解呢?
七、串口優化
這個推薦去看我之前寫的 「51單片機」藍牙從機基本使用方法
地址:http://www.cnblogs.com/Donut/p/4054348.html
嘻嘻 給自己打個廣告~
操作方式一模一樣,只是波特率配置是115200,然后串口么連接zigbee的UART0(僅限這個實驗,之后你定義了哪個串口就連哪個串口的唄~)
