學習z-stack協議棧的serialApp例子(1)

支持原創,如需轉載,請註明地址: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;
  }
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章