RF24Network Arduino/RPi Library - A New Development

What is RF24Network?

It is a library built upon the core RF24 driver for nrf24l01 radio modules that attempts to provide a simple interface for connecting anywhere from a few, to hundreds of wireless radio modules, connected to Arduino or Raspberry Pi devices. Uses don't need to know every detail of the radio modules in order to setup and operate a network of them. The library handles configuration, addressing, interconnections, and routing, so users can focus on creating household or long distance radio sensor networks, with data being routed automatically through the network to its intended destination.

Whats New?

I've been doing quite a bit of work on the core RF24 radio driver, including a wide range of changes and updates, and wanted to test out some of the more interesting features of the modules on a large scale. The existing network layer (originally by ManiacBug) provided a great foundation to begin, as it contained all of the required features of an RF24 radio network, and used a system of routing and addressing that is well suited to small devices like Arduino or ATTiny.
Some of the new features may seem unnecessary or overkill, and some will probably be, but others will support scenarios just not possible in the previous configuration. I've created a development repository and associated documentation that will be kept up roughly in accordance with development, since it will all take time to test fully, but it seems to work well and some of the features are pretty interesting. 

Updated 12/14:
RF24Network now supports fragmentation for all devices, including via multicasting and is enabled for all devices except ATTiny by default. The library is fairly stable at this point, and testing is ongoing with RF24Ethernet and RF24Mesh.

Updated 10/14:
Fragmentation support was recently added for the RPi and Arduino Due. Standard AVR devices (Arduino Uno etc) are able to send large, fragmented payloads, but cannot yet reassemble them. See below to enable. Usage is exactly the same, but any very long payload lengths will be accepted.

 Routing changes have been partially reverted to use a limited number of retries (auto-ack) where possible, and function similarly to the master branch, although network ACKs are still utilized.

Some defaults have been changed:
  • Multicasting is enabled by default. This limits the number of normal child nodes/parent to 4.
  • Fragmentation is totally disabled by default on AVR devices
  • Uncomment defines in RF24Network_config.h to disable/enable these options


Fragmentation and reassembly support is enabled by default, with a maximum payload size of 120 bytes. Users can configure the memory buffers, or disable fragmentation by editing the config.h file. See here for the documenation. When enabled, data is sent as usual, and the network layer manages fragmenting and reassembling the data automatically.

Addressing Formats:

The development version currently uses a different addressing format, using specific addressing chosen for use in radio transmission. See the datasheet and this blog post for more information. Users simply specify an octal address (01-05) as before for example, and the radio addresses will be configured accordingly.

Routing and Acknowledgement

Previously, payloads that were routed over one or more nodes were not acknowledged, so the transmitting radio had no way of knowing if payloads were delivered. *New: In the development version, users have the ability to specify whether or not the network will respond with an ACK, simply by choosing from a range of message types. Typical user message types of  65 through 127 will be acknowledged, and message types 1 through 64 will not be acknowledged. There are a few benefits to this arrangement, since network acks can automatically help to prevent nodes from flooding the network with traffic, and no-acks can be used when a response is desired from the recipient etc.

Direct Routing and Failovers

Network frames can now be logically addressed to one node, and sent to any other node for routing. This allows some dynamic topology, and can also be used to reduce routed traffic on a network. See the documentation links below for specifics and limitations.


A multicasting feature has also been added that allows, for example, the master node to send out a single network payload, and have it delivered to hundreds of network nodes very rapidly. Multicast relay nodes can be used to disperse messages throughout all or partial areas of the network. Multicasting is enabled by un-commenting #define RF24NetworkMulticast in the RF24Network_config.h file, since it requires additional program space and memory.

Improved Performance

Performance on the network will be similar, but users will see a much higher error rate using the development version, as it gives a real indication of success or failure of payloads. Data throughput should be increased in most cases, due to reduced retry periods, and the new routing protocol.

Additional Technical Details and Information:

Documentation for the development version can be found here, and should be kept pretty consistent with changes. It contains information specific to the new routing and delivery protocols, as well as updated class documentation for usage. 
The development repo is found here, and it can be downloaded here. Features and functions found to be stable and beneficial are likely to be merged into the master branch eventually. The development branch should be relatively stable at any given time, but is in no way guaranteed to be.

Arduino AVR Timers and Waveform Generation

I've had a number of questions regarding Arduino timers and the generation of signals and waveforms, so here is (hopefully) a simple tutorial on using Arduino timers.

This tutorial requires a basic understanding of the difference between bits and bytes, along what it means to read or write bits and bytes to AVR registers.

Timers can be seen as additional components used to offload work from the processor. A timer running on its own can create a signal or waveform without any interaction from the CPU, allowing the CPU to do things like interact with the timer to modify the waveform or handle other tasks.

The most basic function of a timer is to count from 0 to a specified number, lets say 255, and then start all over again and count from 0 to 255 again. Using a standard Arduino board running at 16 million cycles/second, the timer will count from 0 to 255 at a set frequency, which can be determined using a simple calculation:

CPU Freq / TIMER TOP  =   Timer Frequency (cycles/second)
16mHz     /         255          =   62,745 cycles/second  ( 62.75kHz )

The UNO has both 8-bit timers (TIMER0, TIMER2) and a 16-bit timer (TIMER1) which can all be used to perform various functions. This means that timer0 and timer2 can handle or count in increments ranging from 0 to 255, and TIMER1 will handle values from 0 to 65,535.

Timer Parameters & How to generate a signal:

Timer TOP:
Basic timer functionality is simple, we set a TOP value, and the timer will count from 0 to TOP, then start counting at 0 again.

Timers can also be set to toggle an output or trigger an interrupt when the timer value reaches a set value. Lets say the TOP value has been set to 255, the compare value is 127, and an LED is connected to pin 9 on an Arduino UNO. The following will take place:

a: The timer will increment its value by 1 every 16-millionth of a second, starting at 0
b: The LED stays lit until the counter value reaches 127, then is turned off.
c: The LED turns back on once the counter value is reset to 0 (after reaching its defined TOP value)
d: GoTo b:

Result: This produces an approximately 50% duty cycle PWM signal at 62.75kHz

Example Code:

void setup(){

   //This sets the timer1 mode, prescaler and enables timer PWM output on Uno pin 9 (not covered in this tutorial)
    TCCR1A = _BV(WGM11) | _BV(COM1A1);
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);

    //This sets the TOP value, or the value that the timer will count up to before resetting to 0
    ICR1 = 255;
   // This sets the OUTPUT COMPARE value A ( Compare A for pin 9, Compare B is linked to pin 10)
   OCR1A = 127;


Changing the Duty Cycle (how long the pin output is HIGH vs LOW):

Changing the duty cycle is as simple as changing the value of the OCR1A register. Since these examples use timer1, 16-bit (0-65535) values could be used, but since the TOP value is set at 255, only those values can be used for this demonstration.

Example Code (Uses the above setup() code) :

void loop(){
    OCR1A = OCR1A + 1;
    if(OCR1A == 255){
         OCR1A = 0;

Result: The example above will increment the duty cycle by 1/255 (0.4%) every 15 milliseconds until it has reached 100%, then it will reset to 0 and continue to increment. A connected LED will be seen to get brighter and brighter until the timer compare register reaches the TOP value, and the duty cycle (OCR1A) will be reset to 0.

Changing the Frequency:

To change the frequency, simply adjust the timer TOP value in the above example code:
ICR1 = 30000;

Calculation:  16,000,000 / 30000 = 533.33... HZ (cycles/second)

Note: The compare register (OCR1A) can be set to any value between 0 and 30,000 in this example.


The frequency can be made lower by adjusting the prescaler. The system clock signal will be divided by the prescaler before being made available to the timer. This is not required to be understood for this tutorial.

Calculation: CPU Freq. / Pre-Scaler / TIMER TOP = frequency

Basic Theory of Creating Audio:

One of the simplest and most common forms of uncompressed audio is the WAV or PCM format. PCM stands for Pulse-Code-Modulation, which can be replicated using the Arduino timers, and the information provided above.

In the case of recording 8-bit audio, the voltage of an audio signal is converted to a number ranging from 0 to 255. The higher the voltage, the higher the number. This is done at a set frequency, say 16kHz or 16,000 times a second.

Recreating an audio signal can get fairly complicated and in-depth, but basically just needs to meet the following requirements:

1. Frequency of the timer generally should equal the frequency that the audio was sampled at (sample rate)
2. Sample values need to be fed into the timer every cycle

To create audio of 16khz sample rate, the timer needs to run at 16,000 cycles/second, and a new sample value needs to be fed into the timer every time it reaches its TOP value (every 62.5 microseconds) This generally requires the use of interrupts, which is out of the scope of this tutorial.

The above example code can be used to create simple audio tones by varying the frequency from 0 to about 10,000 hz max, and volume controlled by adjusting the duty cycle.


This overview is intended to cover very basic timer usage in Fast PWM mode via TIMER1. Additional timers, modes, details, usage, etc can be determined by referencing the datasheet, which is really the best reference for any kind of advanced timer usage. All of the timers are accessed in very similar ways, but some settings like prescaling are different between timers.