nRF24L01+ RF Module Tutorial

nRF24L01+

nRF24L01+

Previously, we covered a tutorial on ESP8266-01 (ESP8266 Setup Tutorial), a small-footprint WiFi module designed to allow users to easily add WiFi functionality to their projects.  Today, we’ll be discussing the nRF24L01+ RF module, a kind of sister module to the ESP8266 ESP-01 that allows users to add wireless radio frequency communication to their projects.  The nRF24L01+ and the ESP8266 ESP-01 share similar form factors and pin layouts (and even look the same from afar!) but are controlled and function quite differently.  In this tutorial, we hope to introduce the fundamentals of using this RF module, while also explaining how it communicates with other RF modules and microcontrollers.  For the purposes of this tutorial, we’ll be demonstrating interfacing the module with an Arduino Uno microcontroller.

The nRF24L01+ is based on the Nordic Semiconductor nRF24L01+ “RF transceiver IC for the 2.4GHz ISM (Industrial, Scientific, and Medical) band.”  

Specs:

2.4GHz ISM Band Operation

3.3V Nominal Vcc (5V tolerant inputs)

On-chip Voltage regulation

250kbps, 1 Mbps, 2Mbps on air transmission data rates

Ultra low power operation

Low current draw (900nA – 26μA)

6 data pipes

First, we’ll cover the hardware portion of using the module.  Similar to the ESP-01, the RF module has a 4 x 2 male header interface.  The actual pinout, however, differs from the ESP-01 module because the RF module uses a different communication protocol—SPI—to communicate with other devices.  If you’d like to learn more about the SPI protocol, check out our Arduino Communication Protocols Tutorial!

The pinout for the RF module is summarized in the following diagram from Addicore.  

nRF24L01+

Figure 1. The pinout for the nRF24L01+ RF module. / ©Addicore

The RF module is set up to act as an SPI slave, which means that it can only be used with devices that have dedicated SPI communication lines. This means that the SPI MOSI, MISO, and SCK (clock) pins indicated on the diagram must be connected to their corresponding pins on the microcontroller.  On the Arduino, these pins are as follows:

  • MOSI: Arduino D11
  • MISO: Arduino D12
  • SCK: Arduino D13

The CE and the CSN pins can be connected to any output GPIO pin on the Arduino.  In software, they are specified appropriately when the SPI communication is initialized.  

Here’s an example of what the connection between the RF module and the Arduino might look like:

nRF24L01+

Figure 2. The CE and CSN pins (yellow and green wires in the diagram) can be connected to any two unused digital pins.  In the software, you’ll specify these pins when constructing the RF module object.

To interface the Arduino with the module, we’ll be using TMRh20’s RF24 library, which conveniently packages the low-level communications between the RF module and the MCU into an easy-to-use C++ class.

Before we dive into using the module, we’ll first cover some fundamentals behind its operation.  In the United States, devices that use radio frequency waves are limited to frequency ranges allocated by the FCC.  The ISM band is one such range reserved for scientific and medical instruments, and our RF module communicates via frequencies within this ISM range.  For the purposes of working with the RF module, it is not necessary to know the details of these frequencies or of how exactly the communication over these frequencies occurs.  We’ll instead focus on the different aspects of the wireless RF communication that can be controlled.

If you scroll through the RF24 library documentation, you will notice that there are many parameters that can be set.  Some key parameters are

  • Channel: the specific frequency channel that communication will occur on (frequencies are mapped to integers between 0 and 125
  • Reading pipe: the reading pipe is a unique 24, 32, or 40-bit address from which the module reads data
  • Writing pipe: the writing pipe is a unique address to which the module writes data
  • Power Amplifier (PA) level: the PA level sets the power draw of the chip and thereby the transmission power.  For the purposes of this tutorial (use with the Arduino) we will use the minimum power setting.  

The RF24 library documentation page provides some great example code for getting started with the library.  The example projects can be found here: http://tmrh20.github.io/RF24/examples.html  

We’ll be taking a look at how the parameters listed above are initialized in the “Getting Started” Arduino code that can be found here:

http://tmrh20.github.io/RF24/GettingStarted_8ino-example.html

GettingStarted.ino

The first two lines of interest are the two C++ #include directives at the top of the file: one for including the Arduino SPI library (recall from earlier that the RF module uses SPI to communicate with the Arduino), and one for including the RF24 library.  

#include <SPI.h>
#include “RF24.h”

The next line of interest is the line that constructs the RF24 object: RF24 radio(7,8);  The two parameters passed to the constructor are the CE and CSN digital pins that are connected to the module.  Although the MOSI, MISO, and SCK pins must be digital pins 11, 12, and 13 respectively, the CE and CSN pins can be any two digital pins!  

Next we’ll look at the pipe addresses for reading and writing.  As you can probably guess, the writing and reading pipe addresses are swapped between the two radios that are communicating with each other, as the writing pipe for each radio is the reading pipe for the other.  The radio addresses are of size 24, 32, or 40 bit.  In the example code, these addresses are converted from C++ string literals, but you can also specify them in binary or hexadecimal format.  For example, a 40-bit address specified as a hex value might be 0xF0F0F0F0F0.  A good programming practice for storing the writing and reading pipe addresses is to put the two values inside an array.  In the example code, the writing and reading pipe addresses are stored in a byte array named “addresses.”

In the void setup() method, we need to provide instructions for initializing the radios with the address pipe parameters and the other parameters as well.  

First, the RF24::begin() method is called.  The begin() must be called before any of the other RF24 library methods are called on the radio object because it initializes the operation of the RF chip.  

Next, the power amplifier (PA) level is set by calling the RF24::setPALevel() method.  The RF24 library provides different constant values to specify the power amplifier level.  Higher PA levels mean that the module can communicate over longer distances, but in turn draws more current during operation.  For use in the getting started sketch, we pass the RF_24_LOW constant as a parameter to the setPALevel() method because the distance between the two communicating modules is not going to be very great.  In general, when using the RF module with an Arduino board, it is probably a good idea to keep the PA level as low as possible to reduce the current draw on the Arduino’s regulated power supply.  

Next, we’ll look at how to initialize the writing and reading pipes.  We’ve already defined the writing and reading pipes as some byte values.  We must now pass these definitions to the radio object so that it also has knowledge of the writing and reading pipe addresses.  The writing pipe is set with the openWritingPipe() method, and the reading pipe is set with the openReadingPipe() method.  An example of opening a writing and reading pipe is:

radio.openWritingPipe(addresses[1]);

radio.openReadingPipe(1, addresses[0]);

Note that the openReadingPipe() method must be passed an additional integer parameter that describes which reading pipe is being initialized.  This is because the RF module can have up to 6 reading pipes open at a given time!

The example code appropriately assigns the reading and writing pipe values through a role boolean value.  Based on the value of role, the program determines whether the RF module is the ping or pong device.  Similar code can be employed in your projects as well.  Just ensure that the reading and writing addresses are swapped on the two devices, or no data will be transmitted or read!

The radio module is instructed to begin listening by calling the RF24::startListening() method.  It is important to note that the reading pipe must be initialized before the RF module is instructed to begin listening for data (i.e. the openReadingPipe() method must be called before the startListening() method!)

Similarly, the RF24 class also provides a stopListening() method, which must be called before the radio module can begin writing.

In the example code, you may notice that the radio module is instructed to check for incoming data through use of the RF24::available() method.  This is similar to the Serial::available() and SoftwareSerial::available() methods that we’ve seen before–if data is available over the RF connection, the available() method returns true, and the data can be read.  

Finally, the RF24 class provides methods to actually write and read data.  The parameters for the RF24::write() and RF24::read() methods are (1) a pointer to a variable of the same type as the data being transmitted, and (2) the size of the data being transmitted.  In the read() method, the variable to which the pointer points receives the data being read.  In the write() method, the variable to which the pointer points holds the data that is being written.  In both methods, it is absolutely necessary to ensure that the pointers point to variables of the same type as the data being transmitted, and that the size passed to the method actually reflects the size of the data.  Passing incorrect types or values of size to the read() and write() methods can result in undesired value truncations, which may render the data transmitted useless.  In the “Getting Started” code, the data being transmitted is an unsigned long.  Therefore the pointer passed as an argument to the read() and write() methods points to a variable also of type unsigned long.  It is also clear that the size of the data transmitted is always the size of an unsigned long.  In this situation, the size does not need to be passed explicitly as an integer, and the size argument can instead simply be sizeof(unsigned long).

The only parameter that the “Getting Started” code does not cover is the communication channel.  If a specific channel is desired (for example, if you have multiple RF networks that you do not want to interfere with each other), then the channel can be set by passing an 8-bit integer parameter to the RF24::setChannel() method.  An example of this code might be radio.setChannel(10);  

Try connecting two RF modules to two separate Arduino boards and upload the “Getting Started” code on each (for one of the boards, you will have to change the role boolean to 1).  You should be now be able to send and receive back messages with corresponding ping times!  Here’s an image of the two “ping” and “pong” serial monitors side by side:

nRF24L01+

Figure 3. Left: “ping” monitor statements (device 1) ; Right “pong” monitor statements (device 0)

Congratulations on making it through the nRF24L01+ tutorial!  You are now equipped with the skills and knowledge to make your own projects using these nifty RF modules!  You can check out the Device Plus blog for projects that involve these RF modules!

Rahul Iyer
Rahul Iyer
Studying Electrical Engineering at UCLA, Rahul loves to work on electronics and robotics projects as a hobby. He is especially enthusiastic about electric vehicle technology and assistive robotics.

Check us out on Social Media