]> oss.titaniummirror.com Git - tinyos-2.x.git/blobdiff - tos/chips/cc2420/transmit/CC2420TransmitP.nc
New CC2420 architecture in place for 2.0.2 release. See the README.txt file for...
[tinyos-2.x.git] / tos / chips / cc2420 / transmit / CC2420TransmitP.nc
diff --git a/tos/chips/cc2420/transmit/CC2420TransmitP.nc b/tos/chips/cc2420/transmit/CC2420TransmitP.nc
new file mode 100644 (file)
index 0000000..4fe02eb
--- /dev/null
@@ -0,0 +1,702 @@
+/*
+ * Copyright (c) 2005-2006 Arch Rock Corporation
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the
+ *   distribution.
+ * - Neither the name of the Arch Rock Corporation nor the names of
+ *   its contributors may be used to endorse or promote products derived
+ *   from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * ARCHED ROCK OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE
+ */
+
+/**
+ * @author Jonathan Hui <jhui@archrock.com>
+ * @author David Moss
+ * @author Jung Il Choi Initial SACK implementation
+ * @version $Revision$ $Date$
+ */
+
+#include "CC2420.h"
+#include "crc.h"
+#include "message.h"
+
+module CC2420TransmitP {
+
+  provides interface Init;
+  provides interface StdControl;
+  provides interface CC2420Transmit as Send;
+  provides interface RadioBackoff;
+  provides interface RadioTimeStamping as TimeStamp;
+  provides interface ReceiveIndicator as EnergyIndicator;
+  provides interface ReceiveIndicator as ByteIndicator;
+  
+  uses interface Alarm<T32khz,uint32_t> as BackoffTimer;
+  uses interface CC2420Packet;
+  uses interface CC2420PacketBody;
+  uses interface GpioCapture as CaptureSFD;
+  uses interface GeneralIO as CCA;
+  uses interface GeneralIO as CSN;
+  uses interface GeneralIO as SFD;
+
+  uses interface Resource as SpiResource;
+  uses interface ChipSpiResource;
+  uses interface CC2420Fifo as TXFIFO;
+  uses interface CC2420Ram as TXFIFO_RAM;
+  uses interface CC2420Register as TXCTRL;
+  uses interface CC2420Strobe as SNOP;
+  uses interface CC2420Strobe as STXON;
+  uses interface CC2420Strobe as STXONCCA;
+  uses interface CC2420Strobe as SFLUSHTX;
+  uses interface CC2420Register as MDMCTRL1;
+
+  uses interface CC2420Receive;
+  uses interface Leds;
+}
+
+implementation {
+
+  typedef enum {
+    S_STOPPED,
+    S_STARTED,
+    S_LOAD,
+    S_SAMPLE_CCA,
+    S_BEGIN_TRANSMIT,
+    S_SFD,
+    S_EFD,
+    S_ACK_WAIT,
+    S_LOAD_CANCEL,
+    S_TX_CANCEL,
+    S_CCA_CANCEL,
+  } cc2420_transmit_state_t;
+
+  // This specifies how many jiffies the stack should wait after a
+  // TXACTIVE to receive an SFD interrupt before assuming something is
+  // wrong and aborting the send. There seems to be a condition
+  // on the micaZ where the SFD interrupt is never handled.
+  enum {
+    CC2420_ABORT_PERIOD = 320
+  };
+  
+  norace message_t *m_msg;
+  
+  norace bool m_cca;
+  
+  norace uint8_t m_tx_power;
+  
+  cc2420_transmit_state_t m_state = S_STOPPED;
+  
+  bool m_receiving = FALSE;
+  
+  uint16_t m_prev_time;
+  
+  /** Byte reception/transmission indicator */
+  bool sfdHigh;
+  
+  /** Let the CC2420 driver keep a lock on the SPI while waiting for an ack */
+  bool abortSpiRelease;
+  
+  /** Total CCA checks that showed no activity before the NoAck LPL send */
+  norace int8_t totalCcaChecks;
+  
+  /** The initial backoff period */
+  norace uint16_t myInitialBackoff;
+  
+  /** The congestion backoff period */
+  norace uint16_t myCongestionBackoff;
+  
+
+  /***************** Prototypes ****************/
+  error_t send( message_t *p_msg, bool cca );
+  error_t resend( bool cca );
+  void loadTXFIFO();
+  void attemptSend();
+  void congestionBackoff();
+  error_t acquireSpiResource();
+  error_t releaseSpiResource();
+  void signalDone( error_t err );
+  
+  
+  /***************** Init Commands *****************/
+  command error_t Init.init() {
+    call CCA.makeInput();
+    call CSN.makeOutput();
+    call SFD.makeInput();
+    return SUCCESS;
+  }
+
+  /***************** StdControl Commands ****************/
+  command error_t StdControl.start() {
+    atomic {
+      call CaptureSFD.captureRisingEdge();
+      m_state = S_STARTED;
+      m_receiving = FALSE;
+      abortSpiRelease = FALSE;
+      m_tx_power = 0;
+    }
+    return SUCCESS;
+  }
+
+  command error_t StdControl.stop() {
+    atomic {
+      m_state = S_STOPPED;
+      call BackoffTimer.stop();
+      call CaptureSFD.disable();
+      call SpiResource.release();  // REMOVE
+      call CSN.set();
+    }
+    return SUCCESS;
+  }
+
+
+  /**************** Send Commands ****************/
+  async command error_t Send.send( message_t* p_msg, bool useCca ) {
+    return send( p_msg, useCca );
+  }
+
+  async command error_t Send.resend(bool useCca) {
+    return resend( useCca );
+  }
+
+  async command error_t Send.cancel() {
+    atomic {
+      switch( m_state ) {
+      case S_LOAD:
+        m_state = S_LOAD_CANCEL;
+        break;
+        
+      case S_SAMPLE_CCA:
+        m_state = S_CCA_CANCEL;
+        break;
+        
+      case S_BEGIN_TRANSMIT:
+        m_state = S_TX_CANCEL;
+        break;
+        
+      default:
+        // cancel not allowed while radio is busy transmitting
+        return FAIL;
+      }
+    }
+
+    return SUCCESS;
+  }
+
+  async command error_t Send.modify( uint8_t offset, uint8_t* buf, 
+                                     uint8_t len ) {
+    call CSN.clr();
+    call TXFIFO_RAM.write( offset, buf, len );
+    call CSN.set();
+    return SUCCESS;
+  }
+  
+  /***************** Indicator Commands ****************/
+  command bool EnergyIndicator.isReceiving() {
+    return !(call CCA.get());
+  }
+  
+  command bool ByteIndicator.isReceiving() {
+    bool high;
+    atomic high = sfdHigh;
+    return high;
+  }
+  
+
+  /***************** RadioBackoff Commands ****************/
+  /**
+   * Must be called within a requestInitialBackoff event
+   * @param backoffTime the amount of time in some unspecified units to backoff
+   */
+  async command void RadioBackoff.setInitialBackoff(uint16_t backoffTime) {
+    myInitialBackoff = backoffTime + 1;
+  }
+  
+  /**
+   * Must be called within a requestCongestionBackoff event
+   * @param backoffTime the amount of time in some unspecified units to backoff
+   */
+  async command void RadioBackoff.setCongestionBackoff(uint16_t backoffTime) {
+    myCongestionBackoff = backoffTime + 1;
+  }
+  
+  async command void RadioBackoff.setCca(bool useCca) {
+  }
+  
+  
+  
+  /**
+   * The CaptureSFD event is actually an interrupt from the capture pin
+   * which is connected to timing circuitry and timer modules.  This
+   * type of interrupt allows us to see what time (being some relative value)
+   * the event occurred, and lets us accurately timestamp our packets.  This
+   * allows higher levels in our system to synchronize with other nodes.
+   *
+   * Because the SFD events can occur so quickly, and the interrupts go
+   * in both directions, we set up the interrupt but check the SFD pin to
+   * determine if that interrupt condition has already been met - meaning,
+   * we should fall through and continue executing code where that interrupt
+   * would have picked up and executed had our microcontroller been fast enough.
+   */
+  async event void CaptureSFD.captured( uint16_t time ) {
+    atomic {
+      switch( m_state ) {
+        
+      case S_SFD:
+        m_state = S_EFD;
+        sfdHigh = TRUE;
+        call CaptureSFD.captureFallingEdge();
+        signal TimeStamp.transmittedSFD( time, m_msg );
+        if ( (call CC2420PacketBody.getHeader( m_msg ))->fcf & ( 1 << IEEE154_FCF_ACK_REQ ) ) {
+          // This is an ack packet, don't release the chip's SPI bus lock.
+          abortSpiRelease = TRUE;
+        }
+        releaseSpiResource();
+        call BackoffTimer.stop();
+
+        
+        if ( ( ( (call CC2420PacketBody.getHeader( m_msg ))->fcf >> IEEE154_FCF_FRAME_TYPE ) & 7 ) == IEEE154_TYPE_DATA ) {
+          (call CC2420PacketBody.getMetadata( m_msg ))->time = time;
+        }
+        
+        if ( call SFD.get() ) {
+          break;
+        }
+        /** Fall Through because the next interrupt was already received */
+        
+      case S_EFD:
+        sfdHigh = FALSE;
+        call CaptureSFD.captureRisingEdge();
+        
+        if ( (call CC2420PacketBody.getHeader( m_msg ))->fcf & ( 1 << IEEE154_FCF_ACK_REQ ) ) {
+          m_state = S_ACK_WAIT;
+          call BackoffTimer.start( CC2420_ACK_WAIT_DELAY );
+        } else {
+          signalDone(SUCCESS);
+        }
+        
+        if ( !call SFD.get() ) {
+          break;
+        }
+        /** Fall Through because the next interrupt was already received */
+        
+      default:
+        if ( !m_receiving ) {
+          sfdHigh = TRUE;
+          call CaptureSFD.captureFallingEdge();
+          signal TimeStamp.receivedSFD( time );
+          call CC2420Receive.sfd( time );
+          m_receiving = TRUE;
+          m_prev_time = time;
+          if ( call SFD.get() ) {
+            // wait for the next interrupt before moving on
+            return;
+          }
+        }
+        
+        sfdHigh = FALSE;
+        call CaptureSFD.captureRisingEdge();
+        m_receiving = FALSE;
+        if ( time - m_prev_time < 10 ) {
+          call CC2420Receive.sfd_dropped();
+        }
+        break;
+      
+      }
+    }
+  }
+
+  /***************** ChipSpiResource Events ****************/
+  async event void ChipSpiResource.releasing() {
+    if(abortSpiRelease) {
+      call ChipSpiResource.abortRelease();
+    }
+  }
+  
+  
+  /***************** CC2420Receive Events ****************/
+  /**
+   * If the packet we just received was an ack that we were expecting,
+   * our send is complete.
+   */
+  async event void CC2420Receive.receive( uint8_t type, message_t* ack_msg ) {
+    cc2420_header_t* ack_header;
+    cc2420_header_t* msg_header;
+    cc2420_metadata_t* msg_metadata;
+    uint8_t* ack_buf;
+    uint8_t length;
+
+    if ( type == IEEE154_TYPE_ACK ) {
+      ack_header = call CC2420PacketBody.getHeader( ack_msg );
+      msg_header = call CC2420PacketBody.getHeader( m_msg );
+
+      
+      if ( m_state == S_ACK_WAIT && msg_header->dsn == ack_header->dsn ) {
+        call BackoffTimer.stop();
+        
+        msg_metadata = call CC2420PacketBody.getMetadata( m_msg );
+        ack_buf = (uint8_t *) ack_header;
+        length = ack_header->length;
+        
+        msg_metadata->ack = TRUE;
+        msg_metadata->rssi = ack_buf[ length - 1 ];
+        msg_metadata->lqi = ack_buf[ length ] & 0x7f;
+        signalDone(SUCCESS);
+      }
+    }
+  }
+
+  /***************** SpiResource Events ****************/
+  event void SpiResource.granted() {
+    uint8_t cur_state;
+
+    atomic {
+      cur_state = m_state;
+    }
+
+    switch( cur_state ) {
+    case S_LOAD:
+      loadTXFIFO();
+      break;
+      
+    case S_BEGIN_TRANSMIT:
+      attemptSend();
+      break;
+      
+    case S_LOAD_CANCEL:
+    case S_CCA_CANCEL:
+    case S_TX_CANCEL:
+      call CSN.clr();
+      call SFLUSHTX.strobe();
+      call CSN.set();
+      releaseSpiResource();
+      atomic {
+        m_state = S_STARTED;
+      }
+      break;
+      
+    default:
+      releaseSpiResource();
+      break;
+    }
+  }
+  
+  /***************** TXFIFO Events ****************/
+  /**
+   * The TXFIFO is used to load packets into the transmit buffer on the
+   * chip
+   */
+  async event void TXFIFO.writeDone( uint8_t* tx_buf, uint8_t tx_len,
+                                     error_t error ) {
+
+    call CSN.set();
+    if ( m_state == S_LOAD_CANCEL ) {
+      atomic {
+        call CSN.clr();
+        call SFLUSHTX.strobe();
+        call CSN.set();
+      }
+      releaseSpiResource();
+      m_state = S_STARTED;
+            
+    } else if ( !m_cca ) {
+      atomic {
+        if (m_state == S_LOAD_CANCEL) {
+          m_state = S_TX_CANCEL;
+        } else {
+          m_state = S_BEGIN_TRANSMIT;
+        }
+      }
+      attemptSend();
+      
+    } else {
+      releaseSpiResource();
+      atomic {
+        if (m_state == S_LOAD_CANCEL) {
+          m_state = S_CCA_CANCEL;
+        } else {
+          m_state = S_SAMPLE_CCA;
+        }
+      }
+      
+      signal RadioBackoff.requestInitialBackoff(m_msg);
+      call BackoffTimer.start(myInitialBackoff);
+    }
+  }
+
+  
+  async event void TXFIFO.readDone( uint8_t* tx_buf, uint8_t tx_len, 
+      error_t error ) {
+  }
+  
+  
+  /***************** Timer Events ****************/
+  /**
+   * The backoff timer is mainly used to wait for a moment before trying
+   * to send a packet again. But we also use it to timeout the wait for
+   * an acknowledgement, and timeout the wait for an SFD interrupt when
+   * we should have gotten one.
+   */
+  async event void BackoffTimer.fired() {
+    atomic {
+      switch( m_state ) {
+        
+      case S_SAMPLE_CCA : 
+        // sample CCA and wait a little longer if free, just in case we
+        // sampled during the ack turn-around window
+        if ( call CCA.get() ) {
+          m_state = S_BEGIN_TRANSMIT;
+          call BackoffTimer.start( CC2420_TIME_ACK_TURNAROUND );
+          
+        } else {
+          congestionBackoff();
+        }
+        break;
+        
+      case S_CCA_CANCEL:
+        m_state = S_TX_CANCEL;
+        /** Fall Through */
+        
+      case S_BEGIN_TRANSMIT:
+      case S_TX_CANCEL:
+        if ( acquireSpiResource() == SUCCESS ) {
+          attemptSend();
+        }
+        break;
+        
+      case S_ACK_WAIT:
+        signalDone( SUCCESS );
+        break;
+
+      case S_SFD:
+        // We didn't receive an SFD interrupt within CC2420_ABORT_PERIOD
+        // jiffies. Assume something is wrong.
+        call SFLUSHTX.strobe();
+        call CaptureSFD.captureRisingEdge();
+        releaseSpiResource();
+        signalDone( ERETRY );
+        break;
+
+      default:
+        break;
+      }
+    }
+  }
+      
+  /***************** Functions ****************/
+  /**
+   * Set up a message to be sent. First load it into the outbound tx buffer
+   * on the chip, then attempt to send it.
+   * @param *p_msg Pointer to the message that needs to be sent
+   * @param cca TRUE if this transmit should use clear channel assessment
+   */
+  error_t send( message_t* p_msg, bool cca ) {
+    atomic {
+      if (m_state == S_LOAD_CANCEL
+          || m_state == S_CCA_CANCEL
+          || m_state == S_TX_CANCEL) {
+        return ECANCEL;
+      }
+      
+      if ( m_state != S_STARTED ) {
+        return FAIL;
+      }
+      
+      m_state = S_LOAD;
+      m_cca = cca;
+      m_msg = p_msg;
+      totalCcaChecks = 0;
+    }
+    
+    if ( acquireSpiResource() == SUCCESS ) {
+      loadTXFIFO();
+    }
+
+    return SUCCESS;
+  }
+  
+  /**
+   * Resend a packet that already exists in the outbound tx buffer on the
+   * chip
+   * @param cca TRUE if this transmit should use clear channel assessment
+   */
+  error_t resend( bool cca ) {
+
+    atomic {
+      if (m_state == S_LOAD_CANCEL
+          || m_state == S_CCA_CANCEL
+          || m_state == S_TX_CANCEL) {
+        return ECANCEL;
+      }
+      
+      if ( m_state != S_STARTED ) {
+        return FAIL;
+      }
+      
+      m_cca = cca;
+      m_state = cca ? S_SAMPLE_CCA : S_BEGIN_TRANSMIT;
+      totalCcaChecks = 0;
+    }
+    
+    if(m_cca) {
+      signal RadioBackoff.requestInitialBackoff(m_msg);
+      call BackoffTimer.start( myInitialBackoff );
+      
+    } else if ( acquireSpiResource() == SUCCESS ) {
+      attemptSend();
+    }
+    
+    return SUCCESS;
+  }
+  
+  /**
+   * Attempt to send the packet we have loaded into the tx buffer on 
+   * the radio chip.  The STXONCCA will send the packet immediately if
+   * the channel is clear.  If we're not concerned about whether or not
+   * the channel is clear (i.e. m_cca == FALSE), then STXON will send the
+   * packet without checking for a clear channel.
+   *
+   * If the packet didn't get sent, then congestion == TRUE.  In that case,
+   * we reset the backoff timer and try again in a moment.
+   *
+   * If the packet got sent, we should expect an SFD interrupt to take
+   * over, signifying the packet is getting sent.
+   */
+  void attemptSend() {
+    uint8_t status;
+    bool congestion = TRUE;
+
+    atomic {
+      if (m_state == S_TX_CANCEL) {
+        call SFLUSHTX.strobe();
+        releaseSpiResource();
+        call CSN.set();
+        m_state = S_STARTED;
+        return;
+      }
+      
+      
+      call CSN.clr();
+
+      status = m_cca ? call STXONCCA.strobe() : call STXON.strobe();
+      if ( !( status & CC2420_STATUS_TX_ACTIVE ) ) {
+        status = call SNOP.strobe();
+        if ( status & CC2420_STATUS_TX_ACTIVE ) {
+          congestion = FALSE;
+        }
+      }
+      
+      m_state = congestion ? S_SAMPLE_CCA : S_SFD;
+      call CSN.set();
+    }
+    
+    if ( congestion ) {
+      totalCcaChecks = 0;
+      releaseSpiResource();
+      congestionBackoff();
+    } else {
+      call BackoffTimer.start(CC2420_ABORT_PERIOD);
+    }
+  }
+  
+  
+  /**  
+   * Congestion Backoff
+   */
+  void congestionBackoff() {
+    atomic {
+      signal RadioBackoff.requestCongestionBackoff(m_msg);
+      call BackoffTimer.start(myCongestionBackoff);
+    }
+  }
+  
+  error_t acquireSpiResource() {
+    error_t error = call SpiResource.immediateRequest();
+    if ( error != SUCCESS ) {
+      call SpiResource.request();
+    }
+    return error;
+  }
+
+  error_t releaseSpiResource() {
+    call SpiResource.release();
+    return SUCCESS;
+  }
+
+
+  /** 
+   * Setup the packet transmission power and load the tx fifo buffer on
+   * the chip with our outbound packet.  
+   *
+   * Warning: the tx_power metadata might not be initialized and
+   * could be a value other than 0 on boot.  Verification is needed here
+   * to make sure the value won't overstep its bounds in the TXCTRL register
+   * and is transmitting at max power by default.
+   *
+   * It should be possible to manually calculate the packet's CRC here and
+   * tack it onto the end of the header + payload when loading into the TXFIFO,
+   * so the continuous modulation low power listening strategy will continually
+   * deliver valid packets.  This would increase receive reliability for
+   * mobile nodes and lossy connections.  The crcByte() function should use
+   * the same CRC polynomial as the CC2420's AUTOCRC functionality.
+   */
+  void loadTXFIFO() {
+    cc2420_header_t* header = call CC2420PacketBody.getHeader( m_msg );
+    uint8_t tx_power = (call CC2420PacketBody.getMetadata( m_msg ))->tx_power;
+
+    if ( !tx_power ) {
+      tx_power = CC2420_DEF_RFPOWER;
+    }
+    
+    call CSN.clr();
+    
+    if ( m_tx_power != tx_power ) {
+      call TXCTRL.write( ( 2 << CC2420_TXCTRL_TXMIXBUF_CUR ) |
+                         ( 3 << CC2420_TXCTRL_PA_CURRENT ) |
+                         ( 1 << CC2420_TXCTRL_RESERVED ) |
+                         ( (tx_power & 0x1F) << CC2420_TXCTRL_PA_LEVEL ) );
+    }
+    
+    m_tx_power = tx_power;
+    
+    call TXFIFO.write( (uint8_t*)header, header->length - 1);
+  }
+  
+  void signalDone( error_t err ) {
+    atomic m_state = S_STARTED;
+    abortSpiRelease = FALSE;
+    call ChipSpiResource.attemptRelease();
+    signal Send.sendDone( m_msg, err );
+  }
+  
+  
+  
+  /***************** Tasks ****************/
+
+  /***************** Defaults ****************/
+  default async event void TimeStamp.transmittedSFD( uint16_t time, message_t* p_msg ) {
+  }
+  
+  default async event void TimeStamp.receivedSFD( uint16_t time ) {
+  }
+
+}
+