nRF24l01 one master 10+ slaves two way communication

Hello,

I'm in the middle of a window shade centralized control system and I'm wondering if I can have 10 slaves with one master for wireless transmission.

Here's my project:
-1 master made out of an arduino Mega, a touch screen and an nRF24l01 module where you can choose between 10 windows or select them all and send an "up" or "down" command.

-10 slaves (one on each window) each made out of an arduino pro mini 2 relays and 3 buttons which can be activated remotely with it's nRF24l01 module or with the push buttons.

I want that when I select a window on the master and send a command, after the command is sent and the shade went up or down the slave send a message back saying that the new position is up or down... so that the logo on the screen can be updated to an open or closed window.

I also want that if I manually control a shade with the slave's push button, after it's done moving, it will send a message to the master saying in wich position it is so that the master can update the logo on the screen

I started to code everything and it's working fine with two slaves I used one different pipe for each slaves, but I just realized while reading through the forum that I'm limited to 6 pipes and I would have needed 10 pipes.

Is there a way for the master to be listening at 10 pipes ?

Otherwise I was thinking about "coding" my command
every receiver would be on the same pipe so they would all receive the command
I would have something like
dataToSend = 100 or 101 or 102 or 201; 202; 203.....
and on receiver1 an if fonction only doing something if dataReceived = 100 or 101 or 102
on receiver2 an if fonction only doing something if dataReceived = 200 or 201 or 202....

and it would send back 110; 111; 112 for slave1 / 210; 211; 212 for slave2 ...
and an if function in the master would sort everything and know that if the message is 110 or 111 or 112 it's from slave1 if the message is 210 or 211 or 212 it's from slave2...

But I don't really like this solution where everybody would be talking on the same pipe.

Don't know if that's really clear...

If anyone have a better idea I'm all ears.

Note that I didn't post my code for now since all the parts for the touch screen would be confusing and it's working for now with 2 slaves.

Thanks

Pierre

You don't need ten pipes - just one will be fine.

Have a look at this Simple nRF24L01+ Tutorial.

It includes an example for a master and 2 slaves that can easily be extended to a larger number of slaves.

...R

I would give each node its own address.

State (regularly) and state changes (on event) would be sent to the master address,
the master listens to that address when it is not commanding one of its slaves.

Commands would be sent to the respective slave address which will be listened to by the slave while it is not sending state data to the master.

Robin, Whandall, thank you for taking the time to answer me.

Robin I had already seen your tutorial, Wich really helped me, I based the wireless part of my code on it.

I read everything again and I think I'm getting confused between pipe and address.

If I understood correctly the nRF24L01 module can be listening to 6 pipes each with 6 different addresses.

So it could be listening to 36 devices ?

So if I want to be listening to 10 slaves (not speaking at the same time) I just have to set 2 pipes each with 5 addresses but then I don't understand why you said I could only use 1 pipe.

Is there a function like pipe_num to know where the data was sent from ?

I'm sorry it might be a really stupid question but I've been reading and looking at a lot of threads and there must be something I don't get (first wireless project and I'm still an Arduino beginner)

Thanks again.

Pierre

Think of the pipes as a bunch of post-boxes in an apartment building. Each post box has the the apartment number and when a letter is popped through the letter box someone looks at it, checks the apartment number and puts it into to the post-box for that apartment. When the occupier comes home s/he checks his/her post-box and collects the letter.

The nRF24 channel serves the same purpose as the apartment building address - everything on that channel is received. The address fulfils the same role as the apartment number - allowing messages to be segregated into different groups (pipes).

One reason for using multiple pipes is so that the receiver can identify who sent the message. IMHO it is easier to do that by including an ID code in each message.

There is only one wireless receiver so, even if multiple pipes are being used, only one message can be received at any one time. If two or more messages are transmitted at the same time both will be garbled.

Unfortunately in many demo programs the author uses a variable named "pipe" to hold the address - which just adds to the confusion.

If all the slaves send to the same address the Master could be listening to 100 slaves using a single pipe.

...R

Ockay16:
Is there a function like pipe_num to know where the data was sent from ?

bool RF24::available(uint8_t* pipe_num)
{
  if (!( read_register(FIFO_STATUS) & _BV(RX_EMPTY) )){

    // If the caller wants the pipe number, include that
    if ( pipe_num ){
	  uint8_t status = get_status();
      *pipe_num = ( status >> RX_P_NO ) & 0x07;
  	}
  	return 1;
  }
  return 0;
}

Thanks again for your help.

If I understand correctly the pipes are mainly used to know who sent the message with the function pipe_num as Whandall confirmed, but are limited to 6.

As confirmed by Robin I think it's going to be easier to put a code at the begining of my Wireless signal and set every slave on the same address.

I won't be able to use acknowledge though.

I'll create a thread for my project with my whole code and put a link here. It might be of interest to someone with the same question.

Thanks again guys!

If each node gets a personal address, communication between any nodes can use acks.

All 'slaves' can report to the 'master' pipe with acks.

All nodes could open an addional pipe (without ack) that can be used to distribute data that all
nodes should see, if they have to ack the recepion of that it could be done via the senders personal address which can be embedded in the request packet.

Ockay16:
I won't be able to use acknowledge though.

I'll create a thread for my project with my whole code and put a link here. It might be of interest to someone with the same question.

I don't see why you would not have acknowledgements?
The only situation where you can't use it is when the Master sends a message that is picked up by several slaves listening on the same address. In my example there is a different address for each slave. In another scenario if the master is listening for messages from slaves the slaves can all call the master on the same address (one at a time, of course) and in that case the master can acknowledge each message.

Just continue this Thread so we have all the info in one place.

...R

You're right, I can us acknowledgement when the slaves are sending to the master one by one.

But since I'm limited to 6 addresses on one pipe I was planing to use only one adresse for the slaves and then have all of them listening for the master and just put a code at the beginning of the transmission so that each slave will know if it's supposed to do something.

Wasn't it what you suggested ?

IMHO it is easier to do that by including an ID code in each message.

It seemed easier to do so because I just don't understand something with the address and pipes (I'm sorry because you really tried to explain :frowning: )

What I'm not sure to understand is where do you put the "ID" for the pipe and the "ID" for the adresse in the code.

If I want to talk to a slave I use the command
radio.openWritingPipe(slaveAddress);

So for 10 slaves it would be
radio.openWritingPipe('R','x','A','A','A'); //for slave 1
radio.openWritingPipe('R','x','A','A','B'); //for slave 2
radio.openWritingPipe('R','x','A','A','C'); //for slave 3
radio.openWritingPipe('R','x','A','A','D'); //for slave 4
radio.openWritingPipe('R','x','A','A','E'); //for slave 5
radio.openWritingPipe('R','x','A','A','F'); //for slave 6
radio.openWritingPipe('R','x','A','A','G'); //for slave 7
radio.openWritingPipe('R','x','A','A','H'); //for slave 8
radio.openWritingPipe('R','x','A','A','I'); //for slave 9
radio.openWritingPipe('R','x','A','A','J'); //for slave 10

But where do I choose the pipe ?

Thanks

Pierre

Ockay16:
Wasn't it what you suggested ?

No. You seem to have got things very confused.

There is only one writing pipe so if you want the master to address each slave separately (which is what I recommend unless you can produce a good reason not to do it that way) then you speak to them one at a time through the single writing pipe on the master, That is how my example works.

...R

OK, I know where I got confused and now I just feel stupid :wink:

I was confused between the pipe and the address and where you use it.

I read again your tuto, the RF24 library and a few other posts and now I'think I got it.

What I (finally) understood is that there is only 1 writing pipe.
You can have only be listening at 6 reading pipes at once and you declare the number of the reading pipe in the openReadingPipe command.

So in my case, I will have one master:
-reading on pipe 1 and on it's master address

  • sending (on pipe 0 which is the only writing pipe) to 10 slaves with 10 different adresses

The 10 slaves will be :
-reading on pipe 1 and there own slave address
-send to the master on the master address

If I want the master to know which slave is talking I will put a "prefix" to code the message from the slave so that the master can sort everything.

I'll try to come with something and post it here in the weekend.

Thanks Robin and Whandall !

Pierre

Yes, it seems you begin to understand the capabilities and limitations of the NRF24L01+.

I fear you still don't get the .available(&uint8_t) meaning, but maybe that's a false impression.

You don't select the pipe you are reading from, you are told on which pipe the next packet to read came in.

Hi!

Here is an extract of my first working solution.
For now it's only with two slaves since I only have 3 boards for testing but I don't see a reason for it not to work with more slaves.

Master code:

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

#define CE_PIN 30
#define CSN_PIN 32

const byte livingRoomAddress[5] = {'R','x','A','A','A'}; // slave 1
const byte kitchenAddress[5] = {'R','x','A','A','B'}; // slave 2
const byte studyAddress[5] = {'R','x','A','A','C'}; // not used for now
const byte room1Address[5] = {'R','x','A','A','D'}; //not used for now
const byte room2Address[5] = {'R','x','A','A','E'}; //not used for now
const byte bathRoom1Address[5] = {'R','x','A','A','F'}; //not used for now
const byte cellarAddress[5] = {'R','x','A','A','G'}; //not used for now
const byte room3Address[5] = {'R','x','A','A','H'}; //not used for now
const byte bathRoom2Address[5] = {'R','x','A','A','I'}; //not used for now
const byte masterAddress[5] = {'T','X','a','a','a'}; // master

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

int dataToSend = 0; // must match dataReceived in slave


bool livingSel = false; // is living room Icon selected on screen ?
bool kitchenSel = false; // is kitchen icon selected on screen ?

bool upSel = false; // is up button selected on screen ?
bool stopSel = false; // is stop button selected on screen ?
bool downSel = false; // is down button selected on screen ?

int livingState = 2; // 1 shade is up; 2 shade is stopped somewhere; 3 shade is down
int kitchenState = 2;


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

  radio.begin();
    radio.setDataRate( RF24_250KBPS );


    radio.openReadingPipe(1, masterAddress);
   

    radio.setRetries(3,5); // delay, count
    radio.startListening();

 

void loop()
{

    if (upSel){ // up command on the screen
      if (livingSel){ // living room logo selected on the screen
        radio.openWritingPipe(livingRoomAddress);
      radio.stopListening();
      dataToSend = 1;
      radio.write( &dataToSend, sizeof(dataToSend) );
      radio.startListening();
      livingSel = false;
      }
      if (kitchenSel){
        radio.openWritingPipe(kitchenAddress);
      radio.stopListening();
      dataToSend = 1;
      radio.write( &dataToSend, sizeof(dataToSend) );
      radio.startListening();
      kitchenSel = false;
      }
      upSel = false;
    }
    if (stopSel){
      if (livingSel){
        radio.openWritingPipe(livingRoomAddress);
      radio.stopListening();
      dataToSend = 2;
      radio.write( &dataToSend, sizeof(dataToSend) );
      radio.startListening();
      livingSel = false;
      }
      if (kitchenSel){
        radio.openWritingPipe(kitchenAddress);
      radio.stopListening();
      dataToSend = 2;
      radio.write( &dataToSend, sizeof(dataToSend) );
      radio.startListening();
      kitchenSel = false;
      }
      stopSel = false;
    }
    if (downSel){
      if (livingSel){
        radio.openWritingPipe(livingRoomAddress);
      radio.stopListening();
      dataToSend = 3;
      radio.write( &dataToSend, sizeof(dataToSend) );
      radio.startListening();
      livingSel = false;
      }
      if (kitchenSel){
        radio.openWritingPipe(kitchenAddress);
      radio.stopListening();
      dataToSend = 3;
      radio.write( &dataToSend, sizeof(dataToSend) );
      radio.startListening();
      kitchenSel = false;
      }
      downSel = false;
    }

    bool newData = false;
    int dataReceived = 0;
      if (radio.available()){
          newData = true;
          radio.read( &dataReceived, sizeof(dataReceived));
          Serial.print ("Data received");
          Serial.println (dataReceived);
          delay(500);
      }

      if (dataReceived < 110){ // living room is sending
        if (newData){
          tft.fillRect(LEFTGAP, BOXUPGAP, BOXSIZE, BOXSIZE, BLACK); // reset icon on screen
          newData = false;        
        }
        if (dataReceived == 101){
          livingState = 1;
        }
        else if (dataReceived == 102){
          livingState = 2;
        }
        else if (dataReceived == 103){
          livingState = 3;
        }
      }
      else if (dataReceived < 210){ // kitchen is sending
        if (newData){
          tft.fillRect(LEFTGAP+COLUMNW, BOXUPGAP, BOXSIZE, BOXSIZE, BLACK); // reset icon on screen
          newData = false;
        }
        if (dataReceived == 201){
          kitchenState = 1;
        }
        else if (dataReceived == 202){
          kitchenState = 2;
        }
        else if (dataReceived == 203){
          kitchenState = 3;
        }
      }
        
      
}// end loop

Slave code:

//Global Variables
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>


#define CE_PIN 10
#define CSN_PIN 9

const byte slaveAddress[5] = {'R','x','A','A','A'};
const byte masterAddress[5] = {'T','X','a','a','a'};
int roomID = 100;

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

int dataReceived = 0; // must match dataToSend in master

const int upSwitch = 3; // Up button
const int stopSwitch = 4; // Stop button
const int downSwitch = 5; // Down button
const int upRelay = 6; // Up relay
const int downRelay = 7; // Down relay

int upRelayState = LOW; //relays are off at startup
int downRelayState = LOW;

unsigned long upRelayOnMillis; // when Up relay is activated
unsigned long upTime = 5000; // how long is allowed to go up
unsigned long downRelayOnMillis; // when Down relay is activated
unsigned long downTime = 5000; // how long is allowed to go down

int shadeState = (roomID + 2); //1 for up; 2 for X and 3 for down
bool shadeGoUp = false; // turn on shade to go up
bool shadeStop = false; // stop the shade where it is
bool shadeGoDown = false; // tun on shade to go down


void setup() {
    Serial.begin(9600);
    
    pinMode(upSwitch,INPUT);
    pinMode(stopSwitch,INPUT);
    pinMode(downSwitch,INPUT);
    pinMode(upRelay,OUTPUT);
    pinMode(downRelay,OUTPUT);

    radio.begin();
    radio.setDataRate( RF24_250KBPS );

    radio.openWritingPipe(masterAddress); // NB these are swapped compared to the master
    radio.openReadingPipe(1, slaveAddress);

    radio.setRetries(3,5); // delay, count
    radio.startListening();
}

void loop() {
 
    if ( radio.available() ) {
        radio.read( &dataReceived, sizeof(dataReceived) );
    }
  
if (digitalRead(upSwitch) == HIGH || dataReceived == 1){ //if up switch pressed or command to go up from radio
  if ((shadeState == (roomID + 1)) || (shadeGoDown) || (shadeGoUp)) { // if shade already up or going up or down
  dataReceived = 0;} // reset radioCommand
  else{
  shadeGoUp = true; // else go up = ok
  upRelayOnMillis = currentMillis; // reset timer for going up
  dataReceived = 0; // reset command so it doesen't go endlessly
}
}
if (digitalRead(stopSwitch) == HIGH || dataReceived == 2){ //if stop switch pressed or command to stop from radio
    shadeStop = true;
    dataReceived = 0;
  }
if (digitalRead(downSwitch) == HIGH || dataReceived == 3){ //if down switch pressed or command to g down from radio
  if ((shadeState == (roomID + 3)) || (shadeGoUp) || (shadeGoDown)){ // if shade already down or moving
  dataReceived = 0;}
  else{    
    shadeGoDown = true;
    downRelayOnMillis = currentMillis;
    dataReceived = false;
}
}

if (shadeGoUp){
  digitalWrite(upRelay,HIGH); // turn relay on
 if((unsigned long)(currentMillis - upRelayOnMillis) >= upTime){ // when enough time has passed
    digitalWrite(upRelay,LOW); // turn relay off
    shadeGoUp = false; // shade is not going up anymore
    shadeState = (roomID + 1);
    delay (200);
    radio.stopListening();
    radio.write( &shadeState, sizeof(shadeState) );
    delay (100);
    radio.write( &shadeState, sizeof(shadeState) );
    radio.startListening();
  }
}

if (shadeStop){
  digitalWrite(upRelay,LOW);
  digitalWrite(downRelay,LOW);
  shadeGoUp = false;
  shadeState = (roomID + 2);
  shadeGoDown = false;
  shadeStop = false;
  delay(200);
  radio.stopListening();
  radio.write( &shadeState, sizeof(shadeState) );
  delay(100);
  radio.write( &shadeState, sizeof(shadeState) );
  radio.startListening();
}

if (shadeGoDown){
    digitalWrite(downRelay,HIGH);
  if ((unsigned long)(currentMillis - downRelayOnMillis) >= downTime){
    digitalWrite(downRelay,LOW);
    shadeGoDown = false;
    shadeState = (roomID + 3);
    delay (200);
    radio.stopListening();
    radio.write( &shadeState, sizeof(shadeState) );
    delay(100);
    radio.write( &shadeState, sizeof(shadeState) );
    radio.startListening();
   }
   } 
  }

It's working for now but it's absolutely not elegant.

I had to send the commands 2 times (100ms delay interval) because it seems that some times the master was not getting the signal from the slave.

I also had to add a different delay for all the slaves (here 200 ms) because when I was talking to 2 receivers at the same time, they were answering at the same time and the master was not receiving one of the two slave's signal properly.

I think there is something to do with acknowlegement I'll have to look into it.

Thanks guys for your help.

Pierre

You have a lot of stuff in your master code that is "// not used for now". It will be a lot easier to help if you make a version of your program that omits all that stuff - so the program is much shorter and has no distractions.

It will also be a lot easier to help if you use the Auto-Format tool to indent your code consistently so that the different code blocks are visually obvious.

Having said all that, please conceptually put the code to one side for a moment and write a simple clear English language description of how you want a master and one of the slaves to work. Write each step of the process on a separate line. The sequence of interactions between the master and slave are important. I need all that in order to understand your requirement before considering whether the program is suitable for meeting the requirement.

...R

Some suboptimal facts about your code

  • delay(500) after reception but before processing,
  • not a single check for a failing send
  • only five retries
  • RF24_PA_MAX
  • only unsynchronized masters
  • convoluted, repeated code (master send)
  • sending of a single int without any further info, id, count, etc