nRF24L01 data synch problem

So, I've got two nRF24L01 modules communicating with each other. Was working fine, now the data transfer is out of synch. I'm looking for help to get an easily understandable way to ACK? the data transfer is correct and continue? Radio communication is not my forte. I have no idea if the way I'm currently transferring data is correct, but it was working last night. Unfortunately, now it's out of synch. Specifically, the Cycle Time data shows up in the first position of Tbit0.

TX

void setup() {
  //  memset (powerupPos0, 90, sizeof(powerupPos0));
  Serial.begin(57600);
  Serial.println("JMRI CMRI Arduino Mega Interface");
  Wire.setClock(400000);                                //sets i2c speed to 100kHz
  pwm0.begin();
  pwm0.setPWMFreq(FREQUENCY);
  pwm1.begin();
  pwm1.setPWMFreq(FREQUENCY);

  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.setRetries(3, 5); // delay, count
  radio.openWritingPipe(slaveAddress);

  for (byte i = 0; i < 16; i++) {
    pinMode(relayPin0[i], OUTPUT);                        //attaches 16 relays to relayPins array
    digitalWrite(relayPin0[i], relayState0[i]);           //sets relays to default state in relayState array
    pinMode(relayPin1[i], OUTPUT);                        //attaches 16 relays to relayPins array
    digitalWrite(relayPin1[i], relayState1[i]);           //sets relays to default state in relayState array
  }
  for (int i = 0; i < 16; i++) {                         // set and remember initial positions using array
    moveservo0(i, (powerupPos0[i] + hornOffset0[i]));
    currentPos0[i] = powerupPos0[i];    // and remember it as current
    moveservo1(i, (powerupPos1[i] + hornOffset1[i]));
    currentPos1[i] = powerupPos1[i];    // and remember it as current
  }                                                       // and remember it as current
}

void loop() {
  Tbit0[4]=1;
  cycleMillis = millis();                            //store time to calculate cycle time at end of loop

  txcurrentMillis = millis();
  if (txcurrentMillis - txprevMillis >= txIntervalMillis) {
    Serial.print("CMRI bits 1-16: ");
    for (byte i = 0; i < 16; i++) {
      Serial.print(Tbit0[i]); Serial.print(", ");
      Serial.print(Tbit1[i]); Serial.print(", ");
    }
    Serial.println();
    radio.write( &Tbit0[0], sizeof(Tbit0) );
    radio.write( &Tbit1[0], sizeof(Tbit1) );
    radio.write( &cycleTime, sizeof(cycleTime) );
    txprevMillis = millis();
  }

RX

#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);

char dataReceived[10]; // this must match dataToSend in the TX
bool newData = false;
byte Tbit0[16];
byte Tbit1[16];
byte cycleTime;

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

void setup() {

  Serial.begin(57600);

  Serial.println("SimpleRx Starting");
  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.openReadingPipe(1, thisSlaveAddress);
  radio.startListening();
}

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

void loop() {
  if ( radio.available() ) {

    radio.read( &Tbit0[ 0 ] , 16 );
    Serial.print("CMRI bits  1-16: ");
    for (byte i = 0; i < 16; i++) {
      Serial.print(Tbit0[i]); Serial.print(", ");
    }
    Serial.println();

    radio.read( &Tbit1[ 0 ] , 16 );
    Serial.print("CMRI bits 17-32: ");
    for (byte i = 0; i < 16; i++) {
      Serial.print(Tbit1[i]); Serial.print(", ");
    }
    Serial.println();

    radio.read( &cycleTime, sizeof(cycleTime));
    Serial.print("Cycle Time: ");
    Serial.print(cycleTime);
    Serial.println(" ms");
  }
}

Thanks,

Mandy

best regards Stefan

If possible, you should shrink the data to fit into a single packet,
if the two 16 byte arrays really represent bits, that should be no problem.

If this approach is not possible, include an identifying header in each packet.

Don't rely on a special packet sequence, you have to handle missing packets.

Checking for the availability of a single packet and then reading three packets is rather silly.

Ok, so I convert each group of 8 bits into a byte and store the new bytes in an array? before sending them in one transmission. Then decode the new bytes back into bits(or whatever I want) on the receiving side.

I can use a for loop and bitshift to create the new bytes or is there an easier way to accomplish this conversion to a new array?

Thanks,

Mandy

You can use a struct instead of an array.

The NRF sends any data as long as its length is below 32.
Often a struct is more appropriate than an array.

You could define an en/decoding function setting/testing single bits or groups of bits.

Soooo, could I use this BoolArray library https://github.com/RobTillaart/Arduino/tree/master/libraries/BoolArray
"The class is optimized for storage by packing 8 elements of the array in one byte"
instead of a creating my own struct?

I think I can wade through creating a struct and I know that they are basically a user-defined data-type that can contain data in different types (int, bool, float etc).

Fyi, I've pretty much got the basics down. Now the fun is trying to combine working examples into something that is actually useful to me.

Thanks for your guidance,

Mandy

If it will help, here is code that I wrote to send and receive a struct made up of LDR (Light Dependent Resistor) readings from one Uno to another using rf24 radios.

Send:


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


const byte CE_PIN = 9;
const byte CSN_PIN = 10;

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


RF24 radio(CE_PIN, CSN_PIN); // Create a Radio

struct LdrValues
{
  int ldr_1;
  int ldr_2; 
}ldrValues;

unsigned long currentMillis;
unsigned long prevMillis;
unsigned long txIntervalMillis = 1000; // send once per second

const byte LDR1 = A0;
const byte LDR2 = A1;

void setup()
{

   Serial.begin(9600);
   Serial.println("SimpleTx Starting");
   pinMode(LDR1, INPUT_PULLUP);
   pinMode(LDR2, INPUT_PULLUP);
   
   radio.begin();
   radio.setChannel(76);  //76 library default
   //RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH and RF24_PA_MAX
   radio.setPALevel(RF24_PA_HIGH);
   radio.setDataRate( RF24_250KBPS );
   radio.setRetries(3, 5); // delay, count
   radio.openWritingPipe(slaveAddress);
}

void loop()
{
   currentMillis = millis();
   if (currentMillis - prevMillis >= txIntervalMillis)
   {
      send();
      Serial.print("LDR 1 = ");
      Serial.print(ldrValues.ldr_1);
      Serial.print("    LDR 2 = ");
      Serial.println(ldrValues.ldr_2);
      prevMillis = millis();
   }
}

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

void send()
{
   ldrValues.ldr_1 = analogRead(LDR1);
   ldrValues.ldr_2 = analogRead(LDR2);
   radio.write( &ldrValues, sizeof(ldrValues) );
}

Receive:



// SimpleRx - the slave or the receiver

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

const byte CE_PIN = 9;
const byte CSN_PIN = 10;

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

RF24 radio(CE_PIN, CSN_PIN);

struct LdrValues
{
   int ldr_1;
   int ldr_2;
} ldrValues;

bool newData = false;

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

void setup()
{

   Serial.begin(9600);

   Serial.println("SimpleRx Starting");

   radio.begin();
   radio.setChannel(76);  //76 library default
   //RF24_PA_MIN, RF24_PA_LOW, RF24_PA_HIGH and RF24_PA_MAX
   radio.setPALevel(RF24_PA_HIGH);
   radio.setDataRate( RF24_250KBPS );
   radio.openReadingPipe(1, thisSlaveAddress);
   radio.startListening();
}

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

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

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

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

void showData()
{
   if (newData == true)
   {
      Serial.print("Data received >> ");
      Serial.print("LDR 1 = ");
      Serial.print(ldrValues.ldr_1);
      Serial.print("    LDR 2 = ");
      Serial.println(ldrValues.ldr_2);
      newData = false;
   }
}

A struct tutorial.

Okay, so I'm getting closer... I'm not sending the correct data yet, but the Rx side gets exactly what is being sent.

TX snippet:

unsigned int cycleTime;
unsigned int together1 = 0;
unsigned int together2 = 0;
 
struct TbitValues
{
unsigned int Tbit_1;         //Tbit0 16 elements
unsigned int Tbit_2;         //Tbit1 16 elements
unsigned int Tbit_3;         //Cycle Time
} TbitValues;

void loop() {
  Tbit0[4] = 1; Tbit0[15] = 1; Tbit0[1] = 1; Tbit0[3] = 1;
  memset (Tbit1, 1, sizeof(Tbit1));
  cycleMillis = millis();                            //store time to calculate cycle time at end of loop

  txcurrentMillis = millis();
  if (txcurrentMillis - txprevMillis >= txIntervalMillis) {
    //      send();
    for (int i = 0; i < 16; i++) {
      together1 += (Tbit0[i] << (15 - i));
      together2 += (Tbit1[i] << (15 - i));
    }

    TbitValues.Tbit_1 = together1;
    TbitValues.Tbit_2 = together2;
    TbitValues.Tbit_3 = cycleTime;
    radio.write( &TbitValues, sizeof(TbitValues) );

    Serial.print("Tbit 1 = ");
    Serial.println(TbitValues.Tbit_1, BIN);
    Serial.print("Tbit 2 = ");
    Serial.println(TbitValues.Tbit_2, BIN);
    Serial.print("Cycle Time: ");
    Serial.print(TbitValues.Tbit_3);
    Serial.println(" ms");


//other code 
}

Tbit 1 = 1000000000110
Tbit 2 = 1111100111111010
Cycle Time: 13 ms
Tbit 1 = 110100000000111
Tbit 2 = 1111100011111001
Cycle Time: 14 ms

So, Cycle Time seems correct. The first two sets of data change when I haven't changed any of the data. There is probably an easier way to structure the packet. The Rx end doesn't need the data back in an array so if the Tx Serial.print data is good the Rx data will be too.

Thanks,

Mandy

I think I've finally got it. This is the current TX code:

unsigned int cycleTime;
unsigned int together1 = 0;
unsigned int together2 = 0;
 
struct TbitValues
{
unsigned int Tbit_1;         //Tbit0 16 elements
unsigned int Tbit_2;         //Tbit1 16 elements
int Tbit_3;         //Cycle Time
} TbitValues;

void loop() {
  Tbit0[4] = 1; Tbit0[15] = 1; Tbit0[1] = 1; Tbit0[3] = 1; //test data
  memset (Tbit1, 1, sizeof(Tbit1));  //test data

  cycleMillis = millis();                            //store time to calculate cycle time at end of loop
  txcurrentMillis = millis();
  if (txcurrentMillis - txprevMillis >= txIntervalMillis) {

    for (int i = 0; i < 16; i++) {
      bitWrite(together1, 15 - i, Tbit0[i]);
      bitWrite(together2, 15 - i, Tbit1[i]);
    }
    TbitValues.Tbit_1 = together1;
    TbitValues.Tbit_2 = together2;
    TbitValues.Tbit_3 = cycleTime;
    radio.write( &TbitValues, sizeof(TbitValues) );
    Serial.print("Tbit 1 = ");
    Serial.println(TbitValues.Tbit_1, BIN);
    Serial.print("Tbit 2 = ");
    Serial.println(TbitValues.Tbit_2, BIN);
    Serial.print("Cycle Time: ");
    Serial.print(TbitValues.Tbit_3);
    Serial.println(" ms");
    txprevMillis = millis();
  }


//other code 
}

And RX code including adding leading 0s:

// 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);

unsigned int together1 = 0;
unsigned int together2 = 0;
unsigned int cycleTime = 0;

struct TbitValues
{
  unsigned int Tbit_1;         //Tbit0 16 elements
  unsigned int Tbit_2;         //Tbit1 16 elements
  unsigned int Tbit_3;         //Cycle Time
} TbitValues;

void prntBits(unsigned int b)
{
  for(int i = 15; i >= 0; i--)
    Serial.print(bitRead(b,i));
  Serial.println();  
}

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

void setup() {

  Serial.begin(57600);
  Serial.println("SimpleRx Starting");
  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.openReadingPipe(1, thisSlaveAddress);
  radio.startListening();
}

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

void loop() {
  if ( radio.available() ) {
    radio.read( &TbitValues, sizeof(TbitValues) );

    together1 = TbitValues.Tbit_1;
    together2 = TbitValues.Tbit_2;
    cycleTime = TbitValues.Tbit_3;

    Serial.print("Tbit 1 = ");
    prntBits(together1);
    Serial.print("Tbit 2 = ");
    prntBits(together2);
    Serial.print("Cycle Time: ");
    Serial.print(cycleTime);
    Serial.println(" ms");
  }
}

Thanks so much to Whandall for pointing me in the right direction and to groundFungus for providing a useful example. I can now happily read data off my Mega wirelessly!

Thanks,

Mandy

2 Likes

It’s good that it has been solved by compressing the data to fit within the maximum 32bit payload. There is, however, another, relatively simple, approach to sending multiple packets illustrated here: https://forum.arduino.cc/t/simple-nrf24l01-2-4ghz-transceiver-demo/405123/51?u=6v6gt if that becomes necessary.

In principle, on the transmitting side, the data is packed into a large struct and is sent in 32bit chunks in quick succession (but with a short pause between each) then, after a longer pause, the next batch of packets is sent and so on.

On the receiver side, on receiving the first packet in a batch, it starts a timer and loads that packet into the receiving struct. It then loads subsequent packets into the struct, at the appropriate position, until the last one of that batch has been received. Then the struct is processed. However, if, within a timeout period, not all of the expected number packets have been received, the current receive attempt is abandoned and it simply waits for the first packet in the next batch.

The timeout period is somewhat less than the pause between batches on the transmitting side.