Skip to content

A separate dive into Radio Communication using the nRF24

This is mainly a (what I hope to be useful for others) knowledge base regarding how I did radio communication using the nRF24 modules!

For a bit of context, I worked on creating a transmitter and receiver Remote control systems for controlling drones and other other things as can be seen on my project page. Both my transmitter and receiver used the nRF24 module which is a decent radio communication device however is not ideal for longer distance communication(about 1 KM LoS).

I will try my best in explaining my code and the nRF connections.

Physical Connection:

alt text

As you can see the nRF24 requires 8 pins for connection.

GND: is the Ground Pin. It is placed inside a square for easy identification.

  • VCC: supplies power to the module. Voltage can range from 1.9v to 3.9v. So, you can connect it directly to the 3.3V pin of our Arduino. Remember connecting it to a 5V pin will likely destroy your nRF24L01+ module!

  • CE: (Chip Enable) is an active-HIGH pin. When selected the module will either transmit or receive, depending upon which mode it is currently in.

  • CSN: (Chip Select Not) is an active-LOW pin and is normally kept HIGH. When this pin goes low, the module begins listening on its SPI port for data and processes it accordingly.

  • SCK: (Serial Clock) it accepts clock pulses provided by the SPI bus Master.

  • MOSI: (Master Out Slave In) It is SPI input to the module. It is used to receive data from the microcontroller.

  • MISO: (Master In Slave Out) It is SPI output from the module. It is used to send data to the microcontroller.

  • IRQ: It is the interrupt pin that alerts the master when new data is available to process.

Naturally, for radio communication you need two nRF24’s, one for transmitting and one for receiving. The connections would be the same on both:

alt text

The typical pins for connection:

alt text

Programming:

You can find below the codes for my transmitter and receiver! I got the original code from youtube (available on my programming page) and tweaked it as per my requirements. The nRF24L01.h, SPI.h, and RF24.h libraries is needed for utilizing the nRF modules.

the code for the transmitter:

// original code:4 Channel Transmitter by KendinYap
// Customized and edited by Thinley Wangchuk Fabacademy 2023
// 6 Channels

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

const uint64_t pipeOut = 0xE9E8F0F0E1LL;   //IMPORTANT: has to be the same as in the receiver 0xE9E8F0F0E1LL 
RF24 radio(9, 10); //select CE,CSN pin

struct Signal {
byte throttle;
byte pitch;
byte roll;
byte yaw;
byte aux1;
byte aux2;
};

Signal data;

void ResetData() 
{
data.throttle = 0; // Motor Stop (254/2=127)| Motor (Signal lost position )
data.pitch = 127; // Center  (Signal lost position)
data.roll = 127; // Center  (Signal lost position )
data.yaw = 127; // Center  (Signal lost position)
data.aux1 = 127;    // Center (Signal lost position)
data.aux2 = 127;    // Center (Signal lost position
}

void setup()
{

//Start everything up


radio.begin(); //This function initializes the NRF24 module. It sets up the SPI communication and prepares the module for further configuration.
radio.openWritingPipe(pipeOut); //This function configures the NRF24 module to use a specific address for writing data. The pipeOut variable represents the address to which the transmitter will send data
radio.setAutoAck(false); //This function disables automatic acknowledgment
radio.setDataRate(RF24_250KBPS); //This function sets the data rate for communication. In this case, it's set to 250 kbps (kilobits per second), which is a moderate data rate suitable for most applications. Lower data rates can provide better range and reliability, while higher data rates offer faster transmission speeds. Tweak it to your requirements
radio.setPALevel(RF24_PA_HIGH); //This function sets the power amplifier level for the NRF24 module. It determines the output power of the module, which affects the communication range. Setting it to RF24_PA_HIGH configures the module for the highest available power level, which can improve range but may consume more power
radio.stopListening(); //start the radio communication for Transmitter. Transmitter will be transmitting data so stopListening is used. This function stops the NRF24 module from listening for incoming data. In this context, it's used in the transmitter code because the transmitter will be sending data, not receiving it. By stopping listening, the NRF24 module can focus on transmitting data efficiently without wasting resources on reception
ResetData();

}

void loop()
{

/*If your channel is reversed, just swap 0 to 255 by 255 to 0 below
EXAMPLE:
Normal:    data.ch1 = map( analogRead(A0), 0, 1024, 0, 255);
Reversed:  data.ch1 = map( analogRead(A0), 0, 1024, 255, 0);  */

/* The two channels to which the servos are linked to may need their Analog input pin declarations swapped for the correct roll directions.
Such as "(A1)" being swapped with "(A0)" or vice versa, all below.*/

data.throttle = map( analogRead(A3), 0, 1024, 0, 255 );
data.roll = map( analogRead(A1), 0, 1024, 0, 255 );      
data.pitch = map( analogRead(A0), 0, 1024, 0, 255 );     
data.yaw = map( analogRead(A2), 0, 1024, 0, 255 );

data.aux1 = map( analogRead(A6), 0, 1024, 0, 255);     
data.aux2 = map( analogRead(A7), 0, 1024, 0, 255 );
radio.write(&data, sizeof(Signal));
}

the code for the receiver:

//  6 Channel Receiver from KedinYAP.
// Customized by Thinley wangchuk fabacademy 2023 (into 6 channels)
//  PWM output on pins D2, D3, D4, D5, D6, D7

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

int ch_width_1 = 0;
int ch_width_2 = 0;
int ch_width_3 = 0;
int ch_width_4 = 0;
int ch_width_5 = 0;
int ch_width_6 = 0;

Servo ch1;
Servo ch2;
Servo ch3;
Servo ch4;
Servo ch5;
Servo ch6;

struct Signal {
byte throttle;      
byte pitch;
byte roll;
byte yaw;
byte aux1;
byte aux2;
};

Signal data;

const uint64_t pipeIn = 0xE9E8F0F0E1LL; //IMPORTANT: has to be the same as in the receiver 0xE9E8F0F0E1LL 
RF24 radio(9, 10);

void ResetData()
{
// Define the initial value of each data input
// The middle position for Potentiometers. (254/2=127) 
data.throttle = 0; // Motor Stop 
data.pitch = 127;  // Center
data.roll = 127;   // Center 
data.yaw = 127;   // Center 
data.aux1 = 127;   // Center 
data.aux2 = 127;   // Center 
}


void setup()
{
//Set the pins for each PWM signal | 
ch1.attach(2);
ch2.attach(3);
ch3.attach(4);
ch4.attach(5);
ch5.attach(6);
ch6.attach(7);

//Configure the NRF24 module (old code)
//  ResetData();
//  radio.begin();
//  radio.openReadingPipe(1,pipeIn);
//  
//  radio.startListening(); //start the radio comunication for receiver 
//}

//Configure the NRF24 module
ResetData();
radio.begin();
radio.openReadingPipe(1,pipeIn);
radio.setAutoAck(false);
radio.setDataRate(RF24_250KBPS);
radio.setPALevel(RF24_PA_HIGH);
radio.startListening(); //start the radio communication for receiver

pinMode(6,OUTPUT);

}
unsigned long lastRecvTime = 0;

void recvData() //This function is responsible for receiving data from the NRF24 module. It continuously checks if there's any data available to be read from the module.
{
while ( radio.available() ) //this function returns true if there's data available to be read from the NRF24 module
{
radio.read(&data, sizeof(Signal));// If data is available, this function reads the data packet into the data struct
lastRecvTime = millis();   // receive the data 
}
}

void loop()
{
recvData();
unsigned long now = millis();
if ( now - lastRecvTime > 1000 ) {
ResetData(); //
}

ch_width_1 = map(data.throttle, 0, 255, 1000, 2000);     // pin D2 (PWM signal)
ch_width_2 = map(data.pitch,    0, 255, 1000, 2000);     // pin D3 (PWM signal)
ch_width_3 = map(data.roll,     0, 255, 1000, 2000);     // pin D4 (PWM signal)
ch_width_4 = map(data.yaw,      0, 255, 1000, 2000);     // pin D5 (PWM signal)

ch_width_5 = map(data.aux1,     0, 255, 1000, 2000);     // pin D6 (PWM signal)
ch_width_6 = map(data.aux2,     0, 255, 1000, 2000);     // pin D7 (PWM signal)


// Write the PWM signal 
ch1.writeMicroseconds(ch_width_1);
ch2.writeMicroseconds(ch_width_2);
ch3.writeMicroseconds(ch_width_3);
ch4.writeMicroseconds(ch_width_4);
}

Important sections of the code and its explanation (magnified)

include : essential library for functioning

include : essential library for functioning

include : essential library for functioning

const uint64_t pipeOut = 0xE9E8F0F0E1LL; essentially the address for communication so both the receiver and transmitter needs to have this same code.

radio.begin(); //This function initializes the NRF24 module. It sets up the SPI communication and prepares the module for further configuration.

radio.openWritingPipe(pipeOut); //This function configures the NRF24 module to use a specific address for writing data. The pipeOut variable represents the address to which the transmitter will send data

radio.setAutoAck(false); //This function disables automatic acknowledgment

radio.setDataRate(RF24_250KBPS); //This function sets the data rate for communication. In this case, it's set to 250 kbps (kilobits per second), which is a moderate data rate suitable for most applications. Lower data rates can provide better range and reliability, while higher data rates offer faster transmission speeds. Tweak it to your requirements

radio.setPALevel(RF24_PA_HIGH); //This function sets the power amplifier level for the NRF24 module. It determines the output power of the module, which affects the communication range. Setting it to RF24_PA_HIGH configures the module for the highest available power level, which can improve range but may consume more power

radio.stopListening(); //start the radio communication for Transmitter. Transmitter will be transmitting data so stopListening is used. This function stops the NRF24 module from listening for incoming data. In this context, it's used in the transmitter code because the transmitter will be sending data, not receiving it. By stopping listening, the NRF24 module can focus on transmitting data efficiently without wasting resources on reception

void recvData() //This function is responsible for receiving data from the NRF24 module. It continuously checks if there's any data available to be read from the module.

while ( radio.available() ) //this function returns true if there's data available to be read from the NRF24 module

radio.read(&data, sizeof(Signal)); // If data is available, this function reads the data packet into the data struct