Web
Analytics
 

Getting Started with the RF Nano

Updated: Apr 21, 2020

The Arduino of choice for almost all of my projects these days is the Arduino Nano. More specifically, the Keywish Nano with an upgraded micro USB plug. That little guy was my daily diver for months, that is until the guys at Keywish took things even further and blew me away with some new boards. Miraculously, they came out with a series of Arduino Nanos with one of my favorite wireless chips, integrated into the board! And so, the RF Nano was born. Since this little guy came out a few months ago, I have loved writing code for it! We have a few up in the shop, so if you have experience with the NRF24L01, and haven't tried it out, I highly suggest picking one up. This tutorial is meant to serve as a guide to getting anyone, regardless of experience, up and running with wireless communication using two RF Nanos. Keep in mind, any of the code used in this tutorial can be used with a regular Arduino and a standard external NRF24L01. Let's take a closer look at the RF Nano!



Some of it's best features include a really useful micro-USB plug for power and uploading code, the extract same layout and pinout as a traditional Arduino Nano, as well as the NRF24L01 integrated into the board itself! This means that we can achieve wireless communication between the two modules without any additional hardware!


if you'd like, we also cover most of this data in one of the videos on your YouTube Channel




By the end of this tutorial, you'll have everything you need to start sending all kinds of awesome data between RF Nanos, but to show you just how easy it is to get these things talking, we're going to start by generating a random number and sending it to our receiver RF Nano. Let's take a look at our send code!


Part One: Our Send Code


   /*
    
   RF Nano Send Example
   
   Hey guys! This example code is designed to send one random 
   integer from one RF Nano to a second RF Nano, to show just how 
   simple the code can be. This code is also compatible with an RF Nano 
   (Arduino Nano with integrated NRF24L01)

   - Matt (ACBR 2020)
   
   Pins for Radio
   1 - GND 
   2 - VCC 5v
   3 - CE - Arduino pin 9
   4 - CSN - Arduino pin 10
   5 - SCK - Arduino pin 13
   6 - MOSI - Arduino pin 11
   7 - MISO - Arduino pin 12
   8 - UNUSED
 */
 
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#define CE_PIN   10
#define CSN_PIN 9

const uint64_t pipe = 0x1; // This is the transmit pipeline
int sendData[1];  // One element array holding our random number

RF24 radio(CE_PIN, CSN_PIN); // Activate the Radio

void setup()   
{
  Serial.begin(9600);
  radio.begin();
  radio.openWritingPipe(pipe);
  randomSeed(analogRead(A0));
}


void loop()   
{
  radio.write( sendData, sizeof(sendData));
  sendData[0] = random(10);
  Serial.println(sendData[0]);
}


Let's break this code down a little bit! Starting with our code above our void setup( )


#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#define CE_PIN   10
#define CSN_PIN 9

const uint64_t pipe = 0x1; // This is the transmit pipeline.
int sendData[1];  // One element array holding our random number

RF24 radio(CE_PIN, CSN_PIN); // Activate  the Radio


There are a few very important library files we need here. The first is the SPI Library, we'll need this to talk to our NRF radio. Now the next two libraries are a part of the rf24 Library, and this is where things get a little tricky. There are two popular libraries that have a very similar filename structure. All of my examples here are using the rf24 Library from GitHub user maniacbug. There are other libraries out there, but be sure to download the maniacbug bug rf24 library here.


https://github.com/maniacbug/RF24


Now if you're wiring up an external NRF24L01 make sure that you wire up the CE pin to pin D10 and the CSN pin to pin D9 of your Arduino.

#define CE_PIN 10 
#define CSN_PIN 9

Our next line of code is our communication pipeline, it's usually structured as a hexadecimal address. In this case, I have our communication pipeline labeled "0x1", to keep things really simple.

const uint64_t pipe = 0x1; // This is the transmit pipeline.

The last thing to focus on here is our array. This will make a little more sense when we're trying to send multiple things at once in our next example. But in this case, we have an array with only one element, and we've labeled it "sendData". We're going to populate our array with data down in the void loop.

int sendData[1];  // One element array holding our random number 

Next in our void setup, there are just a few things we need to take care of, the most important being initializing our radio.

void setup()   
{
  Serial.begin(9600); 
  radio.begin();
  radio.openWritingPipe(pipe);
  randomSeed(analogRead(A0));
}

These lines of code here are pretty straight forward. One thing to note is that even though our goal is to send data out, and not just print to our Serial Monitor, it is really helpful in initializing the Serial Monitor to help with debugging. If we set up this send circuit, and we don't receive any data on the receiver circuit, this can help us verify that the sender side is getting data from its sensor. The last line of code is meant to help us generate a random number to send out later, it uses whatever voltage it reads on the analog pin A0, to start its randomizer.


Lastly, we have our void loop. This is the part of our code that will generate a random number, and send it to our receiving RF Nano.

 void loop()  
{
  radio.write( sendData, sizeof(sendData));
  sendData[0] = random(10);
  Serial.println(sendData[0]);
}

The radio.write command sends out our Array, in this case, we only have one element in our array, but writing "sizeof(sendData)" in the future, we can add way more elements to our array, and send it out no matter what size it is (so long as it's not too long for our radio to handle).


The next line of code assigns a value to the first element in our array. Keep in mind, computers always start counting at "0" and not "1" as humans do. So in order to assign a value to the first element in our array, (even though there's only one element) we need to assign it to position "0".


Our last line just sends out our data to the serial monitor so we can read it! If you've copied our sender code, then upload it and open up your serial monitor! You should see something like this!



Keep in mind, you can swap out our random number for any kind of data you'd like. There are so many awesome options. Any data your Arduino can read, you can send out wirelessly with this handy little radio chip.



Part Two: Our Reciever Code


/* 
  
   RF Nano Receive Example
 
   Hey guys! This example code is designed to send one random integer 
   from one RF Nano to a second RF Nano, to show just how simple the 
   code can be. This code is also compatible with an RF Nano (Arduino 
   Nano with integrated NRF24L01)

   - Matt (ACBR 2020)
   
   1 - GND 
   2 - VCC 3.3V 
   3 - CE - Arduino pin 9
   4 - CSN - Arduino pin 10
   5 - SCK - Arduino pin 13
   6 - MOSI - Arduino pin 11
   7 - MISO - Arduino pin 12
   8 - UNUSED 
*/

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#define CE_PIN   10
#define CSN_PIN 9

const uint64_t pipe = 0x1;  // This is the transmit pipe
int sendData[1];  // One element array holding our random number

RF24 radio(CE_PIN, CSN_PIN);

void setup()  
{
  Serial.begin(9600); 
  delay(1000);
  Serial.println("Nrf24L01 Receiver Starting");
  radio.begin();
  radio.openReadingPipe(1,pipe);
  radio.startListening();
}


void loop()   
{
  if ( radio.available() )
  {
    bool done = false;
    while (!done)
    {
      done = radio.read(sendData, sizeof(sendData));
      Serial.print("Random Number: ");
      Serial.println(sendData[0]);
      delay(50);
    }
  }
  else
  {    
      Serial.println("Darn, not working yet!");
  }
}

Alright, let's break this code down, notice how similar it is to our send code! That will make figuring out what each line accomplishes nice and easy. That being said, there are a few key differences that we'll get into more detail on now.


One thing that's nice is, the code we have above our void setup is the exact same as our send code! Things only get slightly different in our void setup. Let's take a look.

void setup()  
{
  Serial.begin(9600); 
  delay(1000);
  Serial.println("Nrf24L01 Receiver Starting");
  radio.begin();
  radio.openReadingPipe(1,pipe);
  radio.startListening();
}

The only main difference here is that we're opening up a reading pipeline, as opposed to a writing pipeline. In this case, we're using the exact same pipeline as our send circuit "0x1" which is really important to keep consistent.


If we take a look at our void loop, the entire code is split up into two sections, "If radio.available( )" and if our radio is not available. Although it may seem unnecessary, I usually include this "else" as a sort of error message. If I didn't include this first condition, and I opened up the Serial Monitor and saw nothing, it may be difficult to tell what the issue was. Having this "else" condition helps us narrow down the possible issue if things don't go as planned.


Once we pass through the radio. available condition, we enter our "while loop"

while (!done)     
{
 done = radio.read(sendData, sizeof(sendData));       
 Serial.print("Random Number: ");       
 Serial.println(sendData[0]);       
 delay(50);
 }

Here we use a boolean "done" variable to manage whether or not we still have data coming through. So long as we still have data, read it, and store it in our array. The rest of this loop is simply printing back our data. One final note here, this delay(50); is really important. You may have noticed there was no delay on our send side, that lack of a delay is also really important. In order to have maximum stability, with minimal lag, it's best to have no delay on your send circuit, and as small of a delay as possible on the receiver side. That being said, a delay of fewer than 50 milliseconds can cause a really spotty connection or no connection at all. In all of my experiments, a delay of 50 milliseconds is perfect. Once you have the receiver code all set, send it over to your receiving RF Nano and open up that Serial Monitor! If everything went as planned you should see something like this!




The only difference here is that we have also printed a handy little label "Random Number". Whenever you have multiple bits of data you're sending, as you may for a robot controller, it's really helpful to label all of your data to make sure things are working the way you want.





Part Three: Let's send some more data with the MPU60-50


Now that we have communication established, its time to use that connection to send over some data that's more fun than just a string of numbers. To do that we'll use an awesome chip known as the MPU60-50. This handy little Gyroscope / Accelerometer IC will help us calculate what we'll call "Pitch" and "Roll". Then, rather than simply view that data on the Serial Monitor, we'll plug in our receiver RF Nano into one of our robotic laser cannon kits to use that MPU60-50 data to control our laser cannon! Let's start off by wiring the MPU60-50 to our RF Nano.


The MPU60-50 is an i2c device, so there are only four wires we need to get this thing reading our accelerometer data. Vcc will get wired up to the 5v pin on our Arduino. The GND pin can be plugged into any of the two GND pins on our board. Finally, we have SDA and SCL, these pins are standard for i2c communication. A standard Arduino Uno and Nano have the i2c bus hardwired to pins A4 for SDA and A5 for SCL.


Part 4: Let's write some code for our new sender circuit


   /*
   Hey guys! This example code is designed to send accelerometer data 
   from an MPU-6050, and send it out with the NRF24L01.  This code is 
   also compatible with an RF Nano (Arduino Nano with integrated 
   NRF24L01)

   - Matt (ACBR 2020)
   
   Pins for Radio
   1 - GND 
   2 - VCC 5v
   3 - CE - Arduino pin 9
   4 - CSN - Arduino pin 10
   5 - SCK - Arduino pin 13
   6 - MOSI - Arduino pin 11
   7 - MISO - Arduino pin 12
   8 - UNUSED

   Pins for MPU60-50
   Vcc - 5V
   GND - GND
   SCL - Arduino Pin A5
   SDA - Arduino Pin A4
 */
 
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Wire.h>

#define MPU 0x68  // I2C address of the MPU-6050
#define CE_PIN   10
#define CSN_PIN 9


const uint64_t pipe = 0x1; // This is the transmit pipe 
double AcX,AcY,AcZ;
int Pitch, Roll;
int GYRO[2];  // Two element array holding the pitch and roll data

RF24 radio(CE_PIN, CSN_PIN); // Activate the radio

void setup()   
{
  Serial.begin(9600); 
  radio.begin();
  radio.openWritingPipe(pipe);
  init_MPU();
}


void loop()   
{
  radio.write( GYRO, sizeof(GYRO) );
  FunctionsMPU(); // Returning our accelerometer data AcX, AcY, AcZ.
  Roll = FunctionsPitchRoll(AcX, AcY, AcZ);   
  Pitch = FunctionsPitchRoll(AcY, AcX, AcZ);  
  
  GYRO[0] = Pitch;
  GYRO[1] = Roll;

  Serial.print("Pitch: "); Serial.print(Pitch);
  Serial.print("\t");
  Serial.print("Roll: "); Serial.print(Roll);
  Serial.print("\n");
  
}
 
void init_MPU(){
  Wire.begin();
  Wire.beginTransmission(MPU);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  delay(1000);
}
 

double FunctionsPitchRoll(double A, double B, double C){
  double DatoA, DatoB, Value;
  DatoA = A;
  DatoB = (B*B) + (C*C);
  DatoB = sqrt(DatoB);
  
  Value = atan2(DatoA, DatoB);
  Value = Value * 180/3.14;
  
  return (int)Value;
}

void FunctionsMPU(){
  Wire.beginTransmission(MPU);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU,6,true);  // request a total of 14 registers
  AcX=Wire.read()<<8|Wire.read();      
  AcY=Wire.read()<<8|Wire.read();  
  AcZ=Wire.read()<<8|Wire.read();
}

The main difference in our new send code vs our last one is that in this instance, we need to add in a few variables to access the i2c data from the MPU60-50. the second major difference is that in this case we created an array and labeled it "GYRO" and added in two elements, not just one like our previous example. One for "Pitch", and one for "Roll".


In our void loop, the first thing we need to do is use the write command to send our GYRO array.

radio.write( GYRO, sizeof(GYRO) );

Once we start reading our data, it's time to unpack our array, and assign the element arrays to two variables of our, Pitch and Roll.

  GYRO[0] = Pitch;   
  GYRO[1] = Roll;

The rest of our void loop is a series of print statements to make sure that our MPU60-50 is reading our data correctly. This way we can make sure the data we're sending is the same as the data we're going to end up receiving.


There are three other methods we use in this bit of code underneath our void loop, they're all designed to establish i2c communication, as well as calculate our pitch and roll based on our accelerometer data.


Once you have your new sending code all set, go ahead and upload your code to your RF Nano with the MPU60-50 wired up and open up your Serial Monitor. If everything went as planned, you should see something like this!




Just like our last set of send and receive codes, it's crucial that there is no "delay" in our send code, this may make the data slightly too fast to read, but it will be slower on the receiver side, which is where we'll use that data to control the servos of our laser cannon.





Part 5: Let's write some code for our new receiver circuit


Alright, we have our MPU60-50 data being sent out of one of our RF Nanos, it's time to write the code to..

1. Receive the Pitch and Roll data

2. Put that data to work by using it to increase and decrease two position variables

3. Program our Pan and Tilt servos to change in relation to the position variables.


/* 
   Hey guys! This example code is designed to receive accelerometer 
   data from an NRF24L01, and use the Pitch and Roll dat to control two 
   servos. This code is also compatible with an RF Nano (Arduino Nano 
   with integrated NRF24L01)     
   
   - Matt (ACBR 2020)    
   
   1 - GND 
   2 - VCC 3.3V 
   3 - CE - Arduino pin 9
   4 - CSN - Arduino pin 10
   5 - SCK - Arduino pin 13
   6 - MOSI - Arduino pin 11
   7 - MISO - Arduino pin 12
   8 - UNUSED 
*/

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <Servo.h>
#define CE_PIN   10
#define CSN_PIN 9
const uint64_t pipe = 0x1;  
RF24 radio(CE_PIN, CSN_PIN);
int GYRO[2]; 
int laser = 4;
int posX = 90;
int posY = 90;
Servo servoX;
Servo servoY;

void setup()  
{
  Serial.begin(9600); 
  delay(1000);
  Serial.println("Nrf24L01 Receiver Starting");
  servoX.attach(3);
  servoX.write(posX);
  servoY.attach(5);
  servoY.write(posY);
  pinMode(laser, OUTPUT);
  radio.begin();
  radio.openReadingPipe(1,pipe);
  radio.startListening();
}


void loop()   
{
  digitalWrite(laser, HIGH);
  if ( radio.available() )
  {
    bool done = false;
    while (!done)
    {
      done = radio.read(GYRO, sizeof(GYRO));
      Serial.print("Gyro Data: ");
      Serial.print(GYRO[0]);
      Serial.print("  ");
      Serial.println(GYRO[1]);
      
      if(GYRO[1] < - 30 && posY > 0)
      {
        Serial.println("Forward");
        posY-=5;
        servoY.write(posY);
      }
      else if(GYRO[1] > 30 && posY < 180)
      {
        Serial.println("Backward");
        posY+=5;
        servoY.write(posY);
      }
      else if(GRYO[0] < - 30 && posX < 180)
      {
        Serial.println("Left");
        posX+=5;
        servoX.write(posX);
      }
      else if(GYRO[0] > 30 and posX > 0)
      {
        Serial.println("Right");
        posX-=5;
        servoX.write(posX);
      }
      delay(50);
    }
  }
  else
  {    
      Serial.println("Darn, not working yet!");
  }
}

In the code above our void setup in our new receiver code, the biggest change is probably the addition of our two servos, as well as our position variables. We set our position variables at 90, so that way every time we start up our code, both servos will be in the middle of their range of motion.


The code in our void setup, looks almost identical to our previous example, except this time we need to attach our servos to two PWM pins on our Arduino. In this case, we're using pins 3 and 5.


The most important part of our main void loop is grabbing both elements of our array. One of the first things we do after checking to see if we have a radio connection is print out both elements of our array, one after the other.

      Serial.print("Gyro Data: ");       
      Serial.print(GYRO[0]);       
      Serial.print("  ");       
      Serial.println(GYRO[1]);

Now, what really sets this code apart from our first example, is that we don't simply want to print out our data, we want to use that data to move our servos! We can accomplish this with just a few short lines of code.

      if(GYRO[1] < - 30 && posY > 0)       
      {         
      Serial.println("Forward");         
      posY-=5;         
      servoY.write(posY);       
      }

Here we have a conditional statement with two conditions. The first half of our conditional statement checks if our Pitch variable is less than -30, which in this case is the same as learning the MPU60-50 forward. The second condition checks to see if the servo is already at its minimum position (zero degrees). So if the Pitch value is less than -30, and we haven't hit the limit, we tell the Arduino to print "forward", but that's not all. We are also telling the Arduino to take 5 away from the value of our posY variable. If you think back to the first few lines of code, we made the value of posY 90, for 90 degrees, but if we take away 5 degrees, we're at 85. The last thing we do is tell our servo to move to whatever the value of our position variable happens to be. Now, 5 degrees doesn't sound like a lot, and it's not. But if you continue to hold the MPU60-50 forward, the Arduino will continue to take away 5 degrees, in this case, it's every 5/100th of a second! So our servos actually end up moving pretty quickly! If you find that your servo are moving too fast, you can always swap out the "5" for a smaller number.


Now, the rest of the void loop conditions do the exact same thing, except this time they are adding to the value of posY when you hold the MPU60-50 backward and are adding and subtracting from the posX variable whenever you move the MPU60-50 from side to side.


We designed this code to be used with our robotic laser cannon kit, but it doesn't have to be! If you were to wire up our receiver RF Nano it would look something like this:



Part 6: Take things even further!

We hope you enjoyed this tutorial! There is so much more you could do with this project, but we'll leave it here, for now. If you'd like to customize our robotic laser Cannon PCB you'll find the link below, as well as the link to our Universal Robot Controller and Robotic Laser Cannon kit. Thanks for reading! If you have any questions, don't hesitate to drop us a line!


https://www.anyonecanbuildrobots.com/product-page/robotic-laser-cannon-learning-kit-pre-soldered


https://www.anyonecanbuildrobots.com/product-page/pre-soldered-universal-robot-controller


https://easyeda.com/matt_9455/arduino-laser-cannon

1,836 views3 comments

Recent Posts

See All

Anyone Can Build Robots!

1+ (858) 434 5830

©2020 by Anyone Can Build Robots!