RF Transcievers, Async Communication?

Hi All!

I've got 2 Arduinos, each with it's own Nrf2401a (Nordic RF Transciever). There's a nifty (but a bit too simple) library for them out on the Arduino Playground.

I've gotten them working with the very simple blocking communication style.
ie: A sends to B, changes to RX mode and waits for B to respond. When B receive's A's message, it changes to TX mode and esponds, and then changes to RX mode and waits for A again. etc, etc...

That works fairly well, until A or B misses a packet. Then they both end up in RX mode and quit talking to eachother.

However, I'd love to implement something a bit more complex. Something more like TCP that guarantees packet delivery (ie: that detects lost packets and re-sends). I'm trying to expand upon the library, using it for sending and receiving, and then implementing the TCP protocol on top of that.

Has anyone done something similar that would like to share their experience, tips, or code?

My first attempt didn't work quite right The receiver doesn't detect the SYN-ACK for some reason.

Here's the code:

#include <ByteBuffer.h>
#include "Nrf2401.h"

Nrf2401 Radio;
byte PacketData[256];
ByteBuffer CircularBuffer;

// 0  = CLOSED
// 1  = LISTEN
// 2  = SYN SENT
// 3  = SYN RECEIVED
// 4  = ESTABLISHED
// 5  = FIN WAIT 1
// 6  = FIN WAIT 2
// 7  = CLOSE WAIT
// 8  = CLOSING
// 9  = LAST ACK
// 10 = TIME WAIT

int ConnectionState=0;

int TCPHeaderSize=6;

typedef struct {
  byte SequenceNumber;
  byte AcknowledgementNumber;
  byte Flags;
  byte WindowSize;
  byte Checksum;
  byte DataLength;
} 
TCPHeader;

TCPHeader TCPSendHeader;
TCPHeader TCPReceiveHeader;




void setup(void)
{

  TCPSendHeader.SequenceNumber=0x00;
  TCPSendHeader.AcknowledgementNumber=0x00;
  TCPSendHeader.Flags=0x00;
  TCPSendHeader.WindowSize=0x00;
  TCPSendHeader.Checksum=0x00;
  TCPSendHeader.DataLength=0x00;

  TCPReceiveHeader.SequenceNumber=0x00;
  TCPReceiveHeader.AcknowledgementNumber=0x00;
  TCPReceiveHeader.Flags=0x00;
  TCPReceiveHeader.WindowSize=0x00;
  TCPReceiveHeader.Checksum=0x00;
  TCPReceiveHeader.DataLength=0x00;


  CircularBuffer.init(512);
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  randomSeed(analogRead(7));                    // start the random number generator
  attachInterrupt(0, messageReceived, RISING);  // look for rising edges on digital pin 2
  ConnectionState=0;
  Radio.rxMode(1);
}



void loop(void)
{
  digitalWrite(13, !digitalRead(13));
  //Wait 1-2 seconds
  delay(random(2000)+1000);
if (ConnectionState )


  if (ConnectionState<3)
  {
    Connect();
  }
  else if (ConnectionState==8)
  {
    Disconnect();
  }
  else
  {
    Serial.print("Connection State = ");
    Serial.println(ConnectionState,DEC);
  }

}//End RequestData


void Connect()
{  
  Serial.println("Sending Connect...");
  TCPSendHeader.SequenceNumber = (byte)random(0,256);
  TCPSendHeader.AcknowledgementNumber=0x00;
  TCPSendHeader.Flags=0x02;  // 00000010 = SYN
  TCPSendHeader.WindowSize=0x00;
  TCPSendHeader.Checksum=0x00;
  TCPSendHeader.DataLength=0x00;
  SendPacket();
  ConnectionState=2;  //SYN SENT
}

void Disconnect()
{
  Serial.println("Sending Disconnect...");
  TCPSendHeader.SequenceNumber=TCPSendHeader.SequenceNumber+1;
  TCPSendHeader.AcknowledgementNumber=0x00;
  TCPSendHeader.Flags=0x01;  // 0000 0001 = FIN
  TCPSendHeader.WindowSize=0x00;
  TCPSendHeader.Checksum=0x00;
  TCPSendHeader.DataLength=0x00;
  SendPacket();
  ConnectionState=0;  //CLOSED
}


void SendPacket()
{
  Serial.println("Sending Packet...");
  Radio.txMode(1);

  Radio.write(TCPSendHeader.SequenceNumber);
  delay(50);
  Radio.write(TCPSendHeader.AcknowledgementNumber);
  delay(50);
  Radio.write(TCPSendHeader.Flags);
  delay(50);
  Radio.write(TCPSendHeader.WindowSize);
  delay(50);
  Radio.write(TCPSendHeader.Checksum);
  delay(50);
  Radio.write(TCPSendHeader.DataLength);
  delay(50);
  Radio.rxMode(1);
}







void messageReceived(void)
{


  Serial.println("Received:");
  while(Radio.available())
  {
    Radio.read();

    CircularBuffer.put(Radio.data[0]);
    Serial.print("CircularBuffer[");
    Serial.print(CircularBuffer.getSize()-1,DEC);
    Serial.print("] = ");
    Serial.println(CircularBuffer.peek(CircularBuffer.getSize()-1),DEC);
  }//End Copy Bytes from Receive Buffer.


  if (CircularBuffer.getSize()>=TCPHeaderSize)
  {
    Serial.println("Buffer has enough data to be TCP Header.");

    TCPReceiveHeader.SequenceNumber=CircularBuffer.get();
    TCPReceiveHeader.AcknowledgementNumber=CircularBuffer.get();
    TCPReceiveHeader.Flags=CircularBuffer.get();
    TCPReceiveHeader.WindowSize=CircularBuffer.get();
    TCPReceiveHeader.Checksum=CircularBuffer.get();
    TCPReceiveHeader.DataLength=CircularBuffer.get();


    Serial.print("TCPReceiveHeader.SequenceNumber=");
    Serial.println(TCPReceiveHeader.SequenceNumber,DEC);
    Serial.print("TCPReceiveHeader.AcknowledgementNumber=");
    Serial.println(TCPReceiveHeader.AcknowledgementNumber,DEC);
    Serial.print("TCPReceiveHeader.Flags=");
    Serial.println(TCPReceiveHeader.Flags,DEC);
    Serial.print("TCPReceiveHeader.WindowSize=");
    Serial.println(TCPReceiveHeader.WindowSize,DEC);
    Serial.print("TCPReceiveHeader.Checksum=");
    Serial.println(TCPReceiveHeader.Checksum,DEC);
    Serial.print("TCPReceiveHeader.DataLength=");
    Serial.println(TCPReceiveHeader.DataLength,DEC);


    if (ConnectionState==0)
    {
      //Received a SYN Packet
      if ((TCPReceiveHeader.Flags & 0x02)==0x02)
      {
        Serial.println("Received a SYN Packet.");
        ConnectionState=3;  //SYN RECEIVED

        //Reply with SYN-ACK
        TCPSendHeader.SequenceNumber=(byte)random(0,255);
        TCPSendHeader.AcknowledgementNumber=TCPReceiveHeader.SequenceNumber+1;
        TCPSendHeader.Flags=0x12;  // 00010010 = ACK,SYN
        TCPSendHeader.WindowSize=0x00;
        TCPSendHeader.Checksum=0x00;
        TCPSendHeader.DataLength=0x00;

        Serial.println("Sending SYN ACK...");
        SendPacket();
        ConnectionState=4;  //CONNECTED
      }
    }

    if (ConnectionState==2)
    {
      //Received SYN ACK Packet
      if ((TCPReceiveHeader.Flags & 0x12)==0x12)
      {
        Serial.println("Received SYN ACK Packet.");
        ConnectionState=4;  //CONNECTED
      }
    }

if (ConnectionState==4)
{
    //Received an ACK Packet
    if ((TCPReceiveHeader.Flags & 0x10)==0x10)
    {
      Serial.println("Received an ACK Packet.");
      TCPSendHeader.SequenceNumber=TCPReceiveHeader.AcknowledgementNumber;
      TCPSendHeader.AcknowledgementNumber=TCPReceiveHeader.SequenceNumber+1;
    }
}

    //Received a FIN Packet
    if ((TCPReceiveHeader.Flags & 0x01)==0x01)
    {
      Serial.println("Received FIN Packet.");
      ConnectionState=0;  //CLOSED
    }

  }//End Has whole header


}//End messageReceived
  • I would consider switching to the nRF24L01+, which does have an automated handshake. From the nRF24L01+-datasheet:

Enhanced ShockBurst™ features automatic packet transaction handling that enables the implementation
of a reliable bi-directional data link. An Enhanced ShockBurst™ packet transaction is a packet exchange
between to transceivers, where one transceiver is the Primary Receiver (PRX) and the other is the Primary
Transmitter (PTX). An Enhanced ShockBurst™ packet transaction is always initiated by a packet transmis-
sion from the PTX, the transaction is complete when the PTX has received an acknowledgment packet
(ACK packet) from the PRX.

With Enhanced ShockBurst, the sending entity knows that the receiver didn't get it, and can thus initiate a retransmit, provided your data transmission is not too time-critical. The chip even gives you a hint how often it needed to retransmit the last packet (see the register map).

A TCP-like implementation is probably a much more complicated way to go, with little to gain. For example, such an implementation will be slower than a dedicated hardware implementation like the one the nRF24L01+ is performing.

cpixip,
Thank you for your response. Unfortunately, I already have the nRF2401a, and won't be buyying the upgraded nRF24L01+ anytime soon.

I did manage to code up an Async TCP type communication protocol. This code uses the "Request to Send" / "Clear to Send" method. Both clients may designate themself the host by initiating a RTS and receiving a CTS. But, only one of them will be granted the ability to send data. The negotiation occurs on a first-to-request basis. In the chance that both radios attempt to transmit at the same time, the client waits a random amount of time (100-1100 milliseconds) and tries again. This ensures that the two won't be stuck in an infinate pattern of rx / tx mode blocking.

It currently allows a data payload of 128 bytes per packet, but forces acknowledgement (ACK) of packet receipt before more data can be sent. Although it uses sequence numbers, it will not accept out-of-order packets.

You're right, this method is certainly a LOT slower, but guarantees packet delivery end to end.

Still un-implemented is the FIN packet. This would allow the sender to end termination as "host" and allow the other radio the ability to become host.

I will try to finish up my implementation and produce a more robust library for the nRF2401a.