Lab Guide: Using the Radio

Most of the important details for using the nRF24L01 radio are in my report, but it doesn’t cover Arduino stuff and it’s a little out of date.

Debug Station

There should be a radio debug station in the lab that you can use to test your radio.  If you send a properly formatted packet to the debug station, it will print the message you send and transmit a reply.  Note that if you don’t read this reply using Radio_Receive, your radio will stop working.  In fact, if you ever don’t read a reply out of a radio then it won’t assert the interrupt pin when it receives new data, and the driver will stop working until you either read the packets out or flush the buffer (using Radio_Flush).

The ACK type of message is different from an auto-ack packet.  Each time Radio A transmits a packet to Radio B, Radio B transmits a little acknowledgement packet called an auto-ack.  You will never see this happen, it’s entirely behind the scenes.  If Radio A doesn’t get the auto-ack, it will try re-sending its packet five times before giving up.  Here is a message sequence diagram for the debug station protocol:

nRF24L01 debug station message passing diagram.

Message sequence diagram for debug station protocol. Station A is the user and station B is the debug station (an intelligent person would make this clear this in the diagram).

Station A sends a packet containing data of type MESSAGE.  The radio at station B notifies its Arduino that it received a packet, and automatically sends back an auto-ack so that station A knows that the transmission was successful.  Then, station B sends a packet containing data of type ACK.  The radio at station A notifies its Arduino that it received a packet, and automatically sends back an auto-ack so that station B knows that the transmission was successful (I did not copy and paste that sentence).

Yes, you have to copy your own address into the MESSAGE packet.  The debug station won’t know where to send its reply otherwise.  The receiver has no other way to identify a packet’s source address.

Radio Code Structure

  • Initialize: call the Radio_Init function before your program starts, but after you call the Arduino init function.  Radio_Init uses Arduino’s delay functions, which will hang if init hasn’t been called.
  • Configure:
    • Call Radio_Configure_Rx to configure pipe 0 to your desired address.
    • Call Radio_Configure to set the data rate and transmitter power.  The data rate must be the same on all radios.
  • Transmit:
    • Call Radio_Set_Tx_Addr to set the destination address that you want to send to.
    • Call Radio_Transmit to send a packet to the configured destination address.  If the second parameter to the function is RADIO_WAIT_FOR_TX then the function will not return until the transmission is resolved.  In the debug station diagram above, if RADIO_WAIT_FOR_TX is selected then the call to Radio_Transmit will last from the start of the “Transmit MESSAGE” item until the “Transmit Complete” item is received.   RTFM for details.
  • Receive:
    • The radio_rxhandler() function, written by you in your main program, will be called whenever the radio receives a packet.  This function is called in an interrupt service routine, so do not do any processing inside of it.  Keep it very fast, don’t read the packet out of the radio, and especially don’t do any delays.  A consequence of the function being in an ISR is that any global variables you access from it must be declared volatile (see the example code).
    • Call Radio_Receive to copy the received packet from the radio into your memory.

Packet Structure

Read the first section of this page for information on the packet structure.

I’ve given you a default packet structure that is suitable for communicating with the debug station.  It defines two packet types, a MESSAGE packet and an ACK packet.

In our packet structure, we use a union to choose how to access the packet’s payload.  For those of you who haven’t used unions: a union is just a construct used to access one chunk of data in different ways.  It provides a kind of polymorphism to C code, without adding the danger of casting pointers.  In the default packet, if the packet.type is MESSAGE, then we access the data through the union member “packet.payload.message”.  This allows us to organize the raw data bytes into variables that are relevant to the MESSAGE type of packet.  If packet.type is ACK then we access the data through the “packet.payload.ack” member.  There is nothing stopping you from accessing the contents of an ACK packet through the “packet.payload.message” member, but the contents of that member will be all crazy and useless because the variables will access the wrong bits.

You will have to define data structures and add them to the union in packet.h to define your own packet formats that are relevant to your project.

Arduino Jazz

There’s an Arduino branch for my radio driver available in SVN.  Make sure you get the Arduino branch, and not the trunk (which is pure AVR code and will be a hassle to get working with Arduino).  You can easily tell the difference: the trunk contains .c files and the Arduino branch contains .cpp files.

Some pins are different for different Arduinos.  For the Seeeduino Mega, use pins 50 (MISO), 51 (MOSI), and 52 (SCK); for the Arduino Uno, use pins 11 (MOSI), 12 (MISO), and 13 (SCK).  The latest version of the Arduino library headers defines the SPI pins in the file pins_arduino.h. 

For any Arduino, the radio’s CE and CSN pin are connected to pins 8 and 9 respectively, and the IRQ pin is connected to pin 2.

The CE and CSN pins can be connected to any digital I/O on the Arduino, just change the definition at the top of radio.cpp.  The IRQ pin can be moved to another interrupt if you really really want to.  The MISO, MOSI, and SCK pins cannot be changed.

You’ll notice that on the Uno, the SCK pin is on the same output as the built-in LED.  Don’t use the Uno’s built-in LED if you’re using the radio.  Use your BlinkM LED or we have some other LEDs sitting around in the lab that you can plug into other I/O pins.

If you’re having trouble with my driver, try the one on the Arduino playground.  At your own risk.  I’ve never used it, but I’ve heard it works.

Power Cycling

Resetting the Arduino doesn’t necessarily reset the radio, which can prevent the radio from initializing properly.  You can avoid this problem by wiring the radio’s Vcc pin to an Arduino digital output pin.  At program startup, set that pin low for 100 ms and then high before calling Radio_Init().  This will reset the radio, ensuring that it is ready to be reinitialized.

Aside: The digital I/O pins on Arduino can provide up to 40 mA safely.  The radio uses less than that, so it’s okay to power it from an I/O pin, but it is not safe to power all devices in this way.  Also, the Arduino can’t draw more than 200 mA of current, including the current used to power the AVR microcontroller plus any current being drawn by the digital I/O pins.  Interesting fact: AVR I/O pins are symmetrical, which means that you can set a digital output to 0 and have it sink up to 40 mA of current to ground.

Example Code

Click here to see a couple of example programs that use the radio driver.

Things That Can Go Wrong

Your program will not compile if you don’t define the radio_rxhandler function (it can be empty if you don’t expect to receive packets).

If you are having trouble sending messages to the debug station, it may be due to RF interference with other groups using the radio.  There are several steps you can take to avoid problems:

  1. Every group should write the addresses they are using up on the board, so that nobody uses the same addresses.
  2. Make sure that when you run the receiver example code, you change the default address so that it doesn’t conflict with the debug station.  If you accidentally receive debug station messages it will screw everybody up.
  3. If the debug station isn’t working, reset it and make sure that it prints “STATION START” to the serial terminal upon startup.  If it doesn’t, restart the terminal program.
  4. When you’re not working, power down your equipment so that it doesn’t interfere with other teams.
  5. Don’t transmit packets at a high rate.  While a radio is actively transmitting on a particular channel, no other radio can transmit on that channel (the debug station uses channel 112).  See step 7 below.
  6. Once you successfully send a message to the debug station, stop using it.  Just communicate back and forth between your own radios.
  7. When you stop using the debug station, switch the value of the CHANNEL macro in radio.cpp.  Use even-numbered channels between 100 and 118.  Write the channel you’re using up on the board so that nobody else uses it.  Make sure all your code is using the same channel (radios on different channels can’t talk to each other).  This is the most important step in eliminating RF interference.

If the Radio_Init function never returns, make sure you called it after you called the Arduino init function.

If the Radio_Init function still never returns, the IRQ pin might not be connected (the attachInterrupt call hangs).  You can use the continuity tester in a multimetre to verify that the wire isn’t broken.