/******************************************************************************
*                                                                             *
*        **********                                                           *
*       ************                                                          *
*      ***        ***                                                         *
*     ***    ++    ***                                                        *
*     ***   +  +   ***                      CHIPCON                           *
*     ***   +                                                                 *
*     ***   +  +   ***                                                        *
*     ***    ++    ***                                                        *
*      ***        ***                                                         *
*       ************                                                          *
*        **********                                                           *
*                                                                             *
*******************************************************************************

Filename:     spp.c
Target:       cc2430
Author:       EFU
Revised:      16/12-2005
Revision:     1.0
******************************************************************************/
#include <string.h>
#include "cul.h"
#include <stdio.h>

// protos
void rxCallBack(void);
void ackTimeout(void);
BOOL ackReceived(BYTE sourceAddress);
void sendAck(SPP_RX_STRUCT* receivedPacket);
void waitForAck(void);


static DMA_DESC* dmaTx;          // pointer to the DMA descriptor for transmit.
static DMA_DESC* dmaRx;          // pointer to the DMA descriptor for receive.
static BYTE dmaNumberTx = 0;     // number indicating which DMA channel is used for transmit.
static BYTE dmaNumberRx = 0;     // number indicating which DMA channel is used for receive.
static BYTE myAddress;
volatile BYTE sppRxStatus = 0;
volatile BYTE  sppTxStatus = 0;
static BYTE pAckBuffer[7];
static SPP_TX_STRUCT* pAckData;
static volatile UINT8 retransmissionCounter;
static UINT8 ackTimerNumber;
static FUNCTION* rxCallBackFunction;


//-----------------------------------------------------------------------------
// See cul.h for a description of this function.
//-----------------------------------------------------------------------------
void sppSetRxCallBackFunction(FUNCTION* callBackFunction)
{
   rxCallBackFunction = callBackFunction;
} // Ends sppSetRxCallBackFunction()




void sendAck(SPP_RX_STRUCT* receivedPacket)
{
   RFD = SPP_HEADER_AND_FOOTER_LENGTH + SPP_ACK_LENGTH;
   RFD = receivedPacket->srcAddress;
   RFD = myAddress;
   RFD = ACK;
   RFD = 0;
   RFIF &= ~IRQ_TXDONE;
   ISTXON;
   while(!(RFIF & IRQ_TXDONE));

   return;
}

//------------------------------------------------------------------------------------------------------
// void rxCallBack(...)
//
//  Description:
//      This function is called by the interrupt routine when the Rx DMA channel
//      finishes the data transfer. The received packet's destination address
//      is checked. If not addressed to this node, or if the CRC value is not
//      correct, the packet is erased. An ACK is sent if the packet
//      tells to. A user defined callback function may is run if set (set
//      with setRxCallBackFunction())
//
//  Arguments:
//      void
//
//  Return value:
//      void
//-----------------------------------------------------------------------------
void rxCallBack(void)
{
   SPP_RX_STRUCT __xdata* receivedPacket;
   BYTE res = FALSE;

   if(RXFIFOCNT > 0)
   {
      ISFLUSHRX;
      ISFLUSHRX;
   }

   // Investigating the received packet.
   // Checking the destination address and that the CRC is OK.
   // The message is ACK'ed if it tells to.
   receivedPacket = (SPP_RX_STRUCT __xdata*) GET_DMA_DEST(dmaRx);
   receivedPacket->payloadLength = receivedPacket->payloadLength-SPP_HEADER_AND_FOOTER_LENGTH;

   if((receivedPacket->destAddress == myAddress) || (receivedPacket->destAddress == BROADCAST_ADDRESS))
   {
      if(receivedPacket->payload[receivedPacket->payloadLength+1] & 0x80)
      {
         if(receivedPacket->flags == ACK)
         {
            res = ackReceived(receivedPacket->srcAddress);
         }
         else
         {
            sppRxStatus = PACKET_RECEIVED;
            res = TRUE;
            if(receivedPacket->flags & DO_ACK)
            {
               sendAck(receivedPacket);
            }
            sppRxStatus = RX_COMPLETE;
            if(rxCallBackFunction)
            {
               rxCallBackFunction();
            }
         }
      }
   }

   if(res == FALSE)
   {
      ISFLUSHRX;
      ISFLUSHRX;

      // rearming DMA channel
      DMA_ARM_CHANNEL(dmaNumberRx);
      RFIM |= IRQ_SFD;
      sppRxStatus = RX_WAIT;
   }
   return;
}   // ends rxCallBack




//-----------------------------------------------------------------------------
// void ackTimeout(...)
//
//  Description:
//      This function resends a packet if it is not ACK'ed by the recipient
//      within _ACK_TIMEOUT_ m-seconds. The message is resent _ACK_RETRIES_ times.
//      If the message remains un-ACK'ed, transmission is aborted and spp TX
//      status is set to DEST_UNREACHABLE.
//
//  Arguments:
//      void
//
//  Return value:
//      void
//-----------------------------------------------------------------------------
void ackTimeout(void){
   culTimer4AdmClear(ackTimerNumber);

   if(pAckData != NULL)
   {
      if(retransmissionCounter < ACK_RETRIES)
      {
         // Resending the message.
         pAckData->flags |= RETRANSMISSION;

         TIMER4_RUN(FALSE);

         sppSend(pAckData);

         T4CNT = 0;
         TIMER4_RUN(TRUE);

         retransmissionCounter++;
      }
      else
      {
         // The packet has been resent too many times. Assuming that the node is unreacheble.
         pAckData = 0;
         retransmissionCounter = 0;
         sppTxStatus = DEST_UNREACHABLE;
         RFIM &= ~IRQ_FIFOP;
      }
   }
   return;
} // ends ackTimeout




//-----------------------------------------------------------------------------
// See cul.h for a description of this function.
//-----------------------------------------------------------------------------
BOOL sppInit(UINT32 frequency, BYTE address){
   BYTE res = 0;
   BOOL status = TRUE;

   sppSetAddress(address);

   // Clearing the states of the spp.
   sppTxStatus = TX_IDLE;
   sppRxStatus = RX_IDLE;
   retransmissionCounter = 0;
   ackTimerNumber = 0;
   pAckData = 0;

   // Clearing the RF interrupt flags and enable mask and enabling RF interrupts
   RFIF = 0;
   RFIM = 0;
   INT_SETFLAG(INUM_RF,INT_CLR);
   INT_ENABLE(INUM_RF,INT_ON);

   // Setting the frequency and initialising the radio
   res = halRfConfig(frequency);
   if(res == FALSE){
      status = FALSE;
   }

   // Setting the number of bytes to assert the FIFOP flag
   IOCFG0 = 7;

   INT_SETFLAG(INUM_RFERR, INT_CLR);
   INT_ENABLE(INUM_RFERR, INT_ON);

   // Flushing both Tx and Rx FiFo. The flush-Rx is issued twice to reset the SFD.
   // Calibrating the radio and turning on Rx to evaluate the CCA.
   SRXON;
   SFLUSHTX;
   SFLUSHRX;
   SFLUSHRX;
   STXCALN;
   ISSTART;


   // Using the timer 4 administrator to generate interrupt to check if a message is unacked...
   culTimer4AdmInit();

   // Initialising the DMA administrator
   culDmaInit();

   // Requesting a DMA channel for transmit data. No callback function is used. Instead the TX_DONE
   // interrupt is used to determine when a transfer is finished. Configuring the DMA channel for
   // transmit. The data address and length will be set prior to each specific transmission.
   dmaTx = culDmaAllocChannel(&dmaNumberTx, 0);
   if((dmaNumberTx == 0) || (dmaNumberTx > 4)){
      status = FALSE;
   }

   culDmaToRadio(dmaTx, 0, 0, FALSE);

   // Requesting a DMA channel for receiving data. Giving the address of the callback function.
   // Configuring the DMA channel for receive. The data address will be set prior to each specific
   // reception.
   dmaRx = culDmaAllocChannel(&dmaNumberRx, &rxCallBack);
   if((dmaNumberRx == 0) || (dmaNumberRx > 4)){
      status = FALSE;
   }
   culDmaFromRadio(dmaRx, 0, TRUE);

   // Making sure that none of the channels are armed.
   DMA_ABORT_CHANNEL(dmaNumberRx);
   DMA_ABORT_CHANNEL(dmaNumberTx);
   INT_ENABLE(INUM_DMA, INT_ON);

   return status;
} // ends sppInit




//-----------------------------------------------------------------------------
// See cul.h for a description of this function.
//-----------------------------------------------------------------------------
void sppSetAddress(BYTE address){
   myAddress = address;
} // Ends sppSetAddress()




// Internal function which enables the timeout when waiting for an ACK.
void waitForAck(void)
{
   ackTimerNumber = culTimer4AdmSet(ACK_TIMEOUT, &ackTimeout);
   SET_DMA_DEST(dmaRx,pAckBuffer);
   SET_DMA_LENGTH(dmaRx,7);

}   // Ends waitForAck()




//-----------------------------------------------------------------------------
// See cul.h for a description of this function.
//-----------------------------------------------------------------------------
BYTE sppSend(SPP_TX_STRUCT* pPacketPointer){
   BYTE res = TRUE;

   // If data is to be transmitted, the DMA is set up.
   if(pPacketPointer->payloadLength)
   {
      if (pPacketPointer->payloadLength > SPP_MAX_PAYLOAD_LENGTH)
      {
         res = TOO_LONG;
         sppTxStatus = TX_IDLE;
      }
      else
      {
         // Setting up the DMA
         DMA_ABORT_CHANNEL(dmaNumberTx);
         SET_DMA_SOURCE(dmaTx,pPacketPointer->payload);
         SET_DMA_LENGTH(dmaTx,pPacketPointer->payloadLength);
      }
   }

   // Proceed if the packet length is OK.
   if (res == TRUE)
   {
      // Flipping the sequence bit if the transfer is not a retransmission.
      if(!(pPacketPointer->flags & RETRANSMISSION))
      {
         pPacketPointer->flags ^= SEQUENCE_BIT;
      }

      // Clearing RF interrupt flags and enabling RF interrupts.
      if(FSMSTATE == 6 && RXFIFOCNT > 0)
      {
         ISFLUSHRX;
         ISFLUSHRX;
      }

      RFIF &= ~IRQ_TXDONE;
      RFIM &= ~IRQ_SFD;
      INT_SETFLAG(INUM_RF, INT_CLR);

      // Writing the total packet length, addresses and flags to Tx FiFo.
      // Transferring the payload if any.
      RFD = (pPacketPointer->payloadLength + SPP_HEADER_AND_FOOTER_LENGTH);
      RFD = pPacketPointer->destAddress;
      RFD = myAddress;
      RFD = pPacketPointer->flags;
      if(pPacketPointer->payloadLength)
      {
         DMA_ARM_CHANNEL(dmaNumberTx);
         DMA_START_CHANNEL(dmaNumberTx);
      }

      // If the RSSI value is not valid, enable receiver
      if(RSSIL == 0x80)
      {
         ISRXON;
         // Turning on Rx and waiting 320u-sec to make the RSSI value become valid.
         halWait(1);
      }

      //Transmitting
      ISTXONCCA;
      //if(TX_ACTIVE)
      if(FSMSTATE > 30)
      {
         // Asserting the status flag and enabling ACK reception if expected.
         sppTxStatus = TX_IN_PROGRESS;

         if(pPacketPointer->flags & DO_ACK)
         {
            pAckData = pPacketPointer;
            DMA_ABORT_CHANNEL(dmaNumberRx);
            waitForAck();
         }
         else
         {
            pAckData = NULL;
         }
         RFIM |= IRQ_TXDONE;
      }
      else
      {
         ISFLUSHTX;
         res = CHANNEL_BUSY;
         RFIM &= ~IRQ_TXDONE;
         // De-flipping the sequence bit.
         if(!(pPacketPointer->flags & RETRANSMISSION))
         {
            pPacketPointer->flags ^= SEQUENCE_BIT;
         }
      }
   }
   return res;
} // ends sppSend




// Internal function which is called when an ack is received.
// If the ACK is from the expected node, the retransmission of the packet is cancelled.
BOOL ackReceived(BYTE sourceAddress)
{
   BOOL res = FALSE;
   if(sourceAddress == pAckData->destAddress)
   {
      res = TRUE;
      culTimer4AdmClear(ackTimerNumber);
      sppTxStatus = TX_SUCCESSFUL;
      retransmissionCounter = 0;
      pAckData = 0;
   }

   return res;
}  //Ends ackReceived()



//-----------------------------------------------------------------------------
// See cul.h for a description of this function.
//-----------------------------------------------------------------------------
//#pragma vector=RF_VECTOR
//__interrupt void spp_rf_IRQ(void)
//{
//   BYTE enabledAndActiveInterrupt;
//
//   INT_GLOBAL_ENABLE(INT_OFF);
//   enabledAndActiveInterrupt = RFIF;
//   RFIF = 0x00;                        // Clear all interrupt flags
//   INT_SETFLAG(INUM_RF, INT_CLR);    // Clear MCU interrupt flag
//   enabledAndActiveInterrupt &= RFIM;
//
//   // Start of frame delimiter (SFD)
//   if(enabledAndActiveInterrupt & IRQ_SFD)
//   {
//      if(sppRxStatus == RX_WAIT)
//      {
//         sppRxStatus = RX_IN_PROGRESS;
//         RFIM &= ~IRQ_SFD;
//      }
//   }
//
//   // Transmission of a packet is finished. Enabling reception of ACK if required.
//   if(enabledAndActiveInterrupt & IRQ_TXDONE)
//   {
//      if(sppTxStatus == TX_IN_PROGRESS)
//      {
//         if(pAckData == NULL)
//         {
//            sppTxStatus = TX_SUCCESSFUL;
//         }
//         else
//         {
//            DMA_ARM_CHANNEL(dmaNumberRx);
//         }
//      }
//
//      // Clearing the tx done interrupt enable
//      RFIM &= ~IRQ_TXDONE;
//
//   }
//   INT_GLOBAL_ENABLE(INT_ON);
//}





//-----------------------------------------------------------------------------
// See cul.h for a description of this function.
//-----------------------------------------------------------------------------
void sppReceive(SPP_RX_STRUCT* pReceiveData){

   sppRxStatus = RX_WAIT;

   DMA_ABORT_CHANNEL(dmaNumberRx);
   // Setting the address to where the received data are to be written.
   SET_DMA_DEST(dmaRx,pReceiveData);
   SET_DMA_LENGTH(dmaRx,255);

   // Arming the DMA channel. The receiver will initate the transfer when a packet is received.
   DMA_ARM_CHANNEL(dmaNumberRx);

   if(FSMSTATE == 6 && RXFIFOCNT > 0)
   {
      ISFLUSHRX;
      ISFLUSHRX;
   }

   // Turning on the receiver
   ISRXON;

   return;
}



//-----------------------------------------------------------------------------
// See cul.h for a description of this function.
//-----------------------------------------------------------------------------
#pragma vector=RFERR_VECTOR
__interrupt static void rf_error_IRQ(void)
{
   INT_GLOBAL_ENABLE(INT_OFF);

   // If Rx overflow occurs, the Rx FiFo is reset.
   // The Rx DMA is reset and reception is started over.
   if(FSMSTATE == 17)
   {
      STOP_RADIO();
      ISFLUSHRX;
      ISFLUSHRX;
      DMA_ABORT_CHANNEL(dmaNumberRx);
      DMA_ARM_CHANNEL(dmaNumberRx);
      ISRXON;
   }
   else if(FSMSTATE == 56)
   {
      ISFLUSHTX;
   }

   INT_SETFLAG(INUM_RFERR,INT_CLR);

   INT_GLOBAL_ENABLE(INT_ON);
}
