Multiple Receivers - RF24

Hi all,
I need to transmit a simple command from one transmitter to many receivers. The transmitter will have 10 individual buttons and their will be 10 receivers. When a button is pressed, the associated receiver will activate an LED.

I do not need the receivers to communicate back to the transmitter. So, can I just broadcast, for example, a number and have the receivers activate the LED if they receive their number? Or do I need to have the transmitter send a command to each receiver individually?

Hopefully that makes sense, let me know what you think.

Thanks,
Kyle D

It depends on how important it is that the receivers actually receive the transmission.
Radio is an imperfect transmission medium.

If you want all the receivers to receive the same message you will need to give them all the same address and disable the auto-acknowledgement feature. That simplifies the code for the transmitter but it cannot know if a message is received.

If you give each receiver a separate address and send individual messages then you can use the autoAck feature which will allow for a more reliable system.

Have a look at the examples in this Simple nRF24L01+ Tutorial

...R

Thanks Robin2,

Will this work with more than 6 receivers? I think I am still confused on the difference between pipes and addresses.

Thanks,
Kyle

kadorosz:
Will this work with more than 6 receivers? I think I am still confused on the difference between pipes and addresses.

You could have 100 receivers. Pipes are only relevant if you want a receiver to sort incoming messages into different groups. For your application you will only need 1 pipe on each nRF24.

Think of the pipes as 6 shelves onto which the mail for different residents in an apartment block can be placed. All the letters come through the same mail slot (the radio receiver listening on Channel N) and when they fall on the floor someone picks them up, looks at the name of the recipient (the address the message was sent to) and puts them on the correct shelf (the pipe that has been assigned the same address as the message) or shreds them if they are for a recipient who lives in another block (i.e. if they are for an address that is not assigned to any of the pipes on this nRF24)

...R

Excellent, thanks! So would I do something like this then?

const byte slaveAddress[10][5] = {"00001","00002","00003","00004","00005","00006","00007","00008","00009","00010"};

Then would you put the radio.openWritingPipe(); in a for loop and move it to the Void Loop so that you send transmissions to each receiver sequentially?

kadorosz:
So would I do something like this then?

Yes. There is an example in my tutorial that sends to 2 slaves.

...R

I get the error "'send' not declared in this scope". Am I close?

// MultiTxAckPayload - the master or the transmitter
//   works with two Arduinos as slaves
//     each slave should the SimpleRxAckPayload program
//       one with the adress {'R','x','A','A','A'}
//         and the other with {'R','x','A','A','B'}

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


#define CE_PIN   7
#define CSN_PIN 8

const byte numSlaves = 36;
const byte slaveAddress[numSlaves][5] = {
          "00001","00002","00003","00004","00005","00006",
          "00007","00008","00009","00010","00011","00012",
          "00013","00014","00015","00016","00017","00018",
          "00019","00020","00021","00022","00023","00024",
          "00025","00026","00027","00028","00029","00030",
          "00031","00032","00033","00034","00035","00036",
          };

const int inButtons[numSlaves]={
    1,2,3,4,5,6,
    7,8,9,10,11,12,
    13,14,15,16,17,18,
    19,20,21,22,23,24,
    25,26,27,28,29,30,
    31,32,33,34,35,36
  };
  
int stateButtons[numSlaves]={
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0
};

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

char dataToSend[10] = "ToSlvN  0";
char txNum = '0';
int ackData[2] = {-1, -1}; // to hold the two values coming from the slave
bool newData = false;

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

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

void setup() {

    Serial.begin(9600);
    Serial.println("SimpleTxAckPayload Starting");

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

    radio.enableAckPayload();

    radio.setRetries(3,5); // delay, count
    
    for(int i=0; i<numSlaves; i++){
      pinMode(inButtons[i], INPUT);
    }
}

void loop() {
    for(int i=0; i<numSlaves; i++){
     stateButtons[i] = digitalRead(inButtons[i]);
    }
  
  
    currentMillis = millis();
    if (currentMillis - prevMillis >= txIntervalMillis) {
        send();
    }
}

void send() {

        // call each slave in turn
    for (byte n = 0; n < numSlaves; n++){

            // open the writing pipe with the address of a slave
        radio.openWritingPipe(slaveAddress[n]);

        bool rslt;
        rslt = radio.write( &stateButtons[n], sizeof(stateButtons[n]) );
            // Always use sizeof() as it gives the size as the number of bytes.
            // For example if dataToSend was an int sizeof() would correctly return 2

        Serial.print("  ========  For Slave ");
        Serial.print(n);
        Serial.println("  ========");
        Serial.print("  Data Sent ");
        Serial.print(stateButtons[n]);
        if (rslt) {
            if ( radio.isAckPayloadAvailable() ) {
                radio.read(&ackData, sizeof(ackData));
                newData = true;
            }
            else {
                Serial.println("  Acknowledge but no data ");
            }
            updateMessage();
        }
        else {
            Serial.println("  Tx failed");
        }
        showData();
        Serial.print("\n");
    }

    prevMillis = millis();
 }


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

void showData() {
    if (newData == true) {
        Serial.print("  Acknowledge data ");
        Serial.print(ackData[0]);
        Serial.print(", ");
        Serial.println(ackData[1]);
        Serial.println();
        newData = false;
    }
}

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

void updateMessage() {
        // so you can see that new data is being sent
    txNum += 1;
    if (txNum > '9') {
        txNum = '0';
    }
    dataToSend[8] = txNum;
}

I downloaded the code from Reply #7 and found different problem when I tried to compile it.
This line

const byte slaveAddress[numSlaves][5] = {

should be

const byte slaveAddress[numSlaves][6] = {

to make space for the terminating 0 for each string.

It then compiled without error.

However I'm not sure that the addresses will work properly like that - I have never tried it. It would be worth trying with a single address just to make sure - as it is a simple way to define addresses if it works. The doubt in my mind is which 5 of the 6 bytes are used for the address - the first 5 or the last 5.

In my examples I define addresses as individual characters (or bytes) like this

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

...R

Thanks, Robin2.

I have used that type of address successfully before in a simple program so I know it works.

I did get it to compile but I am not getting and transmissions. Can you look over my transmitter and receiver code and let me know what you think?

You will see that the receiver reads the button state and then outputs a PPM signal on Channel 5. Channel 5 should read 1000 when the button is open and 2000 when the button is pressed.

Transmitter

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


#define CE_PIN   7
#define CSN_PIN 8

const byte numSlaves = 36;
const byte slaveAddress[numSlaves][6] = {
          "00001","00002","00003","00004","00005","00006",
          "00007","00008","00009","00010","00011","00012",
          "00013","00014","00015","00016","00017","00018",
          "00019","00020","00021","00022","00023","00024",
          "00025","00026","00027","00028","00029","00030",
          "00031","00032","00033","00034","00035","00036",
          };

const int inButtons[numSlaves]={
    3,4,1,2,5,6,
    7,8,9,10,11,12,
    13,14,15,16,17,18,
    19,20,21,22,23,24,
    25,26,27,28,29,30,
    31,32,33,34,35,36
  };
  
int stateButtons[numSlaves]={
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0
};

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

char dataToSend[10] = "ToSlvN  0";
char txNum = '0';
int ackData[2] = {-1, -1}; // to hold the two values coming from the slave
bool newData = false;

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

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

void setup() {

    Serial.begin(9600);
    Serial.println("SimpleTxAckPayload Starting");

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

    radio.enableAckPayload();

    radio.setRetries(3,5); // delay, count
    
    for(int i=0; i<numSlaves; i++){
      pinMode(inButtons[i], INPUT);
    }
}

void loop() {
    for(int i=0; i<numSlaves; i++){
     stateButtons[i] = digitalRead(inButtons[i]);
    }
  
  
    currentMillis = millis();
    if (currentMillis - prevMillis >= txIntervalMillis) {
        send();
    }
}

void send() {

        // call each slave in turn
    for (byte n = 0; n < numSlaves; n++){

            // open the writing pipe with the address of a slave
        radio.openWritingPipe(slaveAddress[n]);

        bool rslt;
        rslt = radio.write( &stateButtons[n], sizeof(stateButtons[n]) );
            // Always use sizeof() as it gives the size as the number of bytes.
            // For example if dataToSend was an int sizeof() would correctly return 2

        Serial.print("  ========  For Slave ");
        Serial.print(n);
        Serial.println("  ========");
        Serial.print("  Data Sent ");
        Serial.print(stateButtons[n]);
        if (rslt) {
            if ( radio.isAckPayloadAvailable() ) {
                radio.read(&ackData, sizeof(ackData));
                newData = true;
            }
            else {
                Serial.println("  Acknowledge but no data ");
            }
            updateMessage();
        }
        else {
            Serial.println("  Tx failed");
        }
        showData();
        Serial.print("\n");
    }

    prevMillis = millis();
 }


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

void showData() {
    if (newData == true) {
        Serial.print("  Acknowledge data ");
        Serial.print(ackData[0]);
        Serial.print(", ");
        Serial.println(ackData[1]);
        Serial.println();
        newData = false;
    }
}

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

void updateMessage() {
        // so you can see that new data is being sent
    txNum += 1;
    if (txNum > '9') {
        txNum = '0';
    }
    dataToSend[8] = txNum;
}

Receiver

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

#define chanel_number 7  //set the number of chanels
#define default_servo_value 1000  //set the default servo value
#define PPM_FrLen 22500  //set the PPM frame length in microseconds (1ms = 1000µs)
#define PPM_PulseLen 300  //set the pulse length
#define onState 1  //set polarity of the pulses: 1 is positive, 0 is negative
#define sigPin 10  //set PPM signal output pin on the arduino


RF24 radio(7, 8); // CE, CSN
const byte address[6] = "00001";

int buttonState = 0;
int ppm[chanel_number];

char dataReceived[10]; // this must match dataToSend in the TX
int ackData[2] = {109, -4000}; // the two values to be sent to the master
bool newData = false;


void setup() {
  Serial.begin(115200);
  radio.begin();
  radio.setDataRate( RF24_250KBPS );
  radio.openReadingPipe(0, address);

  radio.enableAckPayload();
  radio.writeAckPayload(1, &ackData, sizeof(ackData)); // pre-load data

  radio.startListening();
  
  //initiallize default ppm values
  for(int i=0; i<chanel_number; i++){
    ppm[i]= default_servo_value;
  }
  
  pinMode(sigPin, OUTPUT);
  digitalWrite(sigPin, !onState);  //set the PPM signal pin to the default state (off)
  
  cli();
  TCCR1A = 0; // set entire TCCR1 register to 0
  TCCR1B = 0;
  
  OCR1A = 100;  // compare match register, change this
  TCCR1B |= (1 << WGM12);  // turn on CTC mode
  TCCR1B |= (1 << CS11);  // 8 prescaler: 0,5 microseconds at 16mhz
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  sei();
}

void loop() {
  if ( radio.available() ) {
        radio.read(&buttonState, sizeof(buttonState));
        updateReplyData();
        newData = true;
  
    if (buttonState == HIGH) {
      ppm[6] = 2000;
      delay(10);
    }
    else {
      ppm[6] = 1000;
    }
  }
  
  Serial.println(ppm[6]);
  
  static int val = 1;
  
  ppm[0] = ppm[0] + val;
  if(ppm[0] >= 2000){ val = -1; }
  if(ppm[0] <= 1000){ val = 1; }
  delay(10);
}

ISR(TIMER1_COMPA_vect){  //leave this alone
  static boolean state = true;
  
  TCNT1 = 0;
  
  if(state) {  //start pulse
    digitalWrite(sigPin, onState);
    OCR1A = PPM_PulseLen * 2;
    state = false;
  }
  else{  //end pulse and calculate when to start the next pulse
    static byte cur_chan_numb;
    static unsigned int calc_rest;
  
    digitalWrite(sigPin, !onState);
    state = true;

    if(cur_chan_numb >= chanel_number){
      cur_chan_numb = 0;
      calc_rest = calc_rest + PPM_PulseLen;// 
      OCR1A = (PPM_FrLen - calc_rest) * 2;
      calc_rest = 0;
    }
    else{
      OCR1A = (ppm[cur_chan_numb] - PPM_PulseLen) * 2;
      calc_rest = calc_rest + ppm[cur_chan_numb];
      cur_chan_numb++;
    }     
  }
}
  
void updateReplyData() {
  ackData[0] -= 1;
  ackData[1] -= 1;
  if (ackData[0] < 100) {
      ackData[0] = 109;
  }
  if (ackData[1] < -4009) {
      ackData[1] = -4000;
  }
  radio.writeAckPayload(1, &ackData, sizeof(ackData)); // load the payload for the next time
  }

kadorosz:
I have used that type of address successfully before in a simple program so I know it works.

Thanks. Good to know.

I did get it to compile but I am not getting and transmissions. Can you look over my transmitter and receiver code and let me know what you think?

I have lost track of what problem you are trying to solve?

If your problem is communicating from a master to multiple slaves then I suggest you strip out all but the essential communication code until you get that to work. Just send "hello" and get the slave to respond by sending its address (as text) so you can know that the correct slave replies.

...R

PS I find master and slave less confusing because both of them transmit and receive

So I stripped everything down to the bare minimum to find the source of the problem.

This first code transmits successfully to one slave:

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

RF24 radio(7, 8); // CE, CSN

const byte address[6] = {'R','x','A','A','A'};

const int button = 4;
int buttonState = 0;

void setup() {
  Serial.begin(115200);
  pinMode(button, INPUT);
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
}

void loop() {
  buttonState = digitalRead(button);
  radio.write(&buttonState, sizeof(buttonState));
  Serial.println(buttonState);
  }

This second program, which should just transmit to multiple addresses, does not work at all. The slave is not receiving any data.

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

RF24 radio(7,8);

const byte numSlaves = 36;
const byte slaveAddress[numSlaves][5] = {
            {'R','x','A','A','A'},
            {'R','x','A','A','B'},
            {'R','x','A','A','C'},
            {'R','x','A','A','D'},
            {'R','x','A','A','F'} //more address to be added once working
          };

const int inButtons[numSlaves]={
    4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
  }; //all buttons set to same input to help troubleshoot
  
int stateButtons[numSlaves]={
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0,
    0,0,0,0,0,0
  };

void setup() {

    Serial.begin(115200);
    Serial.println("Start Transmission");
    radio.begin();
    radio.setPALevel(RF24_PA_MIN);
    radio.stopListening();
}

void loop() {
    for(int i=0; i<numSlaves; i++){
     pinMode(inButtons[i], INPUT);
     stateButtons[i] = digitalRead(inButtons[i]);
     radio.openWritingPipe(slaveAddress[i]);
     radio.write(&stateButtons[i],sizeof(stateButtons[i]));
     Serial.println(i);
     Serial.println(stateButtons[i]);
     Serial.println("--");
    }
}

I'm stumped. Let me know what you think.

Thanks,
Kyle

Try this portion of code in the second sketch:

void loop() {
  for(int i=0; i<numSlaves; i++){
    stateButtons[i] = digitalRead(inButtons[i]);
    radio.stopListening();
    radio.openWritingPipe(slaveAddress[i]);
    radio.write(&stateButtons[i],sizeof(stateButtons[i]));
    Serial.println(i);
    Serial.println(stateButtons[i]);
    Serial.println("--");
   }
}

It's untested but you need call stopListening() since every call to write() turns the receiver back on.

PS: the code to initialize the inputs is unneeded, all digital pins are inputs by default. If you want to keep it, it belongs in setup(), not loop().

kadorosz:
I'm stumped. Let me know what you think.

It is always a good idea to start with something that works, however simple it may be. And the code in my MultiTxAckPayload works.

When you have something simple working then extend it is very small steps (testing as you go) until you get the complete functionality that you require.

My wild guess is that you are repeating things too quickly.

...R

avr_fred:
you need call stopListening() since every call to write() turns the receiver back on.

That has not been necessary in my tutorial program

...R

avr_fred:
Try this portion of code in the second sketch:

void loop() {

for(int i=0; i<numSlaves; i++){
   stateButtons[i] = digitalRead(inButtons[i]);
   radio.stopListening();
   radio.openWritingPipe(slaveAddress[i]);
   radio.write(&stateButtons[i],sizeof(stateButtons[i]));
   Serial.println(i);
   Serial.println(stateButtons[i]);
   Serial.println("--");
  }
}

Thanks, avr_fred. This worked, but only up to 6 addresses. If i try to send to more than 6 address the slave does not receive anything. I assume there is some type of timing issue when sending a signal to that many slaves. Not sure how to remedy this, and ideas? I tried putting a delay between each cycle of the for loop but that didn't help. I also tried adding a delay to the slave, but no luck there either.

kadorosz:
Thanks, avr_fred. This worked, but only up to 6 addresses.

You need to post the complete program that you uploaded to your Arduino.

...R

When the master has 12 slaves, Slave 00001 does not receive a Button Press.
When the master has 6 slaves, Slave 00001 does receive a Button Press.

Master

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

RF24 radio(7,8);

const byte numSlaves = 12;
const byte slaveAddress[numSlaves][6] = {
          "00001","00002","00003","00004","00005","00006",
          "00007","00008","00009","00010","00011","00012"
            };

const int inButtons[numSlaves]={
           4,4,4,4,4,4,4,4,4,4,4,4
          }; //all buttons set to same input to help troubleshoot
  
int stateButtons[numSlaves]={
     0,0,0,0,0,0,0,0,0,0,0,0
    };

void setup() {

    Serial.begin(115200);
    Serial.println("Start Transmission");
    radio.begin();
    radio.setPALevel(RF24_PA_MIN);
}


void loop() {
  for(int i=0; i<numSlaves; i++){
    stateButtons[i] = digitalRead(inButtons[i]);
    radio.stopListening();
    radio.openWritingPipe(slaveAddress[i]);
    radio.write(&stateButtons[i],sizeof(stateButtons[i]));
    Serial.println(i);
    Serial.println(stateButtons[i]);
    Serial.println("--");
   }
}

Slave

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

RF24 radio(7, 8); // CE, CSN
const byte address[6] = "00001";

int buttonState = 0;

void setup() {
  Serial.begin(115200);
  radio.begin();
  radio.openReadingPipe(0, address);
  radio.setPALevel(RF24_PA_MIN);
}

void loop() {
  radio.startListening();
  radio.read(&buttonState, sizeof(buttonState));
  
  Serial.println(buttonState);
  delay(300);
}

In Reply #13 I surmised that you are trying to send things too quickly. Try including delay(200); in loop() in your Tx program and see what happens?

What version of the RF24 library are you using? Where did you download it from?

AFAIK you don't need the line

radio.stopListening();

If none of this works then start with the working code in my tutorial.

...R