2.4GHz RF Radio Transceivers and Library
2.4GHz RF Radio Transceivers and Library
I really want to communicate wirelessly between Arduinos for cheap. I love the idea of “The Internet of Things”, with everything I can look, see, touch all connected together. The problem is the cost. Zigbee modules seem to be the standard, but they are just too much to be a reasonable solution for puttingeverything on the Internet.
The Nordic nRF24L01, built into a small module and sold by mdfly.com for $6.50 is an excellent solution. It’s cheap, fast (2 Mbps), easy, reliable, and low-power. It entirely implements the Data Link Layer in hardware, handling addressing, collisions, and retry, saving us lots of work on the software side. Zigbee has the brand recognition, but this little guy puts it to shame.
Parts
Qty | Vendor# | Description | Price | Comment |
2 | MDFly RF-IS2401 | 2.4Ghz Wireless nRF24L01 Transceiver Module | $6.50 | Datasheet Schematic |
1 | 511-LD1117V33 | Low Dropout (LDO) Regulators 3.3V 0.8A Positive | $0.68 | Datasheet |
Connections
Hooking this up to Arduino is relatively straightforward.
The first issue to confront is that the module’s physical interface is a 4×2-pin header. This doesn’t lend itself to an easy connection with the Arduino or with a breadboard. Fortunately for me, I have a handful of 5×2 breakout boards which I had already designed to break out a 5×2 box header onto a breadboard. The only slightly tricky thing for me is that the pinout has to be backwards, due to the way they fit together.
The second issues is that this module takes 3.3V power. Fortunately its inputs are 5V tolerant, and its outputs are sufficient to drive Arduino inputs high. So the only thing to remember is to get the thing 3.3V power. On a stock Arduino that’s no problem because it has a 3.3V pin, but not all Arduino-compatable boards have it (none of mine do), so sometimes I need to regulate 3.3V power for it.
Signal | RF Module |
Breakout Board |
Arduino |
GND | 1 | 2 | GND |
VCC | 2 | 1 | 3V3 |
CE | 3 | 4 | 8 |
CSN | 4 | 3 | 9 |
SCK | 5 | 6 | 13 |
MOSI | 6 | 5 | 11 |
MISO | 7 | 8 | 12 |
IRQ | 8 | 7 | 2* |
*Note, I am not ever connecting the IRQ pin in my setup, but that is where it would go.
In the setup pictured above, I have a couple things going on. On the left, I have an Arduino Pro Mini (not pictured) connected to the breadboard via a 10-pin cable, using by 5×2 breakout. On that setup is a 3V3 regulator (without caps–shame on me!), and the RF module connected on the other side using another 5×2 breakout. In the middle is there to handle the 5V-to-3V3. It turns out I didn’t need them, so it’s not connected. The wires are there just to make the connections listed in the table just above.
On the right is a stock Arduino with a custom prototyping shield of mine. Since it has 3V3 built in, that’s an easier connection. Again, the radio is connected via a short 10-pin cable and little 5×2 breakout board.
Library
Get the library from github: github:RF24.
Because this part handles so much in hardware, the library is really simple. It just puts a pleasant face on the capabilities of the chip, and handles the SPI interface.
First, it’s worthwhile to look at the code that’s out there. The best I found is the nRF24L01 on Arduino Playground. This code was an invaluable help in bringing up the module–it would have taken hours longer without it. I especially appreciate the author’s simple examples.
Still, I decided to write my own library, because I had a number of goals that were not fulfilled by that library.
- Use standard SPI library. Users will have an easier time of it if the library relies on the the official SPI libary included with the distribution. Otherwise there is an obvious point of confusion for users.
- Protected internals. As a good rule of design, it’s important to keep the internals of the class hidden from the users, and focus on providing a rich external interface. This makes it easier to later refactor the library without breaking anyone who is currently using it.
- Ready for complex topologies. This part can communicate with many devices at once, however this other library is designed for 1-to-1 communication. Ultimately I want to be able to build a Mesh Network out of this part, so I want the library to be ready.
- Full compliance with data sheet. I wanted to make sure that the library behaved exactly as the data sheet intended.
- Similar interface to packed-in libraries. To make it easy on users, I wanted the library to work as much like the packed in libraries as possible, yet without hiding the power of the chip.
Here is the documentation for the full public interface of the driver:
RF24
Constructor. Creates a new instance of this driver. Before using, you create an instance and send in the unique pins that this chip is connected to.
Parameters:
- _cepin: The pin attached to Chip Enable on the RF module
- _cspin: The pin attached to Chip Select
begin
Begin operation of the chip. Call this in setup(), before calling any other methods.
setChannel
Set RF communication channel.
Parameters:
- channel: Which RF channel to communicate on, 0-127
setPayloadSize
Set Payload Size. This implementation uses a pre-stablished fixed payload size for all transmissions.
Parameters:
- size: The number of bytes in the payload
getPayloadSize
Get Payload Size.
Returns:
- The number of bytes in the payload
printDetails
Print a giant block of debugging information to stdout.
Warning: Does nothing if stdout is not defined. See fdevopen in stdio.h
startListening
Start listening on the pipes opened for reading. Be sure to open some pipes for reading first. Do not call ‘write’ while in this mode, without first calling ‘stopListening’.
stopListening
Stop listening for incoming messages. Necessary to do this before writing.
write
Write to the open writing pipe. This blocks until the message is successfully acknowledged by the receiver or the timeout/retransmit maxima are reached. In the current configuration, the max delay here is 60ms.
Parameters:
- buf: Pointer to the data to be sent
Returns:
- True if the payload was delivered successfully false if not
available
Test whether there are bytes available to be read.
Returns:
- True if there is a payload available, false if none is
read
Read the payload. Return the last payload received
Parameters:
- buf: Pointer to a buffer where the data should be written
Returns:
- True if the payload was delivered successfully false if not
openWritingPipe
Open a pipe for writing. Addresses are 40-bit hex values, e.g.:
openWritingPipe(0xF0F0F0F0F0);
Parameters:
- address: The 40-bit address of the pipe to open. This can be any value whatsoever, as long as you are the only one writing to it and only one other radio is listening to it. Coordinate these pipe addresses amongst nodes on the network.
openReadingPipe
Open a pipe for reading.
Warning: all 5 reading pipes should share the first 32 bits. Only the least significant byte should be unique, e.g.
openReadingPipe(0xF0F0F0F0AA);
openReadingPipe(0xF0F0F0F066);
Parameters:
- number: Which pipe# to open, 1-5.
- address: The 40-bit address of the pipe to open.
دانلود آموزش فارسی راه اندازی ماژول وایرلس NRF24L01
Example
Included with the library is the example I used to bring these guys up, the pingpair example.
Hardware Role Switch
One thing I decided to do is write a single piece of software for both the transmitter and receiver unit. This vastly simplifies logistics. Then I have a pin on the hardware that acts as a switch between the two types. I call these ‘roles’. There is a transmitter ‘tole’ and a receiver ‘role’. I used pin 7, connected to ground for one role, to power for another role.
//
// Role management
//
// Set up address & role. This sketch uses the same software for all the nodes
// in this system. Doing so greatly simplifies testing. The hardware itself specifies
// which node it is.
//
// This is done through the addr_pin. Set it low for address #0, high for #1.
//
// The various roles supported by this sketch
typedef enum { role_rx = 1, role_tx1, role_end } role_e;
// The debug-friendly names of those roles
const char* role_friendly_name[] = { "invalid", "Receive", "Transmit"};
// Which role is assumed by each of the possible hardware addresses
const role_e role_map[2] = { role_rx, role_tx1 };
// The role of the current running sketch
role_e role;
void setup(void)
{
//
// Address & Role
//
// set up the address pin
pinMode(addr_pin, INPUT);
digitalWrite(addr_pin,HIGH);
delay(20); // Just to get a solid reading on the addr pin
// read the address pin, establish our address and role
node_address = digitalRead(addr_pin) ? 0 : 1;
role = role_map[node_address];
Radio Setup
The sketch first sets up the radio…
//
// Setup and configure rf radio
//
radio.begin();
// Set channel (optional)
radio.setChannel(1);
// Set size of payload (optional, but recommended)
// The library uses a fixed-size payload, so if you don't set one, it will pick
// one for you!
radio.setPayloadSize(sizeof(unsigned long));
//
// Open pipes to other nodes for communication (required)
//
// This simple sketch opens two pipes for these two nodes to communicate
// back and forth.
// We will open 'our' pipe for writing
radio.openWritingPipe(pipes[node_address]);
// We open the 'other' pipe for reading, in position #1 (we can have up to 5 pipes open for reading)
int other_node_address;
if (node_address == 0)
other_node_address = 1;
else
other_node_address = 0;
radio.openReadingPipe(1,pipes[other_node_address]);
//
// Start listening
//
radio.startListening();
//
// Dump the configuration of the rf unit for debugging
//
radio.print_details();
}
Transmitter Role
The transmitter unit writes the current millis() time out to the other unit every second, listens for the response, measures the difference, and prints that.
void loop(void)
{
//
// Transmitter role. Repeatedly send the current time
//
if (role == role_tx1)
{
// 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 );
// 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 > 250 )
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 );
// Spew it
printf("Got response %lu, round-trip delay: %lu\n\r",got_time,millis()-got_time);
}
// Try again 1s later
delay(1000);
}
Receiver Role
…And the receiver does the opposite, receiving the packet, and mirroring it back out to the other guy.
if ( role == role_rx )
{
// if there is data ready
if ( radio.available() )
{
// Dump the payloads until we've gotten everything
unsigned long got_time;
boolean done = false;
while (!done)
{
// Fetch the payload, and see if this was the last one.
done = radio.read( &got_time );
// Spew it
printf("Got payload %lu...",got_time);
}
// First, stop listening so we can talk
radio.stopListening();
// Send the final one back.
radio.write( &got_time );
printf("Sent response.\n\r");
// Now, resume listening so we catch the next packets.
radio.startListening();
}
}
}
The Magic Makefile
The problem I had developing these initially is that even though the software was the same, it was a pain to switch back and forth in the IDE and remember to install it on both nodes. So, I posted to the Arduino forum: Is there an easy way to simultaneously deploy a sketch to multiple devices? Fortunately, the answer is yes! In that thread, Graynomad posted his Makefile for Arduino. With a little tweaking, I was able to set it up to compile my sketches on the command line and upload them to as many nodes as I have connected all at once. Yay!
Then, to make testing easier still, I created a little script that opens two xterm windows side-by-side and connects to each Arduino. Then I can watch the communication spew back and forth in real time. Makes it super easy to tell that I didn’t break something.
xterm -e "screen /dev/tty.usbserial-A1234ABc" &
xterm -e "screen /dev/tty.usbserial-A5678dEf" &
- ۰ نظر
- ۲۶ مرداد ۹۴ ، ۲۱:۲۲