nRF24L01+ wireless module


Introduction

Let embedded devices communicate to each other is very useful when it comes to remotely control a nearby device. You could also allow those devices communicate via SoftwareSerial communication, but in this way the devices movements would be limited by a couple of cables (TX to send and RX to receive information).

Benefits

nRF24L01 module has some benefits:

  1. Connectivity: It is very easy to pair nRF24L01 module with a variety of microcontroller systems by using the SPI protocol or an RF24 library when pairing with Arduino boards.
  2. High transmission range: nRF24L01 module is able to transmit wavelengths over several kilometres. Moreover, the IPX antenna (shown in the article’s main image) for wireless communication allow up to 1'000 meters communication.
  3. Operating Frequency: 2.4Ghz operating frequency allows for higher bit rate usages rather than other lower operating frequencies. It uses GFSK modulation for data transmission: data transfer rate can either be 250kbps, 1Mbps or 2Mbps.
  4. Low cost: nRF24L01 module is one of the cheapest wireless transceiver modules in the market.

nRF24L01+ VS nRF24L01+ PA

Before comparing those modules, let’s talk about a finesse that you may have not noticed. Some nRF24L01 module on the market are followed by a “+” sign. The nRF24L01+ is a newer version of the nRF24L01, capable of doing an extra 250kbps of on-air data rate while the one without “+” has only 1Mbps and 2Mbps frequencies. Apart from that, they can both be considered as identical to one another. Both versions can be mixed together without any problems as long as 1 or 2 MBps is being used as the data rate.

There are a lot of modules available which are based on the nRF24L01+ chip.

For simplicity, during this article I’ll treat nRF24L01 module in the same way as nRF24L01+ version.

nRF24L01+

This wireless module comes with an onboard antenna. Its compact size could be suitable for narrow spaces. As cons, it loses out in transmission range since it has no antenna to expand the signal.

nRF24L01 1st version

2.4G wireless module nRF24L01+ PA

This module comes with an IPX antenna. The antenna allows for a wider transmission range of up to 1000 meters, as already I already told you above.

nRF24L01 2nd version

Common Software Setup

To avoid repeating myself later on, let’s describe the different environments you’ll need to set up to get everything up and running.

Software Setup for Arduino

Please refer to the official Getting Started guide to install the Arduino IDE on the OS you prefer.

To control the wireless module, you need to install RF24 library from GitHub. Please refer to the official Arduino tutorial about Installing Libraries if you have never installed a library.

The steps to install RF24.h library are:

  1. Press on the green rounded button Code right above the number of commits
  2. Press on Download ZIP.
  3. Open the IDE and click Sketch > Include library > Add .ZIP Library... and select the previously downloaded .zip file

Software Setup for Python

One of the best environments for Python programming (and definitely my favorite) is PyCharm by JetBrains. However, PyCharm is not really suitable for the Raspberry Pi board since it’s very resource-consuming. As an alternative, some light-weight IDEs are:

  1. Visual Studio Code which is also available for Raspberry PI by following this guide
  2. Thonny which is installed by default on Raspberry Pi OS
  3. Mu

You need to install circuitpython-nrf24l01 library from pypi.org. You can find the official documentation on readthedocs.io.

It is recommended to install python libraries in a virtual environment:

# clone with SSH
git clone git@gitlab.com:PitPietro/arduino-projects.git
# or clone with HTTP
# git clone https://gitlab.com/PitPietro/arduino-projects.git

cd projects/nrf24l01-module/raspberry-pi-to-arduino/[PROJECT-FOLDER]
sudo apt install -y python3.10-venv
python3 -m venv .env
source .env/bin/activate

The .env environment has now been activated: you should see (.env) before the $ sign in your terminal. You can now install the library you need:

pip3 install circuitpython-nrf24l01

Please Note: You need to replace “[PROJECT-FOLDER]” with the actual current project you’re working on.

Once done, if you want to remove the .env files and save some unused space:

cd projects/nrf24l01-module/raspberry-pi-to-arduino/[PROJECT-FOLDER]
rm -rf .env

Arduino to Arduino

This article is quite different from the others, since there isn’t a single wiring schema but there are as many schemas as the number of examples shown.

In first place, let’s take a look at the wiring between the Arduino Uno board and the nRF24L01 module.

Arduino Uno to nRF24L01 module writing schema

Numeric Payload

In this example you will be use a couple of Arduino boards with the same code uploaded into them. The main difference is that you have to baptize a board as a sender and the other as receiver and communicate this decision through the Serial Monitor.

Since Arduino IDE can only handle a single Serial Monitor at a time, you should use one of the following solutions to be able to control different Arduino boards from the same computer:

Hardware requirements

Please Note: Follow the wiring schema above.

Code

Download numeric-payload.ino sketch.

Load the code in the first board, open the Serial Monitor and send the string “TX” (without typing the double quotes, too) to set the Arduino as sender.

Load the code in the second board, open the Serial Monitor and send the string “RX” (without typing the double quotes, too) to set the Arduino as receiver.

The comments inside the code describe every single step in a very detailed way, please read them carefully.

Please Note: It’s mandatory to open the Serial Monitors, otherwise the code will not execute. Moreover, I strongly recommend annotating the port to whom the boards are connected. In this way, you know what port is associated to the sender, and what to the receiver. Moreover, don’t forget that if you restart your PC, the port name could change.

The code also allow you to change the behaviour of the board during the execution, without having to reset the board. Take a look at the section below.

Serial Monitor execution output

Plug both the sender and the receiver to the computer, load the code and open the Serial Monitor(s).

Take a look at sender-log and receiver-log to see how the transmission works.

Remote LED control with button

Hardware requirements

Remote LED control with button writing schema

The sender board (in the right, into the wiring schema) has a button that will light-up the LED attached to the receiver board (in the left, into the wiring schema). In this case, there are two different sketches to load on the boards.

Moreover, it is not mandatory to open the Serial Monitor since the code will execute anyway.

Code

This project is made up by two sketches:

  1. LED sender
  2. LED receiver

After uploading the code, you can place the boards in different rooms of the house, power them and try to press the button.

Send messages to remote LCD

If you need a bidirectional wireless communication between two boards, you could use a couple of Serial Monitor attached to them and send the users input as payloads. This constant change of role (from sender to receiver and vice versa) of the boards, could become quite difficult to implement.

But let’s say you need a unidirectional wireless communication, so that the receiver does not need the ability to reply. The use of an LCD display allow you to visualize the received messages without the use of the second Serial Monitor.

Hardware requirements

At this point, you can follow the below wiring schema. The sender is on the left, while the receiver is on the right.

Remote LCD messages from Serial Monitor writing schema

Code

This project is made up by two sketches:

  1. LCD sender
  2. LCD receiver

Let’s explain a little more in depth the major difficulties faced in the development of the two sketches.

Sender Code

In the sender’s code, the correct sending of the string message through the wireless module has been quite tricky.

Arduino implements String data type (unlike standard C programming language), but its use can lead to errors: it is indeed different from an array of char.

To properly send the message, you need to loop through each character of the string, and send it one by one. The message terminates with the line-feed character '\n', which is an escape sequence. It’s now up to the receiver understanding when the message is terminated and act accordingly.

Receiver Code

In the receiver’s code, receiving the string message through the wireless module has been the funny part.

Since the message could be longer than the length of the display, one of the hardest task to perform was to split the message over all the display rows.
Moreover, the code must cover as much edge cases as possible so the number of columns and rows cannot be hardcoded: if you use a different LCD, the code must work the same way.

The solution has been to split the original message into n different sub-messages, one for each row. The carrier position has been set on the left edge of each row before printing the sub-message.

Another very difficult task was to understand when to clear the screen and prepare it for the arrival of the new message.
I don’t want to go any further to make room for the other projects as well but don’t worry, everything is well-explained in the comments inside the code.

Raspberry Pi to Arduino

The main problem of communication between Raspberry Pi and Arduino is that they’re programmed in different languages, which has different data types that may not collide. Raspberry Pi boards are programmed in Python, while Arduino boards in their C/C++ language.

To overcome this issue, you can import struct module to perform conversions between Python values and C structs represented as Python bytes objects. struct module uses Format Strings as compact descriptions of the layout of the C structs and the intended conversion to/from Python values.

Please refer to Python struct module in-depth analysis if you want to learn more.

Simple Payloads

Similar to what was done in Arduino to Arduino’s Numeric Payload example, in this project you’ll send payloads of different data types from the Raspberry Pi to the Arduino and vice versa.

Raspberry Pi and Arduino to nRF24L01 module writing schema

Code

This project is made up by two sketches:

  1. Raspberry Pi’s main.py
  2. Arduino’s arduino-simple-payloads.ino

The default payload sent/received is a char of 1 byte. You can change the payload type by looking in both the codes sketches for lines followed by comments that start with “*uncomment if *” or something similar.

The available payload data types are: char, bool, short, int, long, float and double.

There are many other data types you can share between wireless devices. Please refer to Python struct module slice to find out more.

RPI sends and Arduino receives

Assuming arduino-projects GitLab repository as base director, open a Terminal and type:

cd projects/nrf24l01-module/raspberry-pi-to-arduino/simple-payloads

# run the program in TX mode
python3 main.py -r TX

# verbose alternative
python3 main.py --role TX

Then load the Arduino sketch, open the Serial Monitor, wait for the fist line to appear and type:

RX

# the question you are replying to is:
# "# is this device a sender (TX) or a receiver (RX)?"
Arduino sends and RPI receives

Assuming arduino-projects GitLab repository as base director, open a Terminal and type:

cd projects/nrf24l01-module/raspberry-pi-to-arduino/simple-payloads

# run the program in RX mode
python3 main.py -r RX

# verbose alternative
python3 main.py --role RX

Then load the Arduino sketch, open the Serial Monitor and type:

TX

# the question you are replying to is:
# "# is this device a sender (TX) or a receiver (RX)?"

Conclusion

Here are some useful in-depth analysis to conclude the article.

Software Driven SPI

It may happen that the Arduino needs to be connected to an I/O device that uses the same SPI pins as the nRF24L01 wireless module. Luckily for us, RF24 library provides the ability to change the default SPI pins by manually editing RF24_config.h file.

Assuming you’re working with a Unix-like machine, you need to open a Terminal window:

# move to RF24 directory, which is inside Arduino libraries folder
cd Arduino/libraries/RF24/

# edit the config file
nano RF24_config.h

Uncomment the following line, that should be n° 27 or nearly:

#define SOFTSPI     // Requires library from https://github.com/greiman/DigitalIO

Add below the following directives:

#define SOFT_SPI_MISO_PIN 7
#define SOFT_SPI_MOSI_PIN 8
#define SOFT_SPI_SCK_PIN 9

Of course, it is not mandatory to choose the pins I used: you can choose your own, but be aware of changing the wiring schema accordingly.

Install and include DigitalIO library on the top of your sketches:

#include <SPI.h>
#include "printf.h"
#include "RF24.h"
#include <DigitalIO.h>

// instantiate an object for the nRF24L01 transceiver
// using pin 10 for the CE pin, and pin 13 for the CSN pin
RF24 radio(10, 13);

// ...

In this case, the wiring schema would be: Arduino Uno to nRF24L01 module writing schema - custom pins

Please Note: Since RF24 library has been changed globally, every Arduino board that need to be attached to nRF24L01 module must now follow the above writing schema. If you want to come back to the previous pin configuration, you must undo the edits made to RF24_config.h file.

Python struct module

Format strings are the mechanism used to specify the expected layout when packing and unpacking data. They are built up from Format Characters, which specify the type of data. Moreover, the special characters @, =, <, > and ! control:

  1. Byte Order: native, little-endian, big-endian or network
  2. Size: native or standard
  3. Alignment: native or none

To communicate with the Arduino boards, you need to use <, which stands for:

  1. Byte Order: little-endian
  2. Size: standard
  3. Alignment: none

Here below is shown a short version of the Format characters table.
Standard size column refers to the size of the packed value in bytes when using standard size: so when the format string starts with '<', '>', '!' or '='.

Format characters table

* _Bool type defined by C99. See Boolean type support library. *
** On the Uno and other ATMEGA based boards, double occupies 4 bytes (which is the same as the float: no gain in precision), while on the Arduino Due, double variables occupies 8 bytes (as it should usually be). See double data type. **

Please Note: long long data type exists in C language, while it does not exist in Arduino language, so it was not possible to send or receive a payload made up by 8 bytes.

Documentation

Here are some useful links: