「ZigBee模塊」協議棧-串口透傳,打造無線串口模塊


前面寫比較仔細,后面一個么因為和前面重復了,不多說了,還有個原因...我懶...O(∩_∩)O哈哈~

串口透傳,打造無線串口模塊

一、實驗目的

    兩台PC機各使用串口連接一個zigbee模塊,連接正確后打開串口調試助手發送信息。利用zigbee將從串口接收到的數據無線傳送給另一個zigbee模塊,另一個zigbee模塊通過串口將數據傳給PC端並在屏幕上顯示。

 

二、實驗平台

  硬件:兩個zigbee模塊,兩台PC機(其實一台也許,連接不同串口即可),編譯器,方口轉USB數據線兩根

  軟件:基於Z-stack協議棧的SampleApp工程文件

 

三、實驗過程分析

  打開工程文件,打開MT_UART.c文件,找到函數初始化函數MT_UartInit ()。注意其中部分代碼

 1 #if defined (ZTOOL_P1) || defined (ZTOOL_P2)                     //預編譯
 2 
 3   uartConfig.callBackFunc         = MT_UartProcessZToolData;     //|選擇ZTOOL或者ZAPP
 4 
 5 #elif defined (ZAPP_P1) || defined (ZAPP_P2)                     //|P1-串口0 或 P2-串口1
 6 
 7   uartConfig.callBackFunc         = MT_UartProcessZAppData;      //|在option->c/c++->preprocessor中選擇
 8 
 9 #else                                                            //|
10 
11   uartConfig.callBackFunc         = NULL;                        //|
12 
13 #endif                                                           //|
MT_UART.c

  這部分是對串口進行預編譯,我們定義的是ZTOOL_P1,故協議棧處理的函數是MT_UartProcessZToolData。查看其定義。

  正式看它的定義之前我們先來了解一下協議棧中發送數據的格式。函數定義的上面有一段注釋部分,對串口傳送數據的格式進行了說明(見圖1)。

圖1

  SOP0xFE 數據幀頭

  Data LengthData的數據長度,以字節計

  CMD0:命令低字節

  CMD1:命令高字節

  Data:數據幀具體的數據,長度可變,但必須和Data Length相等。

  FCS:校驗和

 

  看了這個數據格式我們就會發現一個問題,這個數據格式非常適合硬件之間的通信,因為它包括了具體數據以外的很多數據信息,但是卻不適合我們手動發送數據。也就是說如果我們使用串口助手直接發送數據,我們需要在數據前面加上FE、數據長度、命令字,然后數據末尾再計算校驗和。這對於我們來說實在太麻煩了,所以我們必須對這個函數作出一些修改。在修改函數之前我們還是要先來了解一下它原本的代碼。

  順便再提一個東西,串口數據包(我是這樣叫它的,它的英文名是mtOSALSerialData_t)。串口數據包是一個結構體,成員變量是一個事件包(也是我自己叫的,英文名叫osal_event_hdr_t)和一個指針。時間包也是一個結構體,成員變量是事件(事件號)和狀態。也就是說一個串口數據包里面有一個事件號,一個事件狀態,一個指針。很明顯,這個指針等一下一定會指向一個動態數組,然后依次往數值里面放數據嘛~

  好啦,大家久等啦,來看一下MT_UartProcessZToolData()這個函數吧~

  1 //port是串口號,event是事件
  2 
  3 void MT_UartProcessZToolData ( uint8 port, uint8 event )  
  4 
  5 {
  6 
  7   uint8  ch;
  8 
  9   uint8  bytesInRxBuffer;
 10 
 11   
 12 
 13   (void)event;  // Intentionally unreferenced parameter
 14 
 15  
 16 
 17   while (Hal_UART_RxBufLen(port)) //只要緩沖區有數據
 18 
 19   {
 20 
 21     HalUARTRead (port, &ch, 1);   //傳入串口號,讀取1個字符到ch
 22 
 23  
 24 
 25     switch (state)                //state一開始默認0x00
 26 
 27     {
 28 
 29       case SOP_STATE:             //SOP_STATE = 0xFE;
 30 
 31         if (ch == MT_UART_SOF)
 32 
 33           state = LEN_STATE;      //切換狀態
 34 
 35         break;
 36 
 37  
 38 
 39       case LEN_STATE:             //讀取數據長度
 40 
 41         LEN_Token = ch;
 42 
 43         
 44 
 45         //接下去要正式接受有用的數據啦
 46 
 47         //開始之前要為接收數據做一系列初始化
 48 
 49         tempDataLen = 0;          //初始化數據指針
 50 
 51  
 52 
 53         /* Allocate memory for the data */
 54 
 55         //為數據分配內存,其實新建的是串口數據包(我是這樣叫它的)
 56 
 57         pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
 58 
 59                                                         MT_RPC_FRAME_HDR_SZ + LEN_Token );
 60 
 61  
 62 
 63         if (pMsg)                 //如果內存分配成功
 64 
 65         {
 66 
 67           /* Fill up what we can */
 68 
 69           //把我們已知的內容填入數據包
 70 
 71           pMsg->hdr.event = CMD_SERIAL_MSG;        //事件號
 72 
 73           pMsg->msg = (uint8*)(pMsg+1);            //為存放的數據的數組開辟一個空間
 74 
 75           pMsg->msg[MT_RPC_POS_LEN] = LEN_Token;   //數組第一位依舊是長度
 76 
 77           state = CMD_STATE1;                      //初始化結束,切換狀態
 78 
 79         }
 80 
 81         else
 82 
 83         {
 84 
 85           state = SOP_STATE;
 86 
 87           return;
 88 
 89         }
 90 
 91         break;
 92 
 93  
 94 
 95       case CMD_STATE1:                             //寫入CMD0
 96 
 97         pMsg->msg[MT_RPC_POS_CMD0] = ch;
 98 
 99         state = CMD_STATE2;                        //切換狀態
100 
101         break;
102 
103  
104 
105       case CMD_STATE2:                             //寫入CMD1
106 
107         pMsg->msg[MT_RPC_POS_CMD1] = ch;
108 
109         /* If there is no data, skip to FCS state */
110 
111         //切換狀態,如果數據長度為0,則跳過一個狀態
112 
113         if (LEN_Token)
114 
115         {
116 
117           state = DATA_STATE;
118 
119         }
120 
121         else
122 
123         {
124 
125           state = FCS_STATE;
126 
127         }
128 
129         break;
130 
131  
132 
133       case DATA_STATE:                              //依次寫入數據
134 
135  
136 
137         /* Fill in the buffer the first byte of the data */
138 
139         pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen++] = ch;
140 
141  
142 
143         /* Check number of bytes left in the Rx buffer */
144 
145         bytesInRxBuffer = Hal_UART_RxBufLen(port);
146 
147  
148 
149         /* If the remain of the data is there, read them all, otherwise, just read enough */
150 
151         if (bytesInRxBuffer <= LEN_Token - tempDataLen)
152 
153         {
154 
155           HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], bytesInRxBuffer);
156 
157           tempDataLen += bytesInRxBuffer;
158 
159         }
160 
161         else
162 
163         {
164 
165           HalUARTRead (port, &pMsg->msg[MT_RPC_FRAME_HDR_SZ + tempDataLen], LEN_Token - tempDataLen);
166 
167           tempDataLen += (LEN_Token - tempDataLen);
168 
169         }
170 
171  
172 
173         /* If number of bytes read is equal to data length, time to move on to FCS */
174 
175         if ( tempDataLen == LEN_Token )           //寫完切換狀態
176 
177             state = FCS_STATE;
178 
179  
180 
181         break;
182 
183  
184 
185       case FCS_STATE:                             //幀校驗位,確保正確
186 
187  
188 
189         FSC_Token = ch;
190 
191  
192 
193         /* Make sure it's correct */
194 
195         if ((MT_UartCalcFCS ((uint8*)&pMsg->msg[0], MT_RPC_FRAME_HDR_SZ + LEN_Token) == FSC_Token))
196 
197         {
198 
199           osal_msg_send( App_TaskID, (byte *)pMsg ); //校驗正確就把數據包發送給...App_TaskID
200 
201         }                                            //這是什么?就是我們之前登記的任務號!
202 
203                                                      //具體查看MT_UartRegisterTaskID()這個函數!
204 
205         else 
206 
207         {
208 
209           /* deallocate the msg */                
210 
211           //錯誤就把包丟掉(釋放內存)
212 
213           osal_msg_deallocate ( (uint8 *)pMsg );
214 
215         }
216 
217  
218 
219         /* Reset the state, send or discard the buffers at this point */
220 
221         state = SOP_STATE;                        //數據包接收完畢,切換回初始狀態
222 
223  
224 
225         break;
226 
227  
228 
229       default:
230 
231        break;
232 
233     }
234 
235   }
236 
237 }
MT_UartProcessZToolData

  代碼很長,注釋基本寫在代碼里面了,總結一下流程。

  ①判斷起始碼是不是0xFE(不是就別想繼續下去啦)

  ②讀取數據長度,初始化串口數據包pMsgmtOSALSerialData_t  pMsg

  ③給pMsg裝數據

  ④校驗和,正確則把pMsg向上發送,錯誤則丟棄

  ⑤初始化狀態

 

  我們要做的就是簡化流程,因為我們發送的數據格式是只含有數據內容的,因此要把起始碼、數據長度之類的去掉。但是這樣會導致數據長度變成未知的,無法聲明動態數組。改變思路,定義一個定長的數組!

修改后代碼如下:

 1 void MT_UartProcessZToolData ( uint8 port, uint8 event )
 2 
 3 {
 4 
 5   uint8  ch, len = 0;
 6 
 7   uint8  uartData[128];
 8 
 9   uint8  i;
10 
11   
12 
13   (void)event;  // Intentionally unreferenced parameter
14 
15  
16 
17   while (Hal_UART_RxBufLen(port))
18 
19   {
20 
21     HalUARTRead (port, &ch, 1);
22 
23     uartData[len+1] = ch;
24 
25     len ++;
26 
27   }
28 
29   if(len)
30 
31   {
32 
33     uartData[0] = len;
34 
35     pMsg = (mtOSALSerialData_t *)osal_msg_allocate( sizeof ( mtOSALSerialData_t ) +
36 
37                                                     len + 1 );
38 
39     if (pMsg)
40 
41     {
42 
43       /* Fill up what we can */
44 
45       pMsg->hdr.event = CMD_SERIAL_MSG;
46 
47       pMsg->msg = (uint8*)(pMsg+1);
48 
49       for(i=0; i<=len; i++)
50 
51         pMsg->msg[i] = uartData[i];
52 
53       osal_msg_send( App_TaskID, (byte *)pMsg );
54 
55     }           
56 
57   }
58 
59 }
MT_UartProcessZToolData

  修改完接收數據包的代碼之后,我們就應該去考慮下要怎么處理接收的代碼啦。這個自然就是在SampleApp.c中進行的啦。還有,在開始之前要先在SampleApp.c中加入串口初始化,這個過程見上一篇《Z-Stack協議棧基礎和數據傳輸實驗》的5.1串口初始化部分。原諒我比較懶......

  打開SampleApp.c,找到事件處理函數SampleApp_ProcessEvent()。看到兩個if語句里面分別有一個SYS_EVENT_MSGSAMPLEAPP_SEND_PERIODIC_MSG_EVT。這兩個就是事件的編號。關於事件之前有說過,每個任務都可以有16個事件。這個時候會有這樣的疑惑,這個事件和我們之前在串口數據包里面放入的事件有什么區別呢?為了解開這個疑惑,找到之前串口數據包的定義(MT_UART.c->MT_UartProcessZToolData()->pMsg查看定義->mtOSALSerialData_t查看定義->osal_event_hdr_t查看定義)。這樣找到這個event后發現它是uint8型的,說明它只有8位,這個顯然和上面提到的事件不一樣嘛~

  這個問題解決了,那么我們應該寫在哪個事件下面呢?MT_UART.c->MT_UartProcessZToolData()->osal_msg_send()查看定義,看到函數最后一行

osal_set_event( destination_task, SYS_EVENT_MSG );

  看到這里應該就明白了,我們這個是屬於SYS_EVENT_MSG事件噠。至於具體怎么工作,就是把消息放入隊列,處理消息之類的,這里不再多說啦。

  回到SampleApp.c下的事件處理函數SampleApp_ProcessEvent(),在SYS_EVENT_MSG事件下還有一個選擇“MSGpkt->hdr.event”,好啦,這個就是我們熟悉的“小事件”啦(因為只有8位,英文名又叫event,所以我直接這樣叫它啦)。現在明白了吧,我們要寫一個case語句把事件CMD_SERIAL_MSG放進去(這個事件名字就是初始化串口數據包的時候寫進去的那個)。同時要在SampleApp.c文件中添加一個頭文件#include “MT.h”,CMD_SERIAL_MSG是在這個文件中定義的。

代碼如下:

//處理串口數據包

case CMD_SERIAL_MSG:

  SampleApp_SerialMSG((mtOSALSerialData_t *)MSGpkt);

  break;

  代碼里面SampleApp_SerialMSG()是什么函數呢?找了一圈沒有找到,其實它是要自己寫噠~你可以大概瀏覽一下SampleApp.c里面的函數,有沒有發現沒有一個符合我們的需求的?所以要自己寫咯。

代碼如下:

 1 void SampleApp_SerialCMD(mtOSALSerialData_t *sd)
 2 
 3 {
 4 
 5   uint8 i, num = sd->msg[0];
 6 
 7   uint8 *ch = sd->msg;
 8 
 9   
10 
11   for(i=1; i<=num; i++)
12 
13     HalUARTWrite(0, ch+i, 1);
14 
15   HalUARTWrite(0, "\n", 1);
16 
17   
18 
19   //這個是發送數據包的函數,復制后修改參數即可
20 
21   void SampleApp_SerialMSG(mtOSALSerialData_t *sd)
22 
23 {
24 
25   uint8 len = sd->msg[0];
26 
27   uint8 *ch = &sd->msg[1];
28 
29   
30 
31   HalUARTWrite(0, "I:", 2);
32 
33   HalUARTWrite(0, ch, len);
34 
35   HalUARTWrite(0, "\n", 1);
36 
37   
38 
39   if ( AF_DataRequest( &SampleApp_Flash_DstAddr, &SampleApp_epDesc,
40 
41                        SAMPLEAPP_SERIAL_CLUSTERID,
42 
43                        len+1,
44 
45                        ch,
46 
47                        &SampleApp_TransID,
48 
49                        AF_DISCV_ROUTE,
50 
51                        AF_DEFAULT_RADIUS ) == afStatus_SUCCESS )
52 
53   {
54 
55   }
56 
57   else
58 
59   {
60 
61     // Error occurred in request to send.
62 
63   }
64 
65 }
SampleApp_SerialCMD

  代碼其余部分不解釋,需要注意的是發送數據函數里的一個參數SAMPLEAPP_SERIAL_CLUSTERID,你去查看定義會發現查不到......嘿嘿,這個是要自己加上去噠。如圖2所示。

 

圖2

  這個參數的作用之前已經說過啦,名字可以任意取。要注意的是SAMPLEAPP_MAX_CLUSTERS這個的值也要相應變大,看它名字就知道啦,它表示所有這類數中的最大值。

實驗進行到這里,我們已經可以把程序燒錄到一個zigbee進行測試了。因為沒有接收部分代碼,實驗結果只是通過串口助手發送數據給zigbee然后zigbee再發回給PC端。實驗結果見圖3

 

3

  進行到這一步有沒有點小開心?不過還要再堅持下,還有接收部分的呢~

  接收部分代碼和昨天的實驗非常類似,就不詳細說啦,看看代碼應該就能看懂啦~

 1 void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
 2 
 3 {
 4 
 5 //  uint16 flashTime;
 6 
 7 //  uint8 len = pkt->cmd.Data[0];
 8 
 9   uint8 *ch = &pkt->cmd.Data[0];
10 
11  
12 
13   switch ( pkt->clusterId )
14 
15   {
16 
17     case SAMPLEAPP_SERIAL_CLUSTERID:
18 
19       
20 
21       HalUARTWrite(0, "friend:", 7);      
22 
23       HalUARTWrite(0, ch, pkt->cmd.DataLength-1);
24 
25       HalUARTWrite(0, "\n", 1);
26 
27       break;
28 
29  /*   
30 
31     case SAMPLEAPP_PERIODIC_CLUSTERID:
32 
33       break;
34 
35  
36 
37     case SAMPLEAPP_FLASH_CLUSTERID:
38 
39       flashTime = BUILD_UINT16(pkt->cmd.Data[1], pkt->cmd.Data[2] );
40 
41       HalLedBlink( HAL_LED_4, 4, 50, (flashTime / 4) );
42 
43       break;*/
44 
45   }
46 
47 }
SampleApp_MessageMSGCB

  最后還要注意一點!兩個zigbee一個做協調器,一個做路由器!不然無法通信!就因為這個原因我被坑了好幾個小時......

 

四、實驗結果

  

圖4 兩個zigbee實驗結果

 

三個zigbee實驗結果(一個協調器,兩個路由器)

五、總結流程

 

 

 圖6


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM