Simple NRF24L01 data relay system using 6 nodes (with video)

I have been working on a really simple data relaying system using separate NRF24L01 nodes

There is a Host Node at one end and a Root Node at the other end

The intermediate nodes just pass whatever data they are given from one to the next

I was going to use the RF24Network and Mesh libraries that are on here, but decided they were too complex and actually didnt suit the application I have in mind

Anyway, I did a video to explain further...

https://youtu.be/lk2z6wD6QU4

Nice demo but... (there is always a but) Why did you opt for a relay system instead of using a mesh system?

Looks good, including the display. Will you be posting the code?

Riva: Nice demo but... (there is always a but) Why did you opt for a relay system instead of using a mesh system?

I understand how the mesh system works, but my application requires something a bit different, I might look at the mesh system again just to be sure

I shall clean up the code and put an example on here

Here is the code for the example in the video

(excuse the verbose manner, I left it intentionally clunky and wordy)

// Small Linear Network for NRF24L01
// uses I2C OLED Displays (128*64)

// thisNode DEFINITIONS
// the rootNode is the one furthest away at one end, it will have the highest number
// the hostNode is zero, at the other end of the network
// the dataStack is sent from the rootNode towards the hostNode
//
#define rootNode true //false // <-- set to TRUE if this is the rootNode 
#define hostNode false // <-- set to TRUE if this is the hostNode (zero)

/////////////////////////////////////////////
#define thisNode 5 // <-- define "thisNode" HERE 
/////////////////////////////////////////////

#include <Arduino.h>
#include <Wire.h>
#include <MicroLCD.h> // library: https://github.com/stanleyhuangyc/MultiLCD
#include <SPI.h>
#include <nRF24L01.h> // library: https://github.com/maniacbug/RF24
#include <RF24.h>

#define MAX_PAYLOAD 32 // deliberately left at 32, although could be much smaller for this sketch
#define REFRESH_TIME 2000// 7000
#define Ack_LED A2 // digital PIN 8 for Ack LED - Older PCB LED is on A3
#define RF24_CSN A0 // radio chip select-not
#define RF24_CE A1 // radio chip enable

char payload[MAX_PAYLOAD];
uint8_t nextNode_neg, prevNode_pos;
const uint64_t RXTXaddress[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };
uint8_t RXTXchannel = 0;
unsigned long AckLedOnTimer = millis(), refreshTimer = millis();
uint8_t x, LINES;

RF24 radio(RF24_CE,RF24_CSN);
LCD_SSD1306 lcd; /* for SSD1306 OLED module */

void setup(void) {
  pinMode(Ack_LED, OUTPUT); // to save more battery life, perhaps on switch this to an output when it needs to be on, then an input the rest of the time
  nextNode_neg = thisNode-1; // going towards the host node
  prevNode_pos = thisNode+1; // going backwards towards the root node
  radio.begin();
// Test radio module connected and working - this will Flash LED if there is a problem  
  while (!radio.isPVariant() ) { 
    digitalWrite(Ack_LED,HIGH); 
    delay(500); 
    digitalWrite(Ack_LED,LOW); 
    delay(500); 
  }
  radio.setAutoAck(false);
  radio.setPayloadSize(MAX_PAYLOAD);
  radio.setPALevel(RF24_PA_HIGH);  
  radio.setDataRate(RF24_250KBPS);
  radio.setRetries(0,0);
  radio.setChannel(RXTXchannel); // set the channel
  radio.openWritingPipe(RXTXaddress[thisNode&1]); // use alternate addressing/pipes for RX & TX
  radio.openReadingPipe(1,RXTXaddress[!(thisNode&1)]); 
  radio.startListening(); 
  lcd.begin();
  lcd.clear();
  lcd.setFontSize(FONT_SIZE_MEDIUM);
  if (hostNode) { 
    lcd.println("hostNode"); 
  }
  else if (rootNode) { 
    lcd.println("rootNode"); 
  }
  lcd.setFontSize(FONT_SIZE_SMALL);
}

void loop(void) {
// send the text message every few seconds
    if ( millis() - refreshTimer > REFRESH_TIME && (rootNode) ) {
        refreshTimer = millis(); 
        payload[0] = thisNode; // FROM : should be the rootNode
        payload[1] = nextNode_neg; // TO : should be the next Node along the network
        payload[2] = 'H';
        payload[3] = 'e';
        payload[4] = 'l';
        payload[5] = 'l';
        payload[6] = 'o';
        payload[7] = ' ';
        payload[8] = 'F';
        payload[9] = 'r';
        payload[10]= 'o';
        payload[11]= 'm';
        payload[12]= ' ';
        payload[13]= 'R';
        payload[14]= 'o';
        payload[15]= 'o';
        payload[16]= 't';
        radio.stopListening(); // start talking 
        radio.write( payload, sizeof(payload) ); // dump payload to radio
        radio.startListening();
    } 

/////////////////////////////// RECIEVE //////////////////////////////////

/*
Starting with the rootNode, this sends "hello from rootNode" to the first interNode
the first interNode knows the message is for itself and passes the information directly to the next interNode(n)
the message will continue to be passed like this until it reaches the hostNode(0)
The hostNode(0) knows the message is for itself, displays it and sends "hello from hostNode" back via the interNodes
The final internode passes the message to the rootNode where it is displayed
- after a few seconds the rootNode kick off again with another message
*/

// See what data is coming in....  
    if ( radio.available() ) {
      	radio.read( payload, sizeof(payload) ); // get data packet from radio 

// is it for thisNode ?
        if (payload[1] == thisNode) {
// deal with interNode(s) (data coming from rootNode towards hostNode)
// are we an interNode ? 
          if (!hostNode && !rootNode) {
// did it originate at the rootNode ?
             if (payload[0] == prevNode_pos) {
                digitalWrite(Ack_LED,1);
                AckLedOnTimer = millis();
                payload[0] = thisNode;
                payload[1] = nextNode_neg; // towards hostNode
                radio.stopListening(); // start talking 
                radio.write( payload, sizeof(payload) ); // dump payload to radio
                radio.startListening();
             }

// did it originate at the hostNode ?
            else if (payload[0] == nextNode_neg) {
              digitalWrite(Ack_LED,1);
              AckLedOnTimer = millis();
              payload[0] = thisNode;
              payload[1] = prevNode_pos; // towards rootNode
              radio.stopListening(); // start talking 
              radio.write( payload, sizeof(payload) ); // dump payload to radio
              radio.startListening();
            }
          }
          
// are we the hostNode ? did it originate at the rootNode ?
          else if (hostNode && payload[0] == prevNode_pos) {
            digitalWrite(Ack_LED,1);
            AckLedOnTimer = millis();
            if (++LINES == 6) { // clear display after 6 lines
              lcd.clear();
              lcd.setFontSize(FONT_SIZE_MEDIUM);
              lcd.println("hostNode"); 
              lcd.setFontSize(FONT_SIZE_SMALL);
              LINES = 0;
            }
// display rootNodes message on hostNode
            for (uint8_t t=2; t<17; t++) {
              lcd.print( payload[t] );
            }
            lcd.println();
// return message to rootNode 
            payload[0] = thisNode; // should be hostNode (0)
            payload[1] = prevNode_pos; // should be an interNode 
            payload[2] = 'H';
            payload[3] = 'e';
            payload[4] = 'l';
            payload[5] = 'l';
            payload[6] = 'o';
            payload[7] = ' ';
            payload[8] = 'F';
            payload[9] = 'r';
            payload[10] = 'o';
            payload[11] = 'm';
            payload[12]= ' ';
            payload[13]= 'H';
            payload[14]= 'o';
            payload[15]= 's';
            payload[16]= 't';
            radio.stopListening(); // start talking 
            radio.write( payload, sizeof(payload) ); // dump payload to radio
            radio.startListening();
          } 
          
// are we the rootNode ? did it originate at the hostNode ?
          else if (rootNode && payload[0] == nextNode_neg) {
            digitalWrite(Ack_LED,1);
            AckLedOnTimer = millis();
            if (++LINES == 6) { // clear display every 6 lines
              lcd.clear();
              lcd.setFontSize(FONT_SIZE_MEDIUM);
              lcd.println("rootNode"); 
              lcd.setFontSize(FONT_SIZE_SMALL);
              LINES = 0;
            }
// display hostNodes message on rootNode
            for (uint8_t t=2; t<17; t++) {
              lcd.print( payload[t] );
            }
            lcd.println();
          } 
        } 
    } 
// clear LED after 75ms of being on, but only if it is ON !
    if ( digitalRead(Ack_LED) && (millis() - AckLedOnTimer > 75) ) {
      digitalWrite( Ack_LED,0 ); 
    } 
}

I tested my network with one of the newer ‘mini’ design NRF24L01 boards (see pic, on left) and it worked fine, although the range of the ‘new’ boards is somewhat less than its predecessor(DIL version, on right), I dont know why as yet… TBC…

Hey Bob, I posted a question to your youtube channel under this video. Hope you see it and can reply

MrBrooks: Hey Bob, I posted a question to your youtube channel under this video. Hope you see it and can reply

Hello Mr Brooks, I have replied, you can discuss it on here so that others can be involved too Regards

Bob,

Your video was very interesting. I am attempting to use an arduino based system to trigger camera tally lights. The goal of my system would be to be able to show lights on up to eight cameras. The base unit would read a signal from the switcher, then relay that signal to the other units. Each then would interpret that signal and determine whether or not it should light the Led. For instance, if three was sent through the system, only receiver 3 would light up. if five was sent later, 3 would turn off and 5 would light. Could I adapt your code easily to do this?

I am a total newbie when it comes to this type of system.

There may be a better way of doing this so I am open to suggestion. A star type of system as I understand it would only allow six individual units due to the number of pipes on the wireless card, but I could possibly use a tree system with two branches of 4 pipes. Ideas would be welcome!

How do the signals get back to the switcher from the cameras and what make/model are they? Are you just taking a video feed from them or are you using an ambilical/triax lead to also power them.

perhaps you could just broadcast the number to ALL receivers, If the number matches the units number then switch on LED If a number is received that doesnt match the receivers number, then switch the LED off

its a bit simpler

Riva, We are using canon XA-10's. Feed is SDI one way back to the switcher. Camera operators are told via wireless intercom when they are online, but have no visual cue. Breaks aren't regular, but happen. If they had a visual indication that they were online, it would probably happen less.

Bob, I agree. Best case scenerio is that the cameras (tally light at the camera) could talk back to the base that they are online and receiving information., but that is secondary. If I could transmit to all tally lights and only have the one that is online actually show indication, that would be acceptable. My thought is build a box with an uno as the base station, build smaller boxes with nano's as receivers with the LED that would screw into an available 1/4 20 port on the side of the field monitor. This as I said would give the camera operator a visual indication that the camera is currently selected by the switcher.

MrBrooks: Riva, We are using canon XA-10's. Feed is SDI one way back to the switcher.

I think Bobs idea of transmit tally on single channel and have numbered receivers. Maybe continually transmit tally number ever second (or less) and if receiver does not get a confirmation within a couple of seconds or sees a non matching tally number then turn tally light off. If the monitor support twin tallies then maybe use red for live, green for unknown (signal lost) and off for not live.

I like the sound of Rivas idea, I wish I had more time to spend with this at the moment

Thanks Riva. Great idea for the signal lost light. I really do not need two way communication, only an indication that it is not happening. As I think about it, I could send two codes, one to light a green light (all receivers) one to light a specific red light (on air). That way there is positive information to the camera operator that the system is working. No lights?, the operator could just tell the director via headset.

It looks like this project will work, so I need to go buy a little equipment and start playing. Can one of you point me in the right direction for a sketch that can get me started?

I really, really appreciate the ideas here. Thanks so much

How many receiver stations do you think you will need ?
If it was 8 or less you could easily send an 8bit binary value for Green, and an 8bit binary value for red

The station number would correspond to the bit position in the byte

eg

red = 0b00000001 = station 1 red on, all others off
green = 0b10000000 = station 8 green on, all others off

just send these codes every second or so, to update all receivers

On the receivers, when the received bit position matches its own number (1-8) then the led will light
You could make a board that has a set of dip switches (or rotary hex switch) that would select which station it was and make the software read the dip/hex switch to make its comparison, that way all receivers boards and receiver software would be the same, just the switch position would determine which camera it belonged to

:slight_smile:

Thanks Bob,

Better and Better. I like the idea of externally identifying the receiver from a switch. That way I could easily make a few extras because kids (and adults) drop things. There will be no more than eight receivers (limited by our switcher input on the number of cameras). Again, thanks for the great ideas. If you think of more, don't hesitate :)

If you are interested in seeing what we do, take a look at our YouTube Channel. It is pretty amazing what our kids can do. We have been the State Champions in News Broadcasting for 11 years in a row and National Champs for 5 of the last 6 years.

MrBrooks: If you are interested in seeing what we do, take a look at our YouTube Channel.

Thanks, I will, and please subscribe to my channel for more weird and wonderful Arduino projects :)