第十六讲 ZigBee串口透传 一、 串口透明传输工程说明 串口透明传输工程是在SampleApp工程基础之上进行修改而成,主要功能是完成简单的串口透明传输,功能要求: 1、 设备上电后自动选择设备类型。第一个启动的设备为协调器,后续启动的为路由器。(所有设备中程序相同) 2、 路由器的232串口接收到数据后将数据以单播的形式发送到协调器。当路由器接收到来自空中的数据包将数据写入232串口。 3、 协调器的232串口接收到数据后将数据以广播的形式发送到网络中所有的设备。当协调器接收到来自空中的数据包将数据写入232串口。 file:///C:\Users\WANGJI~1\AppData\Local\Temp\ksohtml\wps_clip_image-27697.png file:///C:\Users\WANGJI~1\AppData\Local\Temp\ksohtml\wps_clip_image-32668.png 图X 串口透明传输应用 二、 编译选项说明 串口透明传输工程在SampleApp工程基础之上进行修改,但是编译选项使用原SampleApp工程的编译选项,SampleApp工程编译选项具体如下: CC2430EB、ZTOOL_P1、MT_TASK、SOFT_START 通过编译选项ZTOOL_P1 编译选项SOFT_START。 三、 工程初始化与事件处理函数 串口透明传输工程来源于对SampleApp工程的修改,工程初始化函数与SampleApp工程的初始化函数完全相同,读者可以参见前面章节。串口透明传输工程事件处理函数在SampleApp工程事件处理函数的基础之上添加了对事件UART_RX_CB_EVT的处理,具体代码如下。 程序代码: uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events ) { if ( events & SYS_EVENT_MSG ) { while ( MSGpkt ) { switch ( MSGpkt->hdr.event ) { case KEY_CHANGE: …… break; case AF_INCOMING_MSG_CMD: …… break; case ZDO_STATE_CHANGE: …… break; default: break; } } return (events ^ SYS_EVENT_MSG); } if ( events & SAMPLEAPP_SEND_PERIODIC_MSG_EVT ) { …… return (events ^ SAMPLEAPP_SEND_PERIODIC_MSG_EVT); } if ( events & UART_RX_CB_EVT ) //串口有数据需要处理 { SampleApp_SPI_SendData( rbuf, rxlen+1); //调用处理函数,将信息发送出去 return (events ^ UART_RX_CB_EVT); } return 0; } 四、 工程修改 1、 添加事件 串口透明传输工程在SampleApp工程基础上添加事件UART_RX_CB_EVT,当串口回调函数有接收到数据后将触发该事件,该事件在SampleApp.h中被定义,具体代码如下。 程序代码: #define UART_RX_CB_EVT 0x0002 2、 回调函数 编译选项中的ZTOOL_P1说明原SampleApp工程默认使用Z-Tool,我们这里对其修改,将其在串口初始化中的回调函数函数进行修改。 程序代码: void SPIMgr_Init () { …… #if defined (ZTOOL_P1) || defined (ZTOOL_P2) uartConfig.callBackFunc = rxCB; …… } 相应我们在SPIMgr.c文件中添加该回调函数rxCB( uint8 port, uint8 event ),具体代码如下。 程序代码 static void rxCB( uint8 port, uint8 event ) { extern uint8 SampleApp_TaskID; rxlen=Hal_UART_RxBufLen(SPI_MGR_DEFAULT_PORT); //接收缓冲区数据长度,字节为单位 rbuf=osal_mem_alloc(rxlen+1); //多分配1字节,分配如下 rbuf[0]=rxlen; //一字节存放数据长度 HalUARTRead ( SPI_MGR_DEFAULT_PORT, rbuf+1, rxlen); //读接收缓冲区数据到内存databuf+1 if(!rxlen) osal_mem_free( rbuf ); //释放内存 osal_set_event(SampleApp_TaskID,UART_RX_CB_EVT); } 3、 发送函数 当串口接收到数据在串口回调函数中触发事件UART_RX_CB_EVT,应用层对其处理,并最终调用数据发送函数将串口接收到的数据发送出去。为此我们在SampleApp工程基础之上添加了数据发送函数SampleApp_SPI_SendData(),该函数具体如下。 程序代码 void SampleApp_SPI_SendData( uint8 *buf, uint8 len ) { if ( devStartMode == MODE_HARD )//如果启动的时候是协调器,则用广播 { SampleApp_SPI_SendData_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast; SampleApp_SPI_SendData_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; SampleApp_SPI_SendData_DstAddr.addr.shortAddr = 0xFFFF; } else//其它设备类型则用单播 { SampleApp_SPI_SendData_DstAddr.addrMode = (afAddrMode_t)Addr16Bit; SampleApp_SPI_SendData_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; SampleApp_SPI_SendData_DstAddr.addr.shortAddr = 0x0000; } if ( AF_DataRequest ( &SampleApp_SPI_SendData_DstAddr, (endPointDesc_t *)&SampleApp_epDesc, SAMPLEAPP_FLASH_CLUSTERID, len, buf, &SampleApp_TransID, 0, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) { osal_mem_free( rbuf ); //必须释放内存,不然造成溢出! } else { osal_mem_free( rbuf ); //必须释放内存,不然造成溢出! } } 五、 数据流程 该串口透明传输工程与同为串口透明传输的SerialApp工程相比数据流程有很大的区别。该工程主要实现了多对一的数据透明传输,即协调器向所有路由广播,而路由器单播发送数据到协调器。数据流可分为两大部分,协调器到路由器和路由器到协调器。 下面我们梳理协调器到路由器数据流程,而路由器到协调器数据流程与其类似,读者可以自行对其梳理。协调器到路由器数据流程的流程图如图X所示。 file:///C:\Users\WANGJI~1\AppData\Local\Temp\ksohtml\wps_clip_image-17622.png 图X 协调器到路由器数据流程图 1、 回调函数 当与协调器连接的设备(通常为PC机)向协调器通过232串口写入数据时,协调器的串口回调函数将处理相关信息,并在串口回调函数中触发事件UART_RX_CB_EVT通知应用层进行处理。串口回调函数代码如下。 程序代码 static void rxCB( uint8 port, uint8 event ) { extern uint8 SampleApp_TaskID; rxlen=Hal_UART_RxBufLen(SPI_MGR_DEFAULT_PORT); //接收缓冲区数据长度,字节为单位 rbuf=osal_mem_alloc(rxlen+1); //多分配3字节,分配如下 rbuf[0]=rxlen; //一字节存放数据长度 HalUARTRead ( SPI_MGR_DEFAULT_PORT, rbuf+1, rxlen); //读接收缓冲区数据到内存databuf+3 if(!rxlen) osal_mem_free( rbuf ); //释放内存 osal_set_event(SampleApp_TaskID,UART_RX_CB_EVT); } 2、 处理事件UART_RX_CB_EVT 当接收到来自串口的信息后,在串口回调函数的最后触发了事件UART_RX_CB_EVT,而该事件的任务ID为SampleApp_TaskID,即应用层任务ID,所以会被SampleApp工程应用层的事件处理函数SampleApp_ProcessEvent()处理,具体程序如下。 程序代码 uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events ) { …… if ( events & UART_RX_CB_EVT ) //串口有数据需要处理 { SampleApp_SPI_SendData( rbuf, rxlen+1); //调用处理函数,将信息发送出去 return (events ^ UART_RX_CB_EVT); } …… } 3、 数据发送函数SampleApp_SPI_SendData( ) 在处理事件UART_RX_CB_EVT时,协议栈调用了函数SampleApp_SPI_SendData()将串口数据发送到空中,具体代码如下。 程序代码 void SampleApp_SPI_SendData( uint8 *buf, uint8 len ) { if ( devStartMode == MODE_HARD )//如果启动的时候是协调器,则用广播 { SampleApp_SPI_SendData_DstAddr.addrMode = (afAddrMode_t)AddrBroadcast; SampleApp_SPI_SendData_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; SampleApp_SPI_SendData_DstAddr.addr.shortAddr = 0xFFFF; } else//其它设备类型则用单播 { SampleApp_SPI_SendData_DstAddr.addrMode = (afAddrMode_t)Addr16Bit; SampleApp_SPI_SendData_DstAddr.endPoint = SAMPLEAPP_ENDPOINT; SampleApp_SPI_SendData_DstAddr.addr.shortAddr = 0x0000; } if ( AF_DataRequest ( &SampleApp_SPI_SendData_DstAddr, (endPointDesc_t *)&SampleApp_epDesc, SAMPLEAPP_FLASH_CLUSTERID, len, buf, &SampleApp_TransID, 0, AF_DEFAULT_RADIUS ) == afStatus_SUCCESS ) { osal_mem_free( rbuf ); //必须释放内存,不然造成溢出! } else { osal_mem_free( rbuf ); //必须释放内存,不然造成溢出! } } 在函数SampleApp_SPI_SendData()中根据设备的类型确定了地址为广播地址还是单播地址,因为我们现在梳理的是协调器到路由器的数据流程,所示devStartMode == MODE_HARD,即将地址设置为广播地址,串口数据被以广播的形式发送到网络中的所有设备。 4、 数据的接收 当协调器的串口接收到数据后将信息以广播的形式发送到网络中所有设备,网络中的路由器接收到来自空中的信息时会触发事件AF_INCOMING_MSG_CMD,具体程序如下。 程序代码 uint16 SampleApp_ProcessEvent( uint8 task_id, uint16 events ) { if ( events & SYS_EVENT_MSG )//判断任务(一个任务可以有多个事件) { while ( MSGpkt )//对比事件 { switch ( MSGpkt->hdr.event ) { case AF_INCOMING_MSG_CMD: SampleApp_MessageMSGCB( MSGpkt ); break; …… } 当触发事件AF_INCOMING_MSG_CMD时,协议中中调用了函数SampleApp_MessageMSGCB()对来自空中的信息包进行进一步的处理。 5、 数据处理函数SampleApp_MessageMSGCB() 当路由器接收到来自协调器空中的信息后,触发事件AF_INCOMING_MSG_CMD并调用了函数SampleApp_MessageMSGCB()对其处理,函数SampleApp_MessageMSGCB()具体代码如下。 程序代码 void SampleApp_MessageMSGCB( afIncomingMSGPacket_t *pkt ) { switch ( pkt->clusterId ) { uint8 *pointer1; case SAMPLEAPP_PERIODIC_CLUSTERID: break; case SAMPLEAPP_FLASH_CLUSTERID: pointer1=&pkt->cmd.Data[1]; //接收数据指针,指向数据 HalUARTWrite(0,pointer1,pkt->cmd.Data[0]);//cmd.Data[0]是数据的大小 break; } }
|