#if HAL_UART_0_ENABLE
HAL_ISR_FUNCTION( halUart0RxIsr, URX0_VECTOR )
{
cfg0->rxBuf[cfg0->rxHead] = U0DBUF;
if ( cfg0->rxHead == cfg0->rxMax )
{
cfg0->rxHead = 0;
}
else
{
cfg0->rxHead++;
}
}
#endif
该中断函数主要是把U0DBUF寄存器,也就是接收到数据的寄存器,把数据读取来放到UART的结构体中的,cfg0->rxBuf[],中,这个数组的内存分配是在HalUARTOpen()函数中。
SerialApp.c中有下面的定义
#if !defined( SERIAL_APP_RX_MAX )
#if (defined( HAL_UART_DMA )) && HAL_UART_DMA
#define SERIAL_APP_RX_MAX 128
#else
/* The generic safe Rx minimum is 48, but if you know your PC App will not
* continue to send more than a byte after receiving the ~CTS, lower max
* here and safe min in _hal_uart.c to just 8.
*/
#define SERIAL_APP_RX_MAX 64
#endif
#endif
SerialApp_Init()函数中有下面的赋值,
uartConfig.rx.maxBufSize = SERIAL_APP_RX_MAX;
HalUARTOpen()函数中有下面的赋值:所以其cfg->rxMax=128,
cfg->rxMax = config->rx.maxBufSize;
其中rxHead这个参数始终指向像一个参数被存放到rxBuf的位置。因为硬件串口缓存器U0DBUF只能存放一个字节,如果不及时把这个接收到的转移出去,那么就会被下一个到来的字节覆盖掉,所以rxHead变量就指向了这个存放的地址,当然是基于定义的rxBuf存储空间。
而if ( cfg0->rxHead == cfg0->rxMax )这一句判断也说明的很清楚,一旦这个计数达到了定义的最大接收数量,也就是说已经把rxBuf存储空间占满了,那么就不能在继续存放了。
中断函数执行完后,就应该跳到发生中断时执行的地方,这时程序继续执行,然后在osal_start_system()开始系统后,会循环调用Hal_ProcessPoll()来读取时间和串口,
void Hal_ProcessPoll ()
{
/* Timer Poll */
HalTimerTick();
/* UART Poll */
#if (defined HAL_UART) && (HAL_UART == TRUE)
HalUARTPoll();
#endif
}
下面是HalUARTPoll();函数的源代码,在这里有对接收到的数据进行处理的程序。
void HalUARTPoll( void )
{
#if ( HAL_UART_0_ENABLE | HAL_UART_1_ENABLE )
static uint8 tickShdw;
uartCfg_t *cfg;
uint8 tick;
#if HAL_UART_0_ENABLE
//当发生串口接收中断时cfg0就会改变,如果串口没有数据输入cfg0为空,当接收到数据时cfg0将在串口中断服务程序中被改变
if ( cfg0 )
{
cfg = cfg0;
}
#endif
#if HAL_UART_1_ENABLE
if ( cfg1 )
{
cfg = cfg1;
}
#endif
// Use the LSB of the sleep timer (ST0 must be read first anyway).
//系统上电后,睡眠定时器就会自动启动做自增计数ST0即睡眠定时器启动到现在计算值的最低8位
tick = ST0 - tickShdw;
tickShdw = ST0;
//下面是一个无限循环
do
{
//------------发送超时时间
if ( cfg->txTick > tick )
{
cfg->txTick -= tick;
}
else
{
cfg->txTick = 0;
}
//---------------------接收超时时间
if ( cfg->rxTick > tick )
{
cfg->rxTick -= tick;
}
else
{
cfg->rxTick = 0;
}
//是使用DMA方式还是使用中断方式
#if HAL_UART_ISR
#if HAL_UART_DMA
if ( cfg->flag & UART_CFG_DMA )
{
pollDMA( cfg );
}
else//中断方式
#endif
{
pollISR( cfg );
}
#elif HAL_UART_DMA
pollDMA( cfg );
#endif
/* The following logic makes continuous callbacks on any eligible flag
* until the condition corresponding to the flag is rectified.
* So even if new data is not received, continuous callbacks are made.
//如果接收缓存中有数据,当接收数据时rxHead会增计数,当读取数据时rxTail会增计数,两个标志的初始值都为0,所以这两个标志的差值就指示了缓存中有多少的数据*/
if ( cfg->rxHead != cfg->rxTail ) //不相等表示有数据
{
uint8 evt;
if ( cfg->rxHead >= (cfg->rxMax - SAFE_RX_MIN) )
{
//已保存的数据已经超过了安全界限,发送接收满事件
evt = HAL_UART_RX_FULL;
}
else if ( cfg->rxHigh && (cfg->rxHead >= cfg->rxHigh) )
{
//rxBuf[ ]接收到预设值(默认80字节),则触发事件,为什么是80,在上一篇转载的文章中有介绍,这里重点关注执行的流程。
evt = HAL_UART_RX_ABOUT_FULL;
}
else if ( cfg->rxTick == 0 )
{
//超时事件
evt = HAL_UART_RX_TIMEOUT;
}
else
{
evt = 0;
}
//如果发生事件,并且配置了回调函数则调用回调函数
if ( evt && cfg->rxCB )
{
//(cfg->flag & UART_CFG_U1F)!=0)判读是那个串口,如果是串口1则为1,否则为0
cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt );
}
}
#if HAL_UART_0_ENABLE
if ( cfg == cfg0 )
{
#if HAL_UART_1_ENABLE
if ( cfg1 )
{
cfg = cfg1;
}
else
#endif
break;
}
else
#endif
break;
} while ( TRUE );
#else
return;
#endif
}
说明:(1)下面我们看一下pollISR()函数
static void pollISR( uartCfg_t *cfg )
{
//计算rxBuf[]中还有多少数据没有读出(以字节为单位)
uint8 cnt = UART_RX_AVAIL( cfg );
//如果串口没有接收到数据,也就是说没有发生过串口接收中断,那么cfg应为是为空的,则cnt=0如果发生了串口中断,则cnt计算出串口缓存中还有多少数据没有读出,这个缓存并不是硬件寄存器的缓存,而是程序中开辟一段空间
if ( !(cfg->flag & UART_CFG_RXF) )
{
//这里是针对流控制的,如果又有新的数据接收到了那么就要重置超时时间(超时时间由睡眠定时器来控制),而且需要把已经读出的数据数目减去!
// If anything received, reset the Rx idle timer.
if ( cfg->rxCnt != cnt )
{
cfg->rxTick = HAL_UART_RX_IDLE;
cfg->rxCnt = cnt;
}
/* It is necessary to stop Rx flow in advance of a full Rx buffer because
bytes can keep coming while sending H/W fifo flushes.
//当接收缓存超过安全界限的时候停止RX流*/
if ( cfg->rxCnt >= (cfg->rxMax - SAFE_RX_MIN) )
{
RX_STOP_FLOW( cfg );
}
}
}
#endif
pollISR()函数主要作用就是设置rxTick和rxCn,
//关于安全界限,在程序中有下面一段:
/* Need to leave enough of the Rx buffer free to handle the incoming bytes
* after asserting flow control, but before the transmitter has obeyed it.
* At the max expected baud rate of 115.2k, 16 bytes will only take ~1.3 msecs,
* but at the min expected baud rate of 38.4k, they could take ~4.2 msecs.
* SAFE_RX_MIN and DMA_RX_DLY must both be consistent according to
* the min & max expected baud rate.
*/
//如果声明了流控制,为保证数据的正确接收需要在RX缓存区中预留出足够的空间。CC2430可以使用的最大串口波特率为115.2k。这个安全界限的数字跟使用的波特率还有串口tick有关。具体参考《Z-STACK问题之串口结构uartCfg_t乱说》文章。
可以看到,在初始化时rxHead=rxTail=0,如果发生接收中断,在中断服务函数中把U0DBUF寄存器中的数据传送到rxbuf[]中,这时rxHead和rxTail的值不在相等,其中,rxHead是rxBuf[]接收到数据的个数,rxTail是rxBuf[]移出的数据个数,再根据两者的差值,判断具体的事件evt。然后,根据evt和设置的回调函数,通过cfg->rxCB调用相应的回调函数。代码也是体显在下面一句。
cfg->rxCB( ((cfg->flag & UART_CFG_U1F)!=0), evt );
第一个参数主要是判断,是UART1还是UART0.第二个参数是触发的事件类型,那个个回调函数,具体是指向函数呢?
首先,我们在void SerialApp_Init( uint8 task_id )初始化函数中,对串口进行了配置,其中下面两句中有关于回调函数的。
#else
uartConfig.callBackFunc = rxCB;
#endif
HalUARTOpen (SERIAL_APP_PORT, &uartConfig);
其中,在HalUARTOpen()函数中,有下面的一条语句,
uint8 HalUARTOpen( uint8 port, halUARTCfg_t *config )
{
...................
cfg->rxCB = config->callBackFunc;
...................
}
也就是调用下面的rxCB函数。程序中定义了两个串口接收缓冲区:otaBuf上otaBuf2.当otaBuf中无数据时,处于空闲状态时,由otaBuf接收串口数据;当otaBuf中保留有数据时,下等待接收节点发送接收数据响应或由于某些正在重新给接收节点发送数据时,可通过otaBuf2接收数据,当otaBuf和otaBuf2都没有处于空闲状态时,说明数据没有及时发送给接收节点,发生了数据累积,缓冲区被占用,需要进行流量控制,所以直接退出接收回调函数,暂不接收数据。
static void rxCB( uint8 port, uint8 event )
{
uint8 *buf, len;
/* While awaiting retries/response, only buffer 1 next buffer: otaBuf2.
* If allow the DMA Rx to continue to run, allocating Rx buffers, the heap
* will become so depleted that an incoming OTA response cannot be received.
* When the Rx data available is not read, the DMA Rx Machine automatically
* sets flow control off - it is automatically re-enabled upon Rx data read.
* When the back-logged otaBuf2 is sent OTA, an Rx data read is scheduled.
*/
if ( otaBuf2 ) //缓冲区被占用
{
return;
}
if ( !(buf = osal_mem_alloc( SERIAL_APP_RX_CNT )) )
{
return;
}
/* HAL UART Manager will turn flow control back on if it can after read.
* Reserve 1 byte for the 'sequence number'.这里的SERIAL_APP_RX_CNT为80
这里为什么是80,上篇文章中也有分析*/
len = HalUARTRead( port, buf+1, SERIAL_APP_RX_CNT-1 );
if ( !len ) // Length is not expected to ever be zero.
{
osal_mem_free( buf );
return;
}
/* If the local global otaBuf is in use, then either the response handshake
* is being awaited or retries are being attempted. When the wait/retries
* process has been exhausted, the next OTA msg will be attempted from
* otaBuf2, if it is not NULL. otaBuf正在使用,则就使用otaBuf2
*/
if ( otaBuf ) //otaBuf正在被占用
{
otaBuf2 = buf; //otaBuf2接收数据
otaLen2 = len;
}
else
{
otaBuf = buf; //otaBuf接收数据
otaLen = len;
/* Don't call SerialApp_SendData() from here in the callback function.
* Set the event so SerialApp_SendData() runs during this task's time slot.
产生发送数据事件*/
osal_set_event( SerialApp_TaskID, SERIALAPP_MSG_SEND_EVT );
}
}
#endif
在事件处理函数中,有下面的判断。
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_SendData()函数的源代码,调用AF_DataRequest(),通过OTA发送数据。由于在数据包之前增加了序列号SerialApp_SeqTx,多次重发的数据不会被接收节点重复发送到串口。
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;
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();//重发的次数
}
}
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];
// 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;
}
}
UINT16 SerialApp_ProcessEvent( uint8 task_id, UINT16 events )
{
if ( events & SYS_EVENT_MSG ) //ZDO层接收到注册过的消息
{
afIncomingMSGPacket_t *MSGpkt;
while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive(
SerialApp_TaskID )) )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
SerialApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
case KEY_CHANGE:
SerialApp_HandleKeys( ((keyChange_t *)MSGpkt)->state,
((keyChange_t *)MSGpkt)->keys );
break;
//接收到命令,然后执行,zigbee协议信息的传递有两种方式:消息和命令,消息长度不限,命令的大小则严格规定
case AF_INCOMING_MSG_CMD:
//执行发送过来消息命令的回调函数
SerialApp_ProcessMSGCmd( MSGpkt );
break
default:
break;
}
osal_msg_deallocate( (uint8 *)MSGpkt ); // Release the memory.
}
// Return unprocessed events
return ( events ^ SYS_EVENT_MSG );
}
//发送数据的事件,这里是串口通过CC2430发送数据到另一个设备
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();
}
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.
}
在串口通信中设置了多个重发机制,增加数据通信的可靠性,首先是利用重发数据事件SERIALAPP_MSG_RTRY_EVT重发数据,重发的次数由rtyCnt设定。由于在数据包之前增加了序列号SerialApp_SeqTx,多次生发的数据不会被接收节点重复发送到串口。别外,加入了数据接收响应机制,发送节点在发送完数据后,等待接收节点返回接收数据响应,收到返回接收数据响应的命令SERIALAPP_CLUSTERID2:后,判断信息包中的接收状态参数。若接收状态为OTA_DUP_MSG,表明接收节点串口繁忙,应启动重发机制,延时后产生重发数据事件,若接收状态为OTA_SUCCESS,表明接收节点将数据成功发送到串口,就释放缓存区,等待串口接收下一包数据。