|
工程概述
SerialApp工程是TI提供的一个使用串口的实例。该工程的可以完成串口透明传输,其总体数据流图如图X所示。
图X SerialApp工程数据流图
SerialApp工程为了数据准确无误的发送到目标设备采取了两个措施:双缓冲机制和应答机制。
源设备的串口回调函数中有两个缓冲otaBuf和otaBuf2
SerialApp工程的应答机制有别于我们提及的NWK层应答和MAC层应答。
SerialApp工程中提供两种方式寻找一个潜在的目标设备:ZDO终端绑定和简单描述符匹配。与GenericApp工程类似。
ZDO终端绑定:设备发送ZDO终端绑定请求到协调器,如果协调器收到两个相互匹配的信息则为其创建绑定表记录,当两个设备绑定后数据将间接的方式发送到绑定的目标设备。
简单描述符匹配:设备向网络中广播简单描述符匹配信息,允许匹配的设备回应该信息。如果匹配到其它设备则其作为目标设备,将信息发送到该目标设备。
二、SerialApp工程按键说明
SerialApp工程中使用了两个按键分别用来发送ZDO绑定请求和简单描述符匹配请求消息。分别使用了按键SW2和SW4。
SW2----------------ZDO终端绑定请求
SW4----------------简单描述符匹配请求
编译选项
SerialApp工程的主要编译选项有:CC2430EB、REFLECTOR、HAL_UART
SERIAL_APP_PORT=0。
编译选项中有REFLECTOR,说明该工程可以使用绑定机制。该工程使用串口但是并没有像我们梳理串口机制时编译以下四者之一:ZAPP_P1、 ZAPP_P2、 ZTOOL_P1、ZTOOL_P,但是却编译了HAL_UART。我们回顾一下关于串口配置的相关代码,摘录如下。
程序代码:
#ifndef HAL_UART
#if (defined ZAPP_P1) || (defined ZAPP_P2) || (defined ZTOOL_P1) || (defined ZTOOL_P2)
#define HAL_UART TRUE
#else
#define HAL_UART FALSE
#endif
#endif
#if HAL_UART
#define HAL_UART_0_ENABLE TRUE
#define HAL_UART_1_ENABLE FALSE
#if HAL_DMA
#if !defined( HAL_UART_DMA )
……
在该工程的编译选项中同样没有编译MT_Task这一编译选项,,例如SPIMgr_Init ()一些函数并没有包含在SerialApp工程中,那么SerialApp工程是如何使用串口呢?
编译选项SERIAL_APP_PORT=0是我们使用的UART端口,首先我们看一下SerialApp_Init()中的一句代码,代码如下:
HalUARTOpen (SERIAL_APP_PORT, &uartConfig)
我们现在回顾在串口机制梳理时打开串口时传递的参数,代码如下:
HalUARTOpen (SPI_MGR_DEFAULT_PORT, &uartConfig)
读者可以自行追踪SPI_MGR_DEFAULT_PORT,最终可以得到#define HAL_UART_PORT_0 0x00。可以看出其实两者使用的UART端口相同。
SerialApp工程初始化与事件处理函数
SerialApp工程初始化函数
SerialApp工程初始化函数SerialApp_Init()理解为SerialApp工程应用层初始化与串口初始化的“结合体”。具体代码如下。
程序代码:
void SerialApp_Init( uint8 task_id )
{
halUARTCfg_t uartConfig;
SerialApp_MsgID = 0x00;
SerialApp_SeqRx = 0xC3;
SerialApp_TaskID = task_id;
//目标设备地址初始化
SerialApp_DstAddr.endPoint = 0;
SerialApp_DstAddr.addr.shortAddr = 0;
SerialApp_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;
//应答地址初始化
SerialApp_RspDstAddr.endPoint = 0;
SerialApp_RspDstAddr.addr.shortAddr = 0;
SerialApp_RspDstAddr.addrMode = (afAddrMode_t)AddrNotPresent;
//注册端点
afRegister( (endPointDesc_t *)&SerialApp_epDesc );
//注册按键
RegisterForKeys( task_id );
//配置结构体uartConfig
uartConfig.configured = TRUE; // 2430 don't care.
uartConfig.baudRate = SERIAL_APP_BAUD;
uartConfig.flowControl = TRUE;
uartConfig.flowControlThreshold = SERIAL_APP_THRESH;
uartConfig.rx.maxBufSize = SERIAL_APP_RX_MAX;
uartConfig.tx.maxBufSize = SERIAL_APP_TX_MAX;
uartConfig.idleTimeout = SERIAL_APP_IDLE; // 2430 don't care.
uartConfig.intEnable = TRUE; // 2430 don't care.
#if SERIAL_APP_LOOPBACK
uartConfig.callBackFunc = rxCB_Loopback;
#else
uartConfig.callBackFunc = rxCB;
#endif
//打开串口
HalUARTOpen (SERIAL_APP_PORT, &uartConfig);
//注册ZDO消息
ZDO_RegisterForZDOMsg( SerialApp_TaskID, End_Device_Bind_rsp );
ZDO_RegisterForZDOMsg( SerialApp_TaskID, Match_Desc_rsp );
}
SerialApp工程事件处理函数
SerialApp工程事件处理函数SerialApp_ProcessEvent()与其它工程的应用层处理函数一样,OSAL调用该函数将应用层对应的事件进行处理,具体代码如下。
程序代码:
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
if ( events & SYS_EVENT_MSG )
{
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
……
case KEY_CHANGE:
……
case AF_INCOMING_MSG_CMD:
……
default:
break;
}
}
return ( events ^ SYS_EVENT_MSG );
}
if ( events & SERIALAPP_MSG_SEND_EVT )
{
……
}
if ( events & SERIALAPP_MSG_RTRY_EVT )
{
……
}
if ( events & SERIALAPP_RSP_RTRY_EVT )
{
……
}
return ( 0 ); // Discard unknown events.
}
五、确定目标设备的地址
在发送数据前必须要明确目标设备,SerialApp工程提供两种方式去寻找目标设备:ZDO终端绑定和描述符匹配。
描述匹配请求
设备向网络中所有节点广播描述匹配请求,若网络中有允许匹配的设备接收到描述符匹配请求后将回应该匹配信息,回复的信息中包含了设备的短地址、端点等信息。描述符匹配过程如下。
、按键SW4向网络中所有设备发送描述符匹配请求信息,代码摘录如下。
程序代码:
void SerialApp_HandleKeys( uint8 shift, uint8 keys )
{
……
if ( keys & HAL_KEY_SW_4 )
{
HalLedSet ( HAL_LED_4, HAL_LED_MODE_OFF );
dstAddr.addrMode = AddrBroadcast;
dstAddr.addr.shortAddr = NWK_BROADCAST_SHORTADDR;
ZDP_MatchDescReq( &dstAddr, NWK_BROADCAST_SHORTADDR,
SERIALAPP_PROFID, SERIALAPP_MAX_CLUSTERS,
(cId_t*)SerialApp_ClusterList, SERIALAPP_MAX_CLUSTERS,
(cId_t *)SerialApp_ClusterList, FALSE );
}
……
}
②、描述符匹配回复消息处理,相关代码摘录如下。
程序代码:
void SerialApp_ProcessZDOMsgs ( zdoIncomingMsg_t *inMsg )
{
switch ( inMsg->clusterID )
{
……
case Match_Desc_rsp:
{
ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp( inMsg );
if ( pRsp )
{
if ( pRsp->status == ZSuccess && pRsp->cnt )
{
SerialApp_DstAddr.addrMode = (afAddrMode_t)Addr16Bit;
SerialApp_DstAddr.addr.shortAddr = pRsp->nwkAddr;
SerialApp_DstAddr.endPoint = pRsp->epList[0];
HalLedSet( HAL_LED_4, HAL_LED_MODE_ON );
}
osal_mem_free( pRsp );
}
}
break;
……
}
由上面的代码可以看出,如果匹配到设备则地址SerialApp_DstAddr修改为匹配到的目标设备的地址,那么来自PC机的串口信息将被以单播的形式发送到该目标设备。
六、SerialApp工程数据流
串口回调函数
回调函数中使用了双接收缓存otaBuf和otaBuf2,确保PC到源设备直接的数据传输。回调函数代码如下。
程序代码:
static void rxCB( uint8 port, uint8 event )
{
uint8 *buf, len;
if ( otaBuf2 )
{
return;
}
if ( !(buf = osal_mem_alloc( SERIAL_APP_RX_CNT )) )
{
return;
}
len = HalUARTRead( port, buf+1, SERIAL_APP_RX_CNT-1 );
if ( !len )
{
osal_mem_free( buf );
return;
}
if ( otaBuf )
{
otaBuf2 = buf;
otaLen2 = len;
}
else
{
otaBuf = buf;
otaLen = len;
osal_set_event( SerialApp_TaskID, SERIALAPP_MSG_SEND_EVT );
}
}
该回调函数是SerialApp工程学习的重点也是难点
程序代码:
#define FREE_OTABUF() { \
if ( otaBuf ) \
{ \
osal_mem_free( otaBuf ); \
} \
if ( otaBuf2 ) \
{ \
SerialApp_SendData( otaBuf2, otaLen2 ); \
otaBuf2 = NULL; \
} \
else \
{ \
otaBuf = NULL; \
} \
}
图X 回调函数流程图
串口回调函数主要完成了数据由PC机到源设备的传输,为了确保该过程的数据可靠的传输使用了双接收缓存机制。该过程的功能图如图X所示。
图X 串口回调函数功能图
数据的发送与接收
源设备发送数据
程序代码:
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
……
if ( events & SERIALAPP_MSG_SEND_EVT )
{
SerialApp_SendData( otaBuf, otaLen );
return ( events ^ SERIALAPP_MSG_SEND_EVT );
}
……
}
从上面的代码可以看出,事件SERIALAPP_MSG_SEND_EVT的处理是调用了函数SerialApp_SendData()将接收缓存otaBuf中的数据发送出去,该函数的代码如下。
程序代码:
static void SerialApp_SendData( uint8 *buf, uint8 len )
{
afStatus_t stat;
*buf = ++SerialApp_SeqTx;
otaBuf = buf;
otaLen = len+1;
stat = AF_DataRequest( &SerialApp_DstAddr,
(endPointDesc_t *)&SerialApp_epDesc,
SERIALAPP_CLUSTERID1, otaLen, otaBuf,
&SerialApp_MsgID, 0, AF_DEFAULT_RADIUS );
if ( (stat == afStatus_SUCCESS) || (stat == afStatus_MEM_FAIL) )
{
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT,
SERIALAPP_MSG_RTRY_TIMEOUT );
rtryCnt = SERIALAPP_MAX_RETRIES;
}
else
{
FREE_OTABUF();
}
}
可以从上面的代码看出,函数SerialApp_SendData()首先将传递的两个参数*buf、len进行了修改,在发送数据缓存中添加了数据序列号,数据长度自然要加1。然后调用数据发送函数AF_DataRequest()将*buf装载的数据发送出去。
数据序列号(SerialApp_SeqTx)的使用可以唯一确定一个数据,每次调用函数SerialApp_SendData()都会自增1。通过检测数据序列号的合法性断定一个数据包的合法性,同时数据序列号的使用可以防止一个数据被多次接收。从而确保了数据在设备间的可靠传输。
②、目标设备接收数据
由上面的代码分析可知,当源设备接收到来自PC机的串口信息后,将调用函数SerialApp_SendData()将串口信息及附加的数据序列号(SerialApp_SeqTx)发送出去,当目标设备接收到信息后将触发事件AF_INCOMING_MSG_CMD。具体梳理如下。
程序代码:
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
if ( events & SYS_EVENT_MSG )
{
{
switch ( MSGpkt->hdr.event )
{
……
case AF_INCOMING_MSG_CMD:
SerialApp_ProcessMSGCmd( MSGpkt );
break;
……
}
由上面的代码可以知道事件AF_INCOMING_MSG_CMD的处理是通过调用函数SerialApp_ProcessMSGCmd()进行处理的,该函数摘录如下。
程序代码:
void SerialApp_ProcessMSGCmd( afIncomingMSGPacket_t *pkt )
{
switch ( pkt->clusterId )
{
case SERIALAPP_CLUSTERID1:
seqnb = pkt->cmd.Data[0];
//检测数据序列号
if ( (seqnb > SerialApp_SeqRx) || // Normal
((seqnb < 0x80 ) && ( SerialApp_SeqRx > 0x80)) ) // Wrap-around
{
if ( HalUARTWrite( SERIAL_APP_PORT, pkt->cmd.Data+1,
(pkt->cmd.DataLength-1) ) )
{
SerialApp_SeqRx = seqnb;//保存数据序列号
stat = OTA_SUCCESS;//状态为成功
}
else
{
stat = OTA_SER_BUSY;//状态为串口忙
}
}
else
{
stat = OTA_DUP_MSG;//状态为复制品
}
delay = (stat == OTA_SER_BUSY) ? SERIALAPP_NAK_DELAY : SERIALAPP_ACK_DELAY;
rspBuf[0] = stat;
rspBuf[1] = seqnb;
rspBuf[2] = LO_UINT16( delay );
rspBuf[3] = HI_UINT16( delay );
stat = AF_DataRequest( &(pkt->srcAddr), (endPointDesc_t*)&SerialApp_epDesc, SERIALAPP_CLUSTERID2, SERIAL_APP_RSP_CNT , rspBuf, &SerialApp_MsgID, 0, AF_DEFAULT_RADIUS );
if ( stat != afStatus_SUCCESS )
{
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_RSP_RTRY_EVT,
SERIALAPP_RSP_RTRY_TIMEOUT );
osal_memcpy(&SerialApp_RspDstAddr, &(pkt->srcAddr), sizeof( afAddrType_t ));
}
break;
}
该函数的主要功能是将源设备发送来的数据通过串口写到PC机,并向源设备发送确认信息。该函数也是SerialApp工程的重点函数,我们结合图X进行梳理。
检测数据序列号是否正常,若正常则调用函数HalUARTWrite()将接收到的数据写到PC机,若成功写入则将状态stat置为OTA_SUCCESS否则将其置为OTA_SER_BUSY;若数据序列号检测异常,则将状态stat置为OTA_DUP_MSG。然后根据状态stat选择了延时长度delay。
然后将状态值stat、数据序列号seqnb、延时长度delay一并打包将其用函数AF_DataRequest()发送给源设备,作为对源设备发送到目标设备信息的应答。若数据没有发送成功,则定时触发事件SERIALAPP_RSP_RTRY_EVT重新发送,并将此时源设备的地址保存到SerialApp_RspDstAddr。
图X 目标设备处理接收到的信息
由前面的梳理可以知道当目标设备向源设备发送应答消息时,若没有成功则会触发事件SERIALAPP_RSP_RTRY_EVT,
程序代码:
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
……
if ( events & SERIALAPP_RSP_RTRY_EVT )
{
afStatus_t stat = AF_DataRequest( &SerialApp_RspDstAddr,(endPointDesc_t *)&SerialApp_epDesc, SERIALAPP_CLUSTERID2,
SERIAL_APP_RSP_CNT, rspBuf,&SerialApp_MsgID, 0, AF_DEFAULT_RADIUS );
if ( stat != afStatus_SUCCESS )
{
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_RSP_RTRY_EVT,
SERIALAPP_RSP_RTRY_TIMEOUT );
}
return ( events ^ SERIALAPP_RSP_RTRY_EVT );
}
……
}
③、源设备接收应答信息
源设备接收到目标设备的应答信息触发事件AF_INCOMING_MSG_CMD,该事件的处理同样调用了函数SerialApp_ProcessMSGCmd(),代码摘录如下。
程序代码:
void SerialApp_ProcessMSGCmd( afIncomingMSGPacket_t *pkt )
{
……
case SERIALAPP_CLUSTERID2:
if ( (pkt->cmd.Data[1] == SerialApp_SeqTx) &&
((pkt->cmd.Data[0] == OTA_SUCCESS) ||
(pkt->cmd.Data[0] == OTA_DUP_MSG)) )
{
osal_stop_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT );
FREE_OTABUF();
}
else
{
delay = BUILD_UINT16( pkt->cmd.Data[2], pkt->cmd.Data[3] );
osal_start_timerEx(SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT, delay );
}
break;
}
}
检测应答信息数据包中的数据序列号就状态stat,若应答信息的数据序列号与发送信息数据序列号相同,并且stat的值为OTA_SUCCESS或OTA_DUP_MSG表明数据发送成功,已经被写到目标设备连接的PC机。数据发送成功后终止定时器触发事件SERIALAPP_MSG_RTRY_EVT,否则重新定时触发事件SERIALAPP_MSG_RTRY_EVT,定时长度根据应答信息传递的值决定。
数据的发送与接收主要完成了源设备将数据发送到PC机,目标设备接收到源设备的信息将其通过串口写入PC机,并向源设备回复应答消息。该过程的功能图如图X所示。
图X 数据接收与发送功能图
3、数据重发
在上述代码分析中,有两处触发了事件SERIALAPP_MSG_RTRY_EVT。源设备在发送信息,若发送函数返回的状态为afStatus_SUCCESS或afStatus_MEM_FAIL定时触发该事件,并且将rtryCnt设置为SERIALAPP_MAX_RETRIES,即rtryCnt=10;源设备接收到目标设备的应答消息,若检测到数据序列号或者stat状态异常时定时触发该事件。两者对应的任务ID都为SerialApp_TaskID,即SerialApp工程应用层事件处理函数处理该事件,代码摘录如下。
程序代码:
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
……
if ( events & SERIALAPP_MSG_RTRY_EVT )
{
if ( --rtryCnt )
{
AF_DataRequest( &SerialApp_DstAddr,
(endPointDesc_t *)&SerialApp_epDesc,
SERIALAPP_CLUSTERID1, otaLen, otaBuf,
&SerialApp_MsgID, 0, AF_DEFAULT_RADIUS );
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT,
SERIALAPP_MSG_RTRY_TIMEOUT );
}
else
{
FREE_OTABUF();
}
return ( events ^ SERIALAPP_MSG_RTRY_EVT );
}
……
}
由上面的代码可知,首先将rtryCnt自减,然后调用函数AF_DataRequest()再次将otaBuf发送出去,然后定时再次触发事件SERIALAPP_MSG_RTRY_EVT。若重发次数达到最大值,即rtryCnt自减为0(共计10次),仍然没有发送成功则方式该缓存中的数据,调用FREE_OTABUF()释放otaBuf,具体代码参见前面对FREE_OTABUF()的梳理。
源设备数据流程如图X所示
|
|