Wireless Transceiver nRF24L01 2015 Adrian Krag Overview This circuit is a low cost 2.4 GHz transceiver with a built in antenna. The board communicates with the Arduino using SPI. This device allows two Arduinos to talk to each other such that either device can transmit or receive data. The device uses communication addresses called the "pipe". Two communicate the transmitter and receiver must have the same "pipe" address. Any receiver that has the same address as the transmitter will receive data. SPI We have used the I2C interface on other devices. Like I2C, SPI is used to communicate serially between digital devices. Both SPI and I2C provide good support for communication with slow peripheral devices that are accessed intermittently. EEPROMs, displays, and real-time clocks are examples of such devices. But SPI is better suited than I2C for applications that are naturally thought of as data streams (as opposed to reading and writing addressed locations in a slave device). An example of a "stream" application is data communication between microprocessors or digital signal processors. Another is data transfer from analog-to-digital converters. SPI Serial Peripheral Interface. The nRF24 is a wireless communication device. We want to send data from one Arduino to another and get data back in return. SPI is used to link the nRF24 to the Arduino. The SPI interface is a little more complicated than the I2C, and this allows for faster data rates. As with I2C, the communication is master (Arduino) to Slave (nRF24). Unlike I2C, we do not begin every communication by sending an address. Multiple slaves share the same clock (SCLK), MOSI (Master Out Slave In) and MISO (Master In Slave Out) lines, but each slave device has a separate SS (Slave Select) line. The SS line is used to initiate communication and to tell the slave that it is being addressed. Unlike I2C SPI does not have an acknowledgement mechanism to confirm receipt of data. In fact, without a communication protocol, the SPI master has no knowledge of whether a slave even exists. So, I2C, SPI - Which is better? Not for us to say. The nRF24 uses SPI and that's all it uses. The Arduino IDE has an SPI library and we include it in our sketch. If you look at the block diagram for the Arduino processor, you will note an SPI block (internal Peripheral) that is connected to port B, and that Port B, pin 2 (D10) is capable of the SS function, Port B, pin 3 (D11) is capable of MOSI function, Port B pin 4 (D12) is the MISO function and Port B pin 5 (D13) is SCLK. nRF24L01 Let's look at the nRF24 - this device is built around the Nordic nRF24L01 integrated circuit which is a wireless transceiver chip. http://www.nordicsemi.com/eng/Products/2.4GHzRF/nRF24L01P Our product integrates an oscillator, voltage regulator, and zig-zag antenna, all on a board with pins that can connect to an Arduino. The part is cheap, because the Nordic chip is used in lots of things so they make lots of them and this brings the cost way down. The part has 8 pins on the board, 6 of which are essential. 7 of which we are going to use. Note on the back of the chips is a pin marked CSN This is the SS pin. CE is a Chip Enable pin that allows the Arduino to shut down the Nordic chip to save power. Of course we need power (VCC = 3.3 Volts). The Nordic chips does not run on 5 volts so you need to connect to the 3.3 Volt Arduino pin. The SPI pins are 5 volt tolerant so it won't hurt if you use normal digital outputs, but DON'T CONNECT VCC to 5 VOLTS. 1 - GND 2 - VCC 3.3V !!! NOT 5V 3 - CE to Arduino pin 8 4 - CSN to Arduino pin 10 5 - SCK to Arduino pin 13 6 - MOSI to Arduino pin 11 7 - MISO to Arduino pin 12 8 - UNUSED */ Connect the pins 1 to 7 on the nRF24 to the appropriate Arduino Pins. Transmitter Program Let's look at a program - As, mentioned, we will need the SPI library and we will need the nRF24L01 library. /*-----( Import needed libraries )-----*/ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> Get these libraries from the https://github.com/maniacbug/RF24 or write to me and I'll send you the zip file. Install the library - Note, it has both the RF24.h and nRFL01.h in the same file. To use the Easy install, you will need to rename the zip file. nRF24-master.zip will download, but you need to delete the -master from the name or the Arduino easy installer will not know what to do with it. /*-----( Declare Constants and Pin Numbers )-----*/ #define CE_PIN 8 #define CSN_PIN 10 These are just definitions - that help in remembering where we put stuff. RF24 radio(CE_PIN, CSN_PIN); // Create a Radio This line is the constructor - it creates a copy of the RF24 class - in this case called radio - just a name. Could be anything. The constructor requires 2 parameters - need to know which pin is the CE (Chip Enable pin) and which pin is the CSN (SS Slave Select Pin). Now that we have the RF24 class (called radio in our program) we can use all the functions. RF24 (uint8_t _cepin, uint8_t _cspin) Constructor. void begin (void) Begin operation of the chip. void startListening (void) Start listening on the pipes opened for reading. void stopListening (void) Stop listening for incoming messages. bool write (const void *buf, uint8_t len) Write to the open writing pipe. bool available (void) Test whether there are bytes available to be read. bool read (void *buf, uint8_t len) Read the payload. void openWritingPipe (uint64_t address) Open a pipe for writing. void openReadingPipe (uint8_t number, uint64_t address) Open a pipe for reading. We are going to use all of these in the transmit and receive programs. More information can be found at http://maniacbug.github.io/RF24/classRF24.html const uint64_t pipe = 0xE7E7E7E7E7LL; // Define the transmit pipe Now things get interesting. The nRF24L01 is a 2.4Ghz transceiver. That means that the data is transmitted as variations in frequency around 2.4Ghz. Just like an FM radio, there are lots of channels, each on a slightly different frequency. To specify a channel, we need to give the program a number - Specifically a 40 bit number. Unfortunately, there aren't any 40 bit numbers (uint40_t) in the Arduino. There are 64 bit numbers (uint64_t) in the C language, but the Atmel328 processor is an 8 bit device so it can't handle numbers that big without some special code instructions. We need to specify a 40 bit number - Above we decide to use E7E7E7E7 - This is a Hex number the 0x in front tells us we are dealing with hex - Each letter and number is 4 bits - total 40. In decimal, this number is 3,890,735,079. Actually, looking at the decimal equivalent, I can't tell if it's 40 bits or not. As mentioned above, the Arduino needs special programs to deal with numbers this big. The LL at the end tells the compiler that we will need the extra processing routines. This is called the pipe - the pipe is the channel that we will be transmitting on and the same channel that we will be receiving. If you don't like 0xE7E7E7E7 - Choose something else 0x9B2438AD for example - however, it's important that whatever you pick for a transmit pipe, you must use the same for the receiver. int Cmd[4]; // 4 element array holding the 4 command integers The SPI interface is used to transmit data, usually large amounts of data. This line defines an integer array of variables, 4 of them to be exact. They are Cmd[0], Cmd[1], Cmd[2] and Cmd[3]. An array of variables is similar to defining individual variables - same rules apply - must start with a letter - no spaces or weird characters. Must specify a type - in this case integers - Could have use char or float. Arrays have the advantage that you can pick the variable you want by using another variable as the index: for (int i = 0; i < 4; i++) // this code will set all 4 variables to be 15. Cmd[i] = 15; // arrays use indirect addressing and this is a powerful capability void setup() { Serial.begin(9600); radio.begin(); radio.openWritingPipe(pipe); } That's all the global things now we are into the setup() procedure. Serial.begin(9600); opens the Serial monitor - This is the begin() that is part of the Serial class. We also have a begin() that is in the newly created radio class. Finally we are going to use a new function openWritingPipe(). This function call activates the channel that we specified with the 40 bit pipe variable. void Snd4(int C0, int C1, int C2, int C3) { Cmd[0] = C0; Cmd[1] = C1; Cmd[2] = C2; Cmd[3] = C3; radio.write( Cmd, sizeof(Cmd) ); } In this program I created a separate function to load the array and send the data. I called it Snd4 and it has 4 variables, the 4 values to fill the Cmd Array. In this function, I use the write() command in the radio class to send the 4 integers - note, that's 8 bytes. The write() function call requires 2 variables. The second is the number of bytes in the array that we are sending. The program uses the sizeof() function to get the total number of bytes to be sent. An interesting thing about arrays - if you write Cmd[x], that means the value of that one variable. However, if you just write Cmd - that means the address of where the start of the array is stored in memory. The first variable required by the write() call is the address of where the data starts. The write function sends bytes - it doesn't know if these are bytes that make up characters, or integers or longs or doubles or floats. The nRF24 doesn’t care what the bytes are. void loop() /****** LOOP: RUNS CONSTANTLY ******/ { Snd4 (128, 128, 0, 0); // forward half Speed. delay (1000); Snd4 (200, 0, 0, 200); // hard Right swival delay (1000); Snd4 (0, 200, 200, 0); // hard Left swival delay (1000); Snd4 (0, 0, 200, 200); // Back up delay (1000); } In the loop() program I send 4 integers, wait 1 second - Then 4 others - Nothing very exciting here. Let's look at the whole program: /* http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo 1 - GND 2 - VCC 3.3V !!! NOT 5V 3 - CE to Arduino pin 8 4 - CSN to Arduino pin 10 5 - SCK to Arduino pin 13 6 - MOSI to Arduino pin 11 7 - MISO to Arduino pin 12 8 - UNUSED */ /*-----( Import needed libraries )-----*/ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> /*-----( Declare Constants and Pin Numbers )-----*/ #define CE_PIN 8 #define CSN_PIN 10 const uint64_t pipe = 0xE7E7E7E7E7LL; // Define the transmit pipe RF24 radio(CE_PIN, CSN_PIN); // Create a Radio /*-----( Declare Variables )-----*/ int Cmd[4]; // 4 element array holding the 4 command integers void setup() /****** SETUP: RUNS ONCE ******/ { Serial.begin(9600); radio.begin(); radio.openWritingPipe(pipe); }//--(end setup )--void loop() /****** LOOP: RUNS CONSTANTLY ******/ { Snd4 (128, 128, 0, 0); // forward half Speed. delay (1000); Snd4 (200, 0, 0, 200); // hard Right swival delay (1000); Snd4 (0, 200, 200, 0); // hard Left swival delay (1000); Snd4 (0, 0, 200, 200); // Back up delay (1000); }//--(end main loop )--void Snd4(int C0, int C1, int C2, int C3) { Cmd[0] = C0; Cmd[1] = C1; Cmd[2] = C2; Cmd[3] = C3; radio.write( Cmd, sizeof(Cmd) ); } This program was intended to control a robot - by sending the 4 integers need to control the 2 pins for the right motor and the 2 pins for the left motor. Receiver Program Much of the Receiver program is similar to the Transmitter program /* http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo 1 - GND 2 - VCC 3.3V !!! NOT 5V 3 - CE to Arduino pin 8 4 - CSN to Arduino pin 10 5 - SCK to Arduino pin 13 6 - MOSI to Arduino pin 11 7 - MISO to Arduino pin 12 8 - UNUSED /*-----( Import needed libraries )-----*/ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> /*-----( Declare Constants and Pin Numbers )-----*/ #define CE_PIN 8 #define CSN_PIN 10 The connections are identical, and we load the same libraries. const uint64_t pipe = 0xE7E7E7E7E7LL; // Define the transmit pipe Again, we define a 64 bit variable - called pipe in this case - This specifies the channel we will be using. It must be the same number as the channel we specified in the transmitter program. You can operate multiple transmitters and multiple receivers simultaneously if they are all on different channels. There is an interesting thing about the receiver channel - a receiver can receive data from up to 6 transmitters - that is it can have 6 open receive pipes simultaneously. void setup() /****** SETUP: RUNS ONCE ******/ { Serial.begin (9600); radio.begin(); radio.openReadingPipe(1,pipe); radio.startListening();; }//--(end setup )--the setup() function is the similar, except, now we openReadingPipe() and startListening(). The openReadingPipe() needs 2 parameters. The second is the pipe variable. A receiver can listen to up to 6 transmitters simultaneously. The first variable is the pipe number, 0 to 5. Pipe 0 is the same as the transmitting pipe, and pipes 1 to 5 should have the same first 32 bits (only the last 8 can be different. The startListening() activates the receiver channel. There is also a stopListening() command that closes the channel. void loop() /****** LOOP: RUNS CONSTANTLY ******/ { while(!radio.available()); radio.read(Cmd, sizeof(Cmd)); // Fetch the data payload analogWrite (3, Cmd[0]); analogWrite (5, Cmd[1]); analogWrite (6, Cmd[2]); analogWrite (9, Cmd[3]); Serial.print (Cmd[0]); Serial.print (" "); Serial.print (Cmd[1]); Serial.print (" "); Serial.print (Cmd[2]); Serial.print (" "); Serial.println (Cmd[3]); }//--(end main loop )--There are only a couple of interesting things in the loop() function. radio.available() is the same as Serial.available() in that it returns the number of bytes in the buffer. The while(!radio.available()); command waits until something is received and radio.available() is no longer 0. radio.read() needs 2 parameters. The first is the address of the start of an array to put the received bytes in. The second is the maximum number of bytes that can be received. If you know exactly how many bytes you are receiving. This is simple. If you don't, then things become a bit more interesting. /* http://arduino-info.wikispaces.com/Nrf24L01-2.4GHz-HowTo 1 - GND 2 - VCC 3.3V !!! NOT 5V 3 - CE to Arduino pin 8 4 - CSN to Arduino pin 10 5 - SCK to Arduino pin 13 6 - MOSI to Arduino pin 11 7 - MISO to Arduino pin 12 8 - UNUSED /*-----( Import needed libraries )-----*/ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> /*-----( Declare Constants and Pin Numbers )-----*/ #define CE_PIN 8 #define CSN_PIN 10 const uint64_t pipe = 0xE7E7E7E7E7LL; // Define the transmit pipe RF24 radio(CE_PIN, CSN_PIN); // Create a Radio int Cmd[4]; // 4 element array holding a 4 integer message void setup() /****** SETUP: RUNS ONCE ******/ { Serial.begin (9600); radio.begin(); radio.openReadingPipe(1,pipe); radio.startListening();; }//--(end setup )--void loop() /****** LOOP: RUNS CONSTANTLY ******/ { while(!radio.available()); radio.read(Cmd, sizeof(Cmd)); // Fetch the data payload analogWrite (3, Cmd[0]); analogWrite (5, Cmd[1]); analogWrite (6, Cmd[2]); analogWrite (9, Cmd[3]); Serial.print (Cmd[0]); Serial.print (" "); Serial.print (Cmd[1]); Serial.print (" "); Serial.print (Cmd[2]); Serial.print (" "); Serial.println (Cmd[3]); }//--(end main loop )--This is the whole program - I have loaded it and it worked. In this case I write the values to the PWM pins to control motor speed and I also write them to the display. Things that go wrong It took me a couple tries to get this to work. Some of the libraries that I downloaded that were supposed to be for this product didn't work. The reference above did work for me. The nRF24L01 requires pretty clean 3.3V power. I had to put a 10uF cap across the power lined to reduce the noise. Wiring is critical - you have to get all 7 of the wires where they are supposed to be - If it doesn't work, check that first. Make sure your transmitter and receiver pipes are exactly the same - easy to make a mistake there.