前面寫比較仔細,后面一個么因為和前面重復了,不多說了,還有個原因...我懶...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 //|
這部分是對串口進行預編譯,我們定義的是ZTOOL_P1,故協議棧處理的函數是MT_UartProcessZToolData。查看其定義。
正式看它的定義之前我們先來了解一下協議棧中發送數據的格式。函數定義的上面有一段注釋部分,對串口傳送數據的格式進行了說明(見圖1)。
圖1
SOP:0xFE 數據幀頭
Data Length:Data的數據長度,以字節計
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 }
代碼很長,注釋基本寫在代碼里面了,總結一下流程。
①判斷起始碼是不是0xFE(不是就別想繼續下去啦)
②讀取數據長度,初始化串口數據包pMsg(mtOSALSerialData_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 }
修改完接收數據包的代碼之后,我們就應該去考慮下要怎么處理接收的代碼啦。這個自然就是在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, "\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_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 }
最后還要注意一點!兩個zigbee一個做協調器,一個做路由器!不然無法通信!就因為這個原因我被坑了好幾個小時......
四、實驗結果
圖4 兩個zigbee實驗結果
圖5 三個zigbee實驗結果(一個協調器,兩個路由器)
五、總結流程
圖6