How to send multiple sensor data through NRF24L01 module at different times?

Hi,
So I’ve been working on a project which involves sending sensor data from about 4 transmitters (Nano) to a single receiver (Mega); all of which are interfaced to a NRF24L01 module.

The challenge I have is how to efficiently configure the transmitters to send their individual data to the receiver at different time intervals and also allowing for each one to only send info when the receiver is free and no other transmitter is transferring its data!

This is a necessary feature because the receiver is supposed to log each transmitter data into different files.

Find my code so far below:

Transmitter…

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

#define ACS712 A0

unsigned long lastData = 0;
int dataSum = 0;
int Count = 0;

float vpc = 4.8828126; // approx. voltage per count
RF24 radio(9, 10); // CE, CSN

struct Sensor1Data {
  float v1;
  float c1;
  float p1;
  float k1;
} Sensor1;

unsigned char ADDRESS0[5]  = {0xb1, 0x43, 0x88, 0x99, 0x45}; // Define a static TX address
//just change b1 to b2 or b3 to send to other pip on resciever

void setup()
{
  radio.begin();
  Serial.begin(115200);
  printf_begin();
  radio.setPALevel(RF24_PA_MIN);
  radio.enableDynamicPayloads();
  radio.setDataRate(RF24_250KBPS);
  radio.setChannel(124);
  radio.openWritingPipe(ADDRESS0);
  radio.openReadingPipe(0, ADDRESS0);
  radio.stopListening();
}

void loop()
{
  if (millis() > lastData + 1) {
    dataSum += sq(analogRead(ACS712) - 512);
    Count++;

    lastData = millis();
  }
  // If statement to make calculations.
  if (Count == 1000) {

    // To get the RMS of the data.
    float mean = dataSum / Count;
    float value = sqrt(mean);

    // To get the Voltage (in volts) consumed.
    float Voltage = value * vpc;

    // To get the Current (in amps) consumed.
    float Current = Voltage / 66;

    // To get the Power (in watts) consumed.
    float Power = Current * 240;

    //To get the Consumption rating (in kilowatthour)
    float Consumption = Power / 1000;

    dataSum = 0;
    Count = 0;

    //Attach values to struct
    Sensor1.v1 = Voltage;
    Sensor1.c1 = Current;
    Sensor1.p1 = Power;
    Sensor1.k1 = Consumption;
    Serial.println(Sensor1.v1);
    Serial.println(Sensor1.c1);
    Serial.println(Sensor1.p1);
    Serial.println(Sensor1.k1);

    //Transmit data and check if it goes succesfully!
      bool ok = radio.write(&Sensor1, sizeof(Sensor1));
      if (ok) {
      Serial.println("Sent!");
      } else {
      Serial.println("Failed!");
      }
      delay(100);
  }
}

MasterReceiver

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

RF24 radio(53, 48); // CE, CSN

#define PloadSize  32  // 32 unsigned chars TX payload
byte pipe;
byte CurrentPloadWidth;
byte newdata;
unsigned char rx_buf[PloadSize] = {0};

struct Sensor1Data {
  float v1;
  float c1;
  float p1;
  float k1;
} Sensor1;

struct Sensor2Data {
  float v2;
  float c2;
  float p2;
  float k2;
} Sensor2;

struct Sensor3Data {
  float v3;
  float c3;
  float p3;
  float k3;
} Sensor3;

unsigned char ADDRESS2[1] = {0xb2};
unsigned char ADDRESS3[1] = {0xb3};
unsigned char ADDRESS4[1] = {0xb4};
unsigned char ADDRESS5[1] = {0xb5};


unsigned char ADDRESS1[5] = {0xb1, 0x43, 0x88, 0x99, 0x45}; // Define a static TX address

unsigned char ADDRESS0[5] = {0xb0, 0x43, 0x88, 0x99, 0x45}; // Define a static TX address

void setup()
{
  radio.begin();
  printf_begin();
  Serial.begin(115200);
  radio.setDataRate(RF24_250KBPS);
  radio.enableDynamicPayloads();
  radio.setChannel(124);
  radio.openWritingPipe(ADDRESS0);
  radio.openReadingPipe(0, ADDRESS0);
  radio.openReadingPipe(1, ADDRESS1);
  radio.openReadingPipe(2, ADDRESS2);
  radio.openReadingPipe(3, ADDRESS3);
  radio.openReadingPipe(4, ADDRESS4);
  radio.openReadingPipe(5, ADDRESS5);

  radio.startListening();
  delay(10);
}

void loop()
{
  if (radio.available(&pipe)){
    
    // Fetch the payload, and see if this was the last one.
    CurrentPloadWidth = radio.getDynamicPayloadSize();

    // If a corrupt dynamic payload is received, it will be flushed
    if (!CurrentPloadWidth) {
    }
    else
    {
      radio.read(rx_buf, CurrentPloadWidth);
      newdata = 1;
    }
  }

  if (newdata == 1) {
    newdata = 0;
    if (pipe == 1 && CurrentPloadWidth == sizeof(Sensor1))
    {
      memcpy(&Sensor1, rx_buf, sizeof(Sensor1));

      Serial.println("Data from Sensor 1: ");
      Serial.println(Sensor1.v1);
      Serial.println(Sensor1.c1);
      Serial.println(Sensor1.p1);
      Serial.println(Sensor1.k1);
    }

    if (pipe == 2 && CurrentPloadWidth == sizeof(Sensor2))
      {
      memcpy(&Sensor2, rx_buf, sizeof(Sensor2));

      Serial.println("Data from Sensor 2: ");
      Serial.println(Sensor2.v2);
      Serial.println(Sensor2.c2);
      Serial.println(Sensor2.p2);
      Serial.println(Sensor2.k2);
      }

      if (pipe == 3 && CurrentPloadWidth == sizeof(Sensor3))
      {
      memcpy(&Sensor3, rx_buf, sizeof(Sensor3));

      Serial.println("Data from Sensor 3");
      Serial.println(Sensor3.v3);
      Serial.println(Sensor3.c3);
      Serial.println(Sensor3.p3);
      Serial.println(Sensor3.k3);
      }

    Serial.println("");
  }
  delay(100);
}

Thanks for your contributions!

Hi,

google NRF24L01 mesh

I think you will find the NRF24L01 system takes care of that as well, to some degree.

Tom... :slight_smile:

Dondoter:
The challenge I have is how to efficiently configure the transmitters to send their individual data to the receiver at different time intervals and also allowing for each one to only send info when the receiver is free and no other transmitter is transferring its data!

If your transmitters can be powered continuously then using the ackPayload feature as in the second example in this Simple nRF24L01+ Tutorial should solve the problem completely.

If you need to have the transmitters sleep to save a battery then things are a little more complicated.

I think I would go for a simple system in which each transmitter sends its message whenever it is ready with, perhaps, just 2 retries. If it does not get an acknowledgement from the master then it waits for a random interval (maybe between 500 and 1500 millisecs) and tries again.

After a successful transmission it should not try to send again for at least a period of about twice the time needed for all the other slaves to have sent their messages. Obviously longer intervals reduce the risk of collisions.

Another strategy might be for each slave to send the next message at the same interval after it receives an acknowledgement for a successful message. (I mean that all the slaves use the same interval). While millis() is not accurate compared to a real-time-clock I think it should be quite sufficient for this.

Or maybe as part of the acknowledgement the master could tell the slave the interval to the next message.

...R

Robin2:
Another strategy might be for each slave to send the next message at the same interval after it receives an acknowledgement for a successful message. (I mean that all the slaves use the same interval). While millis() is not accurate compared to a real-time-clock I think it should be quite sufficient for this.

This is very close to what I'm thinking of implementing!!
Could we please get a little more insight on that?!

Thanks...

Dondoter:
Could we please get a little more insight on that?!

To save me writing a book, can you explain exactly what you want more info about?

…R

Considering the setup of 3 Transmitters each to send their data to 1 master receiver so it then stores each transmitters data in separate files;

  1. Where do we start from:
  • setting up the ack function?
  • communication interval?
  1. Is there a way to save the sensor data into the MC's buffer when it is not transmitting so at its own time it can then transmit that data? Or is there a more efficient way to handle that, that you could recommend?

  2. If there are things you feel we should also consider, please because I really am still clueless!

I think that should do!!
Thanks

Dondoter:

  1. Where do we start from:
  • setting up the ack function?
  • communication interval?

There are probably several approaches to that. One that springs to mind is to use a trial and error system (which I mentioned earlier) until a slave has a success. Then try at the prescribed interval from that point onwards.

  1. Is there a way to save the sensor data into the MC's buffer when it is not transmitting so at its own time it can then transmit that data? Or is there a more efficient way to handle that, that you could recommend?

Won't the data have to be stored in a variable no matter when transmission is possible. I can't conceive of a way to collect data and send it simultaneously.

If you mean that you want to store a number of readings before sending them as a block then put them in an array and send the array.

However I have been assuming that the interval between messages would be short enough that there would be one data item per transmission. If you have something else in mind you need to tell us - my crystal ball is broke.

...R

Robin2:
If you mean that you want to store a number of readings before sending them as a block then put them in an array and send the array.

However I have been assuming that the interval between messages would be short enough that there would be one data item per transmission.

The thing is; the transmitters send the data as a struct package containing 4 variables (you can check the code for reference). I’m not sure it is efficient and possible to store that in an array then transmit the array when transmission is possible!

Another reason why I’m particular about keeping the data is because the system is suppose to give real-time info (preferably each second) about the power consumption and for a more accurate representation on the Grafana Dashboard!

What do you think??

Dondoter:
The thing is; the transmitters send the data as a struct package containing 4 variables

Then just send the struct. Or send an array of structs as long as the total size does not exceed 32 bytes.

...R

Hi,

Robin2:
There are probably several approaches to that. One that springs to mind is to use a trial and error system (which I mentioned earlier) until a slave has a success. Then try at the prescribed interval from that point onwards.

So I've been able to figure out how to set up an interval for the transmitter but I still have a challenge in that: overtime, after a few successful transmissions, there is the occasional simultaneous reception of data from 2-3 transmitters and that causes collision and I'm not able to properly store those set of data in the sd card!

Is there anything I'm neglecting or is there a better way to approach the issue??
Thanks...

Dondoter:
Is there anything I’m neglecting or is there a better way to approach the issue??

As you have given no details about what you are doing it is hard to know what might be missing.

…R

Let me post the updated TX code for 2 transmitters…

Transmitter 1

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

#define ACS712 A0

unsigned long lastData = 0;
int dataSum = 0;
int Count = 0;

float vpc = 4.8828126; // approx. voltage per count
RF24 radio(9, 10); // CE, CSN
RF24Network network (radio);

#define nodeID 1

struct SensorData {
  float v1;
  float c1;
  float p1;
  float k1;
} Sensor;

const uint16_t this_node = 01;        // Address of our node in Octal format
const uint16_t other_node = 00;       // Address of the other node in Octal format
const unsigned long interval = 5000; //ms  // How often to send 'hello world to the other unit
unsigned long last_sent;

void setup()
{
  Serial.begin(115200);

  SPI.begin();
  radio.begin();
  network.begin(90, this_node);
  radio.setDataRate(RF24_2MBPS);
  pinMode(A0, INPUT_PULLUP);
}

void loop()
{
  if (millis() > lastData + 1) {
    dataSum += sq(analogRead(ACS712) - 512);
    Count++;

    lastData = millis();
  }
  // If statement to make calculations.
  if (Count == 1000) {

    // To get the RMS of the data.
    float mean = dataSum / Count;
    float value = sqrt(mean);

    // To get the Voltage (in volts) consumed.
    float Voltage = value * vpc;

    // To get the Current (in amps) consumed.
    float Current = Voltage / 66;

    // To get the Power (in watts) consumed.
    float Power = Current * 240;

    //To get the Consumption rating (in kilowatthour)
    float Consumption = Power / 1000;

    dataSum = 0;
    Count = 0;

    network.update();

    unsigned long now = millis();
    if (now - last_sent >= interval) {
      last_sent = now;

      //Attach values to struct
      Sensor.v1 = Voltage;
      Sensor.c1 = Current;
      Sensor.p1 = Power;
      Sensor.k1 = Consumption;
      Serial.println(Sensor.v1);
      Serial.println(Sensor.c1);
      Serial.println(Sensor.p1);
      Serial.println(Sensor.k1);
      RF24NetworkHeader header(other_node);
      //Transmit data and check if it goes succesfully!
      bool ok = network.write(header, &Sensor, sizeof(Sensor));
      if (ok) {
        Serial.println("Sent!");
      } else {
        Serial.println("Failed!");
      }
    }
  }
}

Transmitter 2

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

#define ACS712 A0

unsigned long lastData = 0;
int dataSum = 0;
int Count = 0;

float vpc = 4.8828126; // approx. voltage per count
RF24 radio(9, 10); // CE, CSN
RF24Network network (radio);

#define nodeID 2

struct SensorData {
  float v1;
  float c1;
  float p1;
  float k1;
} Sensor;

const uint16_t this_node = 02;        // Address of our node in Octal format
const uint16_t other_node = 00;       // Address of the other node in Octal format
const unsigned long interval = 2500; //ms  // How often to send 'hello world to the other unit
unsigned long last_sent;

void setup()
{
  Serial.begin(115200);

  SPI.begin();
  radio.begin();
  network.begin(90, this_node);
  radio.setDataRate(RF24_2MBPS);
  pinMode(A0, INPUT_PULLUP);
}

void loop()
{
  if (millis() > lastData + 1) {
    dataSum += sq(analogRead(ACS712) - 512);
    Count++;

    lastData = millis();
  }
  // If statement to make calculations.
  if (Count == 1000) {

    // To get the RMS of the data.
    float mean = dataSum / Count;
    float value = sqrt(mean);

    // To get the Voltage (in volts) consumed.
    float Voltage = value * vpc;

    // To get the Current (in amps) consumed.
    float Current = Voltage / 66;

    // To get the Power (in watts) consumed.
    float Power = Current * 240;

    //To get the Consumption rating (in kilowatthour)
    float Consumption = Power / 1000;

    dataSum = 0;
    Count = 0;

    network.update();

    unsigned long now = millis();
    if (now - last_sent >= interval) {
      last_sent = now;

      //Attach values to struct
      Sensor.v1 = Voltage;
      Sensor.c1 = Current;
      Sensor.p1 = Power;
      Sensor.k1 = Consumption;
      Serial.println(Sensor.v1);
      Serial.println(Sensor.c1);
      Serial.println(Sensor.p1);
      Serial.println(Sensor.k1);
      RF24NetworkHeader header(other_node);
      //Transmit data and check if it goes succesfully!
      bool ok = network.write(header, &Sensor, sizeof(Sensor));
      if (ok) {
        Serial.println("Sent!");
      } else {
        Serial.println("Failed!");
      }
    }
  }
}

Master Receiver

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

RF24 radio(53, 48); // CE, CSN

struct SensorData {
  float v1;
  float c1;
  float p1;
  float k1;
} Sensor;

RF24Network network(radio);
const uint16_t this_node = 00;   // Address of this node in Octal format ( 04,031, etc)
const uint16_t node1 = 01;      // Address of the other node in Octal format
const uint16_t node2 = 02;
const uint16_t node3 = 03;

void setup(void)
{
  Serial.begin(115200);

  SPI.begin();
  radio.begin();
  network.begin(90, this_node);  //(channel, node address)
  radio.setDataRate(RF24_2MBPS);
}

void loop(void)
{
  network.update();
  while ( network.available() ) {     // Is there any incoming data?
    RF24NetworkHeader header;
    SensorData Sensor;
    network.read(header, &Sensor, sizeof(Sensor));
    if (header.from_node == 1) {    // If data comes from Node 02
      Serial.println("Data from Sensor 1: ");
      Serial.println(Sensor.v1);
      Serial.println(Sensor.c1);
      Serial.println(Sensor.p1);
      Serial.println(Sensor.k1);
    }
    if (header.from_node == 2) {    // If data comes from Node 012
      Serial.println("Data from Sensor 2: ");
      Serial.println(Sensor.v1);
      Serial.println(Sensor.c1);
      Serial.println(Sensor.p1);
      Serial.println(Sensor.k1);
    }
    if (header.from_node == 3) {    // If data comes from Node 012
      Serial.println("Data from Sensor 3: ");
      Serial.println(Sensor.v1);
      Serial.println(Sensor.c1);
      Serial.println(Sensor.p1);
      Serial.println(Sensor.k1);
    }
  }
}

I am not familiar with the network version of the RF24 library - do you really need it?

I see nothing in any of your programs that looks like a strategy to avoid transmit collisions or to recover from them.

This line (in the Tx1 program)

  if (millis() > lastData + 1) {

is not the proper way to use millis()

This other line from the same program is

if (now - last_sent >= interval) {

...R

Why are many of your comments wrong?

const unsigned long interval = 2500; //ms  // How often to send 'hello world to the other unit
const unsigned long interval = 5000; //ms  // How often to send 'hello world to the other unit
    if (header.from_node == 1) {    // If data comes from Node 02
    if (header.from_node == 2) {    // If data comes from Node 012
    if (header.from_node == 3) {    // If data comes from Node 012

Why while and not if?

  while ( network.available() ) {     // Is there any incoming data?

Why are you messing around with octal notation?

const uint16_t this_node = 01;        // Address of our node in Octal format
const uint16_t other_node = 00;       // Address of the other node in Octal format

You are aware that the struct can have functions, let's say print() that prints the whole structure?

What is the point in transmitting power consumption data 20 or 40 times each second?

const unsigned long interval = 2500; //ms  // How often to send 'hello world to the other unit
const unsigned long interval = 5000; //ms  // How often to send 'hello world to the other unit

Whandall:
Why are many of your comments wrong?

Why while and not if?

Why are you messing around with octal notation?

.Comments
Sorry about that! I used an old code as reference and I've not really thought about editing comments since I'm not yet done with the project!

.While()
I thought it would be better to use while loop to keep the receiver always checking for the headers coming from all the transmitters!

.Octal notations
I think it recognizes 01 and 00 and converts it to octal form at execution!

Whandall:
You are aware that the struct can have functions, let's say print() that prints the whole structure?

No, I had no idea. Besides it is to test to ensure the data is correctly written into the struct so.. But again I had no idea!

Whandall:
What is the point in transmitting power consumption data 20 or 40 times each second?

const unsigned long interval = 2500; //ms  // How often to send 'hello world to the other unit

const unsigned long interval = 5000; //ms  // How often to send 'hello world to the other unit

That is not really what I'm trying to do there. The point is to set different transmission intervals per transmitter so each one transmits a single data sample without colliding so the system provides realtime data except you advice that it is not really absolutely necessary!
I thought it was the best option I had... I really I'm learning while developing, hence my many trial and error attempts!

thanks...

Robin2:
I am not familiar with the network version of the RF24 library - do you really need it?

It looks simpler to implement and understand!
Like I said in the previous reply, I'm learning so I'm going for simpler libraries that are kinda straight forward!

Robin2:
I see nothing in any of your programs that looks like a strategy to avoid transmit collisions or to recover from them.

I don't have nor have I found a solution or even reference that could help! I did follow your link but I didn't understand how to apply that to my case!
I really need help, I have no idea what to do about this issue; ive been stuck on just that for the past 2 months now and it's really frustrating!

Robin2:
This line (in the Tx1 program)

if (millis() > lastData + 1) {

is not the proper way to use millis()

This other line from the same program is

if (now - last_sent >= interval) {

...R

Thanks for all so far...

Dondoter:

What is the point in transmitting power consumption data 20 or 40 times each second?

That is not really what I’m trying to do there. The point is to set different transmission intervals per transmitter so each one transmits a single data sample without colliding so the system provides realtime data except you advice that it is not really absolutely necessary!

But it is what you are doing. One transmitter sends 40 the other 20 packets a second.
Wouldn’t there be a much lower chance of a collision if you would send only 1 packet per second/station?

An example for usage of a print function in SensorData (MasterReceiver)

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

const uint16_t this_node = 0;

struct SensorData {
  float v1;
  float c1;
  float p1;
  float k1;
  void print(byte fromNode) {
    Serial.print(F("Data from Sensor "));
    Serial.print(fromNode);
    Serial.print(F(": v = "));
    Serial.print(v1);
    Serial.print(F(", c = "));
    Serial.print(c1);
    Serial.print(F(", p = "));
    Serial.print(p1);
    Serial.print(F(", k = "));
    Serial.println(k1);
  }
};

RF24 radio(53, 48); // CE, CSN
RF24Network network(radio);
RF24NetworkHeader header;
SensorData rxData;

void setup(void) {
  Serial.begin(115200);
  SPI.begin();
  radio.begin();
  network.begin(90, this_node);  //(channel, node address)
  radio.setDataRate(RF24_2MBPS);
}

void loop(void) {
  network.update();
  if (network.available()) {
    network.read(header, &rxData, sizeof(rxData));
    rxData.print(header.from_node);
  }
}

Whandall:
But it is what you are doing. One transmitter sends 40 the other 20 packets a second.
Wouldn't there be a much lower chance of a collision if you would send only 1 packet per second/station?

I reckon there will be a lower chance of collision.
I simply thought that 4000ms and 2000ms represented 4s and 2s delays between transmission. I had no idea it also would increase the packets sent!

From my knowledge of OS memory allocation, I thought doing that would help put transmitters in a queue; each with its time allocated on the system.
Is it then possible to apply the principle of round robin to the system so transmissions run one after another in a chain?
Or could you recommend another method?!

Whandall:
An example for usage of a print function in SensorData (MasterReceiver)

Thanks for the edit though... It worked good!

Dondoter:
I simply thought that 4000ms and 2000ms represented 4s and 2s delays between transmission. I had no idea it also would increase the packets sent!

You are right, somehow I was on the wrong track.
With seconds between transmissons there is absolutely no reason for any fail by collissions.

So my guess is the usual one, unsufficient power supply to the NRFs.

Dondoter:
I don't have nor have I found a solution or even reference that could help! I did follow your link but I didn't understand how to apply that to my case!

If you don't tell me what you don't understand I can't help.

I suggested some strategies for avoiding collisions in Replies #2 and #6 and I got the impression from your Reply #3 that you understood the concepts. Then you went off in another direction with your Reply #7

There is a great deal of common sense in what @Whandall has said in Reply #18

...R