ZigBee串口收发数据

ZigBee串口收发数据

   

本文转载自:http://blog.chinaunix.net/space.php?uid=20788636&do=blog&id=1841411

  串口接收发送数据有两种方式,一种是中断的模式,另一种是DMA方式,这里主要以中断的方式,来看一下使用串口来发送,接收数据的整个流程。这里以SerialApp例程为例子。

  在mian函数中的调用HalDriverInit();函数,在函数中初始化串口,主要是配置管脚和DMA通道
voidHalDriverInit (void)
{
...................................
#if(defined HAL_UART) && (HAL_UART ==TRUE)
  HalUARTInit();
#endif
....................................
}
 从程序中可以看出要想使用协议栈中串口,初始化串口必须定义HAL_UART和HAL_UART TRUE 在hal_board_cfg.h文件中。
#ifndef HAL_UART
#if (defined ZAPP_P1) ||(defined ZAPP_P2) || (defined ZTOOL_P1) || (definedZTOOL_P2)
#define HAL_UARTTRUE
#else
#define HAL_UARTFALSE
#endif
#endif
   然后在osal_start_system()开始系统后,会调用Hal_ProcessPoll()来读取时间和串口。
   在CC2430的数据手册中有这样一段话。
Data reception on theUART is initiatedwhen a 1 is written to the UxCSR.RE bit
The UART will thensearch for a valid start bit on the RXDx input pin and setthe
UxCSR.ACTIVE bit high.When a validstart bit has been detected thereceived  byte is shifted into the receiveregister .The  UxCSR.RX_BYTE bit is set and areceive interrupt is generated when the operation has completed.The received data byte is available through the UxBUF register.When UxBUF is read,  UxCSR.RX_BYTE is cleared byhardware.
   当有数据接收时,UxCSR.RE位将被置1,然后,UART将在RXDx的输入引脚上查找一个有效的开始位,当找到这个开始位时,将设置UxCSR.ACTIVE位为高电平。当一个有效的开始位被查找到,收到的字节将被移动到接收寄存器中。然后,UxCSR.RX_BYTE位设为1.并且,当这个接收操作完成后接收中断会被产生。接收到的数据可以通过操作UxBUF寄存器,当UxBUF寄存器的数据被读出后,UxCSR.RX_BYTE位被硬件清除。串口发生中断首先调用中断的处理函数,这个是接收的中断函数。
#ifHAL_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
    #defineSERIAL_APP_RX_MAX  128
 #else
   
    #defineSERIAL_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()
{
 
 HalTimerTick();
 
#if (defined HAL_UART)&& (HAL_UART == TRUE)
 HalUARTPoll();
#endif
}
下面是HalUARTPoll();函数的源代码,在这里有对接收到的数据进行处理的程序。
void HalUARTPoll( void)
{
#if ( HAL_UART_0_ENABLE |HAL_UART_1_ENABLE )
  static uint8tickShdw;
  uartCfg_t*cfg;
  uint8tick;
 
#ifHAL_UART_0_ENABLE
 //当发生串口接收中断时cfg0就会改变,如果串口没有数据输入cfg0为空,当接收到数据时cfg0将在串口中断服务程序中被改变
  if ( cfg0)
 {
    cfg =cfg0;
 }
#endif
#ifHAL_UART_1_ENABLE
  if ( cfg1)
 {
    cfg =cfg1;
 }
#endif
 
  // Use the LSB of the sleep timer (ST0 must be read firstanyway).
//系统上电后,睡眠定时器就会自动启动做自增计数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
 
   
 
     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 );
   }
   }
 
#ifHAL_UART_0_ENABLE
    if ( cfg== cfg0 )
   {
#ifHAL_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))
 {
//这里是针对流控制的,如果又有新的数据接收到了那么就要重置超时时间(超时时间由睡眠定时器来控制),而且需要把已经读出的数据数目减去!
    // Ifanything received, reset the Rx idle timer.
    if (cfg->rxCnt != cnt )
   {
     cfg->rxTick = HAL_UART_RX_IDLE;
     cfg->rxCnt = cnt;
   }
 
   
    if (cfg->rxCnt >=(cfg->rxMax - SAFE_RX_MIN) )
   {
     RX_STOP_FLOW( cfg );
   }
 }
}
#endif
pollISR()函数主要作用就是设置rxTick和rxCn,
//关于安全界限,在程序中有下面一段:
//如果声明了流控制,为保证数据的正确接收需要在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函数。程序中定义了两个串口接收缓冲区:otaBufotaBuf2.otaBuf中无数据时,处于空闲状态时,由otaBuf接收串口数据;当otaBuf中保留有数据时,下等待接收节点发送接收数据响应或由于某些正在重新给接收节点发送数据时,可通过otaBuf2接收数据,当otaBufotaBuf2都没有处于空闲状态时,说明数据没有及时发送给接收节点,发生了数据累积,缓冲区被占用,需要进行流量控制,所以直接退出接收回调函数,暂不接收数据。
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)  // Length is not expectedto ever be zero.
 {
   osal_mem_free( buf );
   return;
 }
 
 
  if (otaBuf )//otaBuf正在被占用
 {
   otaBuf2 = buf;//otaBuf2接收数据
    otaLen2= len;
 }
 else
 {
   otaBuf = buf;//otaBuf接收数据
    otaLen =len;
   
   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_tstat;
 
  // Pre-pendsequence 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 )
{
  uint8stat;
  uint8seqnb;
  uint8delay;
 
  switch (pkt->clusterId )
 {
  // A messagewith a serial data block to be transmitted on the serialport.
 //接收节点收到的接收数据命令,
  caseSERIALAPP_CLUSTERID1:
    seqnb =pkt->cmd.Data[0];
 
   // Keep message if not a repeatpacket
    if ((seqnb > SerialApp_SeqRx)||                   // Normal
       ((seqnb < 0x80 ) &&( SerialApp_SeqRx > 0x80)) ) //Wrap-around
   {
     // Transmit the data on the serialport.接收到的发送到串口
     if ( HalUARTWrite( SERIAL_APP_PORT,pkt->cmd.Data+1,
                                        (pkt->cmd.DataLength-1) ) )
     {
       // Save for next incomingmessage
       SerialApp_SeqRx = seqnb;
 
       stat = OTA_SUCCESS;
     }
     else
     {
       stat = OTA_SER_BUSY;
     }
   }
   else
   {
     stat = OTA_DUP_MSG;
   }
 
   // Select approproiate OTA flow-controldelay.
    delay =(stat == OTA_SER_BUSY) ? SERIALAPP_NAK_DELAY :SERIALAPP_ACK_DELAY;
 
   // Build & send OTA responsemessage.发送响应消息
   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;
 
  // Aresponse to a received serial data block.接收到接收数据响应命令
  caseSERIALAPP_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 fromother 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 otherdevice.
     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协议信息的传递有两种方式:消息和命令,消息长度不限,命令的大小则严格规定
       caseAF_INCOMING_MSG_CMD:    
//执行发送过来消息命令的回调函数
SerialApp_ProcessMSGCmd(MSGpkt );
         break
       default:
         break;
     }
 
     osal_msg_deallocate( (uint8 *)MSGpkt );  //Release the memory.
   }
 
   // Return unprocessedevents
    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 );
 }
 
#ifSERIAL_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,表明接收节点将数据成功发送到串口,就释放缓存区,等待串口接收下一包数据。
  下面是串口的接受和发送的流程图:
 

你可能感兴趣的:(mcu)