支持原创,如需转载,请注明地址:http://blog.sina.com.cn/litianping0709 作者:叶雨荫城(阿雨))
今天开始接触z-stack的串口通信例子。恩,加油,好好学,一定要搞懂。
先看说明:
本实验是一个取代两个非ZigBee设备之间电缆连接的基本应用。该应用具有实际应用意义,例如RS232<-->ZigBee转换器,给具有RS232接口的设备增加ZigBee通信功能。一个PC通过串口连接一个使用本应用实例的ZigBee设备来发送数据。另一个PC通过串口连接一个使用本应用实例的ZigBee设备来接收数据。串行数据传输被设计为双向全双工,无硬件流控,强制允许OTA(多跳)时间和丢包重传。
注 意: 本实验使用两个ARMSKY-CC2430EB,一个作为协调器设备,另一个作为终端设备。PC机端使
用"串口调试助手 v2.2"软件方便实验操作和现象观察,波特率:38400,无校验位,数据位:8,停止位:1。将两个ARMSKY-CC2430EB连接到PC机,然后给它们上电,协调器设备启动一个网络, 在终端设备成功加入该网络后,分别按下协调器设备和终端设备上的SW2(时间差控制在5s之内)进行绑定。成功绑定后,可以在两台PC机上分别使用"串口调试助手 v2.2"软件互相发送数据。
其实自己学这个例子主要是为了学会怎样利用协议栈与上位机通信,搞清楚串口的调用机制。
以前用过精简版协议栈的,但是感觉那个功能太少了,不知道z-stack的功能怎样,让我拭目以待吧。
首先看初始化函数:
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.configured = TRUE; // 2430 don't care.
uartConfig.baudRate = SERIAL_APP_BAUD;
uartConfig.flowControl = FALSE;
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);
#if defined ( LCD_SUPPORTED )
//HalLcdWriteString( "SerialApp2", HAL_LCD_LINE_2 );
GUI_SetColor(1,0);
GUI_PutString5_7(0,37," SerialApp2 ");
LCM_Refresh();
#ifdef LCD_SD
#ifdef ZTOOL_PORT
debug_str( (byte*)"SerialApp2" );
#endif
#endif
#endif
}
来看看halUARTCfg_t的数据结构:
typedef struct
{
bool configured;
uint8 baudRate;
bool flowControl;
uint16 flowControlThreshold;
uint8 idleTimeout;
halUARTBufControl_t rx;
halUARTBufControl_t tx;
bool intEnable;
uint32 rxChRvdTime;
halUARTCBack_t callBackFunc;
}halUARTCfg_t;
halUARTCfg_t这个数据结构在哪个函数中会用到呢?我猜想这个数据结构肯定和串口的配置相关,于是我查了一下HAL的API接口函数,找到如下信息:(先把最基本的搞懂后咱再回去看例子程序是怎么处理的。)
9.2 HalUARTOpen ()
9.2.1 Description
This function opens a port based on the configuration that is provided. A callback function is also registered so events can be handled correctly.
这个函数主要是根据提供的配置信息打开相应的串口。为了使相应的事件得到正确处理,回调函数在这时也相应的注册。
9.2.2 Prototype
uint8 HalUARTOpen (uint8 port,
halUARTCfg_t *config);
9.2.3 Parameter Details
port – specified serial port to be opened. (Read UART Ports Table)
打开相应的串口(在uart串口表中可以找到相应的口。)
config – Structure that contains the information that is used to configure the port
配置相应串口的配置信息。
typedef struct
{
bool configured;
uint16 baudRate;
bool flowControl;
uint16 flowControlThreshold;
uint8 idleTimeout;
uint16 rx;
uint16 tx;
bool intEnable;
uint32 rxChRvdTime;
halUARTCBack_t callBackFunc;
}halUARTCfg_t;
config.configured – Set by the function when the port is setup correctly and read to be used.
当串口准备好时由函数设置。
config.baudRate – The baud rate of the port to be opened. (Check UART Ports Table)
串口的速率
config.flowControl – UART flow control can be set as TRUE or FALSE. TRUE value will
enable flow control and FALSE value will disable flow control.
config.flowControlThreshold – Number of bytes left before Rx buffer reaches maxRxBufSize.
When Rx buffer reaches this number (maxRxBufSize – flowControlThreshold) and flowControl is
TRUE, a callback will be sent back to the application with HAL_UART_RX_ABOUT_FULL
event.
在RX缓存达到maxRxBufSize之前还有多少字节空余。当到达maxRxBufSize – flowControlThreshold时并且流控制打开时,会触发相应的应用事件:HAL_UART_RX_ABOUT_FULL
config.idleTimeout – Rx timeout period in milliseconds. If Rx buffer haven’t got new data for idleTimout amount of time, a callback will be issued to the application with
HAL_UART_RX_TIMEOUT event. The application can choose to read everything from the Rx
buffer or just partial of it.
如果在idleTimout amount of time时间内RX还没有得到新的数据,将会触发相应的事件HAL_UART_RX_TIMEOUT ,这时应用可以选择读出所有RX的值或者一部分。
config.rx – Contains halUARTBufControl_t structure that used to manipulate Rx buffer
包含 halUARTBufControl_t 数据结构用于控制RX缓存
config.tx – Contains halUARTBufControl_t structure that used to manipulate Tx buffer
包含halUARTBufControl_t 数据结构用于控制TX缓存
typedef struct
{
uint16 bufferHead;
uint16 bufferTail;
uint16 maxBufSize;
uint8 *pBuffer;
}halUARTBufControl_t;
bufferHead – contain the index of the starting position of the Rx/Tx buffer
bufferTail – contains the index of the ending position of the Rx/Tx buffer
maxBufSize – holds maximum size of the Rx/Tx buffer that the UART can hold at a
time. When this number is reached, HAL_UART_RX_FULL or HAL_UART_TX_FULL
will be sent back to the application as an event through the callback system.
*pBuffer – pointer to the buffer that contains the Rx data
config.intEnable – enable/disable interrupt. It can be set as TRUE or FALSE. TRUE value will
enable the interrupt and FALSE value will disable the interrupt.
callBackFunc – This callback is called when there is an event such as Tx done, Rx ready…
void HalUARTCback (uint8 port, uint8 event);
当TX完成,RX准备好的时候将会调用这个回调函数。
port - specified serial port that has the event. (Check UART Ports Table).
event – event that causes the callback (Check Events table).
9.2.4 Return
Status of the function call. (Check Status Table).
上述就是halUARTCfg_t数据结构的一些详细信息,我们来看看在程序中是怎么来配置的:
uartConfig.configured = TRUE; // 2430 don't care.
uartConfig.baudRate = SERIAL_APP_BAUD;//设置速率
uartConfig.flowControl = FALSE;//没有数据流控制
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;
HalUARTOpen (SERIAL_APP_PORT, &uartConfig);
其实简单点说,这个函数就是根据相应的串口配置打开相应的串口。接下来让我们来看看配置里面的回调函数,看回调函数实现了哪些功能(其实,怎么调用相应的回调函数我一直没明白,为什么定时器一溢出,缓存一满就会调用相应的回调函数呢,这里面的过程我还没有搞清楚。),有两个回调函数,需要靠编译的时候来选择,先把回调函数放在这,看完事件处理函数之后再返回来继续看回调函数,继续看:
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
if ( events & SYS_EVENT_MSG )
{
zAddrType_t *dstAddr;
ZDO_NewDstAddr_t *ZDO_NewDstAddr;
afIncomingMSGPacket_t *MSGpkt;
while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive(
SerialApp_TaskID )) )
{
switch ( MSGpkt->hdr.event )
{
case KEY_CHANGE:
SerialApp_HandleKeys( ((keyChange_t *)MSGpkt)->state,
((keyChange_t *)MSGpkt)->keys );//相应的按键处理
break;
case AF_INCOMING_MSG_CMD:
SerialApp_ProcessMSGCmd( MSGpkt );//处理相应的消息
break;
case ZDO_NEW_DSTADDR://设备匹配响应
ZDO_NewDstAddr = (ZDO_NewDstAddr_t *)MSGpkt;
SerialApp_DstAddr.endPoint = ZDO_NewDstAddr->dstAddrDstEP;
dstAddr = &ZDO_NewDstAddr->dstAddr;
SerialApp_DstAddr.addrMode = (afAddrMode_t)dstAddr->addrMode;
SerialApp_DstAddr.addr.shortAddr = dstAddr->addr.shortAddr;
HalLedSet( HAL_LED_4, HAL_LED_MODE_ON );
break;
default:
break;
}
osal_msg_deallocate( (uint8 *)MSGpkt ); // Release the memory.
}
// Return unprocessed events
return ( events ^ SYS_EVENT_MSG );
}
if ( events & SERIALAPP_MSG_SEND_EVT )//数据发送事件
{
SerialApp_SendData( otaBuf, otaLen );//发送数据
return ( events ^ SERIALAPP_MSG_SEND_EVT );
}
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();//这个宏不知道啥意思?查查先,如果重发不成功的话采取的动作不知道是什么,
应该是释放OTA发送缓冲区
}
return ( events ^ SERIALAPP_MSG_RTRY_EVT );
}
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 );
}
#if SERIAL_APP_LOOPBACK
if ( events & SERIALAPP_TX_RTRY_EVT )//发送到上位机吗?
{
if ( rxLen )
{
if ( !HalUARTWrite( SERIAL_APP_PORT, rxBuf, rxLen ) )
{
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_TX_RTRY_EVT,
SERIALAPP_TX_RTRY_TIMEOUT );
}
else
{
rxLen = 0;
}
}
return ( events ^ SERIALAPP_TX_RTRY_EVT );
}
#endif
return ( 0 ); // Discard unknown events.
}
上面总共有四个事件:
SYS_EVENT_MSG:系统事件
SERIALAPP_MSG_SEND_EVT :数据发送
SERIALAPP_MSG_RTRY_EVT :数据重发
SERIALAPP_RSP_RTRY_EVT :响应重发
SERIALAPP_TX_RTRY_EVT :发送数据到上位机事件(我猜的)
来看相应的HalUARTWrite( SERIAL_APP_PORT, rxBuf, rxLen )函数:
This function writes a buffer of specific length into the serial port. The function will check if the Tx buffer is full or not. If the Tx Buffer is not full, the data will be loaded into the buffer and then will be sent to the Tx data register. If the Tx buffer is full, the function will send a callback HAL_UART_TX_FULL and return 0. Otherwise, the length of the data that was sent will be returned.
如果没有发送相应的数据到相应的串口,将会返回零,如果发送成功,则会返回已发送数据的长度。
再来看看相应的几个主要事件处理函数:
(1)按键函数先不讨论。
(2)发送数据函数:SerialApp_SendData( otaBuf, otaLen );
(3)处理数据函数:erialApp_ProcessMSGCmd( MSGpkt );;
当然,为了更好的讨论,我们按照程序的流程或是我们的操作顺序来会更好的理解,首先假设串口收到数据了,这时候肯定会调用回调函数来进行相应的处理,为了方便,我们首先只研究rxCB函数,另外一个等我们搞清楚了再来做进一步的学习用。
首先相应的回调函数rxCB如下:
(1)
*********************************************************************
* @fn rxCB
*
* @brief Process UART Rx event handling.
* May be triggered by an Rx timer expiration - less than max
* Rx bytes have arrived within the Rx max age time.
* May be set by failure to alloc max Rx byte-buffer for the DMA Rx -
* system resources are too low, so set flow control?
*
* @param none
*
* @return none
*/
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 );//读串口数据。第一位用于 保存SerialApp_SeqTx值
if ( !len ) // Length is not expected to ever be zero.
{
osal_mem_free( buf );//如果没有数据则释放内存
return;
}
if ( otaBuf ) //如果otaBuf 这个被占用的话则保存在otaBuf2中
{
otaBuf2 = buf;
otaLen2 = len;
}
else //否则保存在otaBuf 中
{
otaBuf = buf;
otaLen = len;
osal_set_event( SerialApp_TaskID, SERIALAPP_MSG_SEND_EVT );//设置发送数据事件。
}
}
看发送数据的函数:
*********************************************************************
* @fn SerialApp_SendData
*
* @brief Send data OTA.
*
* @param none
*
* @return none
*/
static void SerialApp_SendData( uint8 *buf, uint8 len )
{
afStatus_t stat; //状态
// Pre-pend sequence number to the start of the Rx buffer.
*buf = ++SerialApp_SeqTx; //序列号保存在buf指向缓存区内的第一位,上面已经讲过。
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();//释放缓存。
}
}
再接着看接收处理函数:
*********************************************************************
* @fn SerialApp_ProcessMSGCmd
*
* @brief Data message processor callback. This function processes
* any incoming data - probably from other devices. Based
* on the cluster ID, perform the intended action.
*
* @param pkt - pointer to the incoming message packet
*
* @return TRUE if the 'pkt' parameter is being used and will be freed later,
* FALSE otherwise.
*/
void SerialApp_ProcessMSGCmd( afIncomingMSGPacket_t *pkt )
{
uint8 stat;
uint8 seqnb;//序列号
uint8 delay;
switch ( pkt->clusterId )
{
// A message with a serial data block to be transmitted on the serial port.
case SERIALAPP_CLUSTERID1:
seqnb = pkt->cmd.Data[0];//得到数据包里的序列号 SerialApp_SeqRx保存收到的数据个数
// Keep message if not a repeat packet
if ( (seqnb > SerialApp_SeqRx) || // Normal
((seqnb < 0x80 ) && ( SerialApp_SeqRx > 0x80)) ) // Wrap-around
{
// Transmit the data on the serial port.
if ( HalUARTWrite( SERIAL_APP_PORT, pkt->cmd.Data+1,
(pkt->cmd.DataLength-1) ) )//发送数据到串口
{
// Save for next incoming message
SerialApp_SeqRx = seqnb;
stat = OTA_SUCCESS;
}
else
{
stat = OTA_SER_BUSY;//没写成功状态信号
}
}
else
{
stat = OTA_DUP_MSG; //有重复数据
}
// Select approproiate OTA flow-control delay.
delay = (stat == OTA_SER_BUSY) ? SERIALAPP_NAK_DELAY : SERIALAPP_ACK_DELAY;
// Build & send OTA response message.
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 );//启动响应重发事件
// Store the address for the timeout retry.
osal_memcpy(&SerialApp_RspDstAddr, &(pkt->srcAddr), sizeof( afAddrType_t ));
}
break;
// A response to a received serial data block.
case SERIALAPP_CLUSTERID2:
if ( (pkt->cmd.Data[1] == SerialApp_SeqTx) &&
((pkt->cmd.Data[0] == OTA_SUCCESS) ||
(pkt->cmd.Data[0] == OTA_DUP_MSG)) )
{
// Remove timeout waiting for response from other device.
osal_stop_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT );//当反馈信号回来时取消重发事件
FREE_OTABUF();
}
else
{
delay = BUILD_UINT16( pkt->cmd.Data[2], pkt->cmd.Data[3] );
// Re-start timeout according to delay sent from other device.
osal_start_timerEx( SerialApp_TaskID, SERIALAPP_MSG_RTRY_EVT, delay );//如果对方忙的话过一段时间后再继续发送数据。
}
break;
default:
break;
}
}