「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
SOP:0xFE數據幀頭
DataLength:Data的數據長度,以字節計
CMD0:命令低字節
CMD1:命令高字節
Data:數據幀具體的數據,長度可變,但必須和DataLength相等。
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(不是就別想繼續下去啦)
②讀取數據長度,初始化串口數據包pMsg(mtOSALSerialData_tpMsg)
③給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_MSG和SAMPLEAPP_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, "
", 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, "
", 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, "
", 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實驗結果
圖5三個zigbee實驗結果(一個協調器,兩個路由器)
五、總結流程
圖6
總結
以上是生活随笔為你收集整理的「ZigBee模块」协议栈-串口透传,打造无线串口模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑怎么连接双频路由器的5g频段双频路由
- 下一篇: 路由器这样放如何将路由器放去房间