nRF24L01 unexpected behaviour [SOLVED]

Hello.

I am building a wireless control handset that is using a nRF24 to communicate to the base station. This is all based on Robin2's tutorials.

I've just found a bug and can't figure out what is going on. When the handset is first powered on, the last line of Setup calls a function to send a packet to the base station to confirm that it is powered and ready to go.

In doing some tests I had the serial monitor open on the base station and it would report whenever the radio was available, read the payload into a struct that matched what was transmitted and just dump the contents to the serial monitor. The first time I powered the handset I could see the incoming 'handshake'. But if I then power cycled the handset there was no message in the Serial Monitor to show the incoming handshake. I know the handshake does happen as the handset reports that the transmitted packet was acknowledged. If I power cycle the base station it picks up the handshake message again but only once. Any packets sent after come through and are displayed as expected.

In case there was something wrong with this function I changed the setup to just call my sendRF function. The behaviour of the handset didn't change. At one point I ended up calling my sendRF function twice in the setup, somehow this seems to fix the problem and both packets are shown on the base station when I power cycle the handset.

I don't understand why the base station would always acknowledge the packet but only show radio.available as true once no matter how many times I power cycled the handset when sending a single handshake but acknowledge the packet and show radio.available as true every time I send two packets in a handshake.

sendRF function

//Function to send RF message
void sendRF() {
  bool rslt;
  rslt = radio.write( &rf_sent_data, sizeof(rf_sent_data) );       //Store result of trying to send RF packet
  if (rslt) {
    if ( radio.isAckPayloadAvailable() ) {
      //Packet sent sucessfully and Ack Payload received.
      radio.read(&rf_received_data, sizeof(rf_received_data));  //Read Ack Payload into Struct
      Debug_println(" RF Acknowledge");
      #ifdef DEBUG
      rfDump(); //Call function to dump CAN message to Serial for debugging.
      #endif
      idAck();  //ID and process the Ack payload.
    } else {
      //Packet sent sucessfully but no Ack Payload received.
      Debug_println("  Acknowledge but no data ");
    } 
  lastRecvTime = millis();  //Stores that last time an RF Ack was received.
  } else {
    //Packet failed to send
    Debug_println("  Tx failed");
  }
  txPrevMillis = millis();
  resetData();
}

Base station code (Stripped down to absolute basics for testing).

// SimpleRx - the slave or the receiver

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

#define CE_PIN   9
#define CSN_PIN 10

const byte thisSlaveAddress[5] = {'R','x','A','A','A'};

RF24 radio(CE_PIN, CSN_PIN);

//Data struct's for RF messages
//Struct for RF messages
struct RF_Data {
  byte dlc;        //How many data bytes are included in the message
  byte opCode;     //CBUS OpCode to ID the message type
  byte dat1;       //Data 1
  byte dat2;       //Data 2
  byte dat3;       //Data 3
  byte dat4;       //Data 4
  byte dat5;       //Data 5
  byte dat6;       //Data 6
  byte dat7;       //Data 7
  byte dat8;       //Data 8
};
RF_Data rf_sent_data;
RF_Data dataReceived;
bool newData = false;

//===========

void setup() {

    Serial.begin(9600);
    delay(1000);
    Serial.println("SimpleRx Starting");
    setupRF();
}

//Setup RF Module
void setupRF() {
  radio.begin();                             //Starting the radio communication
  radio.setDataRate( RF24_250KBPS );         //Data Rate of transmission
  radio.enableAckPayload();                  //Enable the Ack payload to receive data from handset
  radio.openReadingPipe(1, thisSlaveAddress);//Setting the address at which we will send the data
  radio.setPALevel(RF24_PA_MAX);             //You can set it as minimum or maximum depending on the distance between the transmitter and receiver.
  //resetAck();
  radio.writeAckPayload(1, &rf_sent_data, sizeof(rf_sent_data)); // pre-load data
  radio.startListening();
}

//=============

void loop() {
    getData();
    showData();
}

//==============

void getData() {
    if ( radio.available() ) {
        radio.read( &dataReceived, sizeof(dataReceived) );
        newData = true;
    }
}

void showData() {
    if (newData == true) {
        Serial.println("Data received ");
        newData = false;
    }
}

Just to add, I've found a couple of posts on here where people have had the same problem. Unfortunately no solution was listed.

I have now tried some more troubleshooting.

As per several suggestions I have added a delay after setting up the radio but this makes no difference.

I saw a port from Robin warning about things not working with the latest library so I downgraded to the version they recommended but this also made no change.

Lastly I just updated to the latest library and modified my code based on the library example included. This didn't resolve the problem either.

Updated sendRF function.

void sendRF() {
  bool report = radio.write(&rf_sent_data, sizeof(rf_sent_data));    // transmit & save the report
  if (report) {
    Serial.print(F("Transmission successful! "));          // payload was delivered
    Serial.print(F("Time to transmit = "));
    Serial.print(F(" us. Sent: "));
    Serial.print(rf_sent_data.opCode);                         // print the outgoing message
    Serial.print(rf_sent_data.dlc);                         // print the outgoing counter
    uint8_t pipe;
    if (radio.available(&pipe)) {                          // is there an ACK payload? grab the pipe number that received it
      radio.read(&rf_received_data, sizeof(rf_received_data));             // get incoming ACK payload
      Serial.print(F(" Recieved "));
      Serial.print(radio.getDynamicPayloadSize());         // print incoming payload size
      Serial.print(F(" bytes on pipe "));
      Serial.print(pipe);                                  // print pipe number that received the ACK
      Serial.print(F(": "));
      Serial.print(rf_received_data.opCode);                      // print incoming message
      Serial.println(rf_received_data.dlc);                    // print incoming counter

    } else {
      Serial.println(F(" Recieved: an empty ACK packet")); // empty ACK packet received
    }


  } else {
    Serial.println(F("Transmission failed or timed out"));    // payload was not delivered
  }

}

Updated section in loop of receiver.

  uint8_t pipe;
  if (radio.available(&pipe)) {                    // is there a payload? get the pipe number that recieved it
    uint8_t bytes = radio.getDynamicPayloadSize(); // get the size of the payload
    radio.read(&rf_received_data, sizeof(rf_received_data));       // get incoming payload
    Serial.print(F("Received "));
    Serial.print(bytes);                           // print the size of the payload
    Serial.print(F(" bytes on pipe "));
    Serial.print(pipe);                            // print the pipe number
    Serial.print(F(": "));
    Serial.print(rf_received_data.opCode);                // print incoming message
    Serial.print(rf_received_data.dlc);                // print incoming counter
    Serial.print(F(" Sent: "));
    Serial.print(rf_sent_data.opCode);                 // print outgoing message
    Serial.println(rf_sent_data.dlc);               // print outgoing counter

    // save incoming counter & increment for next outgoing
    rf_sent_data.dlc = rf_received_data.dlc + 1;
    // load the payload for the first received transmission on pipe 0
    radio.writeAckPayload(1, &rf_sent_data, sizeof(rf_sent_data));
  }

I forgot to include this in my first post.

Setting up the radio module on TX

//Setup RF Module
void setupRF() {
  radio.begin();                           //Starting the radio communication
  radio.setPALevel(RF24_PA_MAX);           //You can set it as minimum or maximum depending on the distance between the transmitter and receiver.
  radio.enableAckPayload();                //Enable the Ack payload to receive data from handset
  radio.enableDynamicPayloads();
  radio.setDataRate( RF24_250KBPS );       //Data Rate of transmission
  radio.setRetries(5, 5);                  // delay, count 5 gives a 1500 µsec delay which is needed for a 32 byte ackPayload
  radio.openWritingPipe(slaveAddress);     //Setting the address at which we will send the data
  radio.stopListening();
}

Setting up radio module on RX

void setupRF() {
  radio.begin();                             //Starting the radio communication
  radio.setPALevel(RF24_PA_MAX);             //You can set it as minimum or maximum depending on the distance between the transmitter and receiver.
  radio.enableDynamicPayloads();
  radio.enableAckPayload();                  //Enable the Ack payload to receive data from handset
  radio.setDataRate( RF24_250KBPS );         //Data Rate of transmission
  radio.openReadingPipe(1, thisSlaveAddress);//Setting the address at which we will send the data
  resetAck();
  radio.writeAckPayload(1, &rf_sent_data, sizeof(rf_sent_data)); // pre-load data
  radio.startListening();
}

Hardware wise, I am using a circuit board I designed ages ago that takes the Arduino Nano, NRF24 module and includes a separate 3.3v regulator to power the NRF24.

It's been a while since I tinkered with the nRF24L01, but I think your problem may lie down in the bowels of the data packet. When you send a packet (using ShockBurst, which I think is the method used by the Arduino libraries), there is a 9-bit Packet Control Field. In there are 2 bits called PID (Packet Identity).

The nRF24L01 datasheet says:

The 2 bit PID field is used to detect if the received packet is new or retransmitted. PID prevents the PRX device from presenting the same payload more than once to the receiving host MCU. The PID field is incremented at the TX side for each new packet received through the SPI.

Maybe:
If the first message from the handset has a PID=1, then the base station may remember this. When you power cycle your handset, and send your message again, the PID will still be 1 and the base station may possibly ignore the message.

The nRF24L01 datasheet explains what happens if the receiver gets 2 data packets with the same PID:

When several data packets are lost on the link, the PID fields may become equal to the last received PID. If a packet has the same PID as the previous packet, nRF24L01+ compares the CRC sums from both packets. If the CRC sums are also equal, the last received packet is considered a copy of the previously received packet and discarded.

If this scenario became an issue, then you could introduce a random element by reading the value of an unused analogue pin and sending that value as part of each of your messages. That should cause the CRC to be different for each message.

Considering how much this twisted my brain last night I could jolly well kiss you right now.

I did have a thought about the messages being identical causing problems so I tried using Random() to populate one of the packet bytes with a random number but I didn't have any success so I moved on.

Going back to it and looking at it in more detail and I realised that random numbers on the Arduino are not that actually random and using Random() at start up was always supplying the same number so the packets were still identical.

I just used RandomSeed() to get a different 'random' number every start up and now every first handshake packet is being reported by the receiver.

I think I will walk away tonight and come back to it tomorrow with a clear head and put my code back to how it was before stripping out chunks of it and implement a better handshake.

Thanks again.

A simple one byte counter that gets incremented after each send works very well.

I don't see how that would work in this instance as the counter would be reset during the power cycle.

Now I understand the reasons that caused this apparent issue to appear I realise that this issue won't actually cause any problems so I may not bother to do anything to 'fix' it.

This would only affect the very first packet, any software retry would then start to work.

Loss of a single packet can occur any time, so you have to prepare for that fact anyway.

Which was exactly the issue I was reporting so adding a counter would not change that.

As I said above, because I didn't understand the problem I thought it was indicative of a more serious issue.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.