< async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"">

Sunday, December 22, 2013

[working] Quick start guide for nRF24L01 on Arduino

ADVERTISEMENT
Making your sensors wireless is always a convenient solution to avoid cabling hassle. The available, cheap 2.4ghz transceiver are a good solution. They have a small wireless chip which makes two way communications between two arduinos or any other micro-controllers much easier.  They are available in different form factors and the commonly available ones are shown in the figure. When you get the parts it is always thrilling to test it out in the shortest possible time. After searching for nrf24l01 arduino tutorials, i ended up with a lot of information's and a huge chunk of codes with a lot of wrong information/misguidance for beginners. So i planned to write one short and quick start guide for using nrf24l01 module with arduino uno and a working sketch to test.

So in short the radio module needs to be connected to arduino board as follows

nRF24L01 Arduino PIN
GND GND
3V3 (VCC) 3V3 (IMPORTANT!!)
CE 9
CSN 10
SCK 13
MOSI 11
MISO 12

Now let us have a look at the pin connections for commonly available nrf24l01 module. See the picture where two most commonly seen variants are shown. One of them has 10 pins (2 grounds (GND) and two vcc (3v3)



Now install RF24 library which makes things easy. Download it from here To install this, unzip the files and rename the folder to RF24 (from RF24-master) and place this folder in your arduino libraries folder (mine was at Desktop/arduino-1.0.5/libraries/) and restart the arduino ide. Now the library is ready to go. There are examples and i was unable to understand them at the first time. So am giving some sketch (basically from the same source) which you can upload and test without thinking too much.

Wireless scanner
----------------------

It just need only one module to test. It acts like a poormans scanner (from Mark) and shows the signals around.  Paste this script in arduino ide and upload it to the uno board. Open the serial monitor by pressing the small magnifier button on the ide and set baud rate to 57600 (else you will see some gibberish text)




#include <SPI.h>

// Poor Man's Wireless 2.4GHz Scanner
//
// uses an nRF24L01p connected to an Arduino
// 
// Cables are:
//     SS CSN) -> 10
//     MOSI     -> 11
//     MISO     -> 12
//     SCK      -> 13
// 
// and CE       ->  9
//
// created March 2011 by Rolf Henkel
//

#define CE  9

// Array to hold Channel data
#define CHANNELS  64
int channel[CHANNELS];

// greyscale mapping 
int  line;
char grey[] = " .:-=+*aRW";

// nRF24L01P registers we need
#define _NRF24_CONFIG      0x00
#define _NRF24_EN_AA       0x01
#define _NRF24_RF_CH       0x05
#define _NRF24_RF_SETUP    0x06
#define _NRF24_RPD         0x09

// get the value of a nRF24L01p register
byte getRegister(byte r)
{
  byte c;
  
  PORTB &=~_BV(2);
  c = SPI.transfer(r&0x1F);
  c = SPI.transfer(0);  
  PORTB |= _BV(2);

  return(c);
}

// set the value of a nRF24L01p register
void setRegister(byte r, byte v)
{
  PORTB &=~_BV(2);
  SPI.transfer((r&0x1F)|0x20);
  SPI.transfer(v);
  PORTB |= _BV(2);
}
  
// power up the nRF24L01p chip
void powerUp(void)
{
  setRegister(_NRF24_CONFIG,getRegister(_NRF24_CONFIG)|0x02);
  delayMicroseconds(130);
}

// switch nRF24L01p off
void powerDown(void)
{
  setRegister(_NRF24_CONFIG,getRegister(_NRF24_CONFIG)&~0x02);
}

// enable RX 
void enable(void)
{
    PORTB |= _BV(1);
}

// disable RX
void disable(void)
{
    PORTB &=~_BV(1);
}

// setup RX-Mode of nRF24L01p
void setRX(void)
{
  setRegister(_NRF24_CONFIG,getRegister(_NRF24_CONFIG)|0x01);
  enable();
  // this is slightly shorter than
  // the recommended delay of 130 usec
  // - but it works for me and speeds things up a little...
  delayMicroseconds(100);
}

// scanning all channels in the 2.4GHz band
void scanChannels(void)
{
  disable();
  for( int j=0 ; j<200  ; j++)
  {
    for( int i=0 ; i<CHANNELS ; i++)
    {
      // select a new channel
      setRegister(_NRF24_RF_CH,(128*i)/CHANNELS);
      
      // switch on RX
      setRX();
      
      // wait enough for RX-things to settle
      delayMicroseconds(40);
      
      // this is actually the point where the RPD-flag
      // is set, when CE goes low
      disable();
      
      // read out RPD flag; set to 1 if 
      // received power > -64dBm
      if( getRegister(_NRF24_RPD)>0 )   channel[i]++;
    }
  }
}

// outputs channel data as a simple grey map
void outputChannels(void)
{
  int norm = 0;
  
  // find the maximal count in channel array
  for( int i=0 ; i<CHANNELS ; i++)
    if( channel[i]>norm ) norm = channel[i];
    
  // now output the data
  Serial.print('|');
  for( int i=0 ; i<CHANNELS ; i++)
  {
    int pos;
    
    // calculate grey value position
    if( norm!=0 ) pos = (channel[i]*10)/norm;
    else          pos = 0;
    
    // boost low values
    if( pos==0 && channel[i]>0 ) pos++;
    
    // clamp large values
    if( pos>9 ) pos = 9;
   
    // print it out
    Serial.print(grey[pos]);
    channel[i] = 0;
  }
  
  // indicate overall power
  Serial.print("| ");
  Serial.println(norm);
}

// give a visual reference between WLAN-channels and displayed data
void printChannels(void)
{
  // output approximate positions of WLAN-channels
  Serial.println(">      1 2  3 4  5  6 7 8  9 10 11 12 13  14                     <");
}

void setup()
{
  Serial.begin(57600);
  
  Serial.println("Starting Poor Man's Wireless 2.4GHz Scanner ...");
  Serial.println();

  // Channel Layout
  // 0         1         2         3         4         5         6
  // 0123456789012345678901234567890123456789012345678901234567890123
  //       1 2  3 4  5  6 7 8  9 10 11 12 13  14                     | 
  //
  Serial.println("Channel Layout");
  printChannels();
  
  // Setup SPI
  SPI.begin();
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.setBitOrder(MSBFIRST);
  
  // Activate Chip Enable
  pinMode(CE,OUTPUT);
  disable();
  
  // now start receiver
  powerUp();
  
  // switch off Shockburst
  setRegister(_NRF24_EN_AA,0x0);
  
  // make sure RF-section is set properly 
  // - just write default value... 
  setRegister(_NRF24_RF_SETUP,0x0F); 
  
  // reset line counter
  line = 0;
}

void loop() 
{ 
  // do the scan
  scanChannels();
  
  // output the result
  outputChannels();
  
  // output WLAN-channel reference every 12th line
  if( line++>12 )
  {
    printChannels();
    line = 0;
  }
}

Now let us make two module speaking to each other. This is from the rf24 examples folder. Make two sets where the first one sends the signal, second the second one receives and send it back to first and the time taken for the loop is shown. When i tried the examples, i didn't set the order of which module to transmit and which one to receive and i was wondering why it was not working! So to make simple i made two sketches which are 99.9% similar except in the rad and write variables. Upload SKETCH1 to the first one which is kept connected to a computer and keep arduino serial monitor open. The second one is on an arduino which is kept far from the computer and can be powered with a 9volt battery. The second one receives the signal and relays it back to the first one and the response can be viewed on a PC. So to make it simple, load sketch2 to an arduino with nrf24l module and then keep it away from pc with a 9v battery attached. Then load sketch1 to the next arduino and open its serial monitor to see the response.

 SKETCH2 (for the arduino which will send pback the response, battery powered)

/*
 Copyright (C) 2011 J. Coliz <[email protected]>

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 version 2 as published by the Free Software Foundation.
 */

/**
 * Example for Getting Started with nRF24L01+ radios. 
 *
 * This is an example of how to use the RF24 class.  Write this sketch to two 
 * different nodes.  Put one of the nodes into 'transmit' mode by connecting 
 * with the serial monitor and sending a 'T'.  The ping node sends the current 
 * time to the pong node, which responds by sending the value back.  The ping 
 * node can then see how long the whole cycle took.
 */

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

int serial_putc( char c, FILE * ) 
{
  Serial.write( c );

  return c;
} 

void printf_begin(void)
{
  fdevopen( &serial_putc, 0 );
}

//
// Hardware configuration
//

// Set up nRF24L01 radio on SPI bus plus pins 9 & 10 

RF24 radio(9,10);

//
// Topology
//

// Radio pipe addresses for the 2 nodes to communicate.
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

//
// Role management
//
// Set up role.  This sketch uses the same software for all the nodes
// in this system.  Doing so greatly simplifies testing.  
//

// The various roles supported by this sketch
typedef enum { role_ping_out = 1, role_pong_back } role_e;

// The debug-friendly names of those roles
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"};

// The role of the current running sketch
role_e role = role_pong_back;
int led = 8;

void setup(void)
{

  Serial.begin(57600);
  analogReference(INTERNAL);
  radio.begin();
  // optionally, increase the delay between retries & # of retries
  radio.setRetries(15,15); 
  radio.openWritingPipe(pipes[0]);
  radio.openReadingPipe(1,pipes[1]);
  //
  // Start listening
  //
  radio.startListening();
  //
  // Dump the configuration of the rf unit for debugging
  //
  radio.printDetails();
}

void loop(void)
{
  //
  // Ping out role.  Repeatedly send the current time
  //      

  if (role == role_ping_out)
  {
    // First, stop listening so we can talk.
    radio.stopListening();

    // Take the time, and send it.  This will block until complete
    unsigned long time = millis();
    
    bool ok = radio.write( &time, sizeof(unsigned long) );

    // Now, continue listening
    radio.startListening();

    // Wait here until we get a response, or timeout (250ms)
    unsigned long started_waiting_at = millis();
    bool timeout = false;
    while ( ! radio.available() && ! timeout )
      if (millis() - started_waiting_at > 200 )
        timeout = true;

    // Describe the results
    if ( timeout )
    {
      //printf("Failed, response timed out.\n\r");
    }
    else
    {
      // Grab the response, compare, and send to debugging spew
      unsigned long got_time;
      radio.read( &got_time, sizeof(unsigned long) );

      // Spew it
      //printf("Got response %lu, round-trip delay: %lu\n\r",got_time,millis()-got_time);
    }

    // Try again 1s later
    delay(1000);
  }

  //
  // Pong back role.  Receive each packet, dump it out, and send it back
  //

  if ( role == role_pong_back )
  {
    // if there is data ready
    if ( radio.available() )
    {
      // Dump the payloads until we've gotten everything
      unsigned long got_time;
      bool done = false;
      while (!done)
      {
        // Fetch the payload, and see if this was the last one.
        done = radio.read( &got_time, sizeof(unsigned long) );     
        
    // Delay just a little bit to let the other unit
    // make the transition to receiver
    delay(20);
        
      }

      // First, stop listening so we can talk
      radio.stopListening();

      // Send the final one back.
      radio.write( &got_time, sizeof(unsigned long) );
      //printf("Sent response.\n\r");

      // Now, resume listening so we catch the next packets.
      radio.startListening();
    }
  }

}


 SKETCH1 (for the arduino connected to the computer with serial monitor and this keeps transmitting to the arduino with SKETCH2 loaded and listen for the reply)

/*
 Copyright (C) 2011 J. Coliz <[email protected]>

 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 version 2 as published by the Free Software Foundation.
 */

/**
 * Example for Getting Started with nRF24L01+ radios. 
 *
 * This is an example of how to use the RF24 class.  Write this sketch to two 
 * different nodes.  Put one of the nodes into 'transmit' mode by connecting 
 * with the serial monitor and sending a 'T'.  The ping node sends the current 
 * time to the pong node, which responds by sending the value back.  The ping 
 * node can then see how long the whole cycle took.
 */

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

int serial_putc( char c, FILE * ) 
{
  Serial.write( c );

  return c;
} 

void printf_begin(void)
{
  fdevopen( &serial_putc, 0 );
}


//
// Hardware configuration
//

// Set up nRF24L01 radio on SPI bus plus pins 9 & 10 

RF24 radio(9,10);

//
// Topology
//

// Radio pipe addresses for the 2 nodes to communicate.
const uint64_t pipes[2] = { 0xF0F0F0F0E1LL, 0xF0F0F0F0D2LL };

//
// Role management
//
// Set up role.  This sketch uses the same software for all the nodes
// in this system.  Doing so greatly simplifies testing.  
//

// The various roles supported by this sketch
typedef enum { role_ping_out = 1, role_pong_back } role_e;

// The debug-friendly names of those roles
const char* role_friendly_name[] = { "invalid", "Ping out", "Pong back"};

// The role of the current running sketch
role_e role = role_pong_back;

void setup(void)
{
  //
  // Print preamble
  //

  Serial.begin(57600);
  printf_begin();
  printf("\n\rRF24/examples/GettingStarted/\n\r");
  printf("ROLE: %s\n\r",role_friendly_name[role]);
  printf("*** PRESS 'T' to begin transmitting to the other node\n\r");

  //
  // Setup and configure rf radio
  //

  radio.begin();

  // optionally, increase the delay between retries & # of retries
  radio.setRetries(15,15);
  radio.openWritingPipe(pipes[1]);
  radio.openReadingPipe(1,pipes[0]);
  
  //
  // Start listening
  //

  radio.startListening();

  //
  // Dump the configuration of the rf unit for debugging
  //

  radio.printDetails();
}

void loop(void)
{
  //
  // Ping out role.  Repeatedly send the current time
  //

  if (role == role_ping_out)
  {
    // First, stop listening so we can talk.
    radio.stopListening();

    // Take the time, and send it.  This will block until complete
    unsigned long time = millis();
    printf("Now sending %lu...",time);
    bool ok = radio.write( &time, sizeof(unsigned long) );
    
    if (ok)
      printf("ok...");
    else
      printf("failed.\n\r");

    // Now, continue listening
    radio.startListening();

    // Wait here until we get a response, or timeout (250ms)
    unsigned long started_waiting_at = millis();
    bool timeout = false;
    while ( ! radio.available() && ! timeout )
      if (millis() - started_waiting_at > 200 )
        timeout = true;

    // Describe the results
    if ( timeout )
    {
      printf("Failed, response timed out.\n\r");
    }
    else
    {
      // Grab the response, compare, and send to debugging spew
      unsigned long got_time;
      radio.read( &got_time, sizeof(unsigned long) );

      // Spew it
      printf("Got response %lu, round-trip delay: %lu\n\r",got_time,millis()-got_time);
    }

    // Try again 1s later
    delay(1000);
  }

  //
  // Pong back role.  Receive each packet, dump it out, and send it back
  //

  if ( role == role_pong_back )
  {
    // if there is data ready
    if ( radio.available() )
    {
      // Dump the payloads until we've gotten everything
      unsigned long got_time;
      bool done = false;
      while (!done)
      {
        // Fetch the payload, and see if this was the last one.
        done = radio.read( &got_time, sizeof(unsigned long) );

        // Spew it
        printf("Got payload %lu...",got_time);

    // Delay just a little bit to let the other unit
    // make the transition to receiver
    delay(20);
      }

      // First, stop listening so we can talk
      radio.stopListening();

      // Send the final one back.
      radio.write( &got_time, sizeof(unsigned long) );
      printf("Sent response.\n\r");

      // Now, resume listening so we catch the next packets.
      radio.startListening();
    }
  }

  //
  // Change roles
  //

  if ( 1 )
  {
    char c = toupper(Serial.read());
    if ( 1 )
    {
      printf("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK\n\r");

      // Become the primary transmitter (ping out)
      role = role_ping_out;
      radio.openWritingPipe(pipes[1]);
      radio.openReadingPipe(1,pipes[0]);
    }
    
  }
}

14 comments:

  1. When I connected the Arduino to external power source ( 9 V battery ) the receiver didnt work and when I connected the receiver to the computer it worked perfectly. Can you help me to solve the problem

    ReplyDelete
    Replies
    1. Might be a grounding issue. Try connecting the Arduino ground and earth ground (water pipe, mains earth connected device case)

      But idk.

      Delete
  2. What is the SS you're referring to? none of the chips have an SS pin

    ReplyDelete
    Replies
    1. It is CSN (chip select/ Slave Select (SS) in spi)

      Delete
  3. I tried this too with an Uno and a Micro. The Uno is transmitting just fine. But the micro isn't doing anything. It is using the sketch2.

    ReplyDelete
    Replies
    1. Could be some pin difference. Check port commands on uno vs micro. PORTB commands are specific to microcontroller

      Delete
  4. Hi RiY,
    i didnt understand the output on the scop:
    Starting Poor Man's Wireless 2.4GHz Scanner ...

    Channel Layout
    > 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <
    | :: : :: : W : | 4
    | W++ | 2
    | . .W . | 6
    | - W - | 3
    | - W - | 3
    | + +++++ ++++ + W W+ | 2
    | -. -...-+--.. -. ... .....W. . . | 6
    | W W W W W W W WWW | 1
    | + +W+WWW++++++WW++ +++ + + | 2
    | +::::W+++:aa:+ :: | 4
    | ++ W + + + + + + | 2
    | : :: : :W ::* | 5
    | . : .W. .... . | 7
    | +W W W + + W | 2
    > 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <
    | - - - W- * - -- | 3
    | - -- - W- - - | 3
    | W WW W | 1
    | W++ | 2
    | --- --*---- W - - | 3
    | -- --- -- ***W---*W**** - - | 3
    | WW W WW | 1
    | W W + + | 2
    | W : : : | 5
    | +W + + + + | 2
    | + ++ + + +W + | 2
    | * W - | 3
    | + + + +W ++ + + | 2
    | + + + W + + | 2

    can you please explain?

    thanks

    ReplyDelete
    Replies
    1. it (probably)showing signals from nearby wifi routers. See the code : char grey[] = " .:-=+*aRW";
      So it shows a . for low signals and a W for stronger ones. You can try changing the loop count in the function void scanChannels(void) ie (in for( int j=0 ; j<200 ; j++) ) from 200 to a lower value for e.g use for( int j=0 ; j<100 ; j++) and see if it shows less clutter

      Also double check there is no loose connection at the connectors and breadboard (if used)

      Delete
    2. thanks for you fast respond.

      what i should see on the scop to be sure that my transmiter responding and speaking with the exact receiver that i wanted to?

      Delete
  5. Thank you very much for this post. I have expanded your post adding a joystick at the transmitter and a couple of servos at the receiver : http://binefa.cat/blog/?p=122

    ReplyDelete
  6. "'RF24' does not name any type"..
    Any ideas?

    ReplyDelete
    Replies
    1. Install library from https://github.com/maniacbug/RF24 , download zip file, extract to arduino/library folder

      Delete
  7. I am trying to build setup having 1 transmitter and 2 receivers , Is it possible with nrf ? if it possible can you please send me some simple sketch of transmitter.

    ReplyDelete
  8. Thanks for the examples, Riya! I am able to build them and see the response. A couple of observations: first I had to change baud rate to 9600 in sketch1 otherwise the terminal characters would turn into gibberish after a few normal outputs. Second, around 50% of the responses failed even though the sketch2 module is only 3 meters away. As I am trying to build a rolling code remote based on your other article:
    http://www.riyas.org/2015/07/a-simple-rf-remote-with-secure-rolling-code-and-arduino.html
    Would these errors be a problem in that project once I adapt it for NRF24L01?

    ReplyDelete