Arduino Communication With the nrf24L01

At some point, most Arduino owners will decide that their next project requires communication between two of their microcontroller boards. You might want to control a long strip of NeoPixels decorating your room from the comfort of your armchair without tripping over cables or read the temperature and humidity of your greenhouse without putting on a coat and wandering up the garden. Another use may be to control a floor robot from a handheld home-built controller with a joystick and buttons. This article explains how to set up two Arduinos with nrf24L01 transceiver modules so that they can ‘talk’ to each other and provides a starting point for such projects.

In this piece, I’m going to explain what the nrf24L01 does and how it is connected to your Arduino. I’ll also compare a couple of alternative devices and then move on to two small demonstration projects.

 The NRF24L01

nrf24L01

This is a radio Transceiver (it both transmits and receives) which operates on the 2.4 GHz Industrial, Scientific and Medical bands. It has a very good range, low power consumption, is cheap to buy, readily available and has several Arduino libraries available. Nordic call it a 6 data pipe MultiCeiver because it can listen to up to 6 other devices at once and transmit to them one at a time.

It can transmit at 250bps, 1Mbps and 2Mbps on-air rates with a choice between 125 channels (1 … 125). The board contains a transceiver chip from Nordic Semiconductor, a crystal, an antenna, a few support components, and 8 connection pins. The user can adjust the power of the transmission to one of four levels, (MIN, LOW, HIGH, and MAX) depending on the range required. Possibly up to 100m outdoors. There is an on-chip voltage regulator that needs a supply voltage of between 1.9 and 3.6V on the VCC pin. It is a 3V device and the VCC pin must not be connected to 5V. The other logic input pins are 5V tolerant which makes things much easier. 

The main downside is that the connection pins on the underside of the board are breadboard hostile. (The 2×4 arrangement of the pins would short out connections.) You have to use Dupont jumper wires to connect it up to an Arduino. Recently a 5V small shield board has become available which drops the voltage from 5V on your Arduino to 3.3V for the nrf24L01. It has an LED to indicate it is receiving power and the connection pins have labels. (More on using the NRF24L01 Wireless RF Transceiver Socket Adapter Board with 3.3v Regulator later but if you have not yet bought your nrf24L01s I suggest you pick up shields at the same time.) 

This chip automatically carries out the assembly of a data packet, its transmission and the checking of an acknowledgement reply from the receiver. It can automatically try resending the data (15 times is the default) if there was an error highlighted by the CRC check on the data received or no ACK received. Nordic call this “Enhanced ShockBurst”.

Each packet is made up of:

PreambleAddressPacket Control FieldPayloadCRC
1 byte3 – 5 bytes9 bytes0 – 32 bytes1 – 2 bytes

The documentation for the chip can be found here:

I found it very interesting and am thankful that several generous people have shared their Arduino libraries for our benefit. The work involved must have been considerable. You get a great deal of Bang for your Buck with these boards.

Other devices you might consider: 

433 MHz General RF receive and send pairs

433 MHz General RF receive and send pairs

These are cheap, very basic, but no longer so readily available as more modern alternatives. The more recent drivers/libraries than the early VirtualWire library such as Radiohead are better but still have quite a steep learning curve. You have to build your own packets of data, add identifiers to your packet information, and carry out acknowledgment data of packets and any integrity checking. The soldered-on antenna wires get in the way. I have had success with these in the past. These are in a similar price bracket to the nrf24L01.

Bluetooth

Bluetooth

This system is now increasing in popularity, but it will seldom be an inexpensive route. There is currently a great deal of ongoing development and you can easily connect to your phone/tablet via free downloadable apps. Adafruit have included the BLE technology in many of their boards which are Arduino IDE and CircuitPython compatible. I’ve used CircuitPython, but not Arduino, on these devices and found them quite simple to set up. If you want an official Arduino with radio you could try an Arduino Nano 33 IoT or Arduino MKR WiFi. Unfortunately, the range of BLE (Bluetooth Low Energy) devices is quite short.

After looking at these three possibilities the nrf24L01 system would appear to be the most versatile and cost effective.

Connecting up an Arduino with nrf24L01

This is a Serial Peripheral Interface (SPI) device and has eight pins. We will need M-F DuPont cables to make the connections to an Arduino.

nrf24L01

Looking at the top of the device and working round clockwise from the top right, orange pin

WirePin nameUNO pin
OrangeVCC – 3V NOT 5V3V
YellowCSN8 or other
GreenMOSI11 SPI
SilverIRQNot connected
GreyMISO12 SPI
PurpleSCK13 SPI
BlueCE7 or other
BlackGNDGND

Unfortunately, this uses up the built in LED on pin 13.

Other Arduino boards, such as a Mega or Piksey Atto have their SPI connections (MISO, MOSI & SCK) on different pins. Please check before connecting up if you are not using an UNO.

Be very careful not to connect VCC to 5V as this will kill the radio chip. We will need to set up two Arduinos with their RF24s. On one of the Arduinos, I have also installed a 10K Ohm potentiometer to provide a variable input for the projects.

pictoriel arduino project

The next step is to install the RF24 library. Start the Arduino IDE. Go to Tools 🡺 Manage Libraries… Type ‘RF24’ into the top right box and press <Return>. There are several different libraries available so scroll down to the one ‘by TMRh20 version 1.3.4’ and then click on install. This one is said to be simple for beginners and has plenty of examples. You can find the examples from the menu via File 🡺 Examples 🡺 RF24. They are long, well-spaced and with plenty of comments.

Test the setup

The first example tests that we have all the items set up correctly. It demonstrates a simple, one-way transmission of a single integer from one Arduino to another, (a ‘Boss’ and ‘Subordinate’ setup) and we can check that we understand the basics, everything is connected together properly and working reliably. The second will allow two-way communication with multiple values transmitted between them.

Project #1: Display the value from a potentiometer on one Arduino on the Serial Monitor of another Arduino.

Components required

  • 2 Arduinos with USB cables
  • 2 nrf24L01s
  • 1 10K Ohm potentiometer
  • Breadboard or stripboard
  • DuPont cables (M-F) and connecting wire

Assemble the components as shown in the previous Fritzing schematic.

We now need to connect our Arduinos to our PC so that we can program them with the Arduino IDE. The Arduino on the right side of the desk, with the potentiometer (‘Boss’), is going to send data to the other Arduino on the left side of the disk (‘Subordinate’). Start up the Arduino IDE, drag it to the right-hand side of your screen and plug the ‘Boss’ into the right most available USB port. Set the board type, check the port you are using, mine was COM 29.

Start another instance of the Arduino IDE, directly from the Start Menu. Do not open another window from the File menu of the first instance. Plugin the ‘Subordinate’ Arduino to the leftmost USB port. Drag this editor window to the left side of your screen, Look in Tools 🡺 Port and you should see two ports listed. Select the new port. Mine was COM 27.

This does not always work the first time but it is essential that both instances of the Arduino IDE have different ports.

Copy and paste the first script into the Boss and second into the Subordinate. Compile both scripts, upload and open the Serial Monitors and adjust the baud rate if necessary. As you twist the top of the potentiometer you should see the value change in both monitor windows.

Send Test script for Arduino with Potentiometer

// 'Boss' computer sending messages to 'subordinate' via nrf24L01
// File name: Send_Test paired with Received_Test - Tony Goodhew 17 June 2020
#include <SPI.h>  
#include "RF24.h"

RF24 myRadio (7, 8);      // These can be changed CE, CSN
byte addrs[][6] = {"T"};  // Data pipe to send on
int potPin = A0;          // 10K Ohm pot for changing input

void setup() {
  Serial.begin(115200);                  // Start Serial Monitor
  delay(1000);
  // Set up the radio
  myRadio.begin();                       // Start radio
  myRadio.setChannel(100);               // Could be changed 1 ... 125
  myRadio.setPALevel(RF24_PA_MIN);       // Power level minimum - radios are close together
  myRadio.setDataRate( RF24_250KBPS );
  myRadio.openWritingPipe( addrs[0]);    // Write to pipe called "T"
  delay(750);
}

void loop() {
  // Get current values
  int potValue = analogRead(potPin);   // Read Pot value
    
  // Send the packet of data called data1 to Subordinate
  myRadio.write(&potValue, sizeof(potValue));   

  // Report sent values to Monitor
  Serial.print("Sent: ");
  Serial.println(potValue);
  delay(800);
} 

Additional notes

At the top we include the necessary libraries, initialise myRadio by providing the pin numbers for CE and CSN and tell the script where the potentiometer is connected.

In the setup routine:

  1. We start the Serial Monitor at 115200 baud, and select the settings for the radio:
  2. Channel at 100 (Any value from 1 to 125)
  3. Power of transmission MIN, (LOW, HIGH or MAX) as both transducers are close together
  4. DataRate 250KBPS (1MBPS or 2MBPS)
  5. Open the WRITE pipe: “T”
  6. Pause for this to be set up

In the loop:

  1. Read the value of the potentiometer
  2. Write the potValue and its size(bytes) to the radio
  3. Print the potValue to the Serial Monitor
  4. Short delay

Receive test for Arduino without potentiometer

// 'Subordinate' receiving messages from 'Boss' via nrf2401
// File name: Receive_Test to be paired with Send_Test - Tony Goodhew 17 June 2020
#include <SPI.h>  
#include "RF24.h" 

RF24 myRadio (7, 8); 
byte addrs[][6] = {"T"}; // Data pipe
int x = 0;               // Variable to hold received data

void setup() {
  Serial.begin(115200); // Start Serial Monitor
  delay(1000);
  // Set up the radio
  myRadio.begin();                       // Start radio
  myRadio.setChannel(100); 
  myRadio.setPALevel(RF24_PA_MIN);       // radios are close together
  myRadio.setDataRate( RF24_250KBPS ); 
  myRadio.openReadingPipe(1, addrs[0]);  // Read from pipe called "T"
  myRadio.startListening();              // Listen for signal from Boss
}

void loop()  {
  if (myRadio.available()) {
    while (myRadio.available()) {         // Picked up transmission from Boss
      myRadio.read(&x, sizeof(x));     // Get the values from radio buffer
    }
    // Print package contents to Monitor for checking
    Serial.print("Received: ");
    Serial.println(x);
  }
}

Additional Notes

The first part mainly matches the sending script but we initialise an integer variable x to receive the value being sent from the pot.

The setup sequence is similar but we need to open a READING pipe. Note the additional 1 in the brackets. We then start listening for a radio signal.

The main loop waits for a radio signal, reads the value sent, stores it in the variable x and prints it to the Serial Monitor. 

Now we have this working we can take the complexity up a notch. If you cannot get this to work look at the end of the article for additional help with problems.

Project #2: Send multiple data items in a packet using Arduino with nrf24L01

The basic outline is the same but we have to send multiple values, of different types in a packet called data1. I have included two integers, a floating-point value and a message string. Twist the knob on the potentiometer to change the values sent.

Script for the transmitting from the Arduino board with the potentiometer on A0

// 'Boss' computer sending messages to 'subordinate' via nrf24L01
// File name: RF_send3 paired with RF_receive3 - Tony Goodhew 17 June 2020
#include <SPI.h>  
#include "RF24.h"

RF24 myRadio (7, 8);      // These can be changed CE CSN
byte addrs[][6] = {"T"};  // Single data1 pipe to send on
int potPin = A0;          // 10K Ohm pot for changing input

struct package {        // Define the data1 packet contents
  int pktNo = 0;        // Count and identify packets sent
  int rawPot = 0;       // 0 .. 1023 from 10K Ohm potentiometer
  float perCent = 0.0;  // floating point value
  char msg[50] = "Here is a MESSAGE";  // Text string - Not changing
};
typedef struct package Package;  
Package data1;   // Package named data1 contains pktNo, rawPot, perPot, perCent & msg

void setup() {
  Serial.begin(115200);                  // Start Serial Monitor
  delay(1000);
  myRadio.begin();                       // Start radio
  myRadio.setChannel(100);               // Could be changed 1 ... 125
  myRadio.setPALevel(RF24_PA_MIN);       // Power level minimum - radios are close together
  myRadio.setDataRate( RF24_250KBPS );
  myRadio.openWritingPipe(addrs[0]);    // Write to pipe called "T"
  delay(750);
}

void loop() {
  // Get current values
  int potValue = analogRead(potPin);   // Read Pot value
  data1.rawPot = potValue;             // Put changed values into next packet
  data1.perCent = potValue * 100.0 / 1023.0;  // FP value
  data1.pktNo = data1.pktNo + 1;
  
  // Send the packet of data called data1 to Subordinate
  myRadio.write(&data1, sizeof(data1)); 

  // Report sent values to Monitor
  Serial.print("\nPackage Sent:");
  Serial.println(data1.pktNo);
  Serial.println(data1.rawPot);
  Serial.println(data1.perCent);
  Serial.println(data1.msg);           // array of chars
  delay(800);
}

 Script for the receiving with the Arduino board using nrf24L01

// 'Subordinate' receiving messages from 'Boss' via nrf2401
// File name: RF_receive3 to be paired with RF_send3 - Tony Goodhew 17 June 2020
#include <SPI.h>  
#include "RF24.h" 

RF24 myRadio (7, 8); 
byte addrs[][6] = {"T"}; 

struct package  {  // Items and order must match packet being sent by 'Boss'
  int pktNo = 0;
  int rawPot = 0;
  float perCent = 0;
  char msg[50] ="_";
};
typedef struct package Package;
Package data1;  // Package named data1 contains pktNo, rawPot, perPot, perCent & msg

void setup() {
  Serial.begin(115200); // Start Serial Monitor
  delay(1000);
  myRadio.begin();                       // Start radio
  myRadio.setChannel(100); 
  myRadio.setPALevel(RF24_PA_MIN);       // radios are close together
  myRadio.setDataRate( RF24_250KBPS ); 
  myRadio.openReadingPipe(1, addrs[0]);  // Read from pipe called "T"
  myRadio.startListening();              // Listen for signal from Boss
}

void loop()  {
  if (myRadio.available()) {
    while (myRadio.available()) {           // Picked up transmission from Boss
      myRadio.read(&data1, sizeof(data1)); // Get the values from radio buffer
    }
    // Print package contents to Monitor for checking
    Serial.print("\nPackage Received:");
    Serial.println(data1.pktNo);
    Serial.println(data1.rawPot);
    Serial.println(data1.perCent);
    Serial.println(data1.msg);
  }
}
output screen

Project #3: Send data in both directions to operate hardware using Arduino with nrf24L01

Additional components

  • 2 LEDs
  • 1 Button switch
  • 2 560 Ohm resistor or near match to protect LEDs
  • 1 10K Ohm resistor – pull-down resistor

Add an LED and 560 Ohm resistor to pin 3 of each Arduino. We need to use pin 3 because that has pulse-width modulation so that we can adjust the brightness.

Add the button switch and 10K Ohm pull-down resistor to pin 2 of the Arduino without the potentiometer.

pictoriel Arduino

These scripts should allow each Arduino to control the LED on the other Arduino. The potentiometer will control brightness/fading and the button switch will turn the other LED ON/OFF.

Here are the scripts:

// Potentiometer End
#include <SPI.h>
#include <RF24.h>
RF24 myRadio(7, 8);

byte addrs[][6] = {"T","G"};
int led = 3;  // LED on pin 3 and 10K Ohm on pin A0

void setup() {
  Serial.begin(115200);
  Serial.println("Starting...");
  pinMode(led, OUTPUT);
  myRadio.begin();  // Start the Radio 
  myRadio.setPALevel(RF24_PA_MIN); // Close together so low power
  myRadio.setDataRate(RF24_2MBPS); // High speed
  myRadio.setChannel(99);
  myRadio.openWritingPipe(addrs[0]);     // Open data pipes
  myRadio.openReadingPipe(1, addrs[1]);  // Opposite way round on other Arduino

  myRadio.startListening();  // Listen for a signal
}
// ============ Main Loop ============
void loop() {
  int dataIn = 0; // Received value - button 1/0
  int dataOut = 0;  // Value to send - from pot - brightness

  if (myRadio.available()) { // Has a signal arrived?

    // Get value and update LED
    while (myRadio.available()) {
      myRadio.read( &dataIn, sizeof(dataIn));
      digitalWrite(led, dataIn); // update LED
    }

    myRadio.stopListening();  // Stop listening
    dataOut = analogRead(A0); // Get value from Potentiometer
    myRadio.write(&dataOut, sizeof(dataOut) ); // Send Raw pot value ( 0 ... 1023)

    myRadio.startListening();  // Resume listening for next pass through loop
  }
}

// Button End
#include <SPI.h>
#include <RF24.h>
RF24 myRadio(7, 8); // Setup radio

byte addrs[][6] = {"T", "G"};
int button = 2; // Button switch on pin 2
int led = 3;    // LED on pin 3

void setup() {
  Serial.begin(115200);
  Serial.println("Starting...");
  pinMode(button, INPUT);
  myRadio.begin();  // Start the Radio
  myRadio.setPALevel(RF24_PA_MIN);  // Close together so low power
  myRadio.setDataRate(RF24_2MBPS);  // High speed
  myRadio.setChannel(99);
  myRadio.openWritingPipe(addrs[1]);     // Open data pipes
  myRadio.openReadingPipe(1, addrs[0]);  // Opposite way round on other Arduino
}
// ============ Main Loop ============
void loop() {
  int dataOut = digitalRead(button); // Get button data to send
  myRadio.stopListening();           // Stop listening - we need to send

  if (! myRadio.write(&dataOut, sizeof(dataOut) )) {
    Serial.println("No ACK ");    
  }

  myRadio.startListening(); // Listen for signal
  unsigned long startTime = millis(); // Record current millisecond time

  // Get an ACK signal OR timeout - We cannot wait forever
  while (! myRadio.available()) {
    if (millis() - startTime > 150 ) { // Have we waited long enough?
      Serial.println("Timeout! No response");
      return;
    }
  }

  int dataIn = 0;  // To hold received value
  myRadio.read( &dataIn, sizeof(dataIn) ); // Get the RAW brightness for the LED
  analogWrite(led, dataIn / 5);            // Update LED brightness – never max
  delay(100); // Short delay for quick reaction
}

Copy, paste, compile and upload both scripts. Twisting the knob on the potentiometer should change the brightness of the LED on the other while closing/opening the button switch should turn the other LED ON/OFF.

There are plenty of comments in the scripts to help you understand how they are working.

Conclusion  

By this point, you should have some idea about the usefulness of these small and inexpensive radio boards and be ready to modify the code I’ve provided to make your own project.

Some project suggestions

suggested Arduino project
  1. Use 3 potentiometers on A0, A1, and A2 to control the color of a common cathode RGB LED on the other Arduino. You can use pins 6, 5, and 3 as they are all PWM pins and a single 560 Ohm resistor on the common cathode to connect to GND
  2. Connect a Joystick to one Arduino and use it to set the speed and direction of the motors on an Arduino controlled floor buggy. You could add buttons to control LEDs or a Neopixel strip
  3. Use temperature, humidity, and air pressure sensors to send current readings from your garden shed to another Arduino fitted with an OLED SSD1306 or LCD display
  4. Look at the examples provided with the RF14 library. They include a few more statements not covered in this piece.

Problems

Using 2 Arduinos at the same time in a project more than doubles the number of things that can go wrong. You have two scripts to debug, 2 sets of hardware, and no easy way to check that your radios are working. It is very easy to update the code on the wrong Arduino when you have 2 instances of the IDE on the screen at the same time. I always keep the windows to the left and right of the screen matching the positions of the Arduinos on my desk and regularly check the ports at the bottom right of the IDE windows before uploading.

I was using a ‘proper’ UNO R3 and a Sparkfun RedBoard UNO R3 clone while developing this article. The one-way demos worked perfectly but as soon as I added the extra components I could not get them to ‘talk‘ to each other. I eventually ordered some of the NRF24L01 Wireless RF Transceiver Socket Adapter Boards with 3.3v Regulators.

I installed one on the RedBoard and at once everything started working. It would appear that adding the extra components reduced the power available on the 3V pin below the level needed to operate the radio successfully. Using the power from the 5V pin, through the 3.3V regulator on the adapter board, supplied enough power. These extra boards also have a power indication LED and all the pins have labels making the correct connection easier.

Leave a Comment

X