首页 > 代码库 > 「ZigBee模块」协议栈-串口透传,打造无线串口模块
「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 //|
这部分是对串口进行预编译,我们定义的是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: //写入CMD1106 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 else122 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 else162 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_TaskID200 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 parameter14 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 else58 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
「ZigBee模块」协议栈-串口透传,打造无线串口模块